Merge branch 'master' into master

This commit is contained in:
Bashar Astifan 2023-07-17 23:20:52 +04:00 committed by GitHub
commit a35c8425e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
520 changed files with 16053 additions and 8419 deletions

View file

@ -155,14 +155,8 @@ jobs:
extra: android extra: android
cc: clang cc: clang
cxx: clang++ cxx: clang++
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_QUEST=1 args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1
id: android-vr-quest id: android-vr
- os: ubuntu-latest
extra: android
cc: clang
cxx: clang++
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_PICO=1
id: android-vr-pico
- os: ubuntu-latest - os: ubuntu-latest
extra: android extra: android
cc: clang cc: clang

2
.gitignore vendored
View file

@ -131,3 +131,5 @@ CMakeFiles
.cache/ .cache/
build build
libretro/obj/local libretro/obj/local
ppsspp_retroachievements.dat

3
.gitmodules vendored
View file

@ -44,3 +44,6 @@
[submodule "cpu_features"] [submodule "cpu_features"]
path = ext/cpu_features path = ext/cpu_features
url = https://github.com/google/cpu_features.git url = https://github.com/google/cpu_features.git
[submodule "ext/rcheevos"]
path = ext/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git

View file

@ -102,6 +102,13 @@ if(ANDROID OR WIN32 OR (UNIX AND NOT ARM_NO_VULKAN))
set(VULKAN ON) set(VULKAN ON)
endif() endif()
# Default to bundled SDL2 on macOS, system SDL2 elsewhere.
if(APPLE AND NOT IOS)
set(DEFAULT_USE_SYSTEM_LIBSDL2 OFF)
else()
set(DEFAULT_USE_SYSTEM_LIBSDL2 ON)
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
if(NOT IOS) if(NOT IOS)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/sdl) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/sdl)
@ -157,7 +164,7 @@ option(USE_ARMIPS "Build with armips support in API/debugger" ON)
option(USE_SYSTEM_SNAPPY "Dynamically link against system snappy" ${USE_SYSTEM_SNAPPY}) option(USE_SYSTEM_SNAPPY "Dynamically link against system snappy" ${USE_SYSTEM_SNAPPY})
option(USE_SYSTEM_FFMPEG "Dynamically link against system FFMPEG" ${USE_SYSTEM_FFMPEG}) option(USE_SYSTEM_FFMPEG "Dynamically link against system FFMPEG" ${USE_SYSTEM_FFMPEG})
option(USE_SYSTEM_LIBZIP "Dynamically link against system libzip" ${USE_SYSTEM_LIBZIP}) option(USE_SYSTEM_LIBZIP "Dynamically link against system libzip" ${USE_SYSTEM_LIBZIP})
option(USE_SYSTEM_LIBSDL2 "Dynamically link against system SDL2" ON) # Ignored on Mac option(USE_SYSTEM_LIBSDL2 "Dynamically link against system SDL2" ${DEFAULT_USE_SYSTEM_LIBSDL2})
option(USE_SYSTEM_LIBPNG "Dynamically link against system libpng" ON) option(USE_SYSTEM_LIBPNG "Dynamically link against system libpng" ON)
option(USE_SYSTEM_ZSTD "Dynamically link against system zstd" ${USE_SYSTEM_ZSTD}) option(USE_SYSTEM_ZSTD "Dynamically link against system zstd" ${USE_SYSTEM_ZSTD})
option(USE_SYSTEM_MINIUPNPC "Dynamically link against system miniUPnPc" ${USE_SYSTEM_MINIUPNPC}) option(USE_SYSTEM_MINIUPNPC "Dynamically link against system miniUPnPc" ${USE_SYSTEM_MINIUPNPC})
@ -167,6 +174,8 @@ option(USE_UBSAN "Use undefined behaviour sanitizer" OFF)
if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN) if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN)
if(USING_X11_VULKAN) if(USING_X11_VULKAN)
message("Using X11 for Vulkan") message("Using X11 for Vulkan")
find_package(X11)
include_directories(${X11_Xlib_INCLUDE_PATH})
add_definitions(-DVK_USE_PLATFORM_XLIB_KHR) add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)
else() else()
message("NOT using X11 for Vulkan") message("NOT using X11 for Vulkan")
@ -242,9 +251,13 @@ if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX)
endif() endif()
if(MACOSX AND NOT IOS) if(MACOSX AND NOT IOS)
if(USE_SYSTEM_LIBSDL2)
find_package(SDL2)
else()
find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS) find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS)
message(STATUS "found SDL2Fwk=${SDL2Fwk}") message(STATUS "found SDL2Fwk=${SDL2Fwk}")
add_definitions(-DHAVE_SYSCTLBYNAME) add_definitions(-DHAVE_SYSCTLBYNAME)
endif()
endif() endif()
include(FindThreads) include(FindThreads)
@ -393,7 +406,7 @@ if(NOT MSVC)
add_definitions(-Wno-psabi) add_definitions(-Wno-psabi)
endif() endif()
add_definitions(-D_XOPEN_SOURCE=700) add_definitions(-D_XOPEN_SOURCE=700)
add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1) add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE)
add_definitions(-D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64) add_definitions(-D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64)
elseif(ANDROID) elseif(ANDROID)
add_definitions(-fsigned-char) add_definitions(-fsigned-char)
@ -406,6 +419,7 @@ else()
endif() endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -D_NDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -D_NDEBUG")
set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:"libcmt.lib")
endif() endif()
if(WIN32) if(WIN32)
@ -555,6 +569,7 @@ add_library(Common STATIC
Common/Data/Collections/FixedSizeQueue.h Common/Data/Collections/FixedSizeQueue.h
Common/Data/Collections/Hashmaps.h Common/Data/Collections/Hashmaps.h
Common/Data/Collections/TinySet.h Common/Data/Collections/TinySet.h
Common/Data/Collections/FastVec.h
Common/Data/Collections/ThreadSafeList.h Common/Data/Collections/ThreadSafeList.h
Common/Data/Color/RGBAUtil.cpp Common/Data/Color/RGBAUtil.cpp
Common/Data/Color/RGBAUtil.h Common/Data/Color/RGBAUtil.h
@ -603,6 +618,8 @@ add_library(Common STATIC
Common/File/VFS/DirectoryReader.h Common/File/VFS/DirectoryReader.h
Common/File/AndroidStorage.h Common/File/AndroidStorage.h
Common/File/AndroidStorage.cpp Common/File/AndroidStorage.cpp
Common/File/AndroidContentURI.h
Common/File/AndroidContentURI.cpp
Common/File/DiskFree.h Common/File/DiskFree.h
Common/File/DiskFree.cpp Common/File/DiskFree.cpp
Common/File/Path.h Common/File/Path.h
@ -617,6 +634,8 @@ add_library(Common STATIC
Common/File/FileDescriptor.h Common/File/FileDescriptor.h
Common/GPU/DataFormat.h Common/GPU/DataFormat.h
Common/GPU/MiscTypes.h Common/GPU/MiscTypes.h
Common/GPU/GPUBackendCommon.cpp
Common/GPU/GPUBackendCommon.h
Common/GPU/thin3d.cpp Common/GPU/thin3d.cpp
Common/GPU/thin3d.h Common/GPU/thin3d.h
Common/GPU/thin3d_create.h Common/GPU/thin3d_create.h
@ -638,6 +657,8 @@ add_library(Common STATIC
Common/GPU/OpenGL/GLFrameData.cpp Common/GPU/OpenGL/GLFrameData.cpp
Common/GPU/OpenGL/GLFrameData.h Common/GPU/OpenGL/GLFrameData.h
Common/GPU/OpenGL/thin3d_gl.cpp Common/GPU/OpenGL/thin3d_gl.cpp
Common/GPU/OpenGL/GLMemory.cpp
Common/GPU/OpenGL/GLMemory.h
Common/GPU/OpenGL/GLRenderManager.cpp Common/GPU/OpenGL/GLRenderManager.cpp
Common/GPU/OpenGL/GLRenderManager.h Common/GPU/OpenGL/GLRenderManager.h
Common/GPU/OpenGL/GLQueueRunner.cpp Common/GPU/OpenGL/GLQueueRunner.cpp
@ -723,6 +744,8 @@ add_library(Common STATIC
Common/System/NativeApp.h Common/System/NativeApp.h
Common/System/Request.cpp Common/System/Request.cpp
Common/System/Request.h Common/System/Request.h
Common/System/OSD.cpp
Common/System/OSD.h
Common/Thread/Channel.h Common/Thread/Channel.h
Common/Thread/ParallelLoop.cpp Common/Thread/ParallelLoop.cpp
Common/Thread/ParallelLoop.h Common/Thread/ParallelLoop.h
@ -741,6 +764,8 @@ add_library(Common STATIC
Common/UI/UI.h Common/UI/UI.h
Common/UI/Context.cpp Common/UI/Context.cpp
Common/UI/Context.h Common/UI/Context.h
Common/UI/IconCache.cpp
Common/UI/IconCache.h
Common/UI/UIScreen.cpp Common/UI/UIScreen.cpp
Common/UI/UIScreen.h Common/UI/UIScreen.h
Common/UI/Tween.cpp Common/UI/Tween.cpp
@ -778,8 +803,10 @@ add_library(Common STATIC
Common/MemArenaDarwin.cpp Common/MemArenaDarwin.cpp
Common/MemArenaPosix.cpp Common/MemArenaPosix.cpp
Common/MemArenaWin32.cpp Common/MemArenaWin32.cpp
Common/MemArenaHorizon.cpp
Common/MemArena.h Common/MemArena.h
Common/MemoryUtil.cpp Common/MemoryUtil.cpp
Common/MemoryUtilHorizon.cpp
Common/MemoryUtil.h Common/MemoryUtil.h
Common/OSVersion.cpp Common/OSVersion.cpp
Common/OSVersion.h Common/OSVersion.h
@ -919,6 +946,7 @@ endif()
find_package(LIBZIP) find_package(LIBZIP)
if(LIBZIP_FOUND AND USE_SYSTEM_LIBZIP) if(LIBZIP_FOUND AND USE_SYSTEM_LIBZIP)
include_directories(${LIBZIP_INCLUDE_DIRS})
add_definitions(-DSHARED_LIBZIP) add_definitions(-DSHARED_LIBZIP)
else() else()
add_library(libzip STATIC add_library(libzip STATIC
@ -1297,7 +1325,12 @@ else()
set_source_files_properties(SDL/PPSSPPAboutViewController.m PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(SDL/PPSSPPAboutViewController.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY}) set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY})
if(USE_SYSTEM_LIBSDL2)
set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2)
else()
set(nativeExtraLibs ${nativeExtraLibs} ${SDL2Fwk}) set(nativeExtraLibs ${nativeExtraLibs} ${SDL2Fwk})
endif()
elseif(USING_EGL) elseif(USING_EGL)
set(nativeExtraLibs ${nativeExtraLibs} pthread SDL2::SDL2) set(nativeExtraLibs ${nativeExtraLibs} pthread SDL2::SDL2)
else() else()
@ -1345,6 +1378,8 @@ list(APPEND NativeAppSource
UI/MiscScreens.cpp UI/MiscScreens.cpp
UI/PauseScreen.h UI/PauseScreen.h
UI/PauseScreen.cpp UI/PauseScreen.cpp
UI/TabbedDialogScreen.h
UI/TabbedDialogScreen.cpp
UI/GameScreen.h UI/GameScreen.h
UI/GameScreen.cpp UI/GameScreen.cpp
UI/GameSettingsScreen.h UI/GameSettingsScreen.h
@ -1385,6 +1420,8 @@ list(APPEND NativeAppSource
UI/CustomButtonMappingScreen.cpp UI/CustomButtonMappingScreen.cpp
UI/Theme.h UI/Theme.h
UI/Theme.cpp UI/Theme.cpp
UI/RetroAchievementScreens.cpp
UI/RetroAchievementScreens.h
) )
if(ANDROID) if(ANDROID)
@ -1786,6 +1823,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/KeyMap.h Core/KeyMap.h
Core/KeyMapDefaults.cpp Core/KeyMapDefaults.cpp
Core/KeyMapDefaults.h Core/KeyMapDefaults.h
Core/RetroAchievements.h
Core/RetroAchievements.cpp
Core/ThreadEventQueue.h Core/ThreadEventQueue.h
Core/TiltEventProcessor.h Core/TiltEventProcessor.h
Core/TiltEventProcessor.cpp Core/TiltEventProcessor.cpp
@ -1812,6 +1851,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Debugger/WebSocket/GameBroadcaster.h Core/Debugger/WebSocket/GameBroadcaster.h
Core/Debugger/WebSocket/GameSubscriber.cpp Core/Debugger/WebSocket/GameSubscriber.cpp
Core/Debugger/WebSocket/GameSubscriber.h Core/Debugger/WebSocket/GameSubscriber.h
Core/Debugger/WebSocket/ClientConfigSubscriber.cpp
Core/Debugger/WebSocket/ClientConfigSubscriber.h
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Core/Debugger/WebSocket/GPUBufferSubscriber.h Core/Debugger/WebSocket/GPUBufferSubscriber.h
Core/Debugger/WebSocket/GPURecordSubscriber.cpp Core/Debugger/WebSocket/GPURecordSubscriber.cpp
@ -2179,7 +2220,7 @@ else()
include_directories(ext/zstd/lib) include_directories(ext/zstd/lib)
endif() endif()
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash ${GlslangLibs} target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs}
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) ${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
target_compile_features(${CoreLibName} PUBLIC cxx_std_17) target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
@ -2328,7 +2369,6 @@ set(WindowsFiles
Windows/Debugger/Debugger_MemoryDlg.h Windows/Debugger/Debugger_MemoryDlg.h
Windows/Debugger/Debugger_Lists.cpp Windows/Debugger/Debugger_Lists.cpp
Windows/Debugger/Debugger_Lists.h Windows/Debugger/Debugger_Lists.h
Windows/Debugger/Debugger_SymbolMap.h
Windows/Debugger/Debugger_VFPUDlg.cpp Windows/Debugger/Debugger_VFPUDlg.cpp
Windows/Debugger/Debugger_VFPUDlg.h Windows/Debugger/Debugger_VFPUDlg.h
Windows/Debugger/WatchItemWindow.cpp Windows/Debugger/WatchItemWindow.cpp
@ -2376,6 +2416,13 @@ set(WindowsFiles
Windows/W32Util/ShellUtil.h Windows/W32Util/ShellUtil.h
Windows/W32Util/TabControl.cpp Windows/W32Util/TabControl.cpp
Windows/W32Util/TabControl.h Windows/W32Util/TabControl.h
Windows/W32Util/IatHook.h
Windows/W32Util/ContextMenu.h
Windows/W32Util/ContextMenu.cpp
Windows/W32Util/DarkMode.h
Windows/W32Util/DarkMode.cpp
Windows/W32Util/UAHMenuBar.h
Windows/W32Util/UAHMenuBar.cpp
Windows/WindowsHost.cpp Windows/WindowsHost.cpp
Windows/WindowsHost.h Windows/WindowsHost.h
Windows/MainWindow.cpp Windows/MainWindow.cpp
@ -2399,7 +2446,7 @@ set(WindowsFiles
list(APPEND LinkCommon ${CoreLibName} ${CMAKE_THREAD_LIBS_INIT}) list(APPEND LinkCommon ${CoreLibName} ${CMAKE_THREAD_LIBS_INIT})
if(WIN32) if(WIN32)
list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser mf mfplat mfreadwrite mfuuid shlwapi) list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser mf uxtheme mfplat mfreadwrite mfuuid shlwapi)
#setup_target_project(${TargetBin} Windows) #setup_target_project(${TargetBin} Windows)
list(APPEND NativeAppSource ${WindowsFiles}) list(APPEND NativeAppSource ${WindowsFiles})
endif() endif()
@ -2558,7 +2605,7 @@ if(TargetBin)
file(INSTALL "${CMAKE_SOURCE_DIR}/ext/vulkan/macOS/Frameworks/libMoltenVK.dylib" DESTINATION "${CMAKE_BINARY_DIR}/${TargetBin}.app/Contents/Frameworks/") file(INSTALL "${CMAKE_SOURCE_DIR}/ext/vulkan/macOS/Frameworks/libMoltenVK.dylib" DESTINATION "${CMAKE_BINARY_DIR}/${TargetBin}.app/Contents/Frameworks/")
if(USING_QT_UI) if(USING_QT_UI)
add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/Qt/macbundle.sh" "${CMAKE_BINARY_DIR}/PPSSPPQt.app") add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/Qt/macbundle.sh" "${CMAKE_BINARY_DIR}/PPSSPPQt.app")
else() elseif(NOT USE_SYSTEM_LIBSDL2)
add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/SDL/macbundle.sh" "${CMAKE_BINARY_DIR}/${TargetBin}.app" "${TargetBin}") add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/SDL/macbundle.sh" "${CMAKE_BINARY_DIR}/${TargetBin}.app" "${TargetBin}")
endif() endif()
endif() endif()

View file

@ -315,6 +315,14 @@ const u8* ARM64XEmitter::AlignCodePage()
return m_code; return m_code;
} }
const u8 *ARM64XEmitter::NopAlignCode16() {
int bytes = ((-(intptr_t)m_code) & 15);
for (int i = 0; i < bytes / 4; i++) {
Write32(0xD503201F); // official nop instruction
}
return m_code;
}
void ARM64XEmitter::FlushIcache() void ARM64XEmitter::FlushIcache()
{ {
FlushIcacheSection(m_lastCacheFlushEnd, m_code); FlushIcacheSection(m_lastCacheFlushEnd, m_code);

View file

@ -94,7 +94,7 @@ enum ARM64Reg
// R19-R28. R29 (FP), R30 (LR) are always saved and FP updated appropriately. // R19-R28. R29 (FP), R30 (LR) are always saved and FP updated appropriately.
const u32 ALL_CALLEE_SAVED = 0x1FF80000; const u32 ALL_CALLEE_SAVED = 0x1FF80000;
const u32 ALL_CALLEE_SAVED_FP = 0x0000FF00; // d8-d15 const u32 ALL_CALLEE_SAVED_FP = 0x0000FF00; // q8-q15
inline bool Is64Bit(ARM64Reg reg) { return (reg & 0x20) != 0; } inline bool Is64Bit(ARM64Reg reg) { return (reg & 0x20) != 0; }
inline bool IsSingle(ARM64Reg reg) { return (reg & 0xC0) == 0x40; } inline bool IsSingle(ARM64Reg reg) { return (reg & 0xC0) == 0x40; }
@ -401,6 +401,7 @@ public:
void ReserveCodeSpace(u32 bytes); void ReserveCodeSpace(u32 bytes);
const u8* AlignCode16(); const u8* AlignCode16();
const u8* AlignCodePage(); const u8* AlignCodePage();
const u8 *NopAlignCode16();
void FlushIcache(); void FlushIcache();
void FlushIcacheSection(const u8* start, const u8* end); void FlushIcacheSection(const u8* start, const u8* end);
u8* GetWritableCodePtr(); u8* GetWritableCodePtr();

View file

@ -32,14 +32,15 @@
#if defined(CPU_FEATURES_OS_LINUX) #if defined(CPU_FEATURES_OS_LINUX)
#define USE_CPU_FEATURES 1 #define USE_CPU_FEATURES 1
#endif #endif
#elif PPSSPP_ARCH(ARM64) && defined(__aarch64__) #elif PPSSPP_ARCH(ARM64)
#include "ext/cpu_features/include/cpuinfo_aarch64.h" #include "ext/cpu_features/include/cpuinfo_aarch64.h"
#if defined(CPU_FEATURES_OS_LINUX) || defined(CPU_FEATURES_OS_ANDROID) #if defined(CPU_FEATURES_OS_LINUX) || defined(CPU_FEATURES_OS_ANDROID) || defined(CPU_FEATURES_OS_WINDOWS)
#define USE_CPU_FEATURES 1 #define USE_CPU_FEATURES 1
#endif #endif
#endif #endif
#include <cstring>
#include <ctype.h> #include <ctype.h>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -54,7 +55,7 @@
std::string GetCPUBrandString(); std::string GetCPUBrandString();
#else #else
// No CPUID on ARM, so we'll have to read the registry // No CPUID on ARM, so we'll have to read the registry
#include <windows.h> #include "Common/CommonWindows.h"
std::string GetCPUBrandString() { std::string GetCPUBrandString() {
std::string cpu_string; std::string cpu_string;

View file

@ -613,6 +613,14 @@ const u8 *ARMXEmitter::AlignCode16()
return code; return code;
} }
const u8 *ARMXEmitter::NopAlignCode16() {
int bytes = ((-(intptr_t)code) & 15);
for (int i = 0; i < bytes / 4; i++) {
Write32(0xE320F000); // one of many possible nops
}
return code;
}
const u8 *ARMXEmitter::AlignCodePage() const u8 *ARMXEmitter::AlignCodePage()
{ {
ReserveCodeSpace((-(intptr_t)code) & 4095); ReserveCodeSpace((-(intptr_t)code) & 4095);

View file

@ -446,6 +446,8 @@ public:
void ReserveCodeSpace(u32 bytes); void ReserveCodeSpace(u32 bytes);
const u8 *AlignCode16(); const u8 *AlignCode16();
const u8 *AlignCodePage(); const u8 *AlignCodePage();
const u8 *NopAlignCode16();
void FlushIcache(); void FlushIcache();
void FlushIcacheSection(u8 *start, u8 *end); void FlushIcacheSection(u8 *start, u8 *end);
u8 *GetWritableCodePtr(); u8 *GetWritableCodePtr();

View file

@ -10,6 +10,11 @@
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/MemoryUtil.h" #include "Common/MemoryUtil.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <cstdio>
#include <switch.h>
#endif // PPSSPP_PLATFORM(SWITCH)
// Everything that needs to generate code should inherit from this. // Everything that needs to generate code should inherit from this.
// You get memory management for free, plus, you can use all emitter functions without // You get memory management for free, plus, you can use all emitter functions without
// having to prefix them with gen-> or something similar. // having to prefix them with gen-> or something similar.
@ -65,9 +70,20 @@ public:
// Call this before you generate any code. // Call this before you generate any code.
void AllocCodeSpace(int size) { void AllocCodeSpace(int size) {
region_size = size; region_size = size;
#if PPSSPP_PLATFORM(SWITCH)
Result rc = jitCreate(&jitController, size);
if(R_FAILED(rc)) {
printf("Failed to create Jitbuffer of size 0x%x err: 0x%x\n", size, rc);
}
printf("[NXJIT]: Initialized RX: %p RW: %p\n", jitController.rx_addr, jitController.rw_addr);
region = (u8 *)jitController.rx_addr;
writableRegion = (u8 *)jitController.rw_addr;
#else // PPSSPP_PLATFORM(SWITCH)
// The protection will be set to RW if PlatformIsWXExclusive. // The protection will be set to RW if PlatformIsWXExclusive.
region = (u8 *)AllocateExecutableMemory(region_size); region = (u8 *)AllocateExecutableMemory(region_size);
writableRegion = region; writableRegion = region;
#endif // !PPSSPP_PLATFORM(SWITCH)
T::SetCodePointer(region, writableRegion); T::SetCodePointer(region, writableRegion);
} }
@ -135,8 +151,13 @@ public:
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job. // Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
void FreeCodeSpace() { void FreeCodeSpace() {
#if !PPSSPP_PLATFORM(SWITCH)
ProtectMemoryPages(region, region_size, MEM_PROT_READ | MEM_PROT_WRITE); ProtectMemoryPages(region, region_size, MEM_PROT_READ | MEM_PROT_WRITE);
FreeExecutableMemory(region, region_size); FreeExecutableMemory(region, region_size);
#else // !PPSSPP_PLATFORM(SWITCH)
jitClose(&jitController);
printf("[NXJIT]: Jit closed\n");
#endif // PPSSPP_PLATFORM(SWITCH)
region = nullptr; region = nullptr;
writableRegion = nullptr; writableRegion = nullptr;
region_size = 0; region_size = 0;
@ -176,5 +197,7 @@ private:
const uint8_t *writeStart_ = nullptr; const uint8_t *writeStart_ = nullptr;
uint8_t *writableRegion = nullptr; uint8_t *writableRegion = nullptr;
size_t writeEstimated_ = 0; size_t writeEstimated_ = 0;
#if PPSSPP_PLATFORM(SWITCH)
Jit jitController;
#endif // PPSSPP_PLATFORM(SWITCH)
}; };

View file

@ -434,6 +434,7 @@
<ClInclude Include="Data\Text\Parsers.h" /> <ClInclude Include="Data\Text\Parsers.h" />
<ClInclude Include="Data\Text\WrapText.h" /> <ClInclude Include="Data\Text\WrapText.h" />
<ClInclude Include="FakeEmitter.h" /> <ClInclude Include="FakeEmitter.h" />
<ClInclude Include="File\AndroidContentURI.h" />
<ClInclude Include="File\AndroidStorage.h" /> <ClInclude Include="File\AndroidStorage.h" />
<ClInclude Include="File\DirListing.h" /> <ClInclude Include="File\DirListing.h" />
<ClInclude Include="File\DiskFree.h" /> <ClInclude Include="File\DiskFree.h" />
@ -449,11 +450,13 @@
<ClInclude Include="GPU\D3D9\D3D9ShaderCompiler.h" /> <ClInclude Include="GPU\D3D9\D3D9ShaderCompiler.h" />
<ClInclude Include="GPU\D3D9\D3D9StateCache.h" /> <ClInclude Include="GPU\D3D9\D3D9StateCache.h" />
<ClInclude Include="GPU\DataFormat.h" /> <ClInclude Include="GPU\DataFormat.h" />
<ClInclude Include="GPU\GPUBackendCommon.h" />
<ClInclude Include="GPU\MiscTypes.h" /> <ClInclude Include="GPU\MiscTypes.h" />
<ClInclude Include="GPU\OpenGL\DataFormatGL.h" /> <ClInclude Include="GPU\OpenGL\DataFormatGL.h" />
<ClInclude Include="GPU\OpenGL\gl3stub.h" /> <ClInclude Include="GPU\OpenGL\gl3stub.h" />
<ClInclude Include="GPU\OpenGL\GLFeatures.h" /> <ClInclude Include="GPU\OpenGL\GLFeatures.h" />
<ClInclude Include="GPU\OpenGL\GLFrameData.h" /> <ClInclude Include="GPU\OpenGL\GLFrameData.h" />
<ClInclude Include="GPU\OpenGL\GLMemory.h" />
<ClInclude Include="GPU\OpenGL\GLQueueRunner.h" /> <ClInclude Include="GPU\OpenGL\GLQueueRunner.h" />
<ClInclude Include="GPU\OpenGL\GLRenderManager.h" /> <ClInclude Include="GPU\OpenGL\GLRenderManager.h" />
<ClInclude Include="GPU\OpenGL\GLSLProgram.h" /> <ClInclude Include="GPU\OpenGL\GLSLProgram.h" />
@ -558,6 +561,7 @@
<ClInclude Include="Swap.h" /> <ClInclude Include="Swap.h" />
<ClInclude Include="SysError.h" /> <ClInclude Include="SysError.h" />
<ClInclude Include="System\Display.h" /> <ClInclude Include="System\Display.h" />
<ClInclude Include="System\OSD.h" />
<ClInclude Include="System\Request.h" /> <ClInclude Include="System\Request.h" />
<ClInclude Include="System\NativeApp.h" /> <ClInclude Include="System\NativeApp.h" />
<ClInclude Include="System\System.h" /> <ClInclude Include="System\System.h" />
@ -573,6 +577,7 @@
<ClInclude Include="TimeUtil.h" /> <ClInclude Include="TimeUtil.h" />
<ClInclude Include="UI\AsyncImageFileView.h" /> <ClInclude Include="UI\AsyncImageFileView.h" />
<ClInclude Include="UI\Context.h" /> <ClInclude Include="UI\Context.h" />
<ClInclude Include="UI\IconCache.h" />
<ClInclude Include="UI\PopupScreens.h" /> <ClInclude Include="UI\PopupScreens.h" />
<ClInclude Include="UI\Root.h" /> <ClInclude Include="UI\Root.h" />
<ClInclude Include="UI\Screen.h" /> <ClInclude Include="UI\Screen.h" />
@ -857,6 +862,7 @@
</ClCompile> </ClCompile>
<ClCompile Include="ArmEmitter.cpp" /> <ClCompile Include="ArmEmitter.cpp" />
<ClCompile Include="Buffer.cpp" /> <ClCompile Include="Buffer.cpp" />
<ClCompile Include="Data\Collections\FastVec.h" />
<ClCompile Include="Data\Color\RGBAUtil.cpp" /> <ClCompile Include="Data\Color\RGBAUtil.cpp" />
<ClCompile Include="Data\Convert\SmallDataConvert.cpp" /> <ClCompile Include="Data\Convert\SmallDataConvert.cpp" />
<ClCompile Include="Data\Encoding\Base64.cpp" /> <ClCompile Include="Data\Encoding\Base64.cpp" />
@ -874,6 +880,7 @@
<ClCompile Include="Data\Text\I18n.cpp" /> <ClCompile Include="Data\Text\I18n.cpp" />
<ClCompile Include="Data\Text\Parsers.cpp" /> <ClCompile Include="Data\Text\Parsers.cpp" />
<ClCompile Include="Data\Text\WrapText.cpp" /> <ClCompile Include="Data\Text\WrapText.cpp" />
<ClCompile Include="File\AndroidContentURI.cpp" />
<ClCompile Include="File\AndroidStorage.cpp" /> <ClCompile Include="File\AndroidStorage.cpp" />
<ClCompile Include="File\DirListing.cpp" /> <ClCompile Include="File\DirListing.cpp" />
<ClCompile Include="File\DiskFree.cpp" /> <ClCompile Include="File\DiskFree.cpp" />
@ -890,10 +897,12 @@
<ClCompile Include="GPU\D3D9\D3D9ShaderCompiler.cpp" /> <ClCompile Include="GPU\D3D9\D3D9ShaderCompiler.cpp" />
<ClCompile Include="GPU\D3D9\D3D9StateCache.cpp" /> <ClCompile Include="GPU\D3D9\D3D9StateCache.cpp" />
<ClCompile Include="GPU\D3D9\thin3d_d3d9.cpp" /> <ClCompile Include="GPU\D3D9\thin3d_d3d9.cpp" />
<ClCompile Include="GPU\GPUBackendCommon.cpp" />
<ClCompile Include="GPU\OpenGL\DataFormatGL.cpp" /> <ClCompile Include="GPU\OpenGL\DataFormatGL.cpp" />
<ClCompile Include="GPU\OpenGL\gl3stub.c" /> <ClCompile Include="GPU\OpenGL\gl3stub.c" />
<ClCompile Include="GPU\OpenGL\GLFeatures.cpp" /> <ClCompile Include="GPU\OpenGL\GLFeatures.cpp" />
<ClCompile Include="GPU\OpenGL\GLFrameData.cpp" /> <ClCompile Include="GPU\OpenGL\GLFrameData.cpp" />
<ClCompile Include="GPU\OpenGL\GLMemory.cpp" />
<ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp" /> <ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp" />
<ClCompile Include="GPU\OpenGL\GLRenderManager.cpp" /> <ClCompile Include="GPU\OpenGL\GLRenderManager.cpp" />
<ClCompile Include="GPU\OpenGL\GLSLProgram.cpp" /> <ClCompile Include="GPU\OpenGL\GLSLProgram.cpp" />
@ -1014,6 +1023,7 @@
<ClCompile Include="OSVersion.cpp" /> <ClCompile Include="OSVersion.cpp" />
<ClCompile Include="StringUtils.cpp" /> <ClCompile Include="StringUtils.cpp" />
<ClCompile Include="System\Display.cpp" /> <ClCompile Include="System\Display.cpp" />
<ClCompile Include="System\OSD.cpp" />
<ClCompile Include="System\Request.cpp" /> <ClCompile Include="System\Request.cpp" />
<ClCompile Include="Thread\ParallelLoop.cpp" /> <ClCompile Include="Thread\ParallelLoop.cpp" />
<ClCompile Include="Thread\ThreadManager.cpp" /> <ClCompile Include="Thread\ThreadManager.cpp" />
@ -1022,6 +1032,7 @@
<ClCompile Include="TimeUtil.cpp" /> <ClCompile Include="TimeUtil.cpp" />
<ClCompile Include="UI\AsyncImageFileView.cpp" /> <ClCompile Include="UI\AsyncImageFileView.cpp" />
<ClCompile Include="UI\Context.cpp" /> <ClCompile Include="UI\Context.cpp" />
<ClCompile Include="UI\IconCache.cpp" />
<ClCompile Include="UI\PopupScreens.cpp" /> <ClCompile Include="UI\PopupScreens.cpp" />
<ClCompile Include="UI\Root.cpp" /> <ClCompile Include="UI\Root.cpp" />
<ClCompile Include="UI\Screen.cpp" /> <ClCompile Include="UI\Screen.cpp" />

View file

@ -449,9 +449,6 @@
<ClInclude Include="Render\ManagedTexture.h"> <ClInclude Include="Render\ManagedTexture.h">
<Filter>Render</Filter> <Filter>Render</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="GPU\MiscTypes.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h"> <ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h">
<Filter>GPU\Vulkan</Filter> <Filter>GPU\Vulkan</Filter>
</ClInclude> </ClInclude>
@ -497,6 +494,24 @@
<ClInclude Include="System\Request.h"> <ClInclude Include="System\Request.h">
<Filter>System</Filter> <Filter>System</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="File\AndroidContentURI.h">
<Filter>File</Filter>
</ClInclude>
<ClInclude Include="GPU\OpenGL\GLMemory.h">
<Filter>GPU\OpenGL</Filter>
</ClInclude>
<ClInclude Include="GPU\GPUBackendCommon.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="GPU\MiscTypes.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="UI\IconCache.h">
<Filter>UI</Filter>
</ClInclude>
<ClInclude Include="System\OSD.h">
<Filter>System</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="ABI.cpp" /> <ClCompile Include="ABI.cpp" />
@ -926,6 +941,24 @@
<ClCompile Include="System\Request.cpp"> <ClCompile Include="System\Request.cpp">
<Filter>System</Filter> <Filter>System</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="File\AndroidContentURI.cpp">
<Filter>File</Filter>
</ClCompile>
<ClCompile Include="GPU\OpenGL\GLMemory.cpp">
<Filter>GPU\OpenGL</Filter>
</ClCompile>
<ClCompile Include="Data\Collections\FastVec.h">
<Filter>Data\Collections</Filter>
</ClCompile>
<ClCompile Include="GPU\GPUBackendCommon.cpp">
<Filter>GPU</Filter>
</ClCompile>
<ClCompile Include="UI\IconCache.cpp">
<Filter>UI</Filter>
</ClCompile>
<ClCompile Include="System\OSD.cpp">
<Filter>System</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Filter Include="Crypto"> <Filter Include="Crypto">

View file

@ -32,6 +32,9 @@
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) #if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#define Crash() {asm ("int $3");} #define Crash() {asm ("int $3");}
#elif PPSSPP_PLATFORM(SWITCH)
// TODO: Implement Crash() for Switch, lets not use breakpoint for the time being
#define Crash() {*((volatile u32 *)0x0) = 0xDEADC0DE;}
#elif PPSSPP_ARCH(ARM) #elif PPSSPP_ARCH(ARM)
#define Crash() {asm ("bkpt #0");} #define Crash() {asm ("bkpt #0");}
#elif PPSSPP_ARCH(ARM64) #elif PPSSPP_ARCH(ARM64)

View file

@ -36,6 +36,39 @@ typedef signed __int64 s64;
#else #else
#ifdef __SWITCH__
// Some HID conflicts
#define KEY_UP PKEY_UP
#define KEY_DOWN PKEY_DOWN
// Other conflicts
#define Event _Event
#define Framebuffer _Framebuffer
#define Waitable _Waitable
#define ThreadContext _ThreadContext
#include <switch.h>
// Cleanup
#undef KEY_UP
#undef KEY_DOWN
#undef Event
#undef Framebuffer
#undef Waitable
#undef ThreadContext
// Conflicting types with libnx
#ifndef _u64
#define u64 _u64
#endif // _u64
#ifndef s64
#define s64 _s64
#endif // _s64
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
#endif // __SWITCH__
typedef unsigned char u8; typedef unsigned char u8;
typedef unsigned short u16; typedef unsigned short u16;
typedef unsigned int u32; typedef unsigned int u32;

View file

@ -62,7 +62,7 @@
/* /*
* MD5 context setup * MD5 context setup
*/ */
void md5_starts( md5_context *ctx ) void ppsspp_md5_starts( md5_context *ctx )
{ {
ctx->total[0] = 0; ctx->total[0] = 0;
ctx->total[1] = 0; ctx->total[1] = 0;
@ -73,7 +73,7 @@ void md5_starts( md5_context *ctx )
ctx->state[3] = 0x10325476; ctx->state[3] = 0x10325476;
} }
static void md5_process( md5_context *ctx, unsigned char data[64] ) static void ppsspp_md5_process( md5_context *ctx, unsigned char data[64] )
{ {
unsigned long X[16], A, B, C, D; unsigned long X[16], A, B, C, D;
@ -199,7 +199,7 @@ static void md5_process( md5_context *ctx, unsigned char data[64] )
/* /*
* MD5 process buffer * MD5 process buffer
*/ */
void md5_update( md5_context *ctx, unsigned char *input, int ilen ) void ppsspp_md5_update( md5_context *ctx, unsigned char *input, int ilen )
{ {
int fill; int fill;
unsigned long left; unsigned long left;
@ -220,7 +220,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
{ {
memcpy( (void *) (ctx->buffer + left), memcpy( (void *) (ctx->buffer + left),
(void *) input, fill ); (void *) input, fill );
md5_process( ctx, ctx->buffer ); ppsspp_md5_process( ctx, ctx->buffer );
input += fill; input += fill;
ilen -= fill; ilen -= fill;
left = 0; left = 0;
@ -228,7 +228,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
while( ilen >= 64 ) while( ilen >= 64 )
{ {
md5_process( ctx, input ); ppsspp_md5_process( ctx, input );
input += 64; input += 64;
ilen -= 64; ilen -= 64;
} }
@ -251,7 +251,7 @@ static const unsigned char md5_padding[64] =
/* /*
* MD5 final digest * MD5 final digest
*/ */
void md5_finish( md5_context *ctx, unsigned char output[16] ) void ppsspp_md5_finish( md5_context *ctx, unsigned char output[16] )
{ {
unsigned long last, padn; unsigned long last, padn;
unsigned long high, low; unsigned long high, low;
@ -267,8 +267,8 @@ void md5_finish( md5_context *ctx, unsigned char output[16] )
last = ctx->total[0] & 0x3F; last = ctx->total[0] & 0x3F;
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
md5_update( ctx, (unsigned char *) md5_padding, padn ); ppsspp_md5_update( ctx, (unsigned char *) md5_padding, padn );
md5_update( ctx, msglen, 8 ); ppsspp_md5_update( ctx, msglen, 8 );
PUT_ULONG_LE( ctx->state[0], output, 0 ); PUT_ULONG_LE( ctx->state[0], output, 0 );
PUT_ULONG_LE( ctx->state[1], output, 4 ); PUT_ULONG_LE( ctx->state[1], output, 4 );
@ -279,13 +279,13 @@ void md5_finish( md5_context *ctx, unsigned char output[16] )
/* /*
* output = MD5( input buffer ) * output = MD5( input buffer )
*/ */
void md5( unsigned char *input, int ilen, unsigned char output[16] ) void ppsspp_md5( unsigned char *input, int ilen, unsigned char output[16] )
{ {
md5_context ctx; md5_context ctx;
md5_starts( &ctx ); ppsspp_md5_starts( &ctx );
md5_update( &ctx, input, ilen ); ppsspp_md5_update( &ctx, input, ilen );
md5_finish( &ctx, output ); ppsspp_md5_finish( &ctx, output );
memset( &ctx, 0, sizeof( md5_context ) ); memset( &ctx, 0, sizeof( md5_context ) );
} }
@ -293,14 +293,14 @@ void md5( unsigned char *input, int ilen, unsigned char output[16] )
/* /*
* MD5 HMAC context setup * MD5 HMAC context setup
*/ */
void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen ) void ppsspp_md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
{ {
int i; int i;
unsigned char sum[16]; unsigned char sum[16];
if( keylen > 64 ) if( keylen > 64 )
{ {
md5( key, keylen, sum ); ppsspp_md5( key, keylen, sum );
keylen = 16; keylen = 16;
key = sum; key = sum;
} }
@ -314,8 +314,8 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
ctx->opad[i] = (unsigned char)( ctx->opad[i] ^ key[i] ); ctx->opad[i] = (unsigned char)( ctx->opad[i] ^ key[i] );
} }
md5_starts( ctx ); ppsspp_md5_starts( ctx );
md5_update( ctx, ctx->ipad, 64 ); ppsspp_md5_update( ctx, ctx->ipad, 64 );
memset( sum, 0, sizeof( sum ) ); memset( sum, 0, sizeof( sum ) );
} }
@ -323,23 +323,23 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
/* /*
* MD5 HMAC process buffer * MD5 HMAC process buffer
*/ */
void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen ) void ppsspp_md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen )
{ {
md5_update( ctx, input, ilen ); ppsspp_md5_update( ctx, input, ilen );
} }
/* /*
* MD5 HMAC final digest * MD5 HMAC final digest
*/ */
void md5_hmac_finish( md5_context *ctx, unsigned char output[16] ) void ppsspp_md5_hmac_finish( md5_context *ctx, unsigned char output[16] )
{ {
unsigned char tmpbuf[16]; unsigned char tmpbuf[16];
md5_finish( ctx, tmpbuf ); ppsspp_md5_finish( ctx, tmpbuf );
md5_starts( ctx ); ppsspp_md5_starts( ctx );
md5_update( ctx, ctx->opad, 64 ); ppsspp_md5_update( ctx, ctx->opad, 64 );
md5_update( ctx, tmpbuf, 16 ); ppsspp_md5_update( ctx, tmpbuf, 16 );
md5_finish( ctx, output ); ppsspp_md5_finish( ctx, output );
memset( tmpbuf, 0, sizeof( tmpbuf ) ); memset( tmpbuf, 0, sizeof( tmpbuf ) );
} }
@ -347,14 +347,14 @@ void md5_hmac_finish( md5_context *ctx, unsigned char output[16] )
/* /*
* output = HMAC-MD5( hmac key, input buffer ) * output = HMAC-MD5( hmac key, input buffer )
*/ */
void md5_hmac( unsigned char *key, int keylen, unsigned char *input, int ilen, void ppsspp_md5_hmac( unsigned char *key, int keylen, unsigned char *input, int ilen,
unsigned char output[16] ) unsigned char output[16] )
{ {
md5_context ctx; md5_context ctx;
md5_hmac_starts( &ctx, key, keylen ); ppsspp_md5_hmac_starts( &ctx, key, keylen );
md5_hmac_update( &ctx, input, ilen ); ppsspp_md5_hmac_update( &ctx, input, ilen );
md5_hmac_finish( &ctx, output ); ppsspp_md5_hmac_finish( &ctx, output );
memset( &ctx, 0, sizeof( md5_context ) ); memset( &ctx, 0, sizeof( md5_context ) );
} }
@ -464,7 +464,7 @@ static const unsigned char md5_hmac_test_sum[7][16] =
/* /*
* Checkup routine * Checkup routine
*/ */
int md5_self_test( int verbose ) int ppsspp_md5_self_test( int verbose )
{ {
int i, buflen; int i, buflen;
unsigned char buf[1024]; unsigned char buf[1024];

View file

@ -46,7 +46,7 @@ extern "C" {
* *
* \param ctx context to be initialized * \param ctx context to be initialized
*/ */
void md5_starts( md5_context *ctx ); void ppsspp_md5_starts( md5_context *ctx );
/** /**
* \brief MD5 process buffer * \brief MD5 process buffer
@ -55,7 +55,7 @@ void md5_starts( md5_context *ctx );
* \param input buffer holding the data * \param input buffer holding the data
* \param ilen length of the input data * \param ilen length of the input data
*/ */
void md5_update( md5_context *ctx, unsigned char *input, int ilen ); void ppsspp_md5_update( md5_context *ctx, unsigned char *input, int ilen );
/** /**
* \brief MD5 final digest * \brief MD5 final digest
@ -63,7 +63,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen );
* \param ctx MD5 context * \param ctx MD5 context
* \param output MD5 checksum result * \param output MD5 checksum result
*/ */
void md5_finish( md5_context *ctx, unsigned char output[16] ); void ppsspp_md5_finish( md5_context *ctx, unsigned char output[16] );
/** /**
* \brief Output = MD5( input buffer ) * \brief Output = MD5( input buffer )
@ -72,7 +72,7 @@ void md5_finish( md5_context *ctx, unsigned char output[16] );
* \param ilen length of the input data * \param ilen length of the input data
* \param output MD5 checksum result * \param output MD5 checksum result
*/ */
void md5( unsigned char *input, int ilen, unsigned char output[16] ); void ppsspp_md5( unsigned char *input, int ilen, unsigned char output[16] );
/** /**
* \brief Output = MD5( file contents ) * \brief Output = MD5( file contents )
@ -83,7 +83,7 @@ void md5( unsigned char *input, int ilen, unsigned char output[16] );
* \return 0 if successful, 1 if fopen failed, * \return 0 if successful, 1 if fopen failed,
* or 2 if fread failed * or 2 if fread failed
*/ */
int md5_file( char *path, unsigned char output[16] ); int ppsspp_md5_file( char *path, unsigned char output[16] );
/** /**
* \brief MD5 HMAC context setup * \brief MD5 HMAC context setup
@ -92,7 +92,7 @@ int md5_file( char *path, unsigned char output[16] );
* \param key HMAC secret key * \param key HMAC secret key
* \param keylen length of the HMAC key * \param keylen length of the HMAC key
*/ */
void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen ); void ppsspp_md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
/** /**
* \brief MD5 HMAC process buffer * \brief MD5 HMAC process buffer
@ -101,7 +101,7 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
* \param input buffer holding the data * \param input buffer holding the data
* \param ilen length of the input data * \param ilen length of the input data
*/ */
void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen ); void ppsspp_md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
/** /**
* \brief MD5 HMAC final digest * \brief MD5 HMAC final digest
@ -109,7 +109,7 @@ void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
* \param ctx HMAC context * \param ctx HMAC context
* \param output MD5 HMAC checksum result * \param output MD5 HMAC checksum result
*/ */
void md5_hmac_finish( md5_context *ctx, unsigned char output[16] ); void ppsspp_md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
/** /**
* \brief Output = HMAC-MD5( hmac key, input buffer ) * \brief Output = HMAC-MD5( hmac key, input buffer )
@ -120,7 +120,7 @@ void md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
* \param ilen length of the input data * \param ilen length of the input data
* \param output HMAC-MD5 result * \param output HMAC-MD5 result
*/ */
void md5_hmac( unsigned char *key, int keylen, void ppsspp_md5_hmac( unsigned char *key, int keylen,
unsigned char *input, int ilen, unsigned char *input, int ilen,
unsigned char output[16] ); unsigned char output[16] );
@ -129,7 +129,7 @@ void md5_hmac( unsigned char *key, int keylen,
* *
* \return 0 if successful, or 1 if the test failed * \return 0 if successful, or 1 if the test failed
*/ */
int md5_self_test( int verbose ); int ppsspp_md5_self_test( int verbose );
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -0,0 +1,157 @@
#pragma once
// Yet another replacement for std::vector, this time for use in graphics queues.
// Its major difference is that you can append uninitialized structures and initialize them after.
// This is not allows by std::vector but is very useful for our sometimes oversized unions.
// Also, copies during resize are done by memcpy, not by any move constructor or similar.
#include <cstdlib>
#include <cstring>
#ifdef _DEBUG
#include "Common/Log.h"
#endif
template<class T>
class FastVec {
public:
FastVec() {}
FastVec(size_t initialCapacity) {
capacity_ = initialCapacity;
data_ = (T *)malloc(initialCapacity * sizeof(T));
}
~FastVec() { if (data_) free(data_); }
T &push_uninitialized() {
if (size_ < capacity_) {
size_++;
return data_[size_ - 1];
} else {
ExtendByOne();
return data_[size_ - 1];
}
}
void push_back(const T &t) {
T &dest = push_uninitialized();
dest = t;
}
// Move constructor
FastVec(FastVec &&other) {
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
FastVec &operator=(FastVec &&other) {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
return *this;
}
// No copy constructor.
FastVec(const FastVec &other) = delete;
FastVec &operator=(const FastVec &other) = delete;
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
void clear() { size_ = 0; }
bool empty() const { return size_ == 0; }
T *begin() { return data_; }
T *end() { return data_ + size_; }
const T *begin() const { return data_; }
const T *end() const { return data_ + size_; }
// Out of bounds (past size() - 1) is undefined behavior.
T &operator[] (const size_t index) { return data_[index]; }
const T &operator[] (const size_t index) const { return data_[index]; }
T &at(const size_t index) { return data_[index]; }
const T &at(const size_t index) const { return data_[index]; }
// These two are invalid if empty().
const T &back() const { return (*this)[size() - 1]; }
const T &front() const { return (*this)[0]; }
// Limited functionality for inserts and similar, add as needed.
T &insert(T *iter) {
int pos = iter - data_;
ExtendByOne();
if (pos + 1 < size_) {
memmove(data_ + pos + 1, data_ + pos, (size_ - pos) * sizeof(T));
}
return data_[pos];
}
void insert(T *destIter, const T *beginIter, const T *endIter) {
int pos = destIter - data_;
if (beginIter == endIter)
return;
size_t newItems = endIter - beginIter;
IncreaseCapacityTo(size_ + newItems);
memmove(data_ + pos + newItems, data_ + pos, (size_ - pos) * sizeof(T));
memcpy(data_ + pos, beginIter, newItems * sizeof(T));
size_ += newItems;
}
void resize(size_t size) {
if (size < size_) {
size_ = size;
} else {
// TODO
}
}
void reserve(size_t newCapacity) {
IncreaseCapacityTo(newCapacity);
}
void LockCapacity() {
#ifdef _DEBUG
capacityLocked_ = true;
#endif
}
private:
void IncreaseCapacityTo(size_t newCapacity) {
#ifdef _DEBUG
_dbg_assert_(!capacityLocked_);
#endif
if (newCapacity <= capacity_)
return;
T *oldData = data_;
data_ = (T *)malloc(sizeof(T) * newCapacity);
if (capacity_ != 0) {
memcpy(data_, oldData, sizeof(T) * size_);
free(oldData);
}
capacity_ = newCapacity;
}
void ExtendByOne() {
size_t newCapacity = capacity_ * 2;
if (newCapacity < 16) {
newCapacity = 16;
}
IncreaseCapacityTo(newCapacity);
size_++;
}
size_t size_ = 0;
size_t capacity_ = 0;
T *data_ = nullptr;
#ifdef _DEBUG
bool capacityLocked_ = false;
#endif
};

View file

@ -56,7 +56,7 @@ public:
return NullValue; return NullValue;
} }
// Returns false if we already had the key! Which is a bit different. // Asserts if we already had the key!
bool Insert(const Key &key, Value value) { bool Insert(const Key &key, Value value) {
// Check load factor, resize if necessary. We never shrink. // Check load factor, resize if necessary. We never shrink.
if (count_ > capacity_ / 2) { if (count_ > capacity_ / 2) {

View file

@ -187,7 +187,7 @@ struct FixedTinyVec {
bool operator == (const FixedTinyVec<T, MaxSize> &other) const { bool operator == (const FixedTinyVec<T, MaxSize> &other) const {
if (count_ != other.count_) if (count_ != other.count_)
return false; return false;
for (size_t i = 0; i < count_; i++) { for (int i = 0; i < count_; i++) {
if (!(data_[i] == other.data_[i])) { if (!(data_[i] == other.data_[i])) {
return false; return false;
} }

View file

@ -5,6 +5,8 @@
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
#include <inttypes.h>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#endif #endif
@ -192,7 +194,7 @@ void Section::Set(const char* key, uint32_t newValue) {
} }
void Section::Set(const char* key, uint64_t newValue) { void Section::Set(const char* key, uint64_t newValue) {
Set(key, StringFromFormat("0x%016lx", newValue).c_str()); Set(key, StringFromFormat("0x%016" PRIx64, newValue).c_str());
} }
void Section::Set(const char* key, float newValue) { void Section::Set(const char* key, float newValue) {

View file

@ -40,6 +40,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
"UI Elements", "UI Elements",
"Upgrade", "Upgrade",
"VR", "VR",
"Achievements",
}; };
I18NRepo g_i18nrepo; I18NRepo g_i18nrepo;

View file

@ -56,6 +56,7 @@ enum class I18NCat : uint8_t {
UI_ELEMENTS, UI_ELEMENTS,
UPGRADE, UPGRADE,
VR, VR,
ACHIEVEMENTS,
CATEGORY_COUNT, CATEGORY_COUNT,
NONE = CATEGORY_COUNT, NONE = CATEGORY_COUNT,
}; };

View file

@ -0,0 +1,206 @@
#include "Common/File/AndroidContentURI.h"
bool AndroidContentURI::Parse(const std::string &path) {
const char *prefix = "content://";
if (!startsWith(path, prefix)) {
return false;
}
std::string components = path.substr(strlen(prefix));
std::vector<std::string> parts;
SplitString(components, '/', parts);
if (parts.size() == 3) {
// Single file URI.
provider = parts[0];
if (parts[1] == "tree") {
// Single directory URI.
// Not sure when we encounter these?
// file empty signals this type.
root = UriDecode(parts[2]);
return true;
} else if (parts[1] == "document") {
// root empty signals this type.
file = UriDecode(parts[2]);
return true;
} else {
// What's this?
return false;
}
} else if (parts.size() == 5) {
// Tree URI
provider = parts[0];
if (parts[1] != "tree") {
return false;
}
root = UriDecode(parts[2]);
if (parts[3] != "document") {
return false;
}
file = UriDecode(parts[4]);
// Sanity check file path.
return startsWith(file, root);
} else {
// Invalid Content URI
return false;
}
}
AndroidContentURI AndroidContentURI::WithRootFilePath(const std::string &filePath) {
if (root.empty()) {
ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs.");
return *this;
}
AndroidContentURI uri = *this;
uri.file = uri.root;
if (!filePath.empty()) {
uri.file += "/" + filePath;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath) {
AndroidContentURI uri = *this;
if (uri.file.empty()) {
// Not sure what to do.
return uri;
}
if (uri.file.back() == ':') {
// Special case handling for Document URIs: Treat the ':' as a directory separator too (but preserved in the filename).
uri.file = uri.file + filePath;
} else {
uri.file = uri.file + "/" + filePath;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithExtraExtension(const std::string &extension) {
AndroidContentURI uri = *this;
uri.file = uri.file + extension;
return uri;
}
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
AndroidContentURI uri = *this;
if (endsWithNoCase(file, oldExtension)) {
uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &newExtension) const {
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
AndroidContentURI uri = *this;
if (file.empty()) {
return uri;
}
std::string extension = GetFileExtension();
uri.file = file.substr(0, file.size() - extension.size()) + newExtension;
return uri;
}
bool AndroidContentURI::CanNavigateUp() const {
if (IsTreeURI()) {
return file.size() > root.size();
} else {
return file.find(':') != std::string::npos && file.back() != ':';
}
}
// Only goes downwards in hierarchies. No ".." will ever be generated.
bool AndroidContentURI::ComputePathTo(const AndroidContentURI &other, std::string &path) const {
size_t offset = FilePath().size() + 1;
std::string otherFilePath = other.FilePath();
if (offset >= otherFilePath.size()) {
ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str());
return false;
}
path = other.FilePath().substr(FilePath().size() + 1);
return true;
}
std::string AndroidContentURI::GetFileExtension() const {
size_t pos = file.rfind(".");
if (pos == std::string::npos) {
return "";
}
size_t slash_pos = file.rfind("/");
if (slash_pos != std::string::npos && slash_pos > pos) {
// Don't want to detect "df/file" from "/as.df/file"
return "";
}
std::string ext = file.substr(pos);
for (size_t i = 0; i < ext.size(); i++) {
ext[i] = tolower(ext[i]);
}
return ext;
}
std::string AndroidContentURI::GetLastPart() const {
if (file.empty()) {
// Can't do anything anyway.
return std::string();
}
if (!CanNavigateUp()) {
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return std::string();
}
if (file.back() == ':') {
return file;
}
return file.substr(colon + 1);
}
size_t slash = file.rfind('/');
if (slash == std::string::npos) {
// ok, look for the final colon. If it's the last char, we would have been caught above in !CanNavigateUp.
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return std::string();
}
return file.substr(colon + 1);
}
std::string part = file.substr(slash + 1);
return part;
}
bool AndroidContentURI::NavigateUp() {
if (!CanNavigateUp()) {
return false;
}
size_t slash = file.rfind('/');
if (slash == std::string::npos) {
// ok, look for the final colon.
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return false;
}
file = file.substr(0, colon + 1); // Note: we include the colon in these paths.
return true;
}
file = file.substr(0, slash);
return true;
}
std::string AndroidContentURI::ToString() const {
if (file.empty()) {
// Tree URI
return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str());
} else if (root.empty()) {
// Single file URI
return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str());
} else {
// File URI from Tree
return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str());
}
}

View file

@ -0,0 +1,73 @@
#pragma once
#include <string>
#include "Common/StringUtils.h"
#include "Common/Net/URL.h"
#include "Common/Log.h"
// Utility to deal with Android storage URIs of the forms:
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
// This file compiles on all platforms, to reduce the need for ifdefs.
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
// consider them pretty stable. Additionally, the official Document library just manipulates the URIs
// in similar ways...
class AndroidContentURI {
private:
std::string provider;
std::string root;
std::string file;
public:
AndroidContentURI() {}
explicit AndroidContentURI(const std::string &path) {
Parse(path);
}
bool Parse(const std::string &path);
AndroidContentURI WithRootFilePath(const std::string &filePath);
AndroidContentURI WithComponent(const std::string &filePath);
AndroidContentURI WithExtraExtension(const std::string &extension);
AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const;
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const;
bool CanNavigateUp() const;
// Only goes downwards in hierarchies. No ".." will ever be generated.
bool ComputePathTo(const AndroidContentURI &other, std::string &path) const;
std::string GetFileExtension() const;
std::string GetLastPart() const;
bool NavigateUp();
bool TreeContains(const AndroidContentURI &fileURI) {
if (root.empty()) {
return false;
}
return startsWith(fileURI.file, root);
}
std::string ToString() const;
// Never store the output of this, only show it to the user.
std::string ToVisualString() const {
return file;
}
const std::string &FilePath() const {
return file;
}
const std::string &RootPath() const {
return root.empty() ? file : root;
}
bool IsTreeURI() const {
return !root.empty();
}
};

View file

@ -25,7 +25,6 @@
#include "ppsspp_config.h" #include "ppsspp_config.h"
#include "android/jni/app-android.h" #include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <unistd.h> #include <unistd.h>
@ -33,12 +32,14 @@
#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L #define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
#endif #endif
#endif #endif
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <memory> #include <memory>
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/LogReporting.h" #include "Common/LogReporting.h"
#include "Common/File/AndroidContentURI.h"
#include "Common/File/FileUtil.h" #include "Common/File/FileUtil.h"
#include "Common/StringUtils.h" #include "Common/StringUtils.h"
#include "Common/SysError.h" #include "Common/SysError.h"

View file

@ -4,13 +4,13 @@
#include <cstring> #include <cstring>
#include "Common/File/Path.h" #include "Common/File/Path.h"
#include "Common/File/AndroidContentURI.h"
#include "Common/File/FileUtil.h" #include "Common/File/FileUtil.h"
#include "Common/StringUtils.h" #include "Common/StringUtils.h"
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Encoding/Utf8.h"
#include "android/jni/app-android.h" #include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) #if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
#include "UWP/UWPHelpers/StorageManager.h" #include "UWP/UWPHelpers/StorageManager.h"
@ -161,7 +161,7 @@ std::string Path::GetFilename() const {
return path_; return path_;
} }
static std::string GetExtFromString(const std::string &str) { std::string GetExtFromString(const std::string &str) {
size_t pos = str.rfind("."); size_t pos = str.rfind(".");
if (pos == std::string::npos) { if (pos == std::string::npos) {
return ""; return "";
@ -181,7 +181,7 @@ static std::string GetExtFromString(const std::string &str) {
std::string Path::GetFileExtension() const { std::string Path::GetFileExtension() const {
if (type_ == PathType::CONTENT_URI) { if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_); AndroidContentURI uri(path_);
return GetExtFromString(uri.FilePath()); return uri.GetFileExtension();
} }
return GetExtFromString(path_); return GetExtFromString(path_);
} }
@ -262,6 +262,15 @@ std::wstring Path::ToWString() const {
} }
return w; return w;
} }
std::string Path::ToCString() const {
std::string w = path_;
for (size_t i = 0; i < w.size(); i++) {
if (w[i] == '/') {
w[i] = '\\';
}
}
return w;
}
#endif #endif
std::string Path::ToVisualString(const char *relativeRoot) const { std::string Path::ToVisualString(const char *relativeRoot) const {

View file

@ -90,6 +90,11 @@ public:
#if PPSSPP_PLATFORM(WINDOWS) #if PPSSPP_PLATFORM(WINDOWS)
std::wstring ToWString() const; std::wstring ToWString() const;
std::string ToCString() const; // Flips the slashes back to Windows standard, but string still UTF-8.
#else
std::string ToCString() const {
return ToString();
}
#endif #endif
// Pass in a relative root to turn the path into a relative path - if it is one! // Pass in a relative root to turn the path into a relative path - if it is one!
@ -133,6 +138,8 @@ private:
PathType type_; PathType type_;
}; };
// Utility function for parsing out file extensions.
std::string GetExtFromString(const std::string &str);
// Utility function for fixing the case of paths. Only present on Unix-like systems. // Utility function for fixing the case of paths. Only present on Unix-like systems.

View file

@ -17,7 +17,6 @@
#if PPSSPP_PLATFORM(ANDROID) #if PPSSPP_PLATFORM(ANDROID)
#include "android/jni/app-android.h" #include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#endif #endif
bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) { bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {

View file

@ -4,6 +4,7 @@
#include "Common/File/VFS/VFS.h" #include "Common/File/VFS/VFS.h"
#include "Common/File/FileUtil.h" #include "Common/File/FileUtil.h"
#include "Common/File/AndroidStorage.h" #include "Common/File/AndroidStorage.h"
#include "Common/StringUtils.h"
VFS g_VFS; VFS g_VFS;
@ -27,7 +28,7 @@ void VFS::Clear() {
static bool IsLocalAbsolutePath(const char *path) { static bool IsLocalAbsolutePath(const char *path) {
bool isUnixLocal = path[0] == '/'; bool isUnixLocal = path[0] == '/';
#ifdef _WIN32 #ifdef _WIN32
bool isWindowsLocal = isalpha(path[0]) && path[1] == ':'; bool isWindowsLocal = (isalpha(path[0]) && path[1] == ':') || startsWith(path, "\\\\") || startsWith(path, "//");
#else #else
bool isWindowsLocal = false; bool isWindowsLocal = false;
#endif #endif

View file

@ -92,6 +92,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override; Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -130,7 +131,6 @@ public:
stencilDirty_ = true; stencilDirty_ = true;
} }
void EndFrame() override;
void Draw(int vertexCount, int offset) override; void Draw(int vertexCount, int offset) override;
void DrawIndexed(int vertexCount, int offset) override; void DrawIndexed(int vertexCount, int offset) override;
@ -138,6 +138,9 @@ public:
void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) override; void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) override;
void BeginFrame() override; void BeginFrame() override;
void EndFrame() override;
int GetFrameCount() override { return frameCount_; }
std::string GetInfoString(InfoField info) const override { std::string GetInfoString(InfoField info) const override {
switch (info) { switch (info) {
@ -220,6 +223,7 @@ private:
int nextIndexBufferOffset_ = 0; int nextIndexBufferOffset_ = 0;
InvalidationCallback invalidationCallback_; InvalidationCallback invalidationCallback_;
int frameCount_ = 0;
// Dynamic state // Dynamic state
float blendFactor_[4]{}; float blendFactor_[4]{};
@ -422,6 +426,7 @@ void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1
void D3D11DrawContext::EndFrame() { void D3D11DrawContext::EndFrame() {
curPipeline_ = nullptr; curPipeline_ = nullptr;
frameCount_++;
} }
void D3D11DrawContext::SetViewport(const Viewport &viewport) { void D3D11DrawContext::SetViewport(const Viewport &viewport) {
@ -795,35 +800,83 @@ public:
width_ = desc.width; width_ = desc.width;
height_ = desc.height; height_ = desc.height;
depth_ = desc.depth; depth_ = desc.depth;
format_ = desc.format;
mipLevels_ = desc.mipLevels;
} }
~D3D11Texture() { ~D3D11Texture() {
if (tex) if (tex_)
tex->Release(); tex_->Release();
if (stagingTex) if (stagingTex_)
stagingTex->Release(); stagingTex_->Release();
if (view) if (view_)
view->Release(); view_->Release();
} }
ID3D11Texture2D *tex = nullptr; bool Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips);
ID3D11Texture2D *stagingTex = nullptr;
ID3D11ShaderResourceView *view = nullptr; bool CreateStagingTexture(ID3D11Device *device);
void UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t *const *data, TextureCallback initDataCallback, int numLevels);
ID3D11ShaderResourceView *View() { return view_; }
private:
bool FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback);
ID3D11Texture2D *tex_ = nullptr;
ID3D11Texture2D *stagingTex_ = nullptr;
ID3D11ShaderResourceView *view_ = nullptr;
int mipLevels_ = 0;
}; };
Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) { bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback) {
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) { D3D11_MAPPED_SUBRESOURCE mapped;
// D3D11 does not support this format as a texture format. HRESULT hr = context->Map(stagingTex_, level, D3D11_MAP_WRITE, 0, &mapped);
return nullptr; if (!SUCCEEDED(hr)) {
tex_->Release();
tex_ = nullptr;
return false;
} }
D3D11Texture *tex = new D3D11Texture(desc); if (!initDataCallback((uint8_t *)mapped.pData, data[level], w, h, d, mapped.RowPitch, mapped.DepthPitch)) {
for (int s = 0; s < d; ++s) {
bool generateMips = desc.generateMips; for (int y = 0; y < h; ++y) {
if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) { void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
// D3D11 does not support autogenerating mipmaps for this format. uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(format_);
generateMips = false; const void *src = data[level] + byteStride * (y + h * d);
memcpy(dest, src, byteStride);
} }
}
}
context->Unmap(stagingTex_, level);
return true;
}
bool D3D11Texture::CreateStagingTexture(ID3D11Device *device) {
if (stagingTex_)
return true;
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = width_;
descColor.Height = height_;
descColor.MipLevels = mipLevels_;
descColor.ArraySize = 1;
descColor.Format = dataFormatToD3D11(format_);
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
descColor.Usage = D3D11_USAGE_STAGING;
descColor.BindFlags = 0;
descColor.MiscFlags = 0;
descColor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = device->CreateTexture2D(&descColor, nullptr, &stagingTex_);
if (!SUCCEEDED(hr)) {
stagingTex_->Release();
stagingTex_ = nullptr;
return false;
}
return true;
}
bool D3D11Texture::Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips) {
D3D11_TEXTURE2D_DESC descColor{}; D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = desc.width; descColor.Width = desc.width;
descColor.Height = desc.height; descColor.Height = desc.height;
@ -832,25 +885,16 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
descColor.Format = dataFormatToD3D11(desc.format); descColor.Format = dataFormatToD3D11(desc.format);
descColor.SampleDesc.Count = 1; descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0; descColor.SampleDesc.Quality = 0;
if (desc.initDataCallback) {
descColor.Usage = D3D11_USAGE_STAGING;
descColor.BindFlags = 0;
descColor.MiscFlags = 0;
descColor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = device_->CreateTexture2D(&descColor, nullptr, &tex->stagingTex);
if (!SUCCEEDED(hr)) {
delete tex;
return nullptr;
}
}
descColor.Usage = D3D11_USAGE_DEFAULT; descColor.Usage = D3D11_USAGE_DEFAULT;
descColor.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE; descColor.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE;
descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0; descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
descColor.CPUAccessFlags = 0; descColor.CPUAccessFlags = 0;
// Make sure we have a staging texture if we'll need it.
if (desc.initDataCallback && !CreateStagingTexture(device)) {
return false;
}
D3D11_SUBRESOURCE_DATA *initDataParam = nullptr; D3D11_SUBRESOURCE_DATA *initDataParam = nullptr;
D3D11_SUBRESOURCE_DATA initData[12]{}; D3D11_SUBRESOURCE_DATA initData[12]{};
std::vector<uint8_t> initDataBuffer[12]; std::vector<uint8_t> initDataBuffer[12];
@ -870,62 +914,39 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
initDataParam = initData; initDataParam = initData;
} }
HRESULT hr = device_->CreateTexture2D(&descColor, initDataParam, &tex->tex); HRESULT hr = device->CreateTexture2D(&descColor, initDataParam, &tex_);
if (!SUCCEEDED(hr)) { if (!SUCCEEDED(hr)) {
delete tex; tex_ = nullptr;
return nullptr; return false;
} }
hr = device_->CreateShaderResourceView(tex->tex, nullptr, &tex->view); hr = device->CreateShaderResourceView(tex_, nullptr, &view_);
if (!SUCCEEDED(hr)) {
delete tex;
return nullptr;
}
auto populateLevelCallback = [&](int level, int w, int h, int d) {
D3D11_MAPPED_SUBRESOURCE mapped;
hr = context_->Map(tex->stagingTex, level, D3D11_MAP_WRITE, 0, &mapped);
if (!SUCCEEDED(hr)) { if (!SUCCEEDED(hr)) {
return false; return false;
} }
if (!desc.initDataCallback((uint8_t *)mapped.pData, desc.initData[level], w, h, d, mapped.RowPitch, mapped.DepthPitch)) {
for (int s = 0; s < d; ++s) {
for (int y = 0; y < h; ++y) {
void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(desc.format);
const void *src = desc.initData[level] + byteStride * (y + h * d);
memcpy(dest, src, byteStride);
}
}
}
context_->Unmap(tex->stagingTex, level);
return true;
};
if (generateMips && desc.initData.size() >= 1) { if (generateMips && desc.initData.size() >= 1) {
if (desc.initDataCallback) { if (desc.initDataCallback) {
if (!populateLevelCallback(0, desc.width, desc.height, desc.depth)) { if (!FillLevel(context, 0, desc.width, desc.height, desc.depth, desc.initData.data(), desc.initDataCallback)) {
delete tex; tex_->Release();
return nullptr; return false;
} }
context_->CopyResource(tex->stagingTex, tex->stagingTex); context->CopyResource(tex_, stagingTex_);
tex->stagingTex->Release(); stagingTex_->Release();
tex->stagingTex = nullptr; stagingTex_ = nullptr;
} else { } else {
uint32_t byteStride = desc.width * (uint32_t)DataFormatSizeInBytes(desc.format); uint32_t byteStride = desc.width * (uint32_t)DataFormatSizeInBytes(desc.format);
context_->UpdateSubresource(tex->tex, 0, nullptr, desc.initData[0], byteStride, 0); context->UpdateSubresource(tex_, 0, nullptr, desc.initData[0], byteStride, 0);
} }
context_->GenerateMips(tex->view); context->GenerateMips(view_);
} else if (desc.initDataCallback) { } else if (desc.initDataCallback) {
int w = desc.width; int w = desc.width;
int h = desc.height; int h = desc.height;
int d = desc.depth; int d = desc.depth;
for (int i = 0; i < (int)desc.initData.size(); i++) { for (int i = 0; i < (int)desc.initData.size(); i++) {
if (!populateLevelCallback(i, desc.width, desc.height, desc.depth)) { if (!FillLevel(context, i, w, h, d, desc.initData.data(), desc.initDataCallback)) {
if (i == 0) { if (i == 0) {
delete tex; return false;
return nullptr;
} else { } else {
break; break;
} }
@ -936,13 +957,62 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
d = (d + 1) / 2; d = (d + 1) / 2;
} }
context_->CopyResource(tex->tex, tex->stagingTex); context->CopyResource(tex_, stagingTex_);
tex->stagingTex->Release(); stagingTex_->Release();
tex->stagingTex = nullptr; stagingTex_ = nullptr;
} }
return true;
}
void D3D11Texture::UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t * const*data, TextureCallback initDataCallback, int numLevels) {
if (!CreateStagingTexture(device)) {
return;
}
int w = width_;
int h = height_;
int d = depth_;
for (int i = 0; i < (int)numLevels; i++) {
if (!FillLevel(context, i, w, h, d, data, initDataCallback)) {
break;
}
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
context->CopyResource(tex_, stagingTex_);
stagingTex_->Release();
stagingTex_ = nullptr;
}
Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) {
// D3D11 does not support this format as a texture format.
return nullptr;
}
D3D11Texture *tex = new D3D11Texture(desc);
bool generateMips = desc.generateMips;
if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) {
// D3D11 does not support autogenerating mipmaps for this format.
generateMips = false;
}
if (!tex->Create(context_, device_, desc, generateMips)) {
tex->Release();
return nullptr;
}
return tex; return tex;
} }
void D3D11DrawContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
D3D11Texture *tex = (D3D11Texture *)texture;
tex->UpdateTextureLevels(context_, device_, texture, data, initDataCallback, numLevels);
}
ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) { ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) {
if (language != ShaderLanguage::HLSL_D3D11) { if (language != ShaderLanguage::HLSL_D3D11) {
ERROR_LOG(G3D, "Unsupported shader language"); ERROR_LOG(G3D, "Unsupported shader language");
@ -1411,7 +1481,7 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures, Te
_assert_(start + count <= ARRAY_SIZE(views)); _assert_(start + count <= ARRAY_SIZE(views));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
D3D11Texture *tex = (D3D11Texture *)textures[i]; D3D11Texture *tex = (D3D11Texture *)textures[i];
views[i] = tex ? tex->view : nullptr; views[i] = tex ? tex->View() : nullptr;
} }
context_->PSSetShaderResources(start, count, views); context_->PSSetShaderResources(start, count, views);
} }
@ -1771,7 +1841,7 @@ uint64_t D3D11DrawContext::GetNativeObject(NativeObject obj, void *srcObject) {
case NativeObject::FEATURE_LEVEL: case NativeObject::FEATURE_LEVEL:
return (uint64_t)(uintptr_t)featureLevel_; return (uint64_t)(uintptr_t)featureLevel_;
case NativeObject::TEXTURE_VIEW: case NativeObject::TEXTURE_VIEW:
return (uint64_t)(((D3D11Texture *)srcObject)->view); return (uint64_t)(((D3D11Texture *)srcObject)->View());
default: default:
return 0; return 0;
} }

View file

@ -308,14 +308,14 @@ public:
return nullptr; return nullptr;
} }
} }
void UpdateTextureLevels(const uint8_t * const *data, int numLevels, TextureCallback initDataCallback);
private: private:
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback); void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback);
bool Create(const TextureDesc &desc); bool Create(const TextureDesc &desc);
LPDIRECT3DDEVICE9 device_; LPDIRECT3DDEVICE9 device_;
LPDIRECT3DDEVICE9EX deviceEx_; LPDIRECT3DDEVICE9EX deviceEx_;
TextureType type_; TextureType type_;
DataFormat format_;
D3DFORMAT d3dfmt_; D3DFORMAT d3dfmt_;
LPDIRECT3DTEXTURE9 tex_ = nullptr; LPDIRECT3DTEXTURE9 tex_ = nullptr;
LPDIRECT3DVOLUMETEXTURE9 volTex_ = nullptr; LPDIRECT3DVOLUMETEXTURE9 volTex_ = nullptr;
@ -374,25 +374,29 @@ bool D3D9Texture::Create(const TextureDesc &desc) {
break; break;
} }
if (FAILED(hr)) { if (FAILED(hr)) {
ERROR_LOG(G3D, "Texture creation failed"); ERROR_LOG(G3D, "D3D9 Texture creation failed");
return false; return false;
} }
if (desc.initData.size()) { if (desc.initData.size()) {
// In D3D9, after setting D3DUSAGE_AUTOGENMIPS, we can only access the top layer. The rest will be // In D3D9, after setting D3DUSAGE_AUTOGENMIPS, we can only access the top layer. The rest will be
// automatically generated. // automatically generated.
int maxLevel = desc.generateMips ? 1 : (int)desc.initData.size(); int numLevels = desc.generateMips ? 1 : (int)desc.initData.size();
int w = desc.width; UpdateTextureLevels(desc.initData.data(), numLevels, desc.initDataCallback);
int h = desc.height; }
int d = desc.depth; return true;
for (int i = 0; i < maxLevel; i++) { }
SetImageData(0, 0, 0, w, h, d, i, 0, desc.initData[i], desc.initDataCallback);
void D3D9Texture::UpdateTextureLevels(const uint8_t * const *data, int numLevels, TextureCallback initDataCallback) {
int w = width_;
int h = height_;
int d = depth_;
for (int i = 0; i < numLevels; i++) {
SetImageData(0, 0, 0, w, h, d, i, 0, data[i], initDataCallback);
w = (w + 1) / 2; w = (w + 1) / 2;
h = (h + 1) / 2; h = (h + 1) / 2;
d = (d + 1) / 2; d = (d + 1) / 2;
} }
}
return true;
} }
// Just switches R and G. // Just switches R and G.
@ -532,6 +536,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override; Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override { void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override {
// Not implemented // Not implemented
@ -575,6 +580,7 @@ public:
} }
void EndFrame() override; void EndFrame() override;
int GetFrameCount() override { return frameCount_; }
void UpdateDynamicUniformBuffer(const void *ub, size_t size) override; void UpdateDynamicUniformBuffer(const void *ub, size_t size) override;
@ -635,6 +641,7 @@ private:
D3DCAPS9 d3dCaps_; D3DCAPS9 d3dCaps_;
char shadeLangVersion_[64]{}; char shadeLangVersion_[64]{};
DeviceCaps caps_{}; DeviceCaps caps_{};
int frameCount_ = 0;
// Bound state // Bound state
AutoRef<D3D9Pipeline> curPipeline_; AutoRef<D3D9Pipeline> curPipeline_;
@ -934,6 +941,12 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
return tex; return tex;
} }
void D3D9Context::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
D3D9Texture *tex = (D3D9Texture *)texture;
tex->UpdateTextureLevels(data, numLevels, initDataCallback);
}
void D3D9Context::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) { void D3D9Context::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES); _assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) { for (int i = start; i < start + count; i++) {
@ -953,6 +966,7 @@ void D3D9Context::BindNativeTexture(int index, void *nativeTexture) {
void D3D9Context::EndFrame() { void D3D9Context::EndFrame() {
curPipeline_ = nullptr; curPipeline_ = nullptr;
frameCount_++;
} }
static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index) { static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index) {

View file

@ -0,0 +1,28 @@
#include <mutex>
#include <set>
#include "Common/GPU/GPUBackendCommon.h"
// Global push buffer tracker for GPU memory profiling.
// Don't want to manually dig up all the active push buffers.
static std::mutex g_pushBufferListMutex;
static std::set<GPUMemoryManager *> g_pushBuffers;
std::vector<GPUMemoryManager *> GetActiveGPUMemoryManagers() {
std::vector<GPUMemoryManager *> buffers;
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
for (auto iter : g_pushBuffers) {
buffers.push_back(iter);
}
return buffers;
}
void RegisterGPUMemoryManager(GPUMemoryManager *manager) {
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(manager);
}
void UnregisterGPUMemoryManager(GPUMemoryManager *manager) {
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(manager);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <vector>
// Just an abstract thing to get debug information.
class GPUMemoryManager {
public:
virtual ~GPUMemoryManager() {}
virtual void GetDebugString(char *buffer, size_t bufSize) const = 0;
virtual const char *Name() const = 0; // for sorting
};
std::vector<GPUMemoryManager *> GetActiveGPUMemoryManagers();
void RegisterGPUMemoryManager(GPUMemoryManager *manager);
void UnregisterGPUMemoryManager(GPUMemoryManager *manager);

View file

@ -55,7 +55,7 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin
internalFormat = GL_RGB; internalFormat = GL_RGB;
format = GL_RGB; format = GL_RGB;
type = GL_UNSIGNED_BYTE; type = GL_UNSIGNED_BYTE;
alignment = 1; alignment = 3;
break; break;
case DataFormat::R4G4B4A4_UNORM_PACK16: case DataFormat::R4G4B4A4_UNORM_PACK16:

View file

@ -626,11 +626,13 @@ bool CheckGLExtensions() {
} }
void SetGLCoreContext(bool flag) { void SetGLCoreContext(bool flag) {
_assert_msg_(!extensionsDone, "SetGLCoreContext() after CheckGLExtensions()"); if (!extensionsDone) {
useCoreContext = flag; useCoreContext = flag;
// For convenience, it'll get reset later. // For convenience, it'll get reset later.
gl_extensions.IsCoreContext = useCoreContext; gl_extensions.IsCoreContext = useCoreContext;
} else {
_assert_(flag == useCoreContext);
}
} }
void ResetGLExtensions() { void ResetGLExtensions() {

View file

@ -3,6 +3,7 @@
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <vector> #include <vector>
#include <string>
#include <set> #include <set>
#include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLCommon.h"
@ -35,6 +36,15 @@ public:
std::vector<GLPushBuffer *> pushBuffers; std::vector<GLPushBuffer *> pushBuffers;
}; };
struct GLQueueProfileContext {
bool enabled;
double cpuStartTime;
double cpuEndTime;
std::string passesString;
int commandCounts[25]; // Can't grab count from the enum as it would mean a circular include. Might clean this up later.
};
// Per-frame data, round-robin so we can overlap submission with execution of the previous frame. // Per-frame data, round-robin so we can overlap submission with execution of the previous frame.
struct GLFrameData { struct GLFrameData {
bool skipSwap = false; bool skipSwap = false;
@ -49,4 +59,6 @@ struct GLFrameData {
GLDeleter deleter; GLDeleter deleter;
GLDeleter deleter_prev; GLDeleter deleter_prev;
std::set<GLPushBuffer *> activePushBuffers; std::set<GLPushBuffer *> activePushBuffers;
GLQueueProfileContext profile;
}; };

View file

@ -0,0 +1,288 @@
#include "Common/MemoryUtil.h"
#include "Common/GPU/OpenGL/GLMemory.h"
#include "Common/GPU/OpenGL/GLRenderManager.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/Data/Text/Parsers.h"
extern std::thread::id renderThreadId;
#if MAX_LOGLEVEL >= DEBUG_LEVEL
static bool OnRenderThread() {
return std::this_thread::get_id() == renderThreadId;
}
#endif
void *GLRBuffer::Map(GLBufferStrategy strategy) {
_assert_(buffer_ != 0);
GLbitfield access = GL_MAP_WRITE_BIT;
if ((strategy & GLBufferStrategy::MASK_FLUSH) != 0) {
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
}
if ((strategy & GLBufferStrategy::MASK_INVALIDATE) != 0) {
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
}
void *p = nullptr;
bool allowNativeBuffer = strategy != GLBufferStrategy::SUBDATA;
if (allowNativeBuffer) {
glBindBuffer(target_, buffer_);
if (gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage) {
#if !PPSSPP_PLATFORM(IOS)
if (!hasStorage_) {
GLbitfield storageFlags = access & ~(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
#ifdef USING_GLES2
#ifdef GL_EXT_buffer_storage
glBufferStorageEXT(target_, size_, nullptr, storageFlags);
#endif
#else
glBufferStorage(target_, size_, nullptr, storageFlags);
#endif
hasStorage_ = true;
}
#endif
p = glMapBufferRange(target_, 0, size_, access);
} else if (gl_extensions.VersionGEThan(3, 0, 0)) {
// GLES3 or desktop 3.
p = glMapBufferRange(target_, 0, size_, access);
} else if (!gl_extensions.IsGLES) {
#ifndef USING_GLES2
p = glMapBuffer(target_, GL_READ_WRITE);
#endif
}
}
mapped_ = p != nullptr;
return p;
}
bool GLRBuffer::Unmap() {
glBindBuffer(target_, buffer_);
mapped_ = false;
return glUnmapBuffer(target_) == GL_TRUE;
}
GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size, const char *tag) : render_(render), size_(size), target_(target), tag_(tag) {
bool res = AddBuffer();
_assert_(res);
RegisterGPUMemoryManager(this);
}
GLPushBuffer::~GLPushBuffer() {
UnregisterGPUMemoryManager(this);
Destroy(true);
}
void GLPushBuffer::Map() {
_assert_(!writePtr_);
auto &info = buffers_[buf_];
writePtr_ = info.deviceMemory ? info.deviceMemory : info.localMemory;
info.flushOffset = 0;
// Force alignment. This is needed for PushAligned() to work as expected.
while ((intptr_t)writePtr_ & 15) {
writePtr_++;
offset_++;
info.flushOffset++;
}
_assert_(writePtr_);
}
void GLPushBuffer::Unmap() {
_assert_(writePtr_);
if (!buffers_[buf_].deviceMemory) {
// Here we simply upload the data to the last buffer.
// Might be worth trying with size_ instead of offset_, so the driver can replace
// the whole buffer. At least if it's close.
render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].localMemory, false);
} else {
buffers_[buf_].flushOffset = offset_;
}
writePtr_ = nullptr;
}
void GLPushBuffer::Flush() {
// Must be called from the render thread.
_dbg_assert_(OnRenderThread());
if (buf_ >= buffers_.size()) {
_dbg_assert_msg_(false, "buf_ somehow got out of sync: %d vs %d", (int)buf_, (int)buffers_.size());
return;
}
buffers_[buf_].flushOffset = offset_;
if (!buffers_[buf_].deviceMemory && writePtr_) {
auto &info = buffers_[buf_];
if (info.flushOffset != 0) {
_assert_(info.buffer->buffer_);
glBindBuffer(target_, info.buffer->buffer_);
glBufferSubData(target_, 0, info.flushOffset, info.localMemory);
}
// Here we will submit all the draw calls, with the already known buffer and offsets.
// Might as well reset the write pointer here and start over the current buffer.
writePtr_ = info.localMemory;
offset_ = 0;
info.flushOffset = 0;
}
// For device memory, we flush all buffers here.
if ((strategy_ & GLBufferStrategy::MASK_FLUSH) != 0) {
for (auto &info : buffers_) {
if (info.flushOffset == 0 || !info.deviceMemory)
continue;
glBindBuffer(target_, info.buffer->buffer_);
glFlushMappedBufferRange(target_, 0, info.flushOffset);
info.flushOffset = 0;
}
}
}
bool GLPushBuffer::AddBuffer() {
// INFO_LOG(G3D, "GLPushBuffer(%s): Allocating %d bytes", tag_, size_);
BufInfo info;
info.localMemory = (uint8_t *)AllocateAlignedMemory(size_, 16);
if (!info.localMemory)
return false;
info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW);
info.size = size_;
buf_ = buffers_.size();
buffers_.push_back(info);
return true;
}
void GLPushBuffer::Destroy(bool onRenderThread) {
if (buf_ == -1)
return; // Already destroyed
for (BufInfo &info : buffers_) {
// This will automatically unmap device memory, if needed.
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
if (onRenderThread) {
delete info.buffer;
} else {
render_->DeleteBuffer(info.buffer);
}
FreeAlignedMemory(info.localMemory);
}
buffers_.clear();
buf_ = -1;
}
void GLPushBuffer::NextBuffer(size_t minSize) {
// First, unmap the current memory.
Unmap();
buf_++;
if (buf_ >= buffers_.size() || minSize > size_) {
// Before creating the buffer, adjust to the new size_ if necessary.
while (size_ < minSize) {
size_ <<= 1;
}
bool res = AddBuffer();
_assert_(res);
if (!res) {
// Let's try not to crash at least?
buf_ = 0;
}
}
// Now, move to the next buffer and map it.
offset_ = 0;
Map();
}
void GLPushBuffer::Defragment() {
_dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread");
if (buffers_.size() <= 1) {
// Let's take this opportunity to jettison any localMemory we don't need.
for (auto &info : buffers_) {
if (info.deviceMemory) {
FreeAlignedMemory(info.localMemory);
info.localMemory = nullptr;
}
}
return;
}
// Okay, we have more than one. Destroy them all and start over with a larger one.
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
// they won't all have the same size. Sum things up properly.
size_t newSize = 0;
for (int i = 0; i < (int)buffers_.size(); i++) {
newSize += buffers_[i].size;
}
Destroy(false);
// Set some sane but very free limits. If there's another spike, we'll just allocate more anyway.
size_ = std::min(std::max(newSize, (size_t)65536), (size_t)(512 * 1024 * 1024));
bool res = AddBuffer();
_assert_msg_(res, "AddBuffer failed");
}
size_t GLPushBuffer::GetTotalSize() const {
size_t sum = 0;
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
// they won't all have the same size. Sum things up properly.
if (buffers_.size() > 1) {
for (int i = 0; i < (int)buffers_.size() - 1; i++) {
sum += buffers_[i].size;
}
}
sum += offset_;
return sum;
}
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
_dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread");
strategy_ = strategy;
if (strategy_ == GLBufferStrategy::SUBDATA) {
return;
}
bool mapChanged = false;
for (auto &info : buffers_) {
if (!info.buffer->buffer_ || info.deviceMemory) {
// Can't map - no device buffer associated yet or already mapped.
continue;
}
info.deviceMemory = (uint8_t *)info.buffer->Map(strategy_);
mapChanged = mapChanged || info.deviceMemory != nullptr;
if (!info.deviceMemory && !info.localMemory) {
// Somehow it failed, let's dodge crashing.
info.localMemory = (uint8_t *)AllocateAlignedMemory(info.buffer->size_, 16);
mapChanged = true;
}
_dbg_assert_msg_(info.localMemory || info.deviceMemory, "Local or device memory must succeed");
}
if (writePtr_ && mapChanged) {
// This can happen during a sync. Remap.
writePtr_ = nullptr;
Map();
}
}
void GLPushBuffer::UnmapDevice() {
_dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread");
for (auto &info : buffers_) {
if (info.deviceMemory) {
// TODO: Technically this can return false?
info.buffer->Unmap();
info.deviceMemory = nullptr;
}
}
}
void GLPushBuffer::GetDebugString(char *buffer, size_t bufSize) const {
snprintf(buffer, bufSize, "%s: %s/%s (%d)", tag_, NiceSizeFormat(this->offset_).c_str(), NiceSizeFormat(this->size_).c_str(), (int)buffers_.size());
}

View file

@ -0,0 +1,185 @@
#pragma once
#include <vector>
#include <cstdint>
#include <cstring>
#include "Common/GPU/GPUBackendCommon.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/Log.h"
enum class GLBufferStrategy {
SUBDATA = 0,
MASK_FLUSH = 0x10,
MASK_INVALIDATE = 0x20,
// Map/unmap the buffer each frame.
FRAME_UNMAP = 1,
// Map/unmap and also invalidate the buffer on map.
INVALIDATE_UNMAP = MASK_INVALIDATE,
// Map/unmap and explicitly flushed changed ranges.
FLUSH_UNMAP = MASK_FLUSH,
// Map/unmap, invalidate on map, and explicit flush.
FLUSH_INVALIDATE_UNMAP = MASK_FLUSH | MASK_INVALIDATE,
};
static inline int operator &(const GLBufferStrategy &lhs, const GLBufferStrategy &rhs) {
return (int)lhs & (int)rhs;
}
class GLRBuffer {
public:
GLRBuffer(GLuint target, size_t size) : target_(target), size_((int)size) {}
~GLRBuffer() {
if (buffer_) {
glDeleteBuffers(1, &buffer_);
}
}
void *Map(GLBufferStrategy strategy);
bool Unmap();
bool Mapped() const {
return mapped_;
}
GLuint buffer_ = 0;
GLuint target_;
int size_;
private:
bool mapped_ = false;
bool hasStorage_ = false;
};
class GLRenderManager;
// Similar to VulkanPushBuffer but is currently less efficient - it collects all the data in
// RAM then does a big memcpy/buffer upload at the end of the frame. This is at least a lot
// faster than the hundreds of buffer uploads or memory array buffers we used before.
// On modern GL we could avoid the copy using glBufferStorage but not sure it's worth the
// trouble.
// We need to manage the lifetime of this together with the other resources so its destructor
// runs on the render thread.
class GLPushBuffer : public GPUMemoryManager {
public:
friend class GLRenderManager;
struct BufInfo {
GLRBuffer *buffer = nullptr;
uint8_t *localMemory = nullptr;
uint8_t *deviceMemory = nullptr;
size_t flushOffset = 0;
size_t size;
};
GLPushBuffer(GLRenderManager *render, GLuint target, size_t size, const char *tag);
~GLPushBuffer();
void Reset() { offset_ = 0; }
void GetDebugString(char *buffer, size_t bufSize) const override;
const char *Name() const override { return tag_; }; // for sorting
// Utility for users of this class, not used internally.
enum { INVALID_OFFSET = 0xFFFFFFFF };
private:
// Needs context in case of defragment.
void Begin() {
buf_ = 0;
offset_ = 0;
// Note: we must defrag because some buffers may be smaller than size_.
Defragment();
Map();
_dbg_assert_(writePtr_);
}
void BeginNoReset() {
Map();
}
void End() {
Unmap();
}
public:
void Map();
void Unmap();
bool IsReady() const {
return writePtr_ != nullptr;
}
// Recommended - lets you write directly into the buffer through the returned pointer.
// If you didn't end up using all the memory you grabbed here, then before calling Allocate or Push
// again, call Rewind (see below).
uint8_t *Allocate(uint32_t numBytes, uint32_t alignment, GLRBuffer **buf, uint32_t *bindOffset) {
uint32_t offset = ((uint32_t)offset_ + alignment - 1) & ~(alignment - 1);
if (offset + numBytes <= size_) {
// Common path.
offset_ = offset + numBytes;
*buf = buffers_[buf_].buffer;
*bindOffset = offset;
return writePtr_ + offset;
}
NextBuffer(numBytes);
*bindOffset = 0;
*buf = buffers_[buf_].buffer;
// Need to mark the allocated range used in the new buffer. How did things work before this?
offset_ = numBytes;
return writePtr_;
}
// For convenience if all you'll do is to copy.
uint32_t Push(const void *data, uint32_t numBytes, int alignment, GLRBuffer **buf) {
uint32_t bindOffset;
uint8_t *ptr = Allocate(numBytes, alignment, buf, &bindOffset);
memcpy(ptr, data, numBytes);
return bindOffset;
}
uint8_t *GetPtr(uint32_t offset) {
return writePtr_ + offset;
}
// If you didn't use all of the previous allocation you just made (obviously can't be another one),
// you can return memory to the buffer by specifying the offset up until which you wrote data.
// Pass in the buffer you got last time. If that buffer has been filled already, no rewind can be safely done.
// (well technically would be possible but not worth the trouble).
void Rewind(GLRBuffer *buffer, uint32_t offset) {
if (buffer == buffers_[buf_].buffer) {
_dbg_assert_(offset != INVALID_OFFSET);
_dbg_assert_(offset <= offset_);
offset_ = offset;
}
}
size_t GetOffset() const { return offset_; }
size_t GetTotalSize() const;
void Destroy(bool onRenderThread);
void Flush();
protected:
void MapDevice(GLBufferStrategy strategy);
void UnmapDevice();
private:
bool AddBuffer();
void NextBuffer(size_t minSize);
void Defragment();
GLRenderManager *render_;
std::vector<BufInfo> buffers_;
size_t buf_ = 0;
size_t offset_ = 0;
size_t size_ = 0;
uint8_t *writePtr_ = nullptr;
GLuint target_;
GLBufferStrategy strategy_ = GLBufferStrategy::SUBDATA;
const char *tag_;
};

View file

@ -118,7 +118,7 @@ static std::string GetStereoBufferLayout(const char *uniformName) {
else return "undefined"; else return "undefined";
} }
void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps, bool skipGLCalls) { void GLQueueRunner::RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls) {
if (skipGLCalls) { if (skipGLCalls) {
// Some bookkeeping still needs to be done. // Some bookkeeping still needs to be done.
for (size_t i = 0; i < steps.size(); i++) { for (size_t i = 0; i < steps.size(); i++) {
@ -651,7 +651,7 @@ retry_depth:
currentReadHandle_ = fbo->handle; currentReadHandle_ = fbo->handle;
} }
void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCalls, bool keepSteps, bool useVR) { void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR) {
if (skipGLCalls) { if (skipGLCalls) {
if (keepSteps) { if (keepSteps) {
return; return;
@ -700,7 +700,7 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
size_t renderCount = 0; size_t renderCount = 0;
for (size_t i = 0; i < steps.size(); i++) { for (size_t i = 0; i < steps.size(); i++) {
const GLRStep &step = *steps[i]; GLRStep &step = *steps[i];
#if !defined(USING_GLES2) #if !defined(USING_GLES2)
if (useDebugGroups_) if (useDebugGroups_)
@ -711,11 +711,10 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
case GLRStepType::RENDER: case GLRStepType::RENDER:
renderCount++; renderCount++;
if (IsVREnabled()) { if (IsVREnabled()) {
GLRStep vrStep = step; PreprocessStepVR(&step);
PreprocessStepVR(&vrStep); PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount);
} else { } else {
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount); PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
} }
break; break;
case GLRStepType::COPY: case GLRStepType::COPY:
@ -741,11 +740,14 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
if (useDebugGroups_) if (useDebugGroups_)
glPopDebugGroup(); glPopDebugGroup();
#endif #endif
if (frameData.profile.enabled) {
frameData.profile.passesString += StepToString(step);
}
if (!keepSteps) { if (!keepSteps) {
delete steps[i]; delete steps[i];
} }
} }
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
} }
@ -778,7 +780,20 @@ void GLQueueRunner::PerformBlit(const GLRStep &step) {
} }
} }
void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last) { static void EnableDisableVertexArrays(uint32_t prevAttr, uint32_t newAttr) {
int enable = (~prevAttr) & newAttr;
int disable = prevAttr & (~newAttr);
for (int i = 0; i < 7; i++) { // SEM_MAX
if (enable & (1 << i)) {
glEnableVertexAttribArray(i);
}
if (disable & (1 << i)) {
glDisableVertexAttribArray(i);
}
}
}
void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last, GLQueueProfileContext &profile) {
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
PerformBindFramebufferAsRenderTarget(step); PerformBindFramebufferAsRenderTarget(step);
@ -823,12 +838,43 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
bool clipDistanceEnabled[8]{}; bool clipDistanceEnabled[8]{};
GLuint blendEqColor = (GLuint)-1; GLuint blendEqColor = (GLuint)-1;
GLuint blendEqAlpha = (GLuint)-1; GLuint blendEqAlpha = (GLuint)-1;
GLenum blendSrcColor = (GLenum)-1;
GLenum blendDstColor = (GLenum)-1;
GLenum blendSrcAlpha = (GLenum)-1;
GLenum blendDstAlpha = (GLenum)-1;
GLuint stencilWriteMask = (GLuint)-1;
GLenum stencilFunc = (GLenum)-1;
GLuint stencilRef = (GLuint)-1;
GLuint stencilCompareMask = (GLuint)-1;
GLenum stencilSFail = (GLenum)-1;
GLenum stencilZFail = (GLenum)-1;
GLenum stencilPass = (GLenum)-1;
GLenum frontFace = (GLenum)-1;
GLenum cullFace = (GLenum)-1;
GLRTexture *curTex[MAX_GL_TEXTURE_SLOTS]{}; GLRTexture *curTex[MAX_GL_TEXTURE_SLOTS]{};
GLRViewport viewport = {
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
};
GLRect2D scissorRc = { -1, -1, -1, -1 };
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
auto &commands = step.commands; auto &commands = step.commands;
for (const auto &c : commands) { for (const auto &c : commands) {
#ifdef _DEBUG
if (profile.enabled) {
if ((size_t)c.cmd < ARRAY_SIZE(profile.commandCounts)) {
profile.commandCounts[(size_t)c.cmd]++;
}
}
#endif
switch (c.cmd) { switch (c.cmd) {
case GLRRenderCommand::DEPTH: case GLRRenderCommand::DEPTH:
if (c.depth.enabled) { if (c.depth.enabled) {
@ -849,23 +895,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
depthEnabled = false; depthEnabled = false;
} }
break; break;
case GLRRenderCommand::STENCILFUNC: case GLRRenderCommand::STENCIL:
if (c.stencilFunc.enabled) { if (c.stencil.enabled) {
if (!stencilEnabled) { if (!stencilEnabled) {
glEnable(GL_STENCIL_TEST); glEnable(GL_STENCIL_TEST);
stencilEnabled = true; stencilEnabled = true;
} }
glStencilFunc(c.stencilFunc.func, c.stencilFunc.ref, c.stencilFunc.compareMask); if (c.stencil.func != stencilFunc || c.stencil.ref != stencilRef || c.stencil.compareMask != stencilCompareMask) {
glStencilFunc(c.stencil.func, c.stencil.ref, c.stencil.compareMask);
stencilFunc = c.stencil.func;
stencilRef = c.stencil.ref;
stencilCompareMask = c.stencil.compareMask;
}
if (c.stencil.sFail != stencilSFail || c.stencil.zFail != stencilZFail || c.stencil.pass != stencilPass) {
glStencilOp(c.stencil.sFail, c.stencil.zFail, c.stencil.pass);
stencilSFail = c.stencil.sFail;
stencilZFail = c.stencil.zFail;
stencilPass = c.stencil.pass;
}
if (c.stencil.writeMask != stencilWriteMask) {
glStencilMask(c.stencil.writeMask);
stencilWriteMask = c.stencil.writeMask;
}
} else if (/* !c.stencilFunc.enabled && */stencilEnabled) { } else if (/* !c.stencilFunc.enabled && */stencilEnabled) {
glDisable(GL_STENCIL_TEST); glDisable(GL_STENCIL_TEST);
stencilEnabled = false; stencilEnabled = false;
} }
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
case GLRRenderCommand::STENCILOP:
glStencilOp(c.stencilOp.sFail, c.stencilOp.zFail, c.stencilOp.pass);
glStencilMask(c.stencilOp.writeMask);
break;
case GLRRenderCommand::BLEND: case GLRRenderCommand::BLEND:
if (c.blend.enabled) { if (c.blend.enabled) {
if (!blendEnabled) { if (!blendEnabled) {
@ -877,7 +934,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
blendEqColor = c.blend.funcColor; blendEqColor = c.blend.funcColor;
blendEqAlpha = c.blend.funcAlpha; blendEqAlpha = c.blend.funcAlpha;
} }
if (blendSrcColor != c.blend.srcColor || blendDstColor != c.blend.dstColor || blendSrcAlpha != c.blend.srcAlpha || blendDstAlpha != c.blend.dstAlpha) {
glBlendFuncSeparate(c.blend.srcColor, c.blend.dstColor, c.blend.srcAlpha, c.blend.dstAlpha); glBlendFuncSeparate(c.blend.srcColor, c.blend.dstColor, c.blend.srcAlpha, c.blend.dstAlpha);
blendSrcColor = c.blend.srcColor;
blendDstColor = c.blend.dstColor;
blendSrcAlpha = c.blend.srcAlpha;
blendDstAlpha = c.blend.dstAlpha;
}
} else if (/* !c.blend.enabled && */ blendEnabled) { } else if (/* !c.blend.enabled && */ blendEnabled) {
glDisable(GL_BLEND); glDisable(GL_BLEND);
blendEnabled = false; blendEnabled = false;
@ -955,7 +1018,17 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
y = curFBHeight_ - y - c.viewport.vp.h; y = curFBHeight_ - y - c.viewport.vp.h;
// TODO: Support FP viewports through glViewportArrays // TODO: Support FP viewports through glViewportArrays
if (viewport.x != c.viewport.vp.x || viewport.y != y || viewport.w != c.viewport.vp.w || viewport.h != c.viewport.vp.h) {
glViewport((GLint)c.viewport.vp.x, (GLint)y, (GLsizei)c.viewport.vp.w, (GLsizei)c.viewport.vp.h); glViewport((GLint)c.viewport.vp.x, (GLint)y, (GLsizei)c.viewport.vp.w, (GLsizei)c.viewport.vp.h);
viewport.x = c.viewport.vp.x;
viewport.y = y;
viewport.w = c.viewport.vp.w;
viewport.h = c.viewport.vp.h;
}
if (viewport.minZ != c.viewport.vp.minZ || viewport.maxZ != c.viewport.vp.maxZ) {
viewport.minZ = c.viewport.vp.minZ;
viewport.maxZ = c.viewport.vp.maxZ;
#if !defined(USING_GLES2) #if !defined(USING_GLES2)
if (gl_extensions.IsGLES) { if (gl_extensions.IsGLES) {
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ); glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
@ -965,6 +1038,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
#else #else
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ); glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
#endif #endif
}
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
} }
@ -973,7 +1047,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
int y = c.scissor.rc.y; int y = c.scissor.rc.y;
if (!curFB_) if (!curFB_)
y = curFBHeight_ - y - c.scissor.rc.h; y = curFBHeight_ - y - c.scissor.rc.h;
if (scissorRc.x != c.scissor.rc.x || scissorRc.y != y || scissorRc.w != c.scissor.rc.w || scissorRc.h != c.scissor.rc.h) {
glScissor(c.scissor.rc.x, y, c.scissor.rc.w, c.scissor.rc.h); glScissor(c.scissor.rc.x, y, c.scissor.rc.w, c.scissor.rc.h);
scissorRc.x = c.scissor.rc.x;
scissorRc.y = y;
scissorRc.w = c.scissor.rc.w;
scissorRc.h = c.scissor.rc.h;
}
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
} }
@ -1038,28 +1118,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
{ {
_dbg_assert_(curProgram); _dbg_assert_(curProgram);
if (IsMultiviewSupported()) { if (IsMultiviewSupported()) {
int layout = GetStereoBufferIndex(c.uniformMatrix4.name); int layout = GetStereoBufferIndex(c.uniformStereoMatrix4.name);
if (layout >= 0) { if (layout >= 0) {
int size = 2 * 16 * sizeof(float); int size = 2 * 16 * sizeof(float);
glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformMatrix4.loc); glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformStereoMatrix4.loc);
glBindBuffer(GL_UNIFORM_BUFFER, *c.uniformMatrix4.loc); glBindBuffer(GL_UNIFORM_BUFFER, *c.uniformStereoMatrix4.loc);
void *matrices = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); void *matrices = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
memcpy(matrices, c.uniformMatrix4.m, size); memcpy(matrices, c.uniformStereoMatrix4.mData, size);
glUnmapBuffer(GL_UNIFORM_BUFFER); glUnmapBuffer(GL_UNIFORM_BUFFER);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
} }
delete[] c.uniformStereoMatrix4.mData; // We only playback once.
} else { } else {
int loc = c.uniformMatrix4.loc ? *c.uniformMatrix4.loc : -1; int loc = c.uniformStereoMatrix4.loc ? *c.uniformStereoMatrix4.loc : -1;
if (c.uniformMatrix4.name) { if (c.uniformStereoMatrix4.name) {
loc = curProgram->GetUniformLoc(c.uniformMatrix4.name); loc = curProgram->GetUniformLoc(c.uniformStereoMatrix4.name);
} }
if (loc >= 0) { if (loc >= 0) {
if (GetVRFBOIndex() == 0) { if (GetVRFBOIndex() == 0) {
glUniformMatrix4fv(loc, 1, false, c.uniformMatrix4.m); glUniformMatrix4fv(loc, 1, false, c.uniformStereoMatrix4.mData);
} else { } else {
glUniformMatrix4fv(loc, 1, false, &c.uniformMatrix4.m[16]); glUniformMatrix4fv(loc, 1, false, c.uniformStereoMatrix4.mData + 16);
} }
} }
if (GetVRFBOIndex() == 1 || GetVRPassesCount() == 1) {
// Only delete the data if we're rendering the only or the second eye.
// If we delete during the first eye, we get a use-after-free or double delete.
delete[] c.uniformStereoMatrix4.mData;
}
} }
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
@ -1148,52 +1234,37 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
} }
case GLRRenderCommand::BIND_VERTEX_BUFFER: case GLRRenderCommand::DRAW:
{ {
// TODO: Add fast path for glBindVertexBuffer GLRInputLayout *layout = c.draw.inputLayout;
GLRInputLayout *layout = c.bindVertexBuffer.inputLayout; GLuint buf = c.draw.vertexBuffer->buffer_;
GLuint buf = c.bindVertexBuffer.buffer ? c.bindVertexBuffer.buffer->buffer_ : 0; _dbg_assert_(!c.draw.vertexBuffer->Mapped());
_dbg_assert_(!c.bindVertexBuffer.buffer || !c.bindVertexBuffer.buffer->Mapped());
if (buf != curArrayBuffer) { if (buf != curArrayBuffer) {
glBindBuffer(GL_ARRAY_BUFFER, buf); glBindBuffer(GL_ARRAY_BUFFER, buf);
curArrayBuffer = buf; curArrayBuffer = buf;
} }
if (attrMask != layout->semanticsMask_) { if (attrMask != layout->semanticsMask_) {
int enable = layout->semanticsMask_ & ~attrMask; EnableDisableVertexArrays(attrMask, layout->semanticsMask_);
int disable = (~layout->semanticsMask_) & attrMask;
for (int i = 0; i < 7; i++) { // SEM_MAX
if (enable & (1 << i)) {
glEnableVertexAttribArray(i);
}
if (disable & (1 << i)) {
glDisableVertexAttribArray(i);
}
}
attrMask = layout->semanticsMask_; attrMask = layout->semanticsMask_;
} }
for (size_t i = 0; i < layout->entries.size(); i++) { for (size_t i = 0; i < layout->entries.size(); i++) {
auto &entry = layout->entries[i]; auto &entry = layout->entries[i];
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.bindVertexBuffer.offset + entry.offset)); glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.draw.vertexOffset + entry.offset));
} }
CHECK_GL_ERROR_IF_DEBUG(); if (c.draw.indexBuffer) {
break; GLuint buf = c.draw.indexBuffer->buffer_;
} _dbg_assert_(!c.draw.indexBuffer->Mapped());
case GLRRenderCommand::BIND_BUFFER:
{
if (c.bind_buffer.target == GL_ARRAY_BUFFER) {
Crash();
} else if (c.bind_buffer.target == GL_ELEMENT_ARRAY_BUFFER) {
GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer_ : 0;
_dbg_assert_(!(c.bind_buffer.buffer && c.bind_buffer.buffer->Mapped()));
if (buf != curElemArrayBuffer) { if (buf != curElemArrayBuffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
curElemArrayBuffer = buf; curElemArrayBuffer = buf;
} }
if (c.draw.instances == 1) {
glDrawElements(c.draw.mode, c.draw.count, c.draw.indexType, (void *)(intptr_t)c.draw.indexOffset);
} else { } else {
GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer_ : 0; glDrawElementsInstanced(c.draw.mode, c.draw.count, c.draw.indexType, (void *)(intptr_t)c.draw.indexOffset, c.draw.instances);
_dbg_assert_(!(c.bind_buffer.buffer && c.bind_buffer.buffer->Mapped())); }
glBindBuffer(c.bind_buffer.target, buf); } else {
glDrawArrays(c.draw.mode, c.draw.first, c.draw.count);
} }
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
@ -1202,16 +1273,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
// TODO: Should we include the texture handle in the command? // TODO: Should we include the texture handle in the command?
// Also, should this not be an init command? // Also, should this not be an init command?
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
break;
case GLRRenderCommand::DRAW:
glDrawArrays(c.draw.mode, c.draw.first, c.draw.count);
break;
case GLRRenderCommand::DRAW_INDEXED:
if (c.drawIndexed.instances == 1) {
glDrawElements(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices);
} else {
glDrawElementsInstanced(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices, c.drawIndexed.instances);
}
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
break; break;
case GLRRenderCommand::TEXTURESAMPLER: case GLRRenderCommand::TEXTURESAMPLER:
@ -1315,8 +1376,14 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
cullEnabled = true; cullEnabled = true;
} }
if (frontFace != c.raster.frontFace) {
glFrontFace(c.raster.frontFace); glFrontFace(c.raster.frontFace);
frontFace = c.raster.frontFace;
}
if (cullFace != c.raster.cullFace) {
glCullFace(c.raster.cullFace); glCullFace(c.raster.cullFace);
cullFace = c.raster.cullFace;
}
} else if (/* !c.raster.cullEnable && */ cullEnabled) { } else if (/* !c.raster.cullEnable && */ cullEnabled) {
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
cullEnabled = false; cullEnabled = false;
@ -1784,3 +1851,74 @@ GLRFramebuffer::~GLRFramebuffer() {
glDeleteRenderbuffers(1, &stencil_buffer); glDeleteRenderbuffers(1, &stencil_buffer);
CHECK_GL_ERROR_IF_DEBUG(); CHECK_GL_ERROR_IF_DEBUG();
} }
std::string GLQueueRunner::StepToString(const GLRStep &step) const {
char buffer[256];
switch (step.stepType) {
case GLRStepType::RENDER:
{
int w = step.render.framebuffer ? step.render.framebuffer->width : targetWidth_;
int h = step.render.framebuffer ? step.render.framebuffer->height : targetHeight_;
snprintf(buffer, sizeof(buffer), "RENDER %s %s (commands: %d, %dx%d)\n", step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", (int)step.commands.size(), w, h);
break;
}
case GLRStepType::COPY:
snprintf(buffer, sizeof(buffer), "COPY '%s' %s -> %s (%dx%d, %s)\n", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.copy.srcRect.w, step.copy.srcRect.h, GLRAspectToString((GLRAspect)step.copy.aspectMask));
break;
case GLRStepType::BLIT:
snprintf(buffer, sizeof(buffer), "BLIT '%s' %s -> %s (%dx%d->%dx%d, %s)\n", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.blit.srcRect.w, step.blit.srcRect.h, step.blit.dstRect.w, step.blit.dstRect.h, GLRAspectToString((GLRAspect)step.blit.aspectMask));
break;
case GLRStepType::READBACK:
snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)\n", step.tag, step.readback.src ? step.readback.src->Tag() : "(backbuffer)", step.readback.srcRect.w, step.readback.srcRect.h, GLRAspectToString((GLRAspect)step.readback.aspectMask));
break;
case GLRStepType::READBACK_IMAGE:
snprintf(buffer, sizeof(buffer), "READBACK_IMAGE '%s' (%dx%d)\n", step.tag, step.readback_image.srcRect.w, step.readback_image.srcRect.h);
break;
case GLRStepType::RENDER_SKIP:
snprintf(buffer, sizeof(buffer), "(RENDER_SKIP) %s\n", step.tag);
break;
default:
buffer[0] = 0;
break;
}
return std::string(buffer);
}
const char *GLRAspectToString(GLRAspect aspect) {
switch (aspect) {
case GLR_ASPECT_COLOR: return "COLOR";
case GLR_ASPECT_DEPTH: return "DEPTH";
case GLR_ASPECT_STENCIL: return "STENCIL";
default: return "N/A";
}
}
const char *RenderCommandToString(GLRRenderCommand cmd) {
switch (cmd) {
case GLRRenderCommand::DEPTH: return "DEPTH";
case GLRRenderCommand::STENCIL: return "STENCIL";
case GLRRenderCommand::BLEND: return "BLEND";
case GLRRenderCommand::BLENDCOLOR: return "BLENDCOLOR";
case GLRRenderCommand::LOGICOP: return "LOGICOP";
case GLRRenderCommand::UNIFORM4I: return "UNIFORM4I";
case GLRRenderCommand::UNIFORM4UI: return "UNIFORM4UI";
case GLRRenderCommand::UNIFORM4F: return "UNIFORM4F";
case GLRRenderCommand::UNIFORMMATRIX: return "UNIFORMMATRIX";
case GLRRenderCommand::UNIFORMSTEREOMATRIX: return "UNIFORMSTEREOMATRIX";
case GLRRenderCommand::TEXTURESAMPLER: return "TEXTURESAMPLER";
case GLRRenderCommand::TEXTURELOD: return "TEXTURELOD";
case GLRRenderCommand::VIEWPORT: return "VIEWPORT";
case GLRRenderCommand::SCISSOR: return "SCISSOR";
case GLRRenderCommand::RASTER: return "RASTER";
case GLRRenderCommand::CLEAR: return "CLEAR";
case GLRRenderCommand::INVALIDATE: return "INVALIDATE";
case GLRRenderCommand::BINDPROGRAM: return "BINDPROGRAM";
case GLRRenderCommand::BINDTEXTURE: return "BINDTEXTURE";
case GLRRenderCommand::BIND_FB_TEXTURE: return "BIND_FB_TEXTURE";
case GLRRenderCommand::BIND_VERTEX_BUFFER: return "BIND_VERTEX_BUFFER";
case GLRRenderCommand::GENMIPS: return "GENMIPS";
case GLRRenderCommand::DRAW: return "DRAW";
case GLRRenderCommand::TEXTURE_SUBIMAGE: return "TEXTURE_SUBIMAGE";
default: return "N/A";
}
}

View file

@ -11,7 +11,7 @@
#include "Common/GPU/Shader.h" #include "Common/GPU/Shader.h"
#include "Common/GPU/thin3d.h" #include "Common/GPU/thin3d.h"
#include "Common/Data/Collections/TinySet.h" #include "Common/Data/Collections/TinySet.h"
#include "Common/Data/Collections/FastVec.h"
struct GLRViewport { struct GLRViewport {
float x, y, w, h, minZ, maxZ; float x, y, w, h, minZ, maxZ;
@ -25,7 +25,7 @@ struct GLOffset2D {
int x, y; int x, y;
}; };
enum class GLRAllocType { enum class GLRAllocType : uint8_t {
NONE, NONE,
NEW, NEW,
ALIGNED, ALIGNED,
@ -40,8 +40,7 @@ class GLRInputLayout;
enum class GLRRenderCommand : uint8_t { enum class GLRRenderCommand : uint8_t {
DEPTH, DEPTH,
STENCILFUNC, STENCIL,
STENCILOP,
BLEND, BLEND,
BLENDCOLOR, BLENDCOLOR,
LOGICOP, LOGICOP,
@ -61,10 +60,8 @@ enum class GLRRenderCommand : uint8_t {
BINDTEXTURE, BINDTEXTURE,
BIND_FB_TEXTURE, BIND_FB_TEXTURE,
BIND_VERTEX_BUFFER, BIND_VERTEX_BUFFER,
BIND_BUFFER,
GENMIPS, GENMIPS,
DRAW, DRAW,
DRAW_INDEXED,
TEXTURE_SUBIMAGE, TEXTURE_SUBIMAGE,
}; };
@ -72,6 +69,7 @@ enum class GLRRenderCommand : uint8_t {
// type field, smashed right after each other?) // type field, smashed right after each other?)
// Also, all GLenums are really only 16 bits. // Also, all GLenums are really only 16 bits.
struct GLRRenderData { struct GLRRenderData {
GLRRenderData(GLRRenderCommand _cmd) : cmd(_cmd) {}
GLRRenderCommand cmd; GLRRenderCommand cmd;
union { union {
struct { struct {
@ -101,26 +99,23 @@ struct GLRRenderData {
GLenum func; GLenum func;
uint8_t ref; uint8_t ref;
uint8_t compareMask; uint8_t compareMask;
} stencilFunc;
struct {
GLenum sFail; GLenum sFail;
GLenum zFail; GLenum zFail;
GLenum pass; GLenum pass;
uint8_t writeMask; uint8_t writeMask;
} stencilOp; // also write mask } stencil;
struct { struct {
GLRInputLayout *inputLayout;
GLRBuffer *vertexBuffer;
GLRBuffer *indexBuffer;
uint32_t vertexOffset;
uint32_t indexOffset;
GLenum mode; // primitive GLenum mode; // primitive
GLint buffer;
GLint first; GLint first;
GLint count; GLint count;
} draw;
struct {
GLenum mode; // primitive
GLint count;
GLint instances;
GLint indexType; GLint indexType;
void *indices; GLint instances;
} drawIndexed; } draw;
struct { struct {
const char *name; // if null, use loc const char *name; // if null, use loc
const GLint *loc; // NOTE: This is a pointer so we can immediately use things that are "queried" during program creation. const GLint *loc; // NOTE: This is a pointer so we can immediately use things that are "queried" during program creation.
@ -130,8 +125,13 @@ struct GLRRenderData {
struct { struct {
const char *name; // if null, use loc const char *name; // if null, use loc
const GLint *loc; const GLint *loc;
float m[32]; float m[16];
} uniformMatrix4; } uniformMatrix4;
struct {
const char *name; // if null, use loc
const GLint *loc;
float *mData; // new'd, 32 entries
} uniformStereoMatrix4;
struct { struct {
uint32_t clearColor; uint32_t clearColor;
float clearZ; float clearZ;
@ -171,11 +171,6 @@ struct GLRRenderData {
struct { struct {
GLRProgram *program; GLRProgram *program;
} program; } program;
struct {
GLRInputLayout *inputLayout;
GLRBuffer *buffer;
size_t offset;
} bindVertexBuffer;
struct { struct {
int slot; int slot;
GLenum wrapS; GLenum wrapS;
@ -295,16 +290,17 @@ enum class GLRRenderPassAction {
class GLRFramebuffer; class GLRFramebuffer;
enum { enum GLRAspect {
GLR_ASPECT_COLOR = 1, GLR_ASPECT_COLOR = 1,
GLR_ASPECT_DEPTH = 2, GLR_ASPECT_DEPTH = 2,
GLR_ASPECT_STENCIL = 3, GLR_ASPECT_STENCIL = 3,
}; };
const char *GLRAspectToString(GLRAspect aspect);
struct GLRStep { struct GLRStep {
GLRStep(GLRStepType _type) : stepType(_type) {} GLRStep(GLRStepType _type) : stepType(_type) {}
GLRStepType stepType; GLRStepType stepType;
std::vector<GLRRenderData> commands; FastVec<GLRRenderData> commands;
TinySet<const GLRFramebuffer *, 8> dependencies; TinySet<const GLRFramebuffer *, 8> dependencies;
const char *tag; const char *tag;
union { union {
@ -313,8 +309,6 @@ struct GLRStep {
GLRRenderPassAction color; GLRRenderPassAction color;
GLRRenderPassAction depth; GLRRenderPassAction depth;
GLRRenderPassAction stencil; GLRRenderPassAction stencil;
// Note: not accurate.
int numDraws;
} render; } render;
struct { struct {
GLRFramebuffer *src; GLRFramebuffer *src;
@ -358,9 +352,9 @@ public:
caps_ = caps; caps_ = caps;
} }
void RunInitSteps(const std::vector<GLRInitStep> &steps, bool skipGLCalls); void RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls);
void RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCalls, bool keepSteps, bool useVR); void RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR);
void CreateDeviceObjects(); void CreateDeviceObjects();
void DestroyDeviceObjects(); void DestroyDeviceObjects();
@ -385,7 +379,7 @@ private:
void InitCreateFramebuffer(const GLRInitStep &step); void InitCreateFramebuffer(const GLRInitStep &step);
void PerformBindFramebufferAsRenderTarget(const GLRStep &pass); void PerformBindFramebufferAsRenderTarget(const GLRStep &pass);
void PerformRenderPass(const GLRStep &pass, bool first, bool last); void PerformRenderPass(const GLRStep &pass, bool first, bool last, GLQueueProfileContext &profile);
void PerformCopy(const GLRStep &pass); void PerformCopy(const GLRStep &pass);
void PerformBlit(const GLRStep &pass); void PerformBlit(const GLRStep &pass);
void PerformReadback(const GLRStep &pass); void PerformReadback(const GLRStep &pass);
@ -396,6 +390,8 @@ private:
GLenum fbo_get_fb_target(bool read, GLuint **cached); GLenum fbo_get_fb_target(bool read, GLuint **cached);
void fbo_unbind(); void fbo_unbind();
std::string StepToString(const GLRStep &step) const;
GLRFramebuffer *curFB_ = nullptr; GLRFramebuffer *curFB_ = nullptr;
GLuint globalVAO_ = 0; GLuint globalVAO_ = 0;
@ -427,3 +423,5 @@ private:
ErrorCallbackFn errorCallback_ = nullptr; ErrorCallbackFn errorCallback_ = nullptr;
void *errorCallbackUserData_ = nullptr; void *errorCallbackUserData_ = nullptr;
}; };
const char *RenderCommandToString(GLRRenderCommand cmd);

View file

@ -8,6 +8,7 @@
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/TimeUtil.h" #include "Common/TimeUtil.h"
#include "Common/MemoryUtil.h" #include "Common/MemoryUtil.h"
#include "Common/StringUtils.h"
#include "Common/Math/math_util.h" #include "Common/Math/math_util.h"
#if 0 // def _DEBUG #if 0 // def _DEBUG
@ -16,12 +17,7 @@
#define VLOG(...) #define VLOG(...)
#endif #endif
static std::thread::id renderThreadId; std::thread::id renderThreadId;
#if MAX_LOGLEVEL >= DEBUG_LEVEL
static bool OnRenderThread() {
return std::this_thread::get_id() == renderThreadId;
}
#endif
GLRTexture::GLRTexture(const Draw::DeviceCaps &caps, int width, int height, int depth, int numMips) { GLRTexture::GLRTexture(const Draw::DeviceCaps &caps, int width, int height, int depth, int numMips) {
if (caps.textureNPOTFullySupported) { if (caps.textureNPOTFullySupported) {
@ -41,6 +37,11 @@ GLRTexture::~GLRTexture() {
} }
} }
GLRenderManager::GLRenderManager() {
// size_t sz = sizeof(GLRRenderData);
// _dbg_assert_(sz == 88);
}
GLRenderManager::~GLRenderManager() { GLRenderManager::~GLRenderManager() {
_dbg_assert_(!run_); _dbg_assert_(!run_);
@ -128,25 +129,24 @@ bool GLRenderManager::ThreadFrame() {
return false; return false;
} }
GLRRenderThreadTask task; GLRRenderThreadTask *task = nullptr;
// In case of syncs or other partial completion, we keep going until we complete a frame. // In case of syncs or other partial completion, we keep going until we complete a frame.
while (true) { while (true) {
// Pop a task of the queue and execute it. // Pop a task of the queue and execute it.
// NOTE: We need to actually wait for a task, we can't just bail! // NOTE: We need to actually wait for a task, we can't just bail!
{ {
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
while (renderThreadQueue_.empty()) { while (renderThreadQueue_.empty()) {
pushCondVar_.wait(lock); pushCondVar_.wait(lock);
} }
task = renderThreadQueue_.front(); task = std::move(renderThreadQueue_.front());
renderThreadQueue_.pop(); renderThreadQueue_.pop();
} }
// We got a task! We can now have pushMutex_ unlocked, allowing the host to // We got a task! We can now have pushMutex_ unlocked, allowing the host to
// push more work when it feels like it, and just start working. // push more work when it feels like it, and just start working.
if (task.runType == GLRRunType::EXIT) { if (task->runType == GLRRunType::EXIT) {
// Oh, host wanted out. Let's leave, and also let's notify the host. // Oh, host wanted out. Let's leave, and also let's notify the host.
// This is unlike Vulkan too which can just block on the thread existing. // This is unlike Vulkan too which can just block on the thread existing.
std::unique_lock<std::mutex> lock(syncMutex_); std::unique_lock<std::mutex> lock(syncMutex_);
@ -156,11 +156,13 @@ bool GLRenderManager::ThreadFrame() {
} }
// Render the scene. // Render the scene.
VLOG(" PULL: Frame %d RUN (%0.3f)", task.frame, time_now_d()); VLOG(" PULL: Frame %d RUN (%0.3f)", task->frame, time_now_d());
if (Run(task)) { if (Run(*task)) {
// Swap requested, so we just bail the loop. // Swap requested, so we just bail the loop.
delete task;
break; break;
} }
delete task;
}; };
return true; return true;
@ -173,15 +175,21 @@ void GLRenderManager::StopThread() {
run_ = false; run_ = false;
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
GLRRenderThreadTask exitTask{}; renderThreadQueue_.push(new GLRRenderThreadTask(GLRRunType::EXIT));
exitTask.runType = GLRRunType::EXIT;
renderThreadQueue_.push(exitTask);
pushCondVar_.notify_one(); pushCondVar_.notify_one();
} else { } else {
WARN_LOG(G3D, "GL submission thread was already paused."); WARN_LOG(G3D, "GL submission thread was already paused.");
} }
} }
std::string GLRenderManager::GetGpuProfileString() const {
int curFrame = GetCurFrame();
const GLQueueProfileContext &profile = frameData_[curFrame].profile;
float cputime_ms = 1000.0f * (profile.cpuEndTime - profile.cpuStartTime);
return StringFromFormat("CPU time to run the list: %0.2f ms\n\n%s", cputime_ms, profilePassesString_.c_str());
}
void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
_assert_(insideFrame_); _assert_(insideFrame_);
#ifdef _DEBUG #ifdef _DEBUG
@ -206,13 +214,11 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende
step->render.color = color; step->render.color = color;
step->render.depth = depth; step->render.depth = depth;
step->render.stencil = stencil; step->render.stencil = stencil;
step->render.numDraws = 0;
step->tag = tag; step->tag = tag;
steps_.push_back(step); steps_.push_back(step);
GLuint clearMask = 0; GLuint clearMask = 0;
GLRRenderData data; GLRRenderData data(GLRRenderCommand::CLEAR);
data.cmd = GLRRenderCommand::CLEAR;
if (color == GLRRenderPassAction::CLEAR) { if (color == GLRRenderPassAction::CLEAR) {
clearMask |= GL_COLOR_BUFFER_BIT; clearMask |= GL_COLOR_BUFFER_BIT;
data.clear.clearColor = clearColor; data.clear.clearColor = clearColor;
@ -336,7 +342,7 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i
queueRunner_.CopyFromReadbackBuffer(nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels); queueRunner_.CopyFromReadbackBuffer(nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels);
} }
void GLRenderManager::BeginFrame() { void GLRenderManager::BeginFrame(bool enableProfiling) {
#ifdef _DEBUG #ifdef _DEBUG
curProgram_ = nullptr; curProgram_ = nullptr;
#endif #endif
@ -344,6 +350,8 @@ void GLRenderManager::BeginFrame() {
int curFrame = GetCurFrame(); int curFrame = GetCurFrame();
GLFrameData &frameData = frameData_[curFrame]; GLFrameData &frameData = frameData_[curFrame];
frameData.profile.enabled = enableProfiling;
{ {
VLOG("PUSH: BeginFrame (curFrame = %d, readyForFence = %d, time=%0.3f)", curFrame, (int)frameData.readyForFence, time_now_d()); VLOG("PUSH: BeginFrame (curFrame = %d, readyForFence = %d, time=%0.3f)", curFrame, (int)frameData.readyForFence, time_now_d());
std::unique_lock<std::mutex> lock(frameData.fenceMutex); std::unique_lock<std::mutex> lock(frameData.fenceMutex);
@ -369,20 +377,36 @@ void GLRenderManager::Finish() {
frameData_[curFrame].deleter.Take(deleter_); frameData_[curFrame].deleter.Take(deleter_);
VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame); VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame);
GLRRenderThreadTask task; GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::PRESENT);
task.frame = curFrame; task->frame = curFrame;
task.runType = GLRRunType::PRESENT;
{ {
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task); renderThreadQueue_.push(task);
renderThreadQueue_.back().initSteps = std::move(initSteps_); renderThreadQueue_.back()->initSteps = std::move(initSteps_);
renderThreadQueue_.back().steps = std::move(steps_); renderThreadQueue_.back()->steps = std::move(steps_);
initSteps_.clear(); initSteps_.clear();
steps_.clear(); steps_.clear();
pushCondVar_.notify_one(); pushCondVar_.notify_one();
} }
if (frameData.profile.enabled) {
profilePassesString_ = std::move(frameData.profile.passesString);
#ifdef _DEBUG
std::string cmdString;
for (int i = 0; i < ARRAY_SIZE(frameData.profile.commandCounts); i++) {
if (frameData.profile.commandCounts[i] > 0) {
cmdString += StringFromFormat("%s: %d\n", RenderCommandToString((GLRRenderCommand)i), frameData.profile.commandCounts[i]);
}
}
memset(frameData.profile.commandCounts, 0, sizeof(frameData.profile.commandCounts));
profilePassesString_ = cmdString + profilePassesString_;
#endif
frameData.profile.passesString.clear();
}
curFrame_++; curFrame_++;
if (curFrame_ >= inflightFrames_) if (curFrame_ >= inflightFrames_)
curFrame_ = 0; curFrame_ = 0;
@ -412,15 +436,23 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) {
} }
} }
if (frameData.profile.enabled) {
frameData.profile.cpuStartTime = time_now_d();
}
if (IsVREnabled()) { if (IsVREnabled()) {
int passes = GetVRPassesCount(); int passes = GetVRPassesCount();
for (int i = 0; i < passes; i++) { for (int i = 0; i < passes; i++) {
PreVRFrameRender(i); PreVRFrameRender(i);
queueRunner_.RunSteps(task.steps, skipGLCalls_, i < passes - 1, true); queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, i < passes - 1, true);
PostVRFrameRender(); PostVRFrameRender();
} }
} else { } else {
queueRunner_.RunSteps(task.steps, skipGLCalls_, false, false); queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, false, false);
}
if (frameData.profile.enabled) {
frameData.profile.cpuEndTime = time_now_d();
} }
if (!skipGLCalls_) { if (!skipGLCalls_) {
@ -491,14 +523,13 @@ void GLRenderManager::FlushSync() {
{ {
VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame_); VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame_);
GLRRenderThreadTask task; GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::SYNC);
task.frame = curFrame_; task->frame = curFrame_;
task.runType = GLRRunType::SYNC;
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task); renderThreadQueue_.push(task);
renderThreadQueue_.back().initSteps = std::move(initSteps_); renderThreadQueue_.back()->initSteps = std::move(initSteps_);
renderThreadQueue_.back().steps = std::move(steps_); renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one(); pushCondVar_.notify_one();
steps_.clear(); steps_.clear();
} }
@ -513,254 +544,3 @@ void GLRenderManager::FlushSync() {
syncDone_ = false; syncDone_ = false;
} }
} }
GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size) : render_(render), size_(size), target_(target) {
bool res = AddBuffer();
_assert_(res);
}
GLPushBuffer::~GLPushBuffer() {
Destroy(true);
}
void GLPushBuffer::Map() {
_assert_(!writePtr_);
auto &info = buffers_[buf_];
writePtr_ = info.deviceMemory ? info.deviceMemory : info.localMemory;
info.flushOffset = 0;
// Force alignment. This is needed for PushAligned() to work as expected.
while ((intptr_t)writePtr_ & 15) {
writePtr_++;
offset_++;
info.flushOffset++;
}
_assert_(writePtr_);
}
void GLPushBuffer::Unmap() {
_assert_(writePtr_);
if (!buffers_[buf_].deviceMemory) {
// Here we simply upload the data to the last buffer.
// Might be worth trying with size_ instead of offset_, so the driver can replace
// the whole buffer. At least if it's close.
render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].localMemory, false);
} else {
buffers_[buf_].flushOffset = offset_;
}
writePtr_ = nullptr;
}
void GLPushBuffer::Flush() {
// Must be called from the render thread.
_dbg_assert_(OnRenderThread());
buffers_[buf_].flushOffset = offset_;
if (!buffers_[buf_].deviceMemory && writePtr_) {
auto &info = buffers_[buf_];
if (info.flushOffset != 0) {
_assert_(info.buffer->buffer_);
glBindBuffer(target_, info.buffer->buffer_);
glBufferSubData(target_, 0, info.flushOffset, info.localMemory);
}
// Here we will submit all the draw calls, with the already known buffer and offsets.
// Might as well reset the write pointer here and start over the current buffer.
writePtr_ = info.localMemory;
offset_ = 0;
info.flushOffset = 0;
}
// For device memory, we flush all buffers here.
if ((strategy_ & GLBufferStrategy::MASK_FLUSH) != 0) {
for (auto &info : buffers_) {
if (info.flushOffset == 0 || !info.deviceMemory)
continue;
glBindBuffer(target_, info.buffer->buffer_);
glFlushMappedBufferRange(target_, 0, info.flushOffset);
info.flushOffset = 0;
}
}
}
bool GLPushBuffer::AddBuffer() {
BufInfo info;
info.localMemory = (uint8_t *)AllocateAlignedMemory(size_, 16);
if (!info.localMemory)
return false;
info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW);
buf_ = buffers_.size();
buffers_.push_back(info);
return true;
}
void GLPushBuffer::Destroy(bool onRenderThread) {
if (buf_ == -1)
return; // Already destroyed
for (BufInfo &info : buffers_) {
// This will automatically unmap device memory, if needed.
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
if (onRenderThread) {
delete info.buffer;
} else {
render_->DeleteBuffer(info.buffer);
}
FreeAlignedMemory(info.localMemory);
}
buffers_.clear();
buf_ = -1;
}
void GLPushBuffer::NextBuffer(size_t minSize) {
// First, unmap the current memory.
Unmap();
buf_++;
if (buf_ >= buffers_.size() || minSize > size_) {
// Before creating the buffer, adjust to the new size_ if necessary.
while (size_ < minSize) {
size_ <<= 1;
}
bool res = AddBuffer();
_assert_(res);
if (!res) {
// Let's try not to crash at least?
buf_ = 0;
}
}
// Now, move to the next buffer and map it.
offset_ = 0;
Map();
}
void GLPushBuffer::Defragment() {
_dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread");
if (buffers_.size() <= 1) {
// Let's take this chance to jetison localMemory we don't need.
for (auto &info : buffers_) {
if (info.deviceMemory) {
FreeAlignedMemory(info.localMemory);
info.localMemory = nullptr;
}
}
return;
}
// Okay, we have more than one. Destroy them all and start over with a larger one.
size_t newSize = size_ * buffers_.size();
Destroy(false);
size_ = newSize;
bool res = AddBuffer();
_assert_msg_(res, "AddBuffer failed");
}
size_t GLPushBuffer::GetTotalSize() const {
size_t sum = 0;
if (buffers_.size() > 1)
sum += size_ * (buffers_.size() - 1);
sum += offset_;
return sum;
}
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
_dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread");
strategy_ = strategy;
if (strategy_ == GLBufferStrategy::SUBDATA) {
return;
}
bool mapChanged = false;
for (auto &info : buffers_) {
if (!info.buffer->buffer_ || info.deviceMemory) {
// Can't map - no device buffer associated yet or already mapped.
continue;
}
info.deviceMemory = (uint8_t *)info.buffer->Map(strategy_);
mapChanged = mapChanged || info.deviceMemory != nullptr;
if (!info.deviceMemory && !info.localMemory) {
// Somehow it failed, let's dodge crashing.
info.localMemory = (uint8_t *)AllocateAlignedMemory(info.buffer->size_, 16);
mapChanged = true;
}
_dbg_assert_msg_(info.localMemory || info.deviceMemory, "Local or device memory must succeed");
}
if (writePtr_ && mapChanged) {
// This can happen during a sync. Remap.
writePtr_ = nullptr;
Map();
}
}
void GLPushBuffer::UnmapDevice() {
_dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread");
for (auto &info : buffers_) {
if (info.deviceMemory) {
// TODO: Technically this can return false?
info.buffer->Unmap();
info.deviceMemory = nullptr;
}
}
}
void *GLRBuffer::Map(GLBufferStrategy strategy) {
_assert_(buffer_ != 0);
GLbitfield access = GL_MAP_WRITE_BIT;
if ((strategy & GLBufferStrategy::MASK_FLUSH) != 0) {
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
}
if ((strategy & GLBufferStrategy::MASK_INVALIDATE) != 0) {
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
}
void *p = nullptr;
bool allowNativeBuffer = strategy != GLBufferStrategy::SUBDATA;
if (allowNativeBuffer) {
glBindBuffer(target_, buffer_);
if (gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage) {
#if !PPSSPP_PLATFORM(IOS)
if (!hasStorage_) {
GLbitfield storageFlags = access & ~(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
#ifdef USING_GLES2
#ifdef GL_EXT_buffer_storage
glBufferStorageEXT(target_, size_, nullptr, storageFlags);
#endif
#else
glBufferStorage(target_, size_, nullptr, storageFlags);
#endif
hasStorage_ = true;
}
#endif
p = glMapBufferRange(target_, 0, size_, access);
} else if (gl_extensions.VersionGEThan(3, 0, 0)) {
// GLES3 or desktop 3.
p = glMapBufferRange(target_, 0, size_, access);
} else if (!gl_extensions.IsGLES) {
#ifndef USING_GLES2
p = glMapBuffer(target_, GL_READ_WRITE);
#endif
}
}
mapped_ = p != nullptr;
return p;
}
bool GLRBuffer::Unmap() {
glBindBuffer(target_, buffer_);
mapped_ = false;
return glUnmapBuffer(target_) == GL_TRUE;
}

View file

@ -16,6 +16,7 @@
#include "Common/GPU/OpenGL/GLQueueRunner.h" #include "Common/GPU/OpenGL/GLQueueRunner.h"
#include "Common/GPU/OpenGL/GLFrameData.h" #include "Common/GPU/OpenGL/GLFrameData.h"
#include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLMemory.h"
class GLRInputLayout; class GLRInputLayout;
class GLPushBuffer; class GLPushBuffer;
@ -52,13 +53,14 @@ public:
class GLRFramebuffer { class GLRFramebuffer {
public: public:
GLRFramebuffer(const Draw::DeviceCaps &caps, int _width, int _height, bool z_stencil) GLRFramebuffer(const Draw::DeviceCaps &caps, int _width, int _height, bool z_stencil, const char *tag)
: color_texture(caps, _width, _height, 1, 1), z_stencil_texture(caps, _width, _height, 1, 1), : color_texture(caps, _width, _height, 1, 1), z_stencil_texture(caps, _width, _height, 1, 1),
width(_width), height(_height), z_stencil_(z_stencil) { width(_width), height(_height), z_stencil_(z_stencil) {
} }
~GLRFramebuffer(); ~GLRFramebuffer();
const char *Tag() const { return tag_.c_str(); }
GLuint handle = 0; GLuint handle = 0;
GLRTexture color_texture; GLRTexture color_texture;
// Either z_stencil_texture, z_stencil_buffer, or (z_buffer and stencil_buffer) are set. // Either z_stencil_texture, z_stencil_buffer, or (z_buffer and stencil_buffer) are set.
@ -70,8 +72,10 @@ public:
int width; int width;
int height; int height;
GLuint colorDepth = 0; GLuint colorDepth = 0;
bool z_stencil_; bool z_stencil_;
private:
std::string tag_;
}; };
// We need to create some custom heap-allocated types so we can forward things that need to be created on the GL thread, before // We need to create some custom heap-allocated types so we can forward things that need to be created on the GL thread, before
@ -179,178 +183,6 @@ private:
std::unordered_map<std::string, UniformInfo> uniformCache_; std::unordered_map<std::string, UniformInfo> uniformCache_;
}; };
enum class GLBufferStrategy {
SUBDATA = 0,
MASK_FLUSH = 0x10,
MASK_INVALIDATE = 0x20,
// Map/unmap the buffer each frame.
FRAME_UNMAP = 1,
// Map/unmap and also invalidate the buffer on map.
INVALIDATE_UNMAP = MASK_INVALIDATE,
// Map/unmap and explicitly flushed changed ranges.
FLUSH_UNMAP = MASK_FLUSH,
// Map/unmap, invalidate on map, and explicit flush.
FLUSH_INVALIDATE_UNMAP = MASK_FLUSH | MASK_INVALIDATE,
};
static inline int operator &(const GLBufferStrategy &lhs, const GLBufferStrategy &rhs) {
return (int)lhs & (int)rhs;
}
class GLRBuffer {
public:
GLRBuffer(GLuint target, size_t size) : target_(target), size_((int)size) {}
~GLRBuffer() {
if (buffer_) {
glDeleteBuffers(1, &buffer_);
}
}
void *Map(GLBufferStrategy strategy);
bool Unmap();
bool Mapped() const {
return mapped_;
}
GLuint buffer_ = 0;
GLuint target_;
int size_;
private:
bool mapped_ = false;
bool hasStorage_ = false;
};
class GLRenderManager;
// Similar to VulkanPushBuffer but is currently less efficient - it collects all the data in
// RAM then does a big memcpy/buffer upload at the end of the frame. This is at least a lot
// faster than the hundreds of buffer uploads or memory array buffers we used before.
// On modern GL we could avoid the copy using glBufferStorage but not sure it's worth the
// trouble.
// We need to manage the lifetime of this together with the other resources so its destructor
// runs on the render thread.
class GLPushBuffer {
public:
friend class GLRenderManager;
struct BufInfo {
GLRBuffer *buffer = nullptr;
uint8_t *localMemory = nullptr;
uint8_t *deviceMemory = nullptr;
size_t flushOffset = 0;
};
GLPushBuffer(GLRenderManager *render, GLuint target, size_t size);
~GLPushBuffer();
void Reset() { offset_ = 0; }
private:
// Needs context in case of defragment.
void Begin() {
buf_ = 0;
offset_ = 0;
// Note: we must defrag because some buffers may be smaller than size_.
Defragment();
Map();
_dbg_assert_(writePtr_);
}
void BeginNoReset() {
Map();
}
void End() {
Unmap();
}
public:
void Map();
void Unmap();
bool IsReady() const {
return writePtr_ != nullptr;
}
// When using the returned memory, make sure to bind the returned vkbuf.
// This will later allow for handling overflow correctly.
size_t Allocate(size_t numBytes, GLRBuffer **vkbuf) {
size_t out = offset_;
if (offset_ + ((numBytes + 3) & ~3) >= size_) {
NextBuffer(numBytes);
out = offset_;
offset_ += (numBytes + 3) & ~3;
} else {
offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes.
}
*vkbuf = buffers_[buf_].buffer;
return out;
}
// Returns the offset that should be used when binding this buffer to get this data.
size_t Push(const void *data, size_t size, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return off;
}
uint32_t PushAligned(const void *data, size_t size, int align, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return (uint32_t)off;
}
size_t GetOffset() const {
return offset_;
}
// "Zero-copy" variant - you can write the data directly as you compute it.
// Recommended.
void *Push(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
void *PushAligned(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf, int align) {
_dbg_assert_(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
size_t GetTotalSize() const;
void Destroy(bool onRenderThread);
void Flush();
protected:
void MapDevice(GLBufferStrategy strategy);
void UnmapDevice();
private:
bool AddBuffer();
void NextBuffer(size_t minSize);
void Defragment();
GLRenderManager *render_;
std::vector<BufInfo> buffers_;
size_t buf_ = 0;
size_t offset_ = 0;
size_t size_ = 0;
uint8_t *writePtr_ = nullptr;
GLuint target_;
GLBufferStrategy strategy_ = GLBufferStrategy::SUBDATA;
};
class GLRInputLayout { class GLRInputLayout {
public: public:
struct Entry { struct Entry {
@ -374,14 +206,19 @@ enum class GLRRunType {
class GLRenderManager; class GLRenderManager;
class GLPushBuffer; class GLPushBuffer;
// These are enqueued from the main thread, // These are enqueued from the main thread, and the render thread pops them off
// and the render thread pops them off
struct GLRRenderThreadTask { struct GLRRenderThreadTask {
std::vector<GLRStep *> steps; GLRRenderThreadTask(GLRRunType _runType) : runType(_runType) {}
std::vector<GLRInitStep> initSteps;
int frame; std::vector<GLRStep *> steps;
FastVec<GLRInitStep> initSteps;
int frame = -1;
GLRRunType runType; GLRRunType runType;
// Avoid copying these by accident.
GLRRenderThreadTask(GLRRenderThreadTask &) = delete;
GLRRenderThreadTask& operator =(GLRRenderThreadTask &) = delete;
}; };
// Note: The GLRenderManager is created and destroyed on the render thread, and the latter // Note: The GLRenderManager is created and destroyed on the render thread, and the latter
@ -389,9 +226,11 @@ struct GLRRenderThreadTask {
// directly in the destructor. // directly in the destructor.
class GLRenderManager { class GLRenderManager {
public: public:
GLRenderManager() {} GLRenderManager();
~GLRenderManager(); ~GLRenderManager();
GLRenderManager(GLRenderManager &) = delete;
GLRenderManager &operator=(GLRenderManager &) = delete;
void SetInvalidationCallback(InvalidationCallback callback) { void SetInvalidationCallback(InvalidationCallback callback) {
invalidationCallback_ = callback; invalidationCallback_ = callback;
@ -409,8 +248,10 @@ public:
caps_ = caps; caps_ = caps;
} }
std::string GetGpuProfileString() const;
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
void BeginFrame(); void BeginFrame(bool enableProfiling);
// Can run on a different thread! // Can run on a different thread!
void Finish(); void Finish();
@ -418,37 +259,40 @@ public:
// We pass in width/height here even though it's not strictly needed until we support glTextureStorage // We pass in width/height here even though it's not strictly needed until we support glTextureStorage
// and then we'll also need formats and stuff. // and then we'll also need formats and stuff.
GLRTexture *CreateTexture(GLenum target, int width, int height, int depth, int numMips) { GLRTexture *CreateTexture(GLenum target, int width, int height, int depth, int numMips) {
GLRInitStep step { GLRInitStepType::CREATE_TEXTURE }; _dbg_assert_(target != 0);
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_TEXTURE;
step.create_texture.texture = new GLRTexture(caps_, width, height, depth, numMips); step.create_texture.texture = new GLRTexture(caps_, width, height, depth, numMips);
step.create_texture.texture->target = target; step.create_texture.texture->target = target;
initSteps_.push_back(step);
return step.create_texture.texture; return step.create_texture.texture;
} }
GLRBuffer *CreateBuffer(GLuint target, size_t size, GLuint usage) { GLRBuffer *CreateBuffer(GLuint target, size_t size, GLuint usage) {
GLRInitStep step{ GLRInitStepType::CREATE_BUFFER }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_BUFFER;
step.create_buffer.buffer = new GLRBuffer(target, size); step.create_buffer.buffer = new GLRBuffer(target, size);
step.create_buffer.size = (int)size; step.create_buffer.size = (int)size;
step.create_buffer.usage = usage; step.create_buffer.usage = usage;
initSteps_.push_back(step);
return step.create_buffer.buffer; return step.create_buffer.buffer;
} }
GLRShader *CreateShader(GLuint stage, const std::string &code, const std::string &desc) { GLRShader *CreateShader(GLuint stage, const std::string &code, const std::string &desc) {
GLRInitStep step{ GLRInitStepType::CREATE_SHADER }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_SHADER;
step.create_shader.shader = new GLRShader(); step.create_shader.shader = new GLRShader();
step.create_shader.shader->desc = desc; step.create_shader.shader->desc = desc;
step.create_shader.stage = stage; step.create_shader.stage = stage;
step.create_shader.code = new char[code.size() + 1]; step.create_shader.code = new char[code.size() + 1];
memcpy(step.create_shader.code, code.data(), code.size() + 1); memcpy(step.create_shader.code, code.data(), code.size() + 1);
initSteps_.push_back(step);
return step.create_shader.shader; return step.create_shader.shader;
} }
GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil) { GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil, const char *tag) {
GLRInitStep step{ GLRInitStepType::CREATE_FRAMEBUFFER }; _dbg_assert_(width > 0 && height > 0 && tag != nullptr);
step.create_framebuffer.framebuffer = new GLRFramebuffer(caps_, width, height, z_stencil);
initSteps_.push_back(step); GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_FRAMEBUFFER;
step.create_framebuffer.framebuffer = new GLRFramebuffer(caps_, width, height, z_stencil, tag);
return step.create_framebuffer.framebuffer; return step.create_framebuffer.framebuffer;
} }
@ -457,7 +301,8 @@ public:
GLRProgram *CreateProgram( GLRProgram *CreateProgram(
std::vector<GLRShader *> shaders, std::vector<GLRProgram::Semantic> semantics, std::vector<GLRProgram::UniformLocQuery> queries, std::vector<GLRShader *> shaders, std::vector<GLRProgram::Semantic> semantics, std::vector<GLRProgram::UniformLocQuery> queries,
std::vector<GLRProgram::Initializer> initializers, GLRProgramLocData *locData, const GLRProgramFlags &flags) { std::vector<GLRProgram::Initializer> initializers, GLRProgramLocData *locData, const GLRProgramFlags &flags) {
GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_PROGRAM;
_assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders)); _assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders));
step.create_program.program = new GLRProgram(); step.create_program.program = new GLRProgram();
step.create_program.program->semantics_ = semantics; step.create_program.program->semantics_ = semantics;
@ -481,23 +326,22 @@ public:
} }
#endif #endif
step.create_program.num_shaders = (int)shaders.size(); step.create_program.num_shaders = (int)shaders.size();
initSteps_.push_back(step);
return step.create_program.program; return step.create_program.program;
} }
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries) { GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries) {
GLRInitStep step{ GLRInitStepType::CREATE_INPUT_LAYOUT }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_INPUT_LAYOUT;
step.create_input_layout.inputLayout = new GLRInputLayout(); step.create_input_layout.inputLayout = new GLRInputLayout();
step.create_input_layout.inputLayout->entries = entries; step.create_input_layout.inputLayout->entries = entries;
for (auto &iter : step.create_input_layout.inputLayout->entries) { for (auto &iter : step.create_input_layout.inputLayout->entries) {
step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location; step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location;
} }
initSteps_.push_back(step);
return step.create_input_layout.inputLayout; return step.create_input_layout.inputLayout;
} }
GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size) { GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size, const char *tag) {
GLPushBuffer *push = new GLPushBuffer(this, target, size); GLPushBuffer *push = new GLPushBuffer(this, target, size, tag);
RegisterPushBuffer(frame, push); RegisterPushBuffer(frame, push);
return push; return push;
} }
@ -565,7 +409,8 @@ public:
void BufferSubdata(GLRBuffer *buffer, size_t offset, size_t size, uint8_t *data, bool deleteData = true) { void BufferSubdata(GLRBuffer *buffer, size_t offset, size_t size, uint8_t *data, bool deleteData = true) {
// TODO: Maybe should be a render command instead of an init command? When possible it's better as // TODO: Maybe should be a render command instead of an init command? When possible it's better as
// an init command, that's for sure. // an init command, that's for sure.
GLRInitStep step{ GLRInitStepType::BUFFER_SUBDATA }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::BUFFER_SUBDATA;
_dbg_assert_(offset >= 0); _dbg_assert_(offset >= 0);
_dbg_assert_(offset <= buffer->size_ - size); _dbg_assert_(offset <= buffer->size_ - size);
step.buffer_subdata.buffer = buffer; step.buffer_subdata.buffer = buffer;
@ -573,12 +418,12 @@ public:
step.buffer_subdata.size = (int)size; step.buffer_subdata.size = (int)size;
step.buffer_subdata.data = data; step.buffer_subdata.data = data;
step.buffer_subdata.deleteData = deleteData; step.buffer_subdata.deleteData = deleteData;
initSteps_.push_back(step);
} }
// Takes ownership over the data pointer and delete[]-s it. // Takes ownership over the data pointer and delete[]-s it.
void TextureImage(GLRTexture *texture, int level, int width, int height, int depth, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW, bool linearFilter = false) { void TextureImage(GLRTexture *texture, int level, int width, int height, int depth, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW, bool linearFilter = false) {
GLRInitStep step{ GLRInitStepType::TEXTURE_IMAGE }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::TEXTURE_IMAGE;
step.texture_image.texture = texture; step.texture_image.texture = texture;
step.texture_image.data = data; step.texture_image.data = data;
step.texture_image.format = format; step.texture_image.format = format;
@ -588,12 +433,11 @@ public:
step.texture_image.depth = depth; step.texture_image.depth = depth;
step.texture_image.allocType = allocType; step.texture_image.allocType = allocType;
step.texture_image.linearFilter = linearFilter; step.texture_image.linearFilter = linearFilter;
initSteps_.push_back(step);
} }
void TextureSubImage(int slot, GLRTexture *texture, int level, int x, int y, int width, int height, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW) { void TextureSubImage(int slot, GLRTexture *texture, int level, int x, int y, int width, int height, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData _data{ GLRRenderCommand::TEXTURE_SUBIMAGE }; GLRRenderData _data(GLRRenderCommand::TEXTURE_SUBIMAGE);
_data.texture_subimage.texture = texture; _data.texture_subimage.texture = texture;
_data.texture_subimage.data = data; _data.texture_subimage.data = data;
_data.texture_subimage.format = format; _data.texture_subimage.format = format;
@ -608,11 +452,11 @@ public:
} }
void FinalizeTexture(GLRTexture *texture, int loadedLevels, bool genMips) { void FinalizeTexture(GLRTexture *texture, int loadedLevels, bool genMips) {
GLRInitStep step{ GLRInitStepType::TEXTURE_FINALIZE }; GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::TEXTURE_FINALIZE;
step.texture_finalize.texture = texture; step.texture_finalize.texture = texture;
step.texture_finalize.loadedLevels = loadedLevels; step.texture_finalize.loadedLevels = loadedLevels;
step.texture_finalize.genMips = genMips; step.texture_finalize.genMips = genMips;
initSteps_.push_back(step);
} }
void BindTexture(int slot, GLRTexture *tex) { void BindTexture(int slot, GLRTexture *tex) {
@ -623,62 +467,44 @@ public:
} }
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS); _dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::BINDTEXTURE }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BINDTEXTURE;
data.texture.slot = slot; data.texture.slot = slot;
data.texture.texture = tex; data.texture.texture = tex;
curRenderStep_->commands.push_back(data);
} }
void BindProgram(GLRProgram *program) { void BindProgram(GLRProgram *program) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BINDPROGRAM }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BINDPROGRAM;
_dbg_assert_(program != nullptr); _dbg_assert_(program != nullptr);
data.program.program = program; data.program.program = program;
curRenderStep_->commands.push_back(data);
#ifdef _DEBUG #ifdef _DEBUG
curProgram_ = program; curProgram_ = program;
#endif #endif
} }
void BindIndexBuffer(GLRBuffer *buffer) { // Want to support an offset but can't in ES 2.0. We supply an offset when binding the buffers instead.
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BIND_BUFFER};
data.bind_buffer.buffer = buffer;
data.bind_buffer.target = GL_ELEMENT_ARRAY_BUFFER;
curRenderStep_->commands.push_back(data);
}
void BindVertexBuffer(GLRInputLayout *inputLayout, GLRBuffer *buffer, size_t offset) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(inputLayout);
GLRRenderData data{ GLRRenderCommand::BIND_VERTEX_BUFFER };
data.bindVertexBuffer.inputLayout = inputLayout;
data.bindVertexBuffer.offset = offset;
data.bindVertexBuffer.buffer = buffer;
curRenderStep_->commands.push_back(data);
}
void SetDepth(bool enabled, bool write, GLenum func) { void SetDepth(bool enabled, bool write, GLenum func) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::DEPTH }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DEPTH;
data.depth.enabled = enabled; data.depth.enabled = enabled;
data.depth.write = write; data.depth.write = write;
data.depth.func = func; data.depth.func = func;
curRenderStep_->commands.push_back(data);
} }
void SetViewport(const GLRViewport &vp) { void SetViewport(const GLRViewport &vp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::VIEWPORT }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::VIEWPORT;
data.viewport.vp = vp; data.viewport.vp = vp;
curRenderStep_->commands.push_back(data);
} }
void SetScissor(const GLRect2D &rc) { void SetScissor(const GLRect2D &rc) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::SCISSOR }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::SCISSOR;
data.scissor.rc = rc; data.scissor.rc = rc;
curRenderStep_->commands.push_back(data);
} }
void SetUniformI(const GLint *loc, int count, const int *udata) { void SetUniformI(const GLint *loc, int count, const int *udata) {
@ -686,11 +512,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4I }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4I;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = count; data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(int) * count); memcpy(data.uniform4.v, udata, sizeof(int) * count);
curRenderStep_->commands.push_back(data);
} }
void SetUniformI1(const GLint *loc, int udata) { void SetUniformI1(const GLint *loc, int udata) {
@ -698,11 +525,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4I }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4I;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = 1; data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(udata)); memcpy(data.uniform4.v, &udata, sizeof(udata));
curRenderStep_->commands.push_back(data);
} }
void SetUniformUI(const GLint *loc, int count, const uint32_t *udata) { void SetUniformUI(const GLint *loc, int count, const uint32_t *udata) {
@ -710,11 +538,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4UI }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4UI;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = count; data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(uint32_t) * count); memcpy(data.uniform4.v, udata, sizeof(uint32_t) * count);
curRenderStep_->commands.push_back(data);
} }
void SetUniformUI1(const GLint *loc, uint32_t udata) { void SetUniformUI1(const GLint *loc, uint32_t udata) {
@ -722,11 +551,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4UI }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4UI;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = 1; data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(udata)); memcpy(data.uniform4.v, &udata, sizeof(udata));
curRenderStep_->commands.push_back(data);
} }
void SetUniformF(const GLint *loc, int count, const float *udata) { void SetUniformF(const GLint *loc, int count, const float *udata) {
@ -734,11 +564,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = count; data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(float) * count); memcpy(data.uniform4.v, udata, sizeof(float) * count);
curRenderStep_->commands.push_back(data);
} }
void SetUniformF1(const GLint *loc, const float udata) { void SetUniformF1(const GLint *loc, const float udata) {
@ -746,11 +577,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = nullptr;
data.uniform4.loc = loc; data.uniform4.loc = loc;
data.uniform4.count = 1; data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(float)); memcpy(data.uniform4.v, &udata, sizeof(float));
curRenderStep_->commands.push_back(data);
} }
void SetUniformF(const char *name, int count, const float *udata) { void SetUniformF(const char *name, int count, const float *udata) {
@ -758,11 +590,12 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = name; data.uniform4.name = name;
data.uniform4.loc = nullptr;
data.uniform4.count = count; data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(float) * count); memcpy(data.uniform4.v, udata, sizeof(float) * count);
curRenderStep_->commands.push_back(data);
} }
void SetUniformM4x4(const GLint *loc, const float *udata) { void SetUniformM4x4(const GLint *loc, const float *udata) {
@ -770,10 +603,11 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
data.uniformMatrix4.name = nullptr;
data.uniformMatrix4.loc = loc; data.uniformMatrix4.loc = loc;
memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16); memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16);
curRenderStep_->commands.push_back(data);
} }
void SetUniformM4x4Stereo(const char *name, const GLint *loc, const float *left, const float *right) { void SetUniformM4x4Stereo(const char *name, const GLint *loc, const float *left, const float *right) {
@ -781,12 +615,13 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORMSTEREOMATRIX }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.uniformMatrix4.name = name; data.cmd = GLRRenderCommand::UNIFORMSTEREOMATRIX;
data.uniformMatrix4.loc = loc; data.uniformStereoMatrix4.name = name;
memcpy(&data.uniformMatrix4.m[0], left, sizeof(float) * 16); data.uniformStereoMatrix4.loc = loc;
memcpy(&data.uniformMatrix4.m[16], right, sizeof(float) * 16); data.uniformStereoMatrix4.mData = new float[32];
curRenderStep_->commands.push_back(data); memcpy(&data.uniformStereoMatrix4.mData[0], left, sizeof(float) * 16);
memcpy(&data.uniformStereoMatrix4.mData[16], right, sizeof(float) * 16);
} }
void SetUniformM4x4(const char *name, const float *udata) { void SetUniformM4x4(const char *name, const float *udata) {
@ -794,17 +629,19 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
_dbg_assert_(curProgram_); _dbg_assert_(curProgram_);
#endif #endif
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
data.uniformMatrix4.name = name; data.uniformMatrix4.name = name;
data.uniformMatrix4.loc = nullptr;
memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16); memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16);
curRenderStep_->commands.push_back(data);
} }
void SetBlendAndMask(int colorMask, bool blendEnabled, GLenum srcColor, GLenum dstColor, GLenum srcAlpha, GLenum dstAlpha, GLenum funcColor, GLenum funcAlpha) { void SetBlendAndMask(int colorMask, bool blendEnabled, GLenum srcColor, GLenum dstColor, GLenum srcAlpha, GLenum dstAlpha, GLenum funcColor, GLenum funcAlpha) {
// Make this one only a non-debug _assert_, since it often comes first. // Make this one only a non-debug _assert_, since it often comes first.
// Lets us collect info about this potential crash through assert extra data. // Lets us collect info about this potential crash through assert extra data.
_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLEND }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLEND;
data.blend.mask = colorMask; data.blend.mask = colorMask;
data.blend.enabled = blendEnabled; data.blend.enabled = blendEnabled;
data.blend.srcColor = srcColor; data.blend.srcColor = srcColor;
@ -813,96 +650,88 @@ public:
data.blend.dstAlpha = dstAlpha; data.blend.dstAlpha = dstAlpha;
data.blend.funcColor = funcColor; data.blend.funcColor = funcColor;
data.blend.funcAlpha = funcAlpha; data.blend.funcAlpha = funcAlpha;
curRenderStep_->commands.push_back(data);
} }
void SetNoBlendAndMask(int colorMask) { void SetNoBlendAndMask(int colorMask) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLEND }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLEND;
data.blend.mask = colorMask; data.blend.mask = colorMask;
data.blend.enabled = false; data.blend.enabled = false;
curRenderStep_->commands.push_back(data);
} }
#ifndef USING_GLES2 #ifndef USING_GLES2
void SetLogicOp(bool enabled, GLenum logicOp) { void SetLogicOp(bool enabled, GLenum logicOp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::LOGICOP }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::LOGICOP;
data.logic.enabled = enabled; data.logic.enabled = enabled;
data.logic.logicOp = logicOp; data.logic.logicOp = logicOp;
curRenderStep_->commands.push_back(data);
} }
#endif #endif
void SetStencilFunc(bool enabled, GLenum func, uint8_t refValue, uint8_t compareMask) { void SetStencil(bool enabled, GLenum func, uint8_t refValue, uint8_t compareMask, uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::STENCILFUNC }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.stencilFunc.enabled = enabled; data.cmd = GLRRenderCommand::STENCIL;
data.stencilFunc.func = func; data.stencil.enabled = enabled;
data.stencilFunc.ref = refValue; data.stencil.func = func;
data.stencilFunc.compareMask = compareMask; data.stencil.ref = refValue;
curRenderStep_->commands.push_back(data); data.stencil.compareMask = compareMask;
} data.stencil.writeMask = writeMask;
data.stencil.sFail = sFail;
void SetStencilOp(uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) { data.stencil.zFail = zFail;
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); data.stencil.pass = pass;
GLRRenderData data{ GLRRenderCommand::STENCILOP };
data.stencilOp.writeMask = writeMask;
data.stencilOp.sFail = sFail;
data.stencilOp.zFail = zFail;
data.stencilOp.pass = pass;
curRenderStep_->commands.push_back(data);
} }
void SetStencilDisabled() { void SetStencilDisabled() {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::STENCILFUNC; data.cmd = GLRRenderCommand::STENCIL;
data.stencilFunc.enabled = false; data.stencil.enabled = false;
curRenderStep_->commands.push_back(data);
} }
void SetBlendFactor(const float color[4]) { void SetBlendFactor(const float color[4]) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLENDCOLOR }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLENDCOLOR;
CopyFloat4(data.blendColor.color, color); CopyFloat4(data.blendColor.color, color);
curRenderStep_->commands.push_back(data);
} }
void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable, GLboolean depthClamp) { void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable, GLboolean depthClamp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::RASTER }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::RASTER;
data.raster.cullEnable = cullEnable; data.raster.cullEnable = cullEnable;
data.raster.frontFace = frontFace; data.raster.frontFace = frontFace;
data.raster.cullFace = cullFace; data.raster.cullFace = cullFace;
data.raster.ditherEnable = ditherEnable; data.raster.ditherEnable = ditherEnable;
data.raster.depthClampEnable = depthClamp; data.raster.depthClampEnable = depthClamp;
curRenderStep_->commands.push_back(data);
} }
// Modifies the current texture as per GL specs, not global state. // Modifies the current texture as per GL specs, not global state.
void SetTextureSampler(int slot, GLenum wrapS, GLenum wrapT, GLenum magFilter, GLenum minFilter, float anisotropy) { void SetTextureSampler(int slot, GLenum wrapS, GLenum wrapT, GLenum magFilter, GLenum minFilter, float anisotropy) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS); _dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURESAMPLER }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::TEXTURESAMPLER;
data.textureSampler.slot = slot; data.textureSampler.slot = slot;
data.textureSampler.wrapS = wrapS; data.textureSampler.wrapS = wrapS;
data.textureSampler.wrapT = wrapT; data.textureSampler.wrapT = wrapT;
data.textureSampler.magFilter = magFilter; data.textureSampler.magFilter = magFilter;
data.textureSampler.minFilter = minFilter; data.textureSampler.minFilter = minFilter;
data.textureSampler.anisotropy = anisotropy; data.textureSampler.anisotropy = anisotropy;
curRenderStep_->commands.push_back(data);
} }
void SetTextureLod(int slot, float minLod, float maxLod, float lodBias) { void SetTextureLod(int slot, float minLod, float maxLod, float lodBias) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS); _dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURELOD}; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::TEXTURELOD;
data.textureLod.slot = slot; data.textureLod.slot = slot;
data.textureLod.minLod = minLod; data.textureLod.minLod = minLod;
data.textureLod.maxLod = maxLod; data.textureLod.maxLod = maxLod;
data.textureLod.lodBias = lodBias; data.textureLod.lodBias = lodBias;
curRenderStep_->commands.push_back(data);
} }
// If scissorW == 0, no scissor is applied (the whole render target is cleared). // If scissorW == 0, no scissor is applied (the whole render target is cleared).
@ -910,7 +739,8 @@ public:
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
if (!clearMask) if (!clearMask)
return; return;
GLRRenderData data{ GLRRenderCommand::CLEAR }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::CLEAR;
data.clear.clearMask = clearMask; data.clear.clearMask = clearMask;
data.clear.clearColor = clearColor; data.clear.clearColor = clearColor;
data.clear.clearZ = clearZ; data.clear.clearZ = clearZ;
@ -920,30 +750,36 @@ public:
data.clear.scissorY = scissorY; data.clear.scissorY = scissorY;
data.clear.scissorW = scissorW; data.clear.scissorW = scissorW;
data.clear.scissorH = scissorH; data.clear.scissorH = scissorH;
curRenderStep_->commands.push_back(data);
} }
void Draw(GLenum mode, int first, int count) { void Draw(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLenum mode, int first, int count) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); _dbg_assert_(vertexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::DRAW }; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DRAW;
data.draw.inputLayout = inputLayout;
data.draw.vertexOffset = vertexOffset;
data.draw.vertexBuffer = vertexBuffer;
data.draw.indexBuffer = nullptr;
data.draw.mode = mode; data.draw.mode = mode;
data.draw.first = first; data.draw.first = first;
data.draw.count = count; data.draw.count = count;
data.draw.buffer = 0; data.draw.indexType = 0;
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++;
} }
void DrawIndexed(GLenum mode, int count, GLenum indexType, void *indices, int instances = 1) { // Would really love to have a basevertex parameter, but impossible in unextended GLES, without glDrawElementsBaseVertex, unfortunately.
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); void DrawIndexed(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLRBuffer *indexBuffer, uint32_t indexOffset, GLenum mode, int count, GLenum indexType, int instances = 1) {
GLRRenderData data{ GLRRenderCommand::DRAW_INDEXED }; _dbg_assert_(vertexBuffer && indexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
data.drawIndexed.mode = mode; GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.drawIndexed.count = count; data.cmd = GLRRenderCommand::DRAW;
data.drawIndexed.indexType = indexType; data.draw.inputLayout = inputLayout;
data.drawIndexed.instances = instances; data.draw.vertexOffset = vertexOffset;
data.drawIndexed.indices = indices; data.draw.vertexBuffer = vertexBuffer;
curRenderStep_->commands.push_back(data); data.draw.indexBuffer = indexBuffer;
curRenderStep_->render.numDraws++; data.draw.indexOffset = indexOffset;
data.draw.mode = mode;
data.draw.count = count;
data.draw.indexType = indexType;
data.draw.instances = instances;
} }
enum { MAX_INFLIGHT_FRAMES = 3 }; enum { MAX_INFLIGHT_FRAMES = 3 };
@ -1026,7 +862,7 @@ private:
GLRStep *curRenderStep_ = nullptr; GLRStep *curRenderStep_ = nullptr;
std::vector<GLRStep *> steps_; std::vector<GLRStep *> steps_;
std::vector<GLRInitStep> initSteps_; FastVec<GLRInitStep> initSteps_;
// Execution time state // Execution time state
bool run_ = true; bool run_ = true;
@ -1038,7 +874,7 @@ private:
std::mutex pushMutex_; std::mutex pushMutex_;
std::condition_variable pushCondVar_; std::condition_variable pushCondVar_;
std::queue<GLRRenderThreadTask> renderThreadQueue_; std::queue<GLRRenderThreadTask *> renderThreadQueue_;
// For readbacks and other reasons we need to sync with the render thread. // For readbacks and other reasons we need to sync with the render thread.
std::mutex syncMutex_; std::mutex syncMutex_;
@ -1072,5 +908,6 @@ private:
#endif #endif
Draw::DeviceCaps caps_{}; Draw::DeviceCaps caps_{};
std::string profilePassesString_;
InvalidationCallback invalidationCallback_; InvalidationCallback invalidationCallback_;
}; };

View file

@ -180,8 +180,9 @@ public:
void Apply(GLRenderManager *render, uint8_t stencilRef, uint8_t stencilWriteMask, uint8_t stencilCompareMask) { void Apply(GLRenderManager *render, uint8_t stencilRef, uint8_t stencilWriteMask, uint8_t stencilCompareMask) {
render->SetDepth(depthTestEnabled, depthWriteEnabled, depthComp); render->SetDepth(depthTestEnabled, depthWriteEnabled, depthComp);
render->SetStencilFunc(stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask); render->SetStencil(
render->SetStencilOp(stencilWriteMask, stencilFail, stencilZFail, stencilPass); stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask,
stencilWriteMask, stencilFail, stencilZFail, stencilPass);
} }
}; };
@ -328,6 +329,9 @@ public:
DrawContext::SetTargetSize(w, h); DrawContext::SetTargetSize(w, h);
renderManager_.Resize(w, h); renderManager_.Resize(w, h);
} }
void SetDebugFlags(DebugFlags flags) override {
debugFlags_ = flags;
}
const DeviceCaps &GetDeviceCaps() const override { const DeviceCaps &GetDeviceCaps() const override {
return caps_; return caps_;
@ -366,7 +370,12 @@ public:
void BeginFrame() override; void BeginFrame() override;
void EndFrame() override; void EndFrame() override;
int GetFrameCount() override {
return frameCount_;
}
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -404,12 +413,11 @@ public:
stencilWriteMask_ = writeMask; stencilWriteMask_ = writeMask;
stencilCompareMask_ = compareMask; stencilCompareMask_ = compareMask;
// Do we need to update on the fly here? // Do we need to update on the fly here?
renderManager_.SetStencilFunc( renderManager_.SetStencil(
curPipeline_->depthStencil->stencilEnabled, curPipeline_->depthStencil->stencilEnabled,
curPipeline_->depthStencil->stencilCompareOp, curPipeline_->depthStencil->stencilCompareOp,
refValue, refValue,
compareMask); compareMask,
renderManager_.SetStencilOp(
writeMask, writeMask,
curPipeline_->depthStencil->stencilFail, curPipeline_->depthStencil->stencilFail,
curPipeline_->depthStencil->stencilZFail, curPipeline_->depthStencil->stencilZFail,
@ -488,6 +496,7 @@ private:
void ApplySamplers(); void ApplySamplers();
GLRenderManager renderManager_; GLRenderManager renderManager_;
int frameCount_ = 0;
DeviceCaps caps_{}; DeviceCaps caps_{};
@ -514,6 +523,8 @@ private:
GLPushBuffer *push; GLPushBuffer *push;
}; };
FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]{}; FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]{};
DebugFlags debugFlags_ = DebugFlags::NONE;
}; };
static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) { static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) {
@ -626,7 +637,7 @@ OpenGLContext::OpenGLContext() {
caps_.isTilingGPU = gl_extensions.IsGLES && caps_.vendor != GPUVendor::VENDOR_NVIDIA && caps_.vendor != GPUVendor::VENDOR_INTEL; caps_.isTilingGPU = gl_extensions.IsGLES && caps_.vendor != GPUVendor::VENDOR_NVIDIA && caps_.vendor != GPUVendor::VENDOR_INTEL;
for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) {
frameData_[i].push = renderManager_.CreatePushBuffer(i, GL_ARRAY_BUFFER, 64 * 1024); frameData_[i].push = renderManager_.CreatePushBuffer(i, GL_ARRAY_BUFFER, 64 * 1024, "thin3d_vbuf");
} }
if (!gl_extensions.VersionGEThan(3, 0, 0)) { if (!gl_extensions.VersionGEThan(3, 0, 0)) {
@ -778,7 +789,7 @@ OpenGLContext::~OpenGLContext() {
} }
void OpenGLContext::BeginFrame() { void OpenGLContext::BeginFrame() {
renderManager_.BeginFrame(); renderManager_.BeginFrame(debugFlags_ & DebugFlags::PROFILE_TIMESTAMPS);
FrameData &frameData = frameData_[renderManager_.GetCurFrame()]; FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
renderManager_.BeginPushBuffer(frameData.push); renderManager_.BeginPushBuffer(frameData.push);
} }
@ -789,6 +800,7 @@ void OpenGLContext::EndFrame() {
renderManager_.Finish(); renderManager_.Finish();
Invalidate(InvalidationFlags::CACHED_RENDER_STATE); Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
frameCount_++;
} }
void OpenGLContext::Invalidate(InvalidationFlags flags) { void OpenGLContext::Invalidate(InvalidationFlags flags) {
@ -810,7 +822,7 @@ InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) {
return fmt; return fmt;
} }
GLuint TypeToTarget(TextureType type) { static GLuint TypeToTarget(TextureType type) {
switch (type) { switch (type) {
#ifndef USING_GLES2 #ifndef USING_GLES2
case TextureType::LINEAR1D: return GL_TEXTURE_1D; case TextureType::LINEAR1D: return GL_TEXTURE_1D;
@ -848,25 +860,33 @@ public:
return tex_; return tex_;
} }
void UpdateTextureLevels(GLRenderManager *render, const uint8_t *const *data, int numLevels, TextureCallback initDataCallback);
private: private:
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback); void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback);
GLRenderManager *render_; GLRenderManager *render_;
GLRTexture *tex_; GLRTexture *tex_;
DataFormat format_;
TextureType type_; TextureType type_;
int mipLevels_; int mipLevels_;
bool generatedMips_; bool generateMips_; // Generate mips requested
bool generatedMips_; // Has generated mips
}; };
OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) { OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) {
_dbg_assert_(desc.format != Draw::DataFormat::UNDEFINED);
_dbg_assert_(desc.width > 0 && desc.height > 0 && desc.depth > 0);
_dbg_assert_(desc.type != Draw::TextureType::UNKNOWN);
generatedMips_ = false; generatedMips_ = false;
generateMips_ = desc.generateMips;
width_ = desc.width; width_ = desc.width;
height_ = desc.height; height_ = desc.height;
depth_ = desc.depth; depth_ = desc.depth;
format_ = desc.format; format_ = desc.format;
type_ = desc.type; type_ = desc.type;
GLenum target = TypeToTarget(desc.type); GLenum target = TypeToTarget(desc.type);
tex_ = render->CreateTexture(target, desc.width, desc.height, 1, desc.mipLevels); tex_ = render->CreateTexture(target, desc.width, desc.height, 1, desc.mipLevels);
@ -874,21 +894,25 @@ OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) :
if (desc.initData.empty()) if (desc.initData.empty())
return; return;
UpdateTextureLevels(render, desc.initData.data(), (int)desc.initData.size(), desc.initDataCallback);
}
void OpenGLTexture::UpdateTextureLevels(GLRenderManager *render, const uint8_t * const *data, int numLevels, TextureCallback initDataCallback) {
int level = 0; int level = 0;
int width = width_; int width = width_;
int height = height_; int height = height_;
int depth = depth_; int depth = depth_;
for (auto data : desc.initData) { for (int i = 0; i < numLevels; i++) {
SetImageData(0, 0, 0, width, height, depth, level, 0, data, desc.initDataCallback); SetImageData(0, 0, 0, width, height, depth, level, 0, data[i], initDataCallback);
width = (width + 1) / 2; width = (width + 1) / 2;
height = (height + 1) / 2; height = (height + 1) / 2;
depth = (depth + 1) / 2; depth = (depth + 1) / 2;
level++; level++;
} }
mipLevels_ = desc.generateMips ? desc.mipLevels : level; mipLevels_ = generateMips_ ? mipLevels_ : level;
bool genMips = false; bool genMips = false;
if ((int)desc.initData.size() < desc.mipLevels && desc.generateMips) { if (numLevels < mipLevels_ && generateMips_) {
// Assumes the texture is bound for editing // Assumes the texture is bound for editing
genMips = true; genMips = true;
generatedMips_ = true; generatedMips_ = true;
@ -918,7 +942,7 @@ public:
GLRFramebuffer *framebuffer_ = nullptr; GLRFramebuffer *framebuffer_ = nullptr;
}; };
void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback) { void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback) {
if ((width != width_ || height != height_ || depth != depth_) && level == 0) { if ((width != width_ || height != height_ || depth != depth_) && level == 0) {
// When switching to texStorage we need to handle this correctly. // When switching to texStorage we need to handle this correctly.
width_ = width; width_ = width;
@ -934,8 +958,8 @@ void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int
uint8_t *texData = new uint8_t[(size_t)(width * height * depth * alignment)]; uint8_t *texData = new uint8_t[(size_t)(width * height * depth * alignment)];
bool texDataPopulated = false; bool texDataPopulated = false;
if (callback) { if (initDataCallback) {
texDataPopulated = callback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment); texDataPopulated = initDataCallback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment);
} }
if (texDataPopulated) { if (texDataPopulated) {
if (format_ == DataFormat::A1R5G5B5_UNORM_PACK16) { if (format_ == DataFormat::A1R5G5B5_UNORM_PACK16) {
@ -1016,6 +1040,11 @@ Texture *OpenGLContext::CreateTexture(const TextureDesc &desc) {
return new OpenGLTexture(&renderManager_, desc); return new OpenGLTexture(&renderManager_, desc);
} }
void OpenGLContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
OpenGLTexture *tex = (OpenGLTexture *)texture;
tex->UpdateTextureLevels(&renderManager_, data, numLevels, initDataCallback);
}
DepthStencilState *OpenGLContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) { DepthStencilState *OpenGLContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) {
OpenGLDepthStencilState *ds = new OpenGLDepthStencilState(); OpenGLDepthStencilState *ds = new OpenGLDepthStencilState();
ds->depthTestEnabled = desc.depthTestEnabled; ds->depthTestEnabled = desc.depthTestEnabled;
@ -1332,38 +1361,37 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
void OpenGLContext::Draw(int vertexCount, int offset) { void OpenGLContext::Draw(int vertexCount, int offset) {
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call Draw without a vertex buffer"); _dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call Draw without a vertex buffer");
ApplySamplers(); ApplySamplers();
if (curPipeline_->inputLayout) { _assert_(curPipeline_->inputLayout);
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0], curPipeline_->prim, offset, vertexCount);
}
renderManager_.Draw(curPipeline_->prim, offset, vertexCount);
} }
void OpenGLContext::DrawIndexed(int vertexCount, int offset) { void OpenGLContext::DrawIndexed(int vertexCount, int offset) {
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call DrawIndexed without a vertex buffer"); _dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call DrawIndexed without a vertex buffer");
_dbg_assert_msg_(curIBuffer_ != nullptr, "Can't call DrawIndexed without an index buffer"); _dbg_assert_msg_(curIBuffer_ != nullptr, "Can't call DrawIndexed without an index buffer");
ApplySamplers(); ApplySamplers();
if (curPipeline_->inputLayout) { _assert_(curPipeline_->inputLayout);
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); renderManager_.DrawIndexed(
} curPipeline_->inputLayout->inputLayout_,
renderManager_.BindIndexBuffer(curIBuffer_->buffer_); curVBuffers_[0]->buffer_, curVBufferOffsets_[0],
renderManager_.DrawIndexed(curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT, (void *)((intptr_t)curIBufferOffset_ + offset * sizeof(uint32_t))); curIBuffer_->buffer_, curIBufferOffset_ + offset * sizeof(uint32_t),
curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT);
} }
void OpenGLContext::DrawUP(const void *vdata, int vertexCount) { void OpenGLContext::DrawUP(const void *vdata, int vertexCount) {
_assert_(curPipeline_->inputLayout != nullptr); _assert_(curPipeline_->inputLayout != nullptr);
int stride = curPipeline_->inputLayout->stride; int stride = curPipeline_->inputLayout->stride;
size_t dataSize = stride * vertexCount; uint32_t dataSize = stride * vertexCount;
FrameData &frameData = frameData_[renderManager_.GetCurFrame()]; FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
GLRBuffer *buf; GLRBuffer *buf;
size_t offset = frameData.push->Push(vdata, dataSize, &buf); uint32_t offset;
uint8_t *dest = frameData.push->Allocate(dataSize, 4, &buf, &offset);
memcpy(dest, vdata, dataSize);
ApplySamplers(); ApplySamplers();
if (curPipeline_->inputLayout) { _assert_(curPipeline_->inputLayout);
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset); renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, buf, offset, curPipeline_->prim, 0, vertexCount);
}
renderManager_.Draw(curPipeline_->prim, 0, vertexCount);
} }
void OpenGLContext::Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) { void OpenGLContext::Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) {
@ -1443,7 +1471,7 @@ Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) {
// TODO: Support multiview later. (It's our only use case for multi layers). // TODO: Support multiview later. (It's our only use case for multi layers).
_dbg_assert_(desc.numLayers == 1); _dbg_assert_(desc.numLayers == 1);
GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil); GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil, desc.tag);
OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_, framebuffer); OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_, framebuffer);
return fbo; return fbo;
} }

View file

@ -592,10 +592,18 @@ void VulkanContext::ChooseDevice(int physical_device) {
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR}; VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR};
// Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs. // Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs.
VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES }; VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR };
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR };
features2.pNext = &multiViewFeatures; features2.pNext = &multiViewFeatures;
multiViewFeatures.pNext = &presentWaitFeatures;
presentWaitFeatures.pNext = &presentIdFeatures;
vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2); vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2);
deviceFeatures_.available.standard = features2.features; deviceFeatures_.available.standard = features2.features;
deviceFeatures_.available.multiview = multiViewFeatures; deviceFeatures_.available.multiview = multiViewFeatures;
deviceFeatures_.available.presentWait = presentWaitFeatures;
deviceFeatures_.available.presentId = presentIdFeatures;
} else { } else {
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard); vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
deviceFeatures_.available.multiview = {}; deviceFeatures_.available.multiview = {};
@ -615,6 +623,8 @@ void VulkanContext::ChooseDevice(int physical_device) {
deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES }; deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview; deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview;
deviceFeatures_.enabled.presentId = deviceFeatures_.available.presentId;
deviceFeatures_.enabled.presentWait = deviceFeatures_.available.presentWait;
// deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader; // deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader;
GetDeviceLayerExtensionList(nullptr, device_extension_properties_); GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
@ -681,6 +691,10 @@ VkResult VulkanContext::CreateDevice() {
extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME); extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME); extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME);
extensionsLookup_.KHR_present_id = EnableDeviceExtension(VK_KHR_PRESENT_ID_EXTENSION_NAME);
extensionsLookup_.KHR_present_wait = EnableDeviceExtension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME);
extensionsLookup_.GOOGLE_display_timing = EnableDeviceExtension(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
@ -1199,6 +1213,12 @@ static std::string surface_transforms_to_string(VkSurfaceTransformFlagsKHR trans
} }
bool VulkanContext::InitSwapchain() { bool VulkanContext::InitSwapchain() {
_assert_(physical_device_ >= 0 && physical_device_ < physical_devices_.size());
if (!surface_) {
ERROR_LOG(G3D, "VK: No surface, can't create swapchain");
return false;
}
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_); VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_);
if (res == VK_ERROR_SURFACE_LOST_KHR) { if (res == VK_ERROR_SURFACE_LOST_KHR) {
// Not much to do. // Not much to do.

View file

@ -264,6 +264,8 @@ public:
struct AllPhysicalDeviceFeatures { struct AllPhysicalDeviceFeatures {
VkPhysicalDeviceFeatures standard; VkPhysicalDeviceFeatures standard;
VkPhysicalDeviceMultiviewFeatures multiview; VkPhysicalDeviceMultiviewFeatures multiview;
VkPhysicalDevicePresentWaitFeaturesKHR presentWait;
VkPhysicalDevicePresentIdFeaturesKHR presentId;
}; };
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const { const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {

View file

@ -53,8 +53,14 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
return false; return false;
case 1303270965: case 1303270965:
// Benign perf warning, image blit using GENERAL layout. // Benign perf warning, image blit using GENERAL layout.
// UNASSIGNED // TODO: Oops, turns out we filtered out a bit too much here!
// We really need that performance flag check to sort out the stuff that matters.
// Will enable it soon, but it'll take some fixing.
//
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
return false; return false;
break;
case 606910136: case 606910136:
case -392708513: case -392708513:
case -384083808: case -384083808:

View file

@ -121,7 +121,7 @@ VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) {
} }
// Good spot to reset the query pool. // Good spot to reset the query pool.
if (profilingEnabled_) { if (profile.enabled) {
vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES); vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES);
vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, profile.queryPool, 0); vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, profile.queryPool, 0);
} }
@ -138,7 +138,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame
VkFence fenceToTrigger = VK_NULL_HANDLE; VkFence fenceToTrigger = VK_NULL_HANDLE;
if (hasInitCommands) { if (hasInitCommands) {
if (profilingEnabled_) { if (profile.enabled) {
// Pre-allocated query ID 1 - end of init cmdbuf. // Pre-allocated query ID 1 - end of init cmdbuf.
vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1); vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1);
} }

View file

@ -19,11 +19,14 @@ enum class VKRRunType {
}; };
struct QueueProfileContext { struct QueueProfileContext {
bool enabled = false;
bool timestampsEnabled = false;
VkQueryPool queryPool; VkQueryPool queryPool;
std::vector<std::string> timestampDescriptions; std::vector<std::string> timestampDescriptions;
std::string profileSummary; std::string profileSummary;
double cpuStartTime; double cpuStartTime;
double cpuEndTime; double cpuEndTime;
double descWriteTime;
}; };
class VKRFramebuffer; class VKRFramebuffer;
@ -92,8 +95,7 @@ struct FrameData {
uint32_t curSwapchainImage = -1; uint32_t curSwapchainImage = -1;
// Profiling. // Profiling.
QueueProfileContext profile; QueueProfileContext profile{};
bool profilingEnabled_ = false;
// Async readback cache. // Async readback cache.
DenseHashMap<ReadbackKey, CachedReadback*, nullptr> readbacks_; DenseHashMap<ReadbackKey, CachedReadback*, nullptr> readbacks_;

View file

@ -265,7 +265,6 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies // Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) { VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
bool selfDependency = RenderPassTypeHasInput(rpType);
bool isBackbuffer = rpType == RenderPassType::BACKBUFFER; bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
bool hasDepth = RenderPassTypeHasDepth(rpType); bool hasDepth = RenderPassTypeHasDepth(rpType);
bool multiview = RenderPassTypeHasMultiView(rpType); bool multiview = RenderPassTypeHasMultiView(rpType);
@ -330,7 +329,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkAttachmentReference colorReference{}; VkAttachmentReference colorReference{};
colorReference.attachment = colorAttachmentIndex; colorReference.attachment = colorAttachmentIndex;
colorReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference{}; VkAttachmentReference depthReference{};
depthReference.attachment = depthAttachmentIndex; depthReference.attachment = depthAttachmentIndex;
@ -339,20 +338,15 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkSubpassDescription subpass{}; VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0; subpass.flags = 0;
if (selfDependency) {
subpass.inputAttachmentCount = 1;
subpass.pInputAttachments = &colorReference;
} else {
subpass.inputAttachmentCount = 0; subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr; subpass.pInputAttachments = nullptr;
}
subpass.colorAttachmentCount = 1; subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference; subpass.pColorAttachments = &colorReference;
VkAttachmentReference colorResolveReference; VkAttachmentReference colorResolveReference;
if (multisample) { if (multisample) {
colorResolveReference.attachment = 0; // the non-msaa color buffer. colorResolveReference.attachment = 0; // the non-msaa color buffer.
colorResolveReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorResolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
subpass.pResolveAttachments = &colorResolveReference; subpass.pResolveAttachments = &colorResolveReference;
} else { } else {
subpass.pResolveAttachments = nullptr; subpass.pResolveAttachments = nullptr;
@ -396,17 +390,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
numDeps++; numDeps++;
} }
if (selfDependency) {
deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
deps[numDeps].srcSubpass = 0;
deps[numDeps].dstSubpass = 0;
numDeps++;
}
if (numDeps > 0) { if (numDeps > 0) {
rp.dependencyCount = (u32)numDeps; rp.dependencyCount = (u32)numDeps;
rp.pDependencies = deps; rp.pDependencies = deps;
@ -463,10 +446,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkSubpassDescription2KHR subpass2{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR }; VkSubpassDescription2KHR subpass2{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR };
subpass2.colorAttachmentCount = subpass.colorAttachmentCount; subpass2.colorAttachmentCount = subpass.colorAttachmentCount;
subpass2.flags = subpass.flags; subpass2.flags = subpass.flags;
if (selfDependency) {
subpass2.inputAttachmentCount = subpass.inputAttachmentCount;
subpass2.pInputAttachments = &colorReference2;
}
subpass2.pColorAttachments = &colorReference2; subpass2.pColorAttachments = &colorReference2;
if (hasDepth) { if (hasDepth) {
subpass2.pDepthStencilAttachment = &depthReference2; subpass2.pDepthStencilAttachment = &depthReference2;
@ -476,7 +455,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
if (multisample) { if (multisample) {
colorResolveReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; colorResolveReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
colorResolveReference2.attachment = colorResolveReference.attachment; // the non-msaa color buffer. colorResolveReference2.attachment = colorResolveReference.attachment; // the non-msaa color buffer.
colorResolveReference2.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorResolveReference2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
subpass2.pResolveAttachments = &colorResolveReference2; subpass2.pResolveAttachments = &colorResolveReference2;
} else { } else {
subpass2.pResolveAttachments = nullptr; subpass2.pResolveAttachments = nullptr;

View file

@ -13,15 +13,14 @@ enum class RenderPassType {
// These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so // These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so
// they can be OR-ed together in MergeRPTypes. // they can be OR-ed together in MergeRPTypes.
HAS_DEPTH = 1, HAS_DEPTH = 1,
COLOR_INPUT = 2, // input attachment MULTIVIEW = 2,
MULTIVIEW = 4, MULTISAMPLE = 4,
MULTISAMPLE = 8,
// This is the odd one out, and gets special handling in MergeRPTypes. // This is the odd one out, and gets special handling in MergeRPTypes.
// If this flag is set, none of the other flags can be set. // If this flag is set, none of the other flags can be set.
// For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible
// so we don't bother with a non-depth version. // so we don't bother with a non-depth version.
BACKBUFFER = 16, BACKBUFFER = 8,
TYPE_COUNT = BACKBUFFER + 1, TYPE_COUNT = BACKBUFFER + 1,
}; };
@ -107,10 +106,6 @@ inline bool RenderPassTypeHasDepth(RenderPassType type) {
return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER; return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
} }
inline bool RenderPassTypeHasInput(RenderPassType type) {
return (type & RenderPassType::COLOR_INPUT) != 0;
}
inline bool RenderPassTypeHasMultiView(RenderPassType type) { inline bool RenderPassTypeHasMultiView(RenderPassType type) {
return (type & RenderPassType::MULTIVIEW) != 0; return (type & RenderPassType::MULTIVIEW) != 0;
} }

View file

@ -258,6 +258,22 @@ void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelin
prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
} }
void VulkanTexture::PrepareForTransferDst(VkCommandBuffer cmd, int levels) {
TransitionImageLayout2(cmd, image_, 0, levels, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
}
void VulkanTexture::RestoreAfterTransferDst(VkCommandBuffer cmd, int levels) {
TransitionImageLayout2(cmd, image_, 0, levels, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
}
VkImageView VulkanTexture::CreateViewForMip(int mip) { VkImageView VulkanTexture::CreateViewForMip(int mip) {
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image_; view_info.image = image_;

View file

@ -37,6 +37,10 @@ public:
void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute); void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute);
void EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); void EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// For updating levels after creation. Careful with the timelines!
void PrepareForTransferDst(VkCommandBuffer cmd, int levels);
void RestoreAfterTransferDst(VkCommandBuffer cmd, int levels);
// When loading mips from compute shaders, you need to pass VK_IMAGE_LAYOUT_GENERAL to the above function. // When loading mips from compute shaders, you need to pass VK_IMAGE_LAYOUT_GENERAL to the above function.
// In addition, ignore UploadMip and GenerateMip, and instead use GetViewForMip. Make sure to delete the returned views when used. // In addition, ignore UploadMip and GenerateMip, and instead use GetViewForMip. Make sure to delete the returned views when used.
VkImageView CreateViewForMip(int mip); VkImageView CreateViewForMip(int mip);

View file

@ -253,6 +253,9 @@ struct VulkanExtensions {
bool EXT_swapchain_colorspace; bool EXT_swapchain_colorspace;
bool ARM_rasterization_order_attachment_access; bool ARM_rasterization_order_attachment_access;
bool EXT_fragment_shader_interlock; bool EXT_fragment_shader_interlock;
bool KHR_present_id; // Should probably check the feature flags instead.
bool KHR_present_wait; // Same
bool GOOGLE_display_timing;
// bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers. // bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers.
}; };

View file

@ -35,37 +35,15 @@ using namespace PPSSPP_VK;
// Always keep around push buffers at least this long (seconds). // Always keep around push buffers at least this long (seconds).
static const double PUSH_GARBAGE_COLLECTION_DELAY = 10.0; static const double PUSH_GARBAGE_COLLECTION_DELAY = 10.0;
// Global push buffer tracker for vulkan memory profiling.
// Don't want to manually dig up all the active push buffers.
static std::mutex g_pushBufferListMutex;
static std::set<VulkanMemoryManager *> g_pushBuffers;
std::vector<VulkanMemoryManager *> GetActiveVulkanMemoryManagers() {
std::vector<VulkanMemoryManager *> buffers;
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
for (auto iter : g_pushBuffers) {
buffers.push_back(iter);
}
return buffers;
}
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage) VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage)
: vulkan_(vulkan), name_(name), size_(size), usage_(usage) { : vulkan_(vulkan), name_(name), size_(size), usage_(usage) {
{ RegisterGPUMemoryManager(this);
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(this);
}
bool res = AddBuffer(); bool res = AddBuffer();
_assert_(res); _assert_(res);
} }
VulkanPushBuffer::~VulkanPushBuffer() { VulkanPushBuffer::~VulkanPushBuffer() {
{ UnregisterGPUMemoryManager(this);
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(this);
}
_dbg_assert_(!writePtr_); _dbg_assert_(!writePtr_);
_assert_(buffers_.empty()); _assert_(buffers_.empty());
} }
@ -276,11 +254,7 @@ VkResult VulkanDescSetPool::Recreate(bool grow) {
VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage) VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage)
: vulkan_(vulkan), name_(name), originalBlockSize_(originalBlockSize), usage_(usage) { : vulkan_(vulkan), name_(name), originalBlockSize_(originalBlockSize), usage_(usage) {
{ RegisterGPUMemoryManager(this);
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(this);
}
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) { for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
blocks_.push_back(CreateBlock(originalBlockSize)); blocks_.push_back(CreateBlock(originalBlockSize));
blocks_.back().original = true; blocks_.back().original = true;
@ -289,11 +263,7 @@ VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t o
} }
VulkanPushPool::~VulkanPushPool() { VulkanPushPool::~VulkanPushPool() {
{ UnregisterGPUMemoryManager(this);
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(this);
}
_dbg_assert_(blocks_.empty()); _dbg_assert_(blocks_.empty());
} }
@ -323,6 +293,7 @@ VulkanPushPool::Block VulkanPushPool::CreateBlock(size_t size) {
result = vmaMapMemory(vulkan_->Allocator(), block.allocation, (void **)(&block.writePtr)); result = vmaMapMemory(vulkan_->Allocator(), block.allocation, (void **)(&block.writePtr));
_assert_(result == VK_SUCCESS); _assert_(result == VK_SUCCESS);
_assert_msg_(block.writePtr != nullptr, "VulkanPushPool: Failed to map memory on block of size %d", (int)block.size);
return block; return block;
} }
@ -384,6 +355,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
block.used = allocationSize; block.used = allocationSize;
block.lastUsed = time_now_d(); block.lastUsed = time_now_d();
block.frameIndex = curFrameIndex; block.frameIndex = curFrameIndex;
_assert_(block.writePtr != nullptr);
return; return;
} }
curBlockIndex_++; curBlockIndex_++;
@ -391,6 +363,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
double start = time_now_d(); double start = time_now_d();
VkDeviceSize newBlockSize = std::max(originalBlockSize_ * 2, (VkDeviceSize)RoundUpToPowerOf2((uint32_t)allocationSize)); VkDeviceSize newBlockSize = std::max(originalBlockSize_ * 2, (VkDeviceSize)RoundUpToPowerOf2((uint32_t)allocationSize));
// We're still here and ran off the end of blocks. Create a new one. // We're still here and ran off the end of blocks. Create a new one.
blocks_.push_back(CreateBlock(newBlockSize)); blocks_.push_back(CreateBlock(newBlockSize));
blocks_.back().frameIndex = curFrameIndex; blocks_.back().frameIndex = curFrameIndex;

View file

@ -5,7 +5,9 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "Common/Data/Collections/FastVec.h"
#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/GPUBackendCommon.h"
// Forward declaration // Forward declaration
VK_DEFINE_HANDLE(VmaAllocation); VK_DEFINE_HANDLE(VmaAllocation);
@ -14,22 +16,13 @@ VK_DEFINE_HANDLE(VmaAllocation);
// //
// Vulkan memory management utils. // Vulkan memory management utils.
// Just an abstract thing to get debug information.
class VulkanMemoryManager {
public:
virtual ~VulkanMemoryManager() {}
virtual void GetDebugString(char *buffer, size_t bufSize) const = 0;
virtual const char *Name() const = 0; // for sorting
};
// VulkanPushBuffer // VulkanPushBuffer
// Simple incrementing allocator. // Simple incrementing allocator.
// Use these to push vertex, index and uniform data. Generally you'll have two or three of these // Use these to push vertex, index and uniform data. Generally you'll have two or three of these
// and alternate on each frame. Make sure not to reset until the fence from the last time you used it // and alternate on each frame. Make sure not to reset until the fence from the last time you used it
// has completed. // has completed.
// NOTE: This has now been replaced with VulkanPushPool for all uses except the vertex cache. // NOTE: This has now been replaced with VulkanPushPool for all uses except the vertex cache.
class VulkanPushBuffer : public VulkanMemoryManager { class VulkanPushBuffer : public GPUMemoryManager {
struct BufInfo { struct BufInfo {
VkBuffer buffer; VkBuffer buffer;
VmaAllocation allocation; VmaAllocation allocation;
@ -107,7 +100,8 @@ private:
// Simple memory pushbuffer pool that can share blocks between the "frames", to reduce the impact of push memory spikes - // Simple memory pushbuffer pool that can share blocks between the "frames", to reduce the impact of push memory spikes -
// a later frame can gobble up redundant buffers from an earlier frame even if they don't share frame index. // a later frame can gobble up redundant buffers from an earlier frame even if they don't share frame index.
class VulkanPushPool : public VulkanMemoryManager { // NOT thread safe! Can only be used from one thread (our main thread).
class VulkanPushPool : public GPUMemoryManager {
public: public:
VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage); VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage);
~VulkanPushPool(); ~VulkanPushPool();
@ -142,6 +136,8 @@ public:
return blocks_[curBlockIndex_].writePtr; return blocks_[curBlockIndex_].writePtr;
} }
// NOTE: If you can avoid this by writing the data directly into memory returned from Allocate,
// do so. Savings from avoiding memcpy can be significant.
VkDeviceSize Push(const void *data, VkDeviceSize numBytes, int alignment, VkBuffer *vkbuf) { VkDeviceSize Push(const void *data, VkDeviceSize numBytes, int alignment, VkBuffer *vkbuf) {
uint32_t bindOffset; uint32_t bindOffset;
uint8_t *ptr = Allocate(numBytes, alignment, vkbuf, &bindOffset); uint8_t *ptr = Allocate(numBytes, alignment, vkbuf, &bindOffset);
@ -210,6 +206,3 @@ private:
uint32_t usage_ = 0; uint32_t usage_ = 0;
bool grow_; bool grow_;
}; };
std::vector<VulkanMemoryManager *> GetActiveVulkanMemoryManagers();

View file

@ -9,7 +9,7 @@
using namespace PPSSPP_VK; using namespace PPSSPP_VK;
// Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState // Debug help: adb logcat -s DEBUG AndroidRuntime PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState PpssppActivity CameraHelper
static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) { static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) {
if (dest->offset.x > src.offset.x) { if (dest->offset.x > src.offset.x) {
@ -261,31 +261,6 @@ VKRRenderPass *VulkanQueueRunner::GetRenderPass(const RPKey &key) {
return pass; return pass;
} }
// Must match the subpass self-dependency declared above.
void VulkanQueueRunner::SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) {
if (aspect & VK_IMAGE_ASPECT_COLOR_BIT) {
VkAccessFlags srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkAccessFlags dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
recordBarrier->TransitionImage(
img.image,
0,
1,
img.numLayers,
aspect,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL,
srcAccessMask,
dstAccessMask,
srcStageMask,
dstStageMask
);
} else {
_assert_msg_(false, "Depth self-dependencies not yet supported");
}
}
void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) { void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
// Optimizes renderpasses, then sequences them. // Optimizes renderpasses, then sequences them.
// Planned optimizations: // Planned optimizations:
@ -364,7 +339,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
} }
void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) { void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) {
QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr; QueueProfileContext *profile = frameData.profile.enabled ? &frameData.profile : nullptr;
if (profile) if (profile)
profile->cpuStartTime = time_now_d(); profile->cpuStartTime = time_now_d();
@ -437,7 +412,7 @@ void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frame
break; break;
} }
if (profile && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) { if (profile && profile->timestampsEnabled && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) {
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size());
profile->timestampDescriptions.push_back(StepToString(step)); profile->timestampDescriptions.push_back(StepToString(step));
} }
@ -481,7 +456,7 @@ void VulkanQueueRunner::ApplyMGSHack(std::vector<VKRStep *> &steps) {
last = j - 1; last = j - 1;
// should really also check descriptor sets... // should really also check descriptor sets...
if (steps[j]->commands.size()) { if (steps[j]->commands.size()) {
VkRenderData &cmd = steps[j]->commands.back(); const VkRenderData &cmd = steps[j]->commands.back();
if (cmd.cmd == VKRRenderCommand::DRAW_INDEXED && cmd.draw.count != 6) if (cmd.cmd == VKRRenderCommand::DRAW_INDEXED && cmd.draw.count != 6)
last = j - 1; last = j - 1;
} }
@ -918,9 +893,6 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
case VKRRenderCommand::REMOVED: case VKRRenderCommand::REMOVED:
INFO_LOG(G3D, " (Removed)"); INFO_LOG(G3D, " (Removed)");
break; break;
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
INFO_LOG(G3D, " SelfBarrier()");
break;
case VKRRenderCommand::BIND_GRAPHICS_PIPELINE: case VKRRenderCommand::BIND_GRAPHICS_PIPELINE:
INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline); INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline);
break; break;
@ -1241,7 +1213,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
VKRGraphicsPipeline *lastGraphicsPipeline = nullptr; VKRGraphicsPipeline *lastGraphicsPipeline = nullptr;
VKRComputePipeline *lastComputePipeline = nullptr; VKRComputePipeline *lastComputePipeline = nullptr;
auto &commands = step.commands; const auto &commands = step.commands;
// We can do a little bit of state tracking here to eliminate some calls into the driver. // We can do a little bit of state tracking here to eliminate some calls into the driver.
// The stencil ones are very commonly mostly redundant so let's eliminate them where possible. // The stencil ones are very commonly mostly redundant so let's eliminate them where possible.
@ -1361,21 +1333,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
break; break;
} }
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
{
_assert_(step.render.pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT);
_assert_(fb);
VulkanBarrier barrier;
if (fb->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
// Rendering is happening to the multisample buffer, not the color buffer.
SelfDependencyBarrier(fb->msaaColor, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
} else {
SelfDependencyBarrier(fb->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
}
barrier.Flush(cmd);
break;
}
case VKRRenderCommand::PUSH_CONSTANTS: case VKRRenderCommand::PUSH_CONSTANTS:
if (pipelineOK) { if (pipelineOK) {
vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data); vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data);
@ -1400,7 +1357,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
case VKRRenderCommand::DRAW_INDEXED: case VKRRenderCommand::DRAW_INDEXED:
if (pipelineOK) { if (pipelineOK) {
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, (VkIndexType)c.drawIndexed.indexType); vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, VK_INDEX_TYPE_UINT16);
VkDeviceSize voffset = c.drawIndexed.voffset; VkDeviceSize voffset = c.drawIndexed.voffset;
vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset); vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0); vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);

View file

@ -6,6 +6,7 @@
#include "Common/Thread/Promise.h" #include "Common/Thread/Promise.h"
#include "Common/Data/Collections/Hashmaps.h" #include "Common/Data/Collections/Hashmaps.h"
#include "Common/Data/Collections/FastVec.h"
#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h" #include "Common/GPU/Vulkan/VulkanBarrier.h"
#include "Common/GPU/Vulkan/VulkanFrameData.h" #include "Common/GPU/Vulkan/VulkanFrameData.h"
@ -38,7 +39,6 @@ enum class VKRRenderCommand : uint8_t {
DRAW, DRAW,
DRAW_INDEXED, DRAW_INDEXED,
PUSH_CONSTANTS, PUSH_CONSTANTS,
SELF_DEPENDENCY_BARRIER,
DEBUG_ANNOTATION, DEBUG_ANNOTATION,
NUM_RENDER_COMMANDS, NUM_RENDER_COMMANDS,
}; };
@ -47,10 +47,9 @@ enum class PipelineFlags : u8 {
NONE = 0, NONE = 0,
USES_BLEND_CONSTANT = (1 << 1), USES_BLEND_CONSTANT = (1 << 1),
USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers. USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers.
USES_INPUT_ATTACHMENT = (1 << 3), USES_GEOMETRY_SHADER = (1 << 3),
USES_GEOMETRY_SHADER = (1 << 4), USES_MULTIVIEW = (1 << 4), // Inherited from the render pass it was created with.
USES_MULTIVIEW = (1 << 5), // Inherited from the render pass it was created with. USES_DISCARD = (1 << 5),
USES_DISCARD = (1 << 6),
}; };
ENUM_CLASS_BITOPS(PipelineFlags); ENUM_CLASS_BITOPS(PipelineFlags);
@ -80,15 +79,14 @@ struct VkRenderData {
} draw; } draw;
struct { struct {
VkDescriptorSet ds; VkDescriptorSet ds;
int numUboOffsets;
uint32_t uboOffsets[3]; uint32_t uboOffsets[3];
uint16_t numUboOffsets;
uint16_t instances;
VkBuffer vbuffer; VkBuffer vbuffer;
VkBuffer ibuffer; VkBuffer ibuffer;
uint32_t voffset; uint32_t voffset;
uint32_t ioffset; uint32_t ioffset;
uint32_t count; uint32_t count;
int16_t instances;
int16_t indexType;
} drawIndexed; } drawIndexed;
struct { struct {
uint32_t clearColor; uint32_t clearColor;
@ -153,7 +151,7 @@ struct VKRStep {
~VKRStep() {} ~VKRStep() {}
VKRStepType stepType; VKRStepType stepType;
std::vector<VkRenderData> commands; FastVec<VkRenderData> commands;
TinySet<TransitionRequest, 4> preTransitions; TinySet<TransitionRequest, 4> preTransitions;
TinySet<VKRFramebuffer *, 8> dependencies; TinySet<VKRFramebuffer *, 8> dependencies;
const char *tag; const char *tag;
@ -212,9 +210,14 @@ struct VKRStep {
// These are enqueued from the main thread, // These are enqueued from the main thread,
// and the render thread pops them off // and the render thread pops them off
struct VKRRenderThreadTask { struct VKRRenderThreadTask {
VKRRenderThreadTask(VKRRunType _runType) : runType(_runType) {}
std::vector<VKRStep *> steps; std::vector<VKRStep *> steps;
int frame; int frame = -1;
VKRRunType runType; VKRRunType runType;
// Avoid copying these by accident.
VKRRenderThreadTask(VKRRenderThreadTask &) = delete;
VKRRenderThreadTask &operator =(VKRRenderThreadTask &) = delete;
}; };
class VulkanQueueRunner { class VulkanQueueRunner {
@ -309,8 +312,6 @@ private:
static void SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); static void SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
static void SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); static void SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
static void SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
VulkanContext *vulkan_; VulkanContext *vulkan_;
VkFramebuffer backbuffer_ = VK_NULL_HANDLE; VkFramebuffer backbuffer_ = VK_NULL_HANDLE;

View file

@ -308,9 +308,8 @@ bool VulkanRenderManager::CreateBackbuffers() {
void VulkanRenderManager::StopThread() { void VulkanRenderManager::StopThread() {
{ {
// Tell the render thread to quit when it's done. // Tell the render thread to quit when it's done.
VKRRenderThreadTask task; VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
task.frame = vulkan_->GetCurFrame(); task->frame = vulkan_->GetCurFrame();
task.runType = VKRRunType::EXIT;
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task); renderThreadQueue_.push(task);
pushCondVar_.notify_one(); pushCondVar_.notify_one();
@ -494,7 +493,7 @@ void VulkanRenderManager::ThreadFunc() {
SetCurrentThreadName("RenderMan"); SetCurrentThreadName("RenderMan");
while (true) { while (true) {
// Pop a task of the queue and execute it. // Pop a task of the queue and execute it.
VKRRenderThreadTask task; VKRRenderThreadTask *task = nullptr;
{ {
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
while (renderThreadQueue_.empty()) { while (renderThreadQueue_.empty()) {
@ -506,12 +505,15 @@ void VulkanRenderManager::ThreadFunc() {
// Oh, we got a task! We can now have pushMutex_ unlocked, allowing the host to // Oh, we got a task! We can now have pushMutex_ unlocked, allowing the host to
// push more work when it feels like it, and just start working. // push more work when it feels like it, and just start working.
if (task.runType == VKRRunType::EXIT) { if (task->runType == VKRRunType::EXIT) {
// Oh, host wanted out. Let's leave. // Oh, host wanted out. Let's leave.
delete task;
// In this case, there should be no more tasks.
break; break;
} }
Run(task); Run(*task);
delete task;
} }
// Wait for the device to be done with everything, before tearing stuff down. // Wait for the device to be done with everything, before tearing stuff down.
@ -550,13 +552,14 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits; int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits;
// Can't set this until after the fence. // Can't set this until after the fence.
frameData.profilingEnabled_ = enableProfiling && validBits > 0; frameData.profile.enabled = enableProfiling;
frameData.profile.timestampsEnabled = enableProfiling && validBits > 0;
uint64_t queryResults[MAX_TIMESTAMP_QUERIES]; uint64_t queryResults[MAX_TIMESTAMP_QUERIES];
if (frameData.profilingEnabled_) { if (enableProfiling) {
// Pull the profiling results from last time and produce a summary! // Pull the profiling results from last time and produce a summary!
if (!frameData.profile.timestampDescriptions.empty()) { if (!frameData.profile.timestampDescriptions.empty() && frameData.profile.timestampsEnabled) {
int numQueries = (int)frameData.profile.timestampDescriptions.size(); int numQueries = (int)frameData.profile.timestampDescriptions.size();
VkResult res = vkGetQueryPoolResults( VkResult res = vkGetQueryPoolResults(
vulkan_->GetDevice(), vulkan_->GetDevice(),
@ -594,7 +597,12 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
frameData.profile.profileSummary = "(error getting GPU profile - not ready?)"; frameData.profile.profileSummary = "(error getting GPU profile - not ready?)";
} }
} else { } else {
frameData.profile.profileSummary = "(no GPU profile data collected)"; std::stringstream str;
char line[256];
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
renderCPUTimeMs_.Format(line, sizeof(line));
str << line;
frameData.profile.profileSummary = str.str();
} }
} }
@ -605,7 +613,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE); vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
frameData.profile.timestampDescriptions.clear(); frameData.profile.timestampDescriptions.clear();
if (frameData.profilingEnabled_) { if (frameData.profile.timestampsEnabled) {
// For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool, // For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool,
// unless we want to limit ourselves to only measure the main cmd buffer. // unless we want to limit ourselves to only measure the main cmd buffer.
// Later versions of Vulkan have support for clearing queries on the CPU timeline, but we don't want to rely on that. // Later versions of Vulkan have support for clearing queries on the CPU timeline, but we don't want to rely on that.
@ -657,10 +665,6 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe
WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type"); WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
continue; continue;
} }
if ((pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) && !RenderPassTypeHasInput(rpType)) {
WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type");
continue;
}
// Shouldn't hit this, these should have been filtered elsewhere. However, still a good check to do. // Shouldn't hit this, these should have been filtered elsewhere. However, still a good check to do.
if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) { if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) {
WARN_LOG(G3D, "Not compiling single sample pipeline for a multisampled render pass type"); WARN_LOG(G3D, "Not compiling single sample pipeline for a multisampled render pass type");
@ -710,10 +714,6 @@ void VulkanRenderManager::EndCurRenderStep() {
if (!curRenderStep_->render.framebuffer) { if (!curRenderStep_->render.framebuffer) {
rpType = RenderPassType::BACKBUFFER; rpType = RenderPassType::BACKBUFFER;
} else { } else {
if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) {
// Not allowed on backbuffers.
rpType = depthStencil ? (RenderPassType::HAS_DEPTH | RenderPassType::COLOR_INPUT) : RenderPassType::COLOR_INPUT;
}
// Framebuffers can be stereo, and if so, will control the render pass type to match. // Framebuffers can be stereo, and if so, will control the render pass type to match.
// Pipelines can be mono and render fine to stereo etc, so not checking them here. // Pipelines can be mono and render fine to stereo etc, so not checking them here.
// Note that we don't support rendering to just one layer of a multilayer framebuffer! // Note that we don't support rendering to just one layer of a multilayer framebuffer!
@ -764,11 +764,6 @@ void VulkanRenderManager::EndCurRenderStep() {
curPipelineFlags_ = (PipelineFlags)0; curPipelineFlags_ = (PipelineFlags)0;
} }
void VulkanRenderManager::BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits) {
_dbg_assert_(curRenderStep_);
curRenderStep_->commands.push_back(VkRenderData{ VKRRenderCommand::SELF_DEPENDENCY_BARRIER });
}
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
_dbg_assert_(insideFrame_); _dbg_assert_(insideFrame_);
// Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible. // Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible.
@ -999,7 +994,7 @@ void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int
queueRunner_.CopyReadbackBuffer(frameData_[vulkan_->GetCurFrame()], nullptr, w, h, destFormat, destFormat, pixelStride, pixels); queueRunner_.CopyReadbackBuffer(frameData_[vulkan_->GetCurFrame()], nullptr, w, h, destFormat, destFormat, pixelStride, pixels);
} }
static void RemoveDrawCommands(std::vector<VkRenderData> *cmds) { static void RemoveDrawCommands(FastVec<VkRenderData> *cmds) {
// Here we remove any DRAW type commands when we hit a CLEAR. // Here we remove any DRAW type commands when we hit a CLEAR.
for (auto &c : *cmds) { for (auto &c : *cmds) {
if (c.cmd == VKRRenderCommand::DRAW || c.cmd == VKRRenderCommand::DRAW_INDEXED) { if (c.cmd == VKRRenderCommand::DRAW || c.cmd == VKRRenderCommand::DRAW_INDEXED) {
@ -1008,7 +1003,7 @@ static void RemoveDrawCommands(std::vector<VkRenderData> *cmds) {
} }
} }
static void CleanupRenderCommands(std::vector<VkRenderData> *cmds) { static void CleanupRenderCommands(FastVec<VkRenderData> *cmds) {
size_t lastCommand[(int)VKRRenderCommand::NUM_RENDER_COMMANDS]; size_t lastCommand[(int)VKRRenderCommand::NUM_RENDER_COMMANDS];
memset(lastCommand, -1, sizeof(lastCommand)); memset(lastCommand, -1, sizeof(lastCommand));
@ -1266,13 +1261,12 @@ void VulkanRenderManager::Finish() {
FrameData &frameData = frameData_[curFrame]; FrameData &frameData = frameData_[curFrame];
VLOG("PUSH: Frame[%d]", curFrame); VLOG("PUSH: Frame[%d]", curFrame);
VKRRenderThreadTask task; VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT);
task.frame = curFrame; task->frame = curFrame;
task.runType = VKRRunType::PRESENT;
{ {
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task); renderThreadQueue_.push(task);
renderThreadQueue_.back().steps = std::move(steps_); renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one(); pushCondVar_.notify_one();
} }
@ -1382,12 +1376,11 @@ void VulkanRenderManager::FlushSync() {
{ {
VLOG("PUSH: Frame[%d]", curFrame); VLOG("PUSH: Frame[%d]", curFrame);
VKRRenderThreadTask task; VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
task.frame = curFrame; task->frame = curFrame;
task.runType = VKRRunType::SYNC;
std::unique_lock<std::mutex> lock(pushMutex_); std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task); renderThreadQueue_.push(task);
renderThreadQueue_.back().steps = std::move(steps_); renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one(); pushCondVar_.notify_one();
} }

View file

@ -76,7 +76,10 @@ struct BoundingRect {
// All the data needed to create a graphics pipeline. // All the data needed to create a graphics pipeline.
// TODO: Compress this down greatly. // TODO: Compress this down greatly.
struct VKRGraphicsPipelineDesc : Draw::RefCountedObject { class VKRGraphicsPipelineDesc : public Draw::RefCountedObject {
public:
VKRGraphicsPipelineDesc() : Draw::RefCountedObject("VKRGraphicsPipelineDesc") {}
VkPipelineCache pipelineCache = VK_NULL_HANDLE; VkPipelineCache pipelineCache = VK_NULL_HANDLE;
VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
VkPipelineColorBlendAttachmentState blend0{}; VkPipelineColorBlendAttachmentState blend0{};
@ -215,8 +218,6 @@ public:
// get an array texture view. // get an array texture view.
VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer); VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer);
void BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits);
bool CopyFramebufferToMemory(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag); bool CopyFramebufferToMemory(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag);
void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag); void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag);
@ -240,7 +241,8 @@ public:
void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) { void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr); _dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_GRAPHICS_PIPELINE }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BIND_GRAPHICS_PIPELINE;
pipelinesToCheck_.push_back(pipeline); pipelinesToCheck_.push_back(pipeline);
data.graphics_pipeline.pipeline = pipeline; data.graphics_pipeline.pipeline = pipeline;
data.graphics_pipeline.pipelineLayout = pipelineLayout; data.graphics_pipeline.pipelineLayout = pipelineLayout;
@ -249,24 +251,24 @@ public:
// DebugBreak(); // DebugBreak();
// } // }
curPipelineFlags_ |= flags; curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
} }
void BindPipeline(VKRComputePipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) { void BindPipeline(VKRComputePipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr); _dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_COMPUTE_PIPELINE }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BIND_COMPUTE_PIPELINE;
data.compute_pipeline.pipeline = pipeline; data.compute_pipeline.pipeline = pipeline;
data.compute_pipeline.pipelineLayout = pipelineLayout; data.compute_pipeline.pipelineLayout = pipelineLayout;
curPipelineFlags_ |= flags; curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
} }
void SetViewport(const VkViewport &vp) { void SetViewport(const VkViewport &vp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_((int)vp.width >= 0); _dbg_assert_((int)vp.width >= 0);
_dbg_assert_((int)vp.height >= 0); _dbg_assert_((int)vp.height >= 0);
VkRenderData data{ VKRRenderCommand::VIEWPORT }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::VIEWPORT;
data.viewport.vp.x = vp.x; data.viewport.vp.x = vp.x;
data.viewport.vp.y = vp.y; data.viewport.vp.y = vp.y;
data.viewport.vp.width = vp.width; data.viewport.vp.width = vp.width;
@ -276,7 +278,6 @@ public:
// TODO: This should be fixed at the source. // TODO: This should be fixed at the source.
data.viewport.vp.minDepth = clamp_value(vp.minDepth, 0.0f, 1.0f); data.viewport.vp.minDepth = clamp_value(vp.minDepth, 0.0f, 1.0f);
data.viewport.vp.maxDepth = clamp_value(vp.maxDepth, 0.0f, 1.0f); data.viewport.vp.maxDepth = clamp_value(vp.maxDepth, 0.0f, 1.0f);
curRenderStep_->commands.push_back(data);
curStepHasViewport_ = true; curStepHasViewport_ = true;
} }
@ -318,37 +319,37 @@ public:
curRenderArea_.Apply(rc); curRenderArea_.Apply(rc);
VkRenderData data{ VKRRenderCommand::SCISSOR }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::SCISSOR;
data.scissor.scissor = rc; data.scissor.scissor = rc;
curRenderStep_->commands.push_back(data);
curStepHasScissor_ = true; curStepHasScissor_ = true;
} }
void SetStencilParams(uint8_t writeMask, uint8_t compareMask, uint8_t refValue) { void SetStencilParams(uint8_t writeMask, uint8_t compareMask, uint8_t refValue) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
VkRenderData data{ VKRRenderCommand::STENCIL }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::STENCIL;
data.stencil.stencilWriteMask = writeMask; data.stencil.stencilWriteMask = writeMask;
data.stencil.stencilCompareMask = compareMask; data.stencil.stencilCompareMask = compareMask;
data.stencil.stencilRef = refValue; data.stencil.stencilRef = refValue;
curRenderStep_->commands.push_back(data);
} }
void SetBlendFactor(uint32_t color) { void SetBlendFactor(uint32_t color) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
VkRenderData data{ VKRRenderCommand::BLEND }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BLEND;
data.blendColor.color = color; data.blendColor.color = color;
curRenderStep_->commands.push_back(data);
} }
void PushConstants(VkPipelineLayout pipelineLayout, VkShaderStageFlags stages, int offset, int size, void *constants) { void PushConstants(VkPipelineLayout pipelineLayout, VkShaderStageFlags stages, int offset, int size, void *constants) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(size + offset < 40); _dbg_assert_(size + offset < 40);
VkRenderData data{ VKRRenderCommand::PUSH_CONSTANTS }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::PUSH_CONSTANTS;
data.push.stages = stages; data.push.stages = stages;
data.push.offset = offset; data.push.offset = offset;
data.push.size = size; data.push.size = size;
memcpy(data.push.data, constants, size); memcpy(data.push.data, constants, size);
curRenderStep_->commands.push_back(data);
} }
void Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask); void Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask);
@ -380,7 +381,8 @@ public:
void Draw(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) { void Draw(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData data{ VKRRenderCommand::DRAW }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW;
data.draw.count = count; data.draw.count = count;
data.draw.offset = offset; data.draw.offset = offset;
data.draw.ds = descSet; data.draw.ds = descSet;
@ -390,13 +392,13 @@ public:
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.draw.uboOffsets)); _dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.draw.uboOffsets));
for (int i = 0; i < numUboOffsets; i++) for (int i = 0; i < numUboOffsets; i++)
data.draw.uboOffsets[i] = uboOffsets[i]; data.draw.uboOffsets[i] = uboOffsets[i];
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++; curRenderStep_->render.numDraws++;
} }
void DrawIndexed(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances, VkIndexType indexType) { void DrawIndexed(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_); _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData data{ VKRRenderCommand::DRAW_INDEXED }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW_INDEXED;
data.drawIndexed.count = count; data.drawIndexed.count = count;
data.drawIndexed.instances = numInstances; data.drawIndexed.instances = numInstances;
data.drawIndexed.ds = descSet; data.drawIndexed.ds = descSet;
@ -408,8 +410,6 @@ public:
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.drawIndexed.uboOffsets)); _dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.drawIndexed.uboOffsets));
for (int i = 0; i < numUboOffsets; i++) for (int i = 0; i < numUboOffsets; i++)
data.drawIndexed.uboOffsets[i] = uboOffsets[i]; data.drawIndexed.uboOffsets[i] = uboOffsets[i];
data.drawIndexed.indexType = indexType;
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++; curRenderStep_->render.numDraws++;
} }
@ -417,9 +417,9 @@ public:
// in the debugger. // in the debugger.
void DebugAnnotate(const char *annotation) { void DebugAnnotate(const char *annotation) {
_dbg_assert_(curRenderStep_); _dbg_assert_(curRenderStep_);
VkRenderData data{ VKRRenderCommand::DEBUG_ANNOTATION }; VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DEBUG_ANNOTATION;
data.debugAnnotation.annotation = annotation; data.debugAnnotation.annotation = annotation;
curRenderStep_->commands.push_back(data);
} }
VkCommandBuffer GetInitCmd(); VkCommandBuffer GetInitCmd();
@ -453,8 +453,6 @@ public:
return outOfDateFrames_ > VulkanContext::MAX_INFLIGHT_FRAMES; return outOfDateFrames_ > VulkanContext::MAX_INFLIGHT_FRAMES;
} }
void Invalidate(InvalidationFlags flags);
void ResetStats(); void ResetStats();
void DrainCompileQueue(); void DrainCompileQueue();
@ -509,7 +507,7 @@ private:
std::mutex pushMutex_; std::mutex pushMutex_;
std::condition_variable pushCondVar_; std::condition_variable pushCondVar_;
std::queue<VKRRenderThreadTask> renderThreadQueue_; std::queue<VKRRenderThreadTask *> renderThreadQueue_;
// For readbacks and other reasons we need to sync with the render thread. // For readbacks and other reasons we need to sync with the render thread.
std::mutex syncMutex_; std::mutex syncMutex_;

View file

@ -335,8 +335,11 @@ struct DescriptorSetKey {
class VKTexture : public Texture { class VKTexture : public Texture {
public: public:
VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc) VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc)
: vulkan_(vulkan), mipLevels_(desc.mipLevels), format_(desc.format) {} : vulkan_(vulkan), mipLevels_(desc.mipLevels) {
format_ = desc.format;
}
bool Create(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc); bool Create(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc);
void Update(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
~VKTexture() { ~VKTexture() {
Destroy(); Destroy();
@ -356,7 +359,13 @@ public:
return VK_NULL_HANDLE; // This would be bad. return VK_NULL_HANDLE; // This would be bad.
} }
int NumLevels() const {
return mipLevels_;
}
private: private:
void UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
void Destroy() { void Destroy() {
if (vkTex_) { if (vkTex_) {
vkTex_->Destroy(); vkTex_->Destroy();
@ -369,8 +378,6 @@ private:
VulkanTexture *vkTex_ = nullptr; VulkanTexture *vkTex_ = nullptr;
int mipLevels_ = 0; int mipLevels_ = 0;
DataFormat format_ = DataFormat::UNDEFINED;
}; };
class VKFramebuffer; class VKFramebuffer;
@ -421,6 +428,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override; Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override; void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override; void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -430,7 +438,6 @@ public:
// These functions should be self explanatory. // These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override; void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override; void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
void BindCurrentFramebufferForColorInput() override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@ -476,6 +483,10 @@ public:
void EndFrame() override; void EndFrame() override;
void WipeQueue() override; void WipeQueue() override;
int GetFrameCount() override {
return frameCount_;
}
void FlushState() override {} void FlushState() override {}
void ResetStats() override { void ResetStats() override {
@ -520,6 +531,7 @@ private:
VulkanTexture *GetNullTexture(); VulkanTexture *GetNullTexture();
VulkanContext *vulkan_ = nullptr; VulkanContext *vulkan_ = nullptr;
int frameCount_ = 0;
VulkanRenderManager renderManager_; VulkanRenderManager renderManager_;
VulkanTexture *nullTexture_ = nullptr; VulkanTexture *nullTexture_ = nullptr;
@ -684,6 +696,7 @@ VulkanTexture *VKContext::GetNullTexture() {
uint32_t bindOffset; uint32_t bindOffset;
VkBuffer bindBuf; VkBuffer bindBuf;
uint32_t *data = (uint32_t *)push_->Allocate(w * h * 4, 4, &bindBuf, &bindOffset); uint32_t *data = (uint32_t *)push_->Allocate(w * h * 4, 4, &bindBuf, &bindOffset);
_assert_(data != nullptr);
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
// data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard // data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard
@ -747,14 +760,14 @@ enum class TextureState {
PENDING_DESTRUCTION, PENDING_DESTRUCTION,
}; };
bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureDesc &desc) { bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc) {
// Zero-sized textures not allowed. // Zero-sized textures not allowed.
_assert_(desc.width * desc.height * desc.depth > 0); // remember to set depth to 1! _assert_(desc.width * desc.height * desc.depth > 0); // remember to set depth to 1!
if (desc.width * desc.height * desc.depth <= 0) { if (desc.width * desc.height * desc.depth <= 0) {
ERROR_LOG(G3D, "Bad texture dimensions %dx%dx%d", desc.width, desc.height, desc.depth); ERROR_LOG(G3D, "Bad texture dimensions %dx%dx%d", desc.width, desc.height, desc.depth);
return false; return false;
} }
_assert_(push); _dbg_assert_(pushBuffer);
format_ = desc.format; format_ = desc.format;
mipLevels_ = desc.mipLevels; mipLevels_ = desc.mipLevels;
width_ = desc.width; width_ = desc.width;
@ -762,8 +775,6 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
depth_ = desc.depth; depth_ = desc.depth;
vkTex_ = new VulkanTexture(vulkan_, desc.tag); vkTex_ = new VulkanTexture(vulkan_, desc.tag);
VkFormat vulkanFormat = DataFormatToVulkan(format_); VkFormat vulkanFormat = DataFormatToVulkan(format_);
int bpp = GetBpp(vulkanFormat);
int bytesPerPixel = bpp / 8;
int usageBits = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; int usageBits = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
if (mipLevels_ > (int)desc.initData.size()) { if (mipLevels_ > (int)desc.initData.size()) {
// Gonna have to generate some, which requires TRANSFER_SRC // Gonna have to generate some, which requires TRANSFER_SRC
@ -778,32 +789,10 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
} }
VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
if (desc.initData.size()) { if (desc.initData.size()) {
int w = width_; UpdateInternal(cmd, pushBuffer, desc.initData.data(), desc.initDataCallback, (int)desc.initData.size());
int h = height_;
int d = depth_;
int i;
for (i = 0; i < (int)desc.initData.size(); i++) {
uint32_t offset;
VkBuffer buf;
size_t size = w * h * d * bytesPerPixel;
if (desc.initDataCallback) {
uint8_t *dest = (uint8_t *)push->Allocate(size, 16, &buf, &offset);
if (!desc.initDataCallback(dest, desc.initData[i], w, h, d, w * bytesPerPixel, h * w * bytesPerPixel)) {
memcpy(dest, desc.initData[i], size);
}
} else {
offset = push->Push((const void *)desc.initData[i], size, 16, &buf);
}
TextureCopyBatch batch;
vkTex_->CopyBufferToMipLevel(cmd, &batch, i, w, h, 0, buf, offset, w);
vkTex_->FinishCopyBatch(cmd, &batch);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
// Generate the rest of the mips automatically. // Generate the rest of the mips automatically.
if (i < mipLevels_) { if (desc.initData.size() < mipLevels_) {
vkTex_->GenerateMips(cmd, i, false); vkTex_->GenerateMips(cmd, (int)desc.initData.size(), false);
layout = VK_IMAGE_LAYOUT_GENERAL; layout = VK_IMAGE_LAYOUT_GENERAL;
} }
} }
@ -811,6 +800,44 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
return true; return true;
} }
void VKTexture::Update(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t * const *data, TextureCallback initDataCallback, int numLevels) {
// Before we can use UpdateInternal, we need to transition the image to the same state as after CreateDirect,
// making it ready for writing.
vkTex_->PrepareForTransferDst(cmd, numLevels);
UpdateInternal(cmd, pushBuffer, data, initDataCallback, numLevels);
vkTex_->RestoreAfterTransferDst(cmd, numLevels);
}
void VKTexture::UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t * const *data, TextureCallback initDataCallback, int numLevels) {
int w = width_;
int h = height_;
int d = depth_;
int i;
VkFormat vulkanFormat = DataFormatToVulkan(format_);
int bpp = GetBpp(vulkanFormat);
int bytesPerPixel = bpp / 8;
TextureCopyBatch batch;
for (i = 0; i < numLevels; i++) {
uint32_t offset;
VkBuffer buf;
size_t size = w * h * d * bytesPerPixel;
uint8_t *dest = (uint8_t *)pushBuffer->Allocate(size, 16, &buf, &offset);
if (initDataCallback) {
_assert_(dest != nullptr);
if (!initDataCallback(dest, data[i], w, h, d, w * bytesPerPixel, h * w * bytesPerPixel)) {
memcpy(dest, data[i], size);
}
} else {
memcpy(dest, data[i], size);
}
vkTex_->CopyBufferToMipLevel(cmd, &batch, i, w, h, 0, buf, offset, w);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
vkTex_->FinishCopyBatch(cmd, &batch);
}
static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) { static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) {
switch (fmt) { switch (fmt) {
case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D24_UNORM_S8_UINT:
@ -983,9 +1010,9 @@ VKContext::VKContext(VulkanContext *vulkan)
} }
} }
// Limited, through input attachments and self-dependencies. // Vulkan can support this through input attachments and various extensions, but not worth
// We turn it off here already if buggy. // the trouble.
caps_.framebufferFetchSupported = !bugs_.Has(Bugs::SUBPASS_FEEDBACK_BROKEN); caps_.framebufferFetchSupported = false;
caps_.deviceID = deviceProps.deviceID; caps_.deviceID = deviceProps.deviceID;
device_ = vulkan->GetDevice(); device_ = vulkan->GetDevice();
@ -1082,6 +1109,8 @@ void VKContext::EndFrame() {
// Unbind stuff, to avoid accidentally relying on it across frames (and provide some protection against forgotten unbinds of deleted things). // Unbind stuff, to avoid accidentally relying on it across frames (and provide some protection against forgotten unbinds of deleted things).
Invalidate(InvalidationFlags::CACHED_RENDER_STATE); Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
frameCount_++;
} }
void VKContext::Invalidate(InvalidationFlags flags) { void VKContext::Invalidate(InvalidationFlags flags) {
@ -1340,6 +1369,20 @@ Texture *VKContext::CreateTexture(const TextureDesc &desc) {
} }
} }
void VKContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
VkCommandBuffer initCmd = renderManager_.GetInitCmd();
if (!push_ || !initCmd) {
// Too early! Fail.
ERROR_LOG(G3D, "Can't create textures before the first frame has started.");
return;
}
VKTexture *tex = (VKTexture *)texture;
_dbg_assert_(numLevels <= tex->NumLevels());
tex->Update(initCmd, push_, data, initDataCallback, numLevels);
}
static inline void CopySide(VkStencilOpState &dest, const StencilSetup &src) { static inline void CopySide(VkStencilOpState &dest, const StencilSetup &src) {
dest.compareOp = compToVK[(int)src.compareOp]; dest.compareOp = compToVK[(int)src.compareOp];
dest.failOp = stencilOpToVK[(int)src.failOp]; dest.failOp = stencilOpToVK[(int)src.failOp];
@ -1495,7 +1538,7 @@ void VKContext::DrawIndexed(int vertexCount, int offset) {
BindCurrentPipeline(); BindCurrentPipeline();
ApplyDynamicState(); ApplyDynamicState();
renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1, VK_INDEX_TYPE_UINT16); renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1);
} }
void VKContext::DrawUP(const void *vdata, int vertexCount) { void VKContext::DrawUP(const void *vdata, int vertexCount) {
@ -1505,7 +1548,12 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
} }
VkBuffer vulkanVbuf, vulkanUBObuf; VkBuffer vulkanVbuf, vulkanUBObuf;
size_t vbBindOffset = push_->Push(vdata, vertexCount * curPipeline_->stride[0], 4, &vulkanVbuf); size_t dataSize = vertexCount * curPipeline_->stride[0];
uint32_t vbBindOffset;
uint8_t *dataPtr = push_->Allocate(dataSize, 4, &vulkanVbuf, &vbBindOffset);
_assert_(dataPtr != nullptr);
memcpy(dataPtr, vdata, dataSize);
uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf); uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf);
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf); VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
@ -1566,6 +1614,8 @@ std::vector<std::string> VKContext::GetFeatureList() const {
AddFeature(features, "multiview", vulkan_->GetDeviceFeatures().available.multiview.multiview, vulkan_->GetDeviceFeatures().enabled.multiview.multiview); AddFeature(features, "multiview", vulkan_->GetDeviceFeatures().available.multiview.multiview, vulkan_->GetDeviceFeatures().enabled.multiview.multiview);
AddFeature(features, "multiviewGeometryShader", vulkan_->GetDeviceFeatures().available.multiview.multiviewGeometryShader, vulkan_->GetDeviceFeatures().enabled.multiview.multiviewGeometryShader); AddFeature(features, "multiviewGeometryShader", vulkan_->GetDeviceFeatures().available.multiview.multiviewGeometryShader, vulkan_->GetDeviceFeatures().enabled.multiview.multiviewGeometryShader);
AddFeature(features, "presentId", vulkan_->GetDeviceFeatures().available.presentId.presentId, vulkan_->GetDeviceFeatures().enabled.presentId.presentId);
AddFeature(features, "presentWait", vulkan_->GetDeviceFeatures().available.presentWait.presentWait, vulkan_->GetDeviceFeatures().enabled.presentWait.presentWait);
features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat)); features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));
@ -1728,10 +1778,6 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne
boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, layer); boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, layer);
} }
void VKContext::BindCurrentFramebufferForColorInput() {
renderManager_.BindCurrentFramebufferAsInputAttachment0(VK_IMAGE_ASPECT_COLOR_BIT);
}
void VKContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { void VKContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) {
VKFramebuffer *fb = (VKFramebuffer *)fbo; VKFramebuffer *fb = (VKFramebuffer *)fbo;
if (fb) { if (fb) {

View file

@ -119,7 +119,8 @@ bool DataFormatIsBlockCompressed(DataFormat fmt, int *blockSize) {
} }
RefCountedObject::~RefCountedObject() { RefCountedObject::~RefCountedObject() {
_dbg_assert_(refcount_ == 0xDEDEDE); const int rc = refcount_.load();
_dbg_assert_msg_(rc == 0xDEDEDE, "Unexpected refcount %d in object of type '%s'", rc, name_);
} }
bool RefCountedObject::Release() { bool RefCountedObject::Release() {
@ -131,6 +132,7 @@ bool RefCountedObject::Release() {
return true; return true;
} }
} else { } else {
// No point in printing the name here if the object has already been free-d, it'll be corrupt and dangerous to print.
_dbg_assert_msg_(false, "Refcount (%d) invalid for object %p - corrupt?", refcount_.load(), this); _dbg_assert_msg_(false, "Refcount (%d) invalid for object %p - corrupt?", refcount_.load(), this);
} }
return false; return false;
@ -138,11 +140,10 @@ bool RefCountedObject::Release() {
bool RefCountedObject::ReleaseAssertLast() { bool RefCountedObject::ReleaseAssertLast() {
bool released = Release(); bool released = Release();
_dbg_assert_msg_(released, "RefCountedObject: Expected to be the last reference, but isn't!"); _dbg_assert_msg_(released, "RefCountedObject: Expected to be the last reference, but isn't! (%s)", name_);
return released; return released;
} }
// ================================== PIXEL/FRAGMENT SHADERS // ================================== PIXEL/FRAGMENT SHADERS
// The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL. // The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL.

View file

@ -359,7 +359,7 @@ protected:
class RefCountedObject { class RefCountedObject {
public: public:
RefCountedObject() { explicit RefCountedObject(const char *name) : name_(name) {
refcount_ = 1; refcount_ = 1;
} }
RefCountedObject(const RefCountedObject &other) = delete; RefCountedObject(const RefCountedObject &other) = delete;
@ -372,6 +372,7 @@ public:
private: private:
std::atomic<int> refcount_; std::atomic<int> refcount_;
const char * const name_;
}; };
template <typename T> template <typename T>
@ -428,18 +429,22 @@ struct AutoRef {
class BlendState : public RefCountedObject { class BlendState : public RefCountedObject {
public: public:
BlendState() : RefCountedObject("BlendState") {}
}; };
class SamplerState : public RefCountedObject { class SamplerState : public RefCountedObject {
public: public:
SamplerState() : RefCountedObject("SamplerState") {}
}; };
class DepthStencilState : public RefCountedObject { class DepthStencilState : public RefCountedObject {
public: public:
DepthStencilState() : RefCountedObject("DepthStencilState") {}
}; };
class Framebuffer : public RefCountedObject { class Framebuffer : public RefCountedObject {
public: public:
Framebuffer() : RefCountedObject("Framebuffer") {}
int Width() { return width_; } int Width() { return width_; }
int Height() { return height_; } int Height() { return height_; }
int Layers() { return layers_; } int Layers() { return layers_; }
@ -452,15 +457,20 @@ protected:
class Buffer : public RefCountedObject { class Buffer : public RefCountedObject {
public: public:
Buffer() : RefCountedObject("Buffer") {}
}; };
class Texture : public RefCountedObject { class Texture : public RefCountedObject {
public: public:
int Width() { return width_; } Texture() : RefCountedObject("Texture") {}
int Height() { return height_; } int Width() const { return width_; }
int Depth() { return depth_; } int Height() const { return height_; }
int Depth() const { return depth_; }
DataFormat Format() const { return format_; }
protected: protected:
int width_ = -1, height_ = -1, depth_ = -1; int width_ = -1, height_ = -1, depth_ = -1;
DataFormat format_ = DataFormat::UNDEFINED;
}; };
struct BindingDesc { struct BindingDesc {
@ -480,18 +490,28 @@ struct InputLayoutDesc {
std::vector<AttributeDesc> attributes; std::vector<AttributeDesc> attributes;
}; };
class InputLayout : public RefCountedObject { }; class InputLayout : public RefCountedObject {
public:
InputLayout() : RefCountedObject("InputLayout") {}
};
// Uniform types have moved to Shader.h. // Uniform types have moved to Shader.h.
class ShaderModule : public RefCountedObject { class ShaderModule : public RefCountedObject {
public: public:
ShaderModule() : RefCountedObject("ShaderModule") {}
virtual ShaderStage GetStage() const = 0; virtual ShaderStage GetStage() const = 0;
}; };
class Pipeline : public RefCountedObject { }; class Pipeline : public RefCountedObject {
public:
Pipeline() : RefCountedObject("Pipeline") {}
};
class RasterState : public RefCountedObject {}; class RasterState : public RefCountedObject {
public:
RasterState() : RefCountedObject("RasterState") {}
};
struct StencilSetup { struct StencilSetup {
StencilOp failOp; StencilOp failOp;
@ -713,6 +733,11 @@ public:
// Copies data from the CPU over into the buffer, at a specific offset. This does not change the size of the buffer and cannot write outside it. // Copies data from the CPU over into the buffer, at a specific offset. This does not change the size of the buffer and cannot write outside it.
virtual void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) = 0; virtual void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) = 0;
// Used to optimize DrawPixels by re-using previously allocated temp textures.
// Do not try to update a texture that might be used by an in-flight command buffer! In OpenGL and D3D, this will cause stalls
// while in Vulkan this might cause various strangeness like image corruption.
virtual void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) = 0;
virtual void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) = 0; virtual void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) = 0;
virtual bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) = 0; virtual bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) = 0;
@ -816,6 +841,10 @@ public:
// Not very elegant, but more elegant than the old passId hack. // Not very elegant, but more elegant than the old passId hack.
virtual void SetInvalidationCallback(InvalidationCallback callback) = 0; virtual void SetInvalidationCallback(InvalidationCallback callback) = 0;
// Total amount of frames rendered. Unaffected by game pause, so more robust than gpuStats.numFlips
virtual int GetFrameCount() = 0;
virtual int GetFramesInFlight() { return 3; }
protected: protected:
ShaderModule *vsPresets_[VS_MAX_PRESET]; ShaderModule *vsPresets_[VS_MAX_PRESET];
ShaderModule *fsPresets_[FS_MAX_PRESET]; ShaderModule *fsPresets_[FS_MAX_PRESET];

View file

@ -20,7 +20,7 @@ const char *GetDeviceName(int deviceId) {
case DEVICE_ID_PAD_7: return "pad8"; case DEVICE_ID_PAD_7: return "pad8";
case DEVICE_ID_PAD_8: return "pad9"; case DEVICE_ID_PAD_8: return "pad9";
case DEVICE_ID_PAD_9: return "pad10"; case DEVICE_ID_PAD_9: return "pad10";
case DEVICE_ID_XINPUT_0: return "x360"; // keeping these strings for backward compat case DEVICE_ID_XINPUT_0: return "x360"; // keeping these strings for backward compat. Hm, what would break if we changed them to xbox?
case DEVICE_ID_XINPUT_1: return "x360_2"; case DEVICE_ID_XINPUT_1: return "x360_2";
case DEVICE_ID_XINPUT_2: return "x360_3"; case DEVICE_ID_XINPUT_2: return "x360_3";
case DEVICE_ID_XINPUT_3: return "x360_4"; case DEVICE_ID_XINPUT_3: return "x360_4";
@ -39,7 +39,7 @@ std::vector<InputMapping> confirmKeys;
std::vector<InputMapping> cancelKeys; std::vector<InputMapping> cancelKeys;
std::vector<InputMapping> tabLeftKeys; std::vector<InputMapping> tabLeftKeys;
std::vector<InputMapping> tabRightKeys; std::vector<InputMapping> tabRightKeys;
static std::unordered_map<int, int> uiFlipAnalogY; static std::unordered_map<InputDeviceID, int> uiFlipAnalogY;
static void AppendKeys(std::vector<InputMapping> &keys, const std::vector<InputMapping> &newKeys) { static void AppendKeys(std::vector<InputMapping> &keys, const std::vector<InputMapping> &newKeys) {
for (auto iter = newKeys.begin(); iter != newKeys.end(); ++iter) { for (auto iter = newKeys.begin(); iter != newKeys.end(); ++iter) {
@ -69,11 +69,11 @@ void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::ve
tabRightKeys = tabRight; tabRightKeys = tabRight;
} }
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId) { void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId) {
uiFlipAnalogY = flipYByDeviceId; uiFlipAnalogY = flipYByDeviceId;
} }
int GetAnalogYDirection(int deviceId) { int GetAnalogYDirection(InputDeviceID deviceId) {
auto configured = uiFlipAnalogY.find(deviceId); auto configured = uiFlipAnalogY.find(deviceId);
if (configured != uiFlipAnalogY.end()) if (configured != uiFlipAnalogY.end())
return configured->second; return configured->second;
@ -84,8 +84,8 @@ int GetAnalogYDirection(int deviceId) {
InputMapping InputMapping::FromConfigString(const std::string &str) { InputMapping InputMapping::FromConfigString(const std::string &str) {
std::vector<std::string> parts; std::vector<std::string> parts;
SplitString(str, '-', parts); SplitString(str, '-', parts);
int deviceId = atoi(parts[0].c_str()); InputDeviceID deviceId = (InputDeviceID)(atoi(parts[0].c_str()));
int keyCode = atoi(parts[1].c_str()); InputKeyCode keyCode = (InputKeyCode)atoi(parts[1].c_str());
InputMapping mapping; InputMapping mapping;
mapping.deviceId = deviceId; mapping.deviceId = deviceId;
@ -94,15 +94,15 @@ InputMapping InputMapping::FromConfigString(const std::string &str) {
} }
std::string InputMapping::ToConfigString() const { std::string InputMapping::ToConfigString() const {
return StringFromFormat("%d-%d", deviceId, keyCode); return StringFromFormat("%d-%d", (int)deviceId, keyCode);
} }
void InputMapping::FormatDebug(char *buffer, size_t bufSize) const { void InputMapping::FormatDebug(char *buffer, size_t bufSize) const {
if (IsAxis()) { if (IsAxis()) {
int direction; int direction;
int axis = Axis(&direction); int axis = Axis(&direction);
snprintf(buffer, bufSize, "Device: %d Axis: %d (%d)", deviceId, axis, direction); snprintf(buffer, bufSize, "Device: %d Axis: %d (%d)", (int)deviceId, axis, direction);
} else { } else {
snprintf(buffer, bufSize, "Device: %d Key: %d", deviceId, keyCode); snprintf(buffer, bufSize, "Device: %d Key: %d", (int)deviceId, keyCode);
} }
} }

View file

@ -12,7 +12,7 @@
// Default device IDs // Default device IDs
enum { enum InputDeviceID {
DEVICE_ID_ANY = -1, // Represents any device ID DEVICE_ID_ANY = -1, // Represents any device ID
DEVICE_ID_DEFAULT = 0, // Old Android DEVICE_ID_DEFAULT = 0, // Old Android
DEVICE_ID_KEYBOARD = 1, // PC keyboard, android keyboards DEVICE_ID_KEYBOARD = 1, // PC keyboard, android keyboards
@ -38,43 +38,15 @@ enum {
DEVICE_ID_TOUCH = 42, DEVICE_ID_TOUCH = 42,
}; };
inline InputDeviceID operator +(InputDeviceID deviceID, int addend) {
return (InputDeviceID)((int)deviceID + addend);
}
//number of contiguous generic joypad IDs //number of contiguous generic joypad IDs
const int MAX_NUM_PADS = 10; const int MAX_NUM_PADS = 10;
const char *GetDeviceName(int deviceId); const char *GetDeviceName(int deviceId);
enum {
PAD_BUTTON_A = 1,
PAD_BUTTON_B = 2,
PAD_BUTTON_X = 4,
PAD_BUTTON_Y = 8,
PAD_BUTTON_LBUMPER = 16,
PAD_BUTTON_RBUMPER = 32,
PAD_BUTTON_START = 64,
PAD_BUTTON_SELECT = 128,
PAD_BUTTON_UP = 256,
PAD_BUTTON_DOWN = 512,
PAD_BUTTON_LEFT = 1024,
PAD_BUTTON_RIGHT = 2048,
PAD_BUTTON_MENU = 4096,
PAD_BUTTON_BACK = 8192,
// For Qt
PAD_BUTTON_JOY_UP = 1<<14,
PAD_BUTTON_JOY_DOWN = 1<<15,
PAD_BUTTON_JOY_LEFT = 1<<16,
PAD_BUTTON_JOY_RIGHT = 1<<17,
PAD_BUTTON_LEFT_THUMB = 1 << 18, // Click left thumb stick on X360
PAD_BUTTON_RIGHT_THUMB = 1 << 19, // Click right thumb stick on X360
PAD_BUTTON_LEFT_TRIGGER = 1 << 21, // Click left thumb stick on X360
PAD_BUTTON_RIGHT_TRIGGER = 1 << 22, // Click left thumb stick on X360
PAD_BUTTON_FASTFORWARD = 1 << 20, // Click Tab to unthrottle
};
#ifndef MAX_KEYQUEUESIZE #ifndef MAX_KEYQUEUESIZE
#define MAX_KEYQUEUESIZE 20 #define MAX_KEYQUEUESIZE 20
#endif #endif
@ -98,18 +70,18 @@ private:
return AXIS_BIND_NKCODE_START + axisId * 2 + (direction < 0 ? 1 : 0); return AXIS_BIND_NKCODE_START + axisId * 2 + (direction < 0 ? 1 : 0);
} }
public: public:
InputMapping() : deviceId(0), keyCode(0) {} InputMapping() : deviceId(DEVICE_ID_DEFAULT), keyCode(0) {}
// From a key mapping // From a key mapping
InputMapping(int _deviceId, int key) : deviceId(_deviceId), keyCode(key) {} InputMapping(InputDeviceID _deviceId, int key) : deviceId(_deviceId), keyCode(key) {}
// From an axis // From an axis
InputMapping(int _deviceId, int axis, int direction) : deviceId(_deviceId), keyCode(TranslateKeyCodeFromAxis(axis, direction)) { InputMapping(InputDeviceID _deviceId, int axis, int direction) : deviceId(_deviceId), keyCode(TranslateKeyCodeFromAxis(axis, direction)) {
_dbg_assert_(direction != 0); _dbg_assert_(direction != 0);
} }
static InputMapping FromConfigString(const std::string &str); static InputMapping FromConfigString(const std::string &str);
std::string ToConfigString() const; std::string ToConfigString() const;
int deviceId; InputDeviceID deviceId;
int keyCode; // Can also represent an axis with direction, if encoded properly. int keyCode; // Can also represent an axis with direction, if encoded properly.
bool IsAxis() const { bool IsAxis() const {
@ -195,15 +167,19 @@ enum {
struct KeyInput { struct KeyInput {
KeyInput() {} KeyInput() {}
KeyInput(int devId, int code, int fl) : deviceId(devId), keyCode(code), flags(fl) {} KeyInput(InputDeviceID devId, InputKeyCode code, int fl) : deviceId(devId), keyCode(code), flags(fl) {}
int deviceId; KeyInput(InputDeviceID devId, int unicode) : deviceId(devId), unicodeChar(unicode), flags(KEY_CHAR) {}
int keyCode; // Android keycodes are the canonical keycodes, everyone else map to them. InputDeviceID deviceId;
union {
InputKeyCode keyCode; // Android keycodes are the canonical keycodes, everyone else map to them.
int unicodeChar; // for KEY_CHAR
};
int flags; int flags;
}; };
struct AxisInput { struct AxisInput {
int deviceId; InputDeviceID deviceId;
int axisId; // Android axis Ids are the canonical ones. InputAxis axisId;
float value; float value;
}; };
@ -219,5 +195,5 @@ void SetConfirmCancelKeys(const std::vector<InputMapping> &confirm, const std::v
void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::vector<InputMapping> &tabRight); void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::vector<InputMapping> &tabRight);
// 0 means unknown (attempt autodetect), -1 means flip, 1 means original direction. // 0 means unknown (attempt autodetect), -1 means flip, 1 means original direction.
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId); void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId);
int GetAnalogYDirection(int deviceId); int GetAnalogYDirection(InputDeviceID deviceId);

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
typedef enum _keycode_t { // These mostly match Android keycodes.
enum InputKeyCode {
NKCODE_BUTTON_CROSS = 23, // trackpad or X button(Xperia Play) is pressed NKCODE_BUTTON_CROSS = 23, // trackpad or X button(Xperia Play) is pressed
NKCODE_BUTTON_CROSS_PS3 = 96, // PS3 X button is pressed NKCODE_BUTTON_CROSS_PS3 = 96, // PS3 X button is pressed
NKCODE_BUTTON_CIRCLE = 1004, // Special custom keycode generated from 'O' button by our java code. Or 'O' button if Alt is pressed (TODO) NKCODE_BUTTON_CIRCLE = 1004, // Special custom keycode generated from 'O' button by our java code. Or 'O' button if Alt is pressed (TODO)
@ -259,9 +260,10 @@ typedef enum _keycode_t {
NKCODE_EXT_ROTATION_RIGHT = 1114, NKCODE_EXT_ROTATION_RIGHT = 1114,
NKCODE_MAX NKCODE_MAX
} keycode_t; };
enum AndroidJoystickAxis { // These mostly match Android axis IDs.
enum InputAxis {
// Field descriptor #15 I // Field descriptor #15 I
JOYSTICK_AXIS_X = 0, JOYSTICK_AXIS_X = 0,
JOYSTICK_AXIS_Y = 1, JOYSTICK_AXIS_Y = 1,

View file

@ -62,6 +62,9 @@ enum LOG_TYPE {
SCEMISC, SCEMISC,
NUMBER_OF_LOGS, // Must be last NUMBER_OF_LOGS, // Must be last
// Temporary aliases
ACHIEVEMENTS = HLE, // TODO: Make a real category
}; };
enum LOG_LEVELS : int { enum LOG_LEVELS : int {
@ -83,6 +86,7 @@ void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type,
; ;
bool GenericLogEnabled(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type); bool GenericLogEnabled(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type);
// Exception for Windows - enable more log levels in release mode than on other platforms.
#if defined(_DEBUG) || defined(_WIN32) #if defined(_DEBUG) || defined(_WIN32)
#define MAX_LOGLEVEL DEBUG_LEVEL #define MAX_LOGLEVEL DEBUG_LEVEL
@ -128,8 +132,8 @@ void SetExtraAssertInfo(const char *info);
#define __FILENAME__ __FILE__ #define __FILENAME__ __FILE__
#endif #endif
// If we're in "debug" assert mode // If we're a debug build, _dbg_assert_ is active. Not otherwise, even on Windows.
#if MAX_LOGLEVEL >= DEBUG_LEVEL #if defined(_DEBUG)
#define _dbg_assert_(_a_) \ #define _dbg_assert_(_a_) \
if (!(_a_)) {\ if (!(_a_)) {\

View file

@ -32,6 +32,7 @@ struct SimpleStat {
void Format(char *buffer, size_t sz); void Format(char *buffer, size_t sz);
private: private:
SimpleStat() {}
const char *name_; const char *name_;
// These are initialized in Reset(). // These are initialized in Reset().

View file

@ -1,3 +1,4 @@
#include "Common/StringUtils.h"
#include "expression_parser.h" #include "expression_parser.h"
#include <ctype.h> #include <ctype.h>
#include <cstring> #include <cstring>
@ -14,7 +15,7 @@ typedef enum {
typedef enum { EXCOMM_CONST, EXCOMM_CONST_FLOAT, EXCOMM_REF, EXCOMM_OP } ExpressionCommand; typedef enum { EXCOMM_CONST, EXCOMM_CONST_FLOAT, EXCOMM_REF, EXCOMM_OP } ExpressionCommand;
static char expressionError[512]; static std::string expressionError;
typedef struct { typedef struct {
char Name[4]; char Name[4];
@ -219,7 +220,7 @@ bool isAlphaNum(char c)
bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest) bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest)
{ {
expressionError[0] = 0; expressionError.clear();
int infixPos = 0; int infixPos = 0;
int infixLen = (int)strlen(infix); int infixLen = (int)strlen(infix);
@ -253,7 +254,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
isFloat = true; isFloat = true;
else if (parseNumber(subStr,16,subPos,value) == false) else if (parseNumber(subStr,16,subPos,value) == false)
{ {
snprintf(expressionError, sizeof(expressionError), "Invalid number \"%s\"",subStr); expressionError = StringFromFormat("Invalid number \"%s\"", subStr);
return false; return false;
} }
@ -282,14 +283,14 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
continue; continue;
} }
snprintf(expressionError, sizeof(expressionError), "Invalid symbol \"%s\"",subStr); expressionError = StringFromFormat("Invalid symbol \"%s\"", subStr);
return false; return false;
} else { } else {
int len; int len;
ExpressionOpcodeType type = getExpressionOpcode(&infix[infixPos],len,lastOpcode); ExpressionOpcodeType type = getExpressionOpcode(&infix[infixPos],len,lastOpcode);
if (type == EXOP_NONE) if (type == EXOP_NONE)
{ {
snprintf(expressionError, sizeof(expressionError), "Invalid operator at \"%s\"",&infix[infixPos]); expressionError = StringFromFormat("Invalid operator at \"%s\"", &infix[infixPos]);
return false; return false;
} }
@ -304,7 +305,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
{ {
if (opcodeStack.empty()) if (opcodeStack.empty())
{ {
snprintf(expressionError, sizeof(expressionError), "Closing parenthesis without opening one"); expressionError = "Closing parenthesis without opening one";
return false; return false;
} }
ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1]; ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1];
@ -318,7 +319,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
{ {
if (opcodeStack.empty()) if (opcodeStack.empty())
{ {
snprintf(expressionError, sizeof(expressionError), "Closing bracket without opening one"); expressionError = "Closing bracket without opening one";
return false; return false;
} }
ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1]; ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1];
@ -371,7 +372,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
if (t == EXOP_BRACKETL) // opening bracket without closing one if (t == EXOP_BRACKETL) // opening bracket without closing one
{ {
snprintf(expressionError, sizeof(expressionError), "Parenthesis not closed"); expressionError = "Parenthesis not closed";
return false; return false;
} }
dest.push_back(ExpressionPair(EXCOMM_OP,t)); dest.push_back(ExpressionPair(EXCOMM_OP,t));
@ -430,7 +431,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
opcode = exp[num++].second; opcode = exp[num++].second;
if (valueStack.size() < ExpressionOpcodes[opcode].args) if (valueStack.size() < ExpressionOpcodes[opcode].args)
{ {
snprintf(expressionError, sizeof(expressionError), "Not enough arguments"); expressionError = "Not enough arguments";
return false; return false;
} }
for (int l = 0; l < ExpressionOpcodes[opcode].args; l++) for (int l = 0; l < ExpressionOpcodes[opcode].args; l++)
@ -446,12 +447,12 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MEMSIZE: // must be followed by EXOP_MEM case EXOP_MEMSIZE: // must be followed by EXOP_MEM
if (exp[num++].second != EXOP_MEM) if (exp[num++].second != EXOP_MEM)
{ {
snprintf(expressionError, sizeof(expressionError), "Invalid memsize operator"); expressionError = "Invalid memsize operator";
return false; return false;
} }
uint32_t val; uint32_t val;
if(funcs->getMemoryValue(arg[1],arg[0],val,expressionError, sizeof(expressionError)) == false) if (funcs->getMemoryValue(arg[1], arg[0], val, &expressionError) == false)
{ {
return false; return false;
} }
@ -460,7 +461,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MEM: case EXOP_MEM:
{ {
uint32_t val; uint32_t val;
if (funcs->getMemoryValue(arg[0],4,val,expressionError, sizeof(expressionError)) == false) if (funcs->getMemoryValue(arg[0], 4, val, &expressionError) == false)
{ {
return false; return false;
} }
@ -490,7 +491,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_DIV: // a/b case EXOP_DIV: // a/b
if (arg[0] == 0) if (arg[0] == 0)
{ {
snprintf(expressionError, sizeof(expressionError), "Division by zero"); expressionError = "Division by zero";
return false; return false;
} }
if (useFloat) if (useFloat)
@ -501,7 +502,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MOD: // a%b case EXOP_MOD: // a%b
if (arg[0] == 0) if (arg[0] == 0)
{ {
snprintf(expressionError, sizeof(expressionError), "Modulo by zero"); expressionError = "Modulo by zero";
return false; return false;
} }
valueStack.push_back(arg[1]%arg[0]); valueStack.push_back(arg[1]%arg[0]);
@ -574,7 +575,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_TERTELSE: // exp ? exp : exp, else muss zuerst kommen! case EXOP_TERTELSE: // exp ? exp : exp, else muss zuerst kommen!
if (exp[num++].second != EXOP_TERTIF) if (exp[num++].second != EXOP_TERTIF)
{ {
snprintf(expressionError, sizeof(expressionError), "Invalid tertiary operator"); expressionError = "Invalid tertiary operator";
return false; return false;
} }
valueStack.push_back(arg[2]?arg[1]:arg[0]); valueStack.push_back(arg[2]?arg[1]:arg[0]);
@ -595,8 +596,9 @@ bool parseExpression(const char *exp, IExpressionFunctions *funcs, uint32_t &des
return parsePostfixExpression(postfix,funcs,dest); return parsePostfixExpression(postfix,funcs,dest);
} }
const char* getExpressionError() const char *getExpressionError()
{ {
if (expressionError[0] == 0) snprintf(expressionError, sizeof(expressionError), "Invalid expression"); if (expressionError.empty())
return expressionError; expressionError = "Invalid expression";
return expressionError.c_str();
} }

View file

@ -2,6 +2,7 @@
#include <cstdint> #include <cstdint>
#include <cstddef> #include <cstddef>
#include <string>
#include <vector> #include <vector>
typedef std::pair<uint32_t, uint32_t> ExpressionPair; typedef std::pair<uint32_t, uint32_t> ExpressionPair;
@ -21,7 +22,7 @@ public:
virtual bool parseSymbol(char* str, uint32_t& symbolValue) = 0; virtual bool parseSymbol(char* str, uint32_t& symbolValue) = 0;
virtual uint32_t getReferenceValue(uint32_t referenceIndex) = 0; virtual uint32_t getReferenceValue(uint32_t referenceIndex) = 0;
virtual ExpressionType getReferenceType(uint32_t referenceIndex) = 0; virtual ExpressionType getReferenceType(uint32_t referenceIndex) = 0;
virtual bool getMemoryValue(uint32_t address, int size, uint32_t& dest, char *error, size_t errorBufSize) = 0; virtual bool getMemoryValue(uint32_t address, int size, uint32_t& dest, std::string *error) = 0;
}; };
bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest); bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest);

View file

@ -69,9 +69,22 @@ struct Bounds {
Bounds Expand(float xAmount, float yAmount) const { Bounds Expand(float xAmount, float yAmount) const {
return Bounds(x - xAmount, y - yAmount, w + xAmount * 2, h + yAmount * 2); return Bounds(x - xAmount, y - yAmount, w + xAmount * 2, h + yAmount * 2);
} }
Bounds Expand(float left, float top, float right, float bottom) const {
return Bounds(x - left, y - top, w + left + right, h + top + bottom);
}
Bounds Offset(float xAmount, float yAmount) const { Bounds Offset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w, h); return Bounds(x + xAmount, y + yAmount, w, h);
} }
Bounds Inset(float left, float top, float right, float bottom) {
return Bounds(x + left, y + top, w - left - right, h - bottom - top);
}
Bounds Inset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w - xAmount * 2, h - yAmount * 2);
}
Bounds Inset(float left, float top, float right, float bottom) const {
return Bounds(x + left, y + top, w - left - right, h - top - bottom);
}
float x; float x;
float y; float y;

View file

@ -67,6 +67,13 @@ inline T clamp_value(T val, T floor, T cap) {
return val; return val;
} }
// Very common operation, familiar from shaders.
inline float saturatef(float x) {
if (x > 1.0f) return 1.0f;
else if (x < 0.0f) return 0.0f;
else return x;
}
#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1)) #define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
#define ROUND_DOWN(x, a) ((x) & ~((a) - 1)) #define ROUND_DOWN(x, a) ((x) & ~((a) - 1))

View file

@ -39,7 +39,7 @@ public:
bool GrabMemSpace(size_t size); bool GrabMemSpace(size_t size);
void ReleaseSpace(); void ReleaseSpace();
void *CreateView(s64 offset, size_t size, void *base = 0); void *CreateView(s64 offset, size_t size, void *base = 0);
void ReleaseView(void *view, size_t size); void ReleaseView(s64 offset, void *view, size_t size);
// This only finds 1 GB in 32-bit // This only finds 1 GB in 32-bit
u8 *Find4GBBase(); u8 *Find4GBBase();

View file

@ -129,7 +129,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
return retval; return retval;
} }
void MemArena::ReleaseView(void* view, size_t size) { void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
munmap(view, size); munmap(view, size);
} }

View file

@ -79,7 +79,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
return (void *)target; return (void *)target;
} }
void MemArena::ReleaseView(void* view, size_t size) { void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
vm_address_t addr = (vm_address_t)view; vm_address_t addr = (vm_address_t)view;
vm_deallocate(mach_task_self(), addr, size); vm_deallocate(mach_task_self(), addr, size);
} }

View file

@ -0,0 +1,83 @@
// Copyright (C) 2023 M4xw
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <stdio.h>
#include <malloc.h> // memalign
#include <switch.h>
#include "Common/MemArena.h"
static uintptr_t memoryBase = 0;
static uintptr_t memoryCodeBase = 0;
static uintptr_t memorySrcBase = 0;
size_t MemArena::roundup(size_t x) {
return x;
}
bool MemArena::NeedsProbing() {
return false;
}
bool MemArena::GrabMemSpace(size_t size) {
return true;
}
void MemArena::ReleaseSpace() {
if (R_FAILED(svcUnmapProcessCodeMemory(envGetOwnProcessHandle(), (u64)memoryCodeBase, (u64)memorySrcBase, 0x10000000)))
printf("Failed to release view space...\n");
free((void *)memorySrcBase);
memorySrcBase = 0;
}
void *MemArena::CreateView(s64 offset, size_t size, void *base) {
Result rc = svcMapProcessMemory(base, envGetOwnProcessHandle(), (u64)(memoryCodeBase + offset), size);
if (R_FAILED(rc)) {
printf("Fatal error creating the view... base: %p offset: %p size: %p src: %p err: %d\n",
(void *)base, (void *)offset, (void *)size, (void *)(memoryCodeBase + offset), rc);
} else {
printf("Created the view... base: %p offset: %p size: %p src: %p err: %d\n",
(void *)base, (void *)offset, (void *)size, (void *)(memoryCodeBase + offset), rc);
}
return base;
}
void MemArena::ReleaseView(s64 offset, void *view, size_t size) {
if (R_FAILED(svcUnmapProcessMemory(view, envGetOwnProcessHandle(), (u64)(memoryCodeBase + offset), size)))
printf("Failed to unmap view...\n");
}
u8 *MemArena::Find4GBBase() {
memorySrcBase = (uintptr_t)memalign(0x1000, 0x10000000);
if (!memoryBase)
memoryBase = (uintptr_t)virtmemReserve(0x10000000);
if (!memoryCodeBase)
memoryCodeBase = (uintptr_t)virtmemReserve(0x10000000);
if (R_FAILED(svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)memoryCodeBase, (u64)memorySrcBase, 0x10000000)))
printf("Failed to map memory...\n");
if (R_FAILED(svcSetProcessMemoryPermission(envGetOwnProcessHandle(), memoryCodeBase, 0x10000000, Perm_Rx)))
printf("Failed to set perms...\n");
return (u8 *)memoryBase;
}
#endif // PPSSPP_PLATFORM(SWITCH)

View file

@ -17,7 +17,7 @@
#include "ppsspp_config.h" #include "ppsspp_config.h"
#if !defined(_WIN32) && !defined(ANDROID) && !defined(__APPLE__) #if !defined(_WIN32) && !defined(ANDROID) && !defined(__APPLE__) && !PPSSPP_PLATFORM(SWITCH)
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -121,7 +121,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
return retval; return retval;
} }
void MemArena::ReleaseView(void* view, size_t size) { void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
munmap(view, size); munmap(view, size);
} }

View file

@ -54,7 +54,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *viewbase) {
return ptr; return ptr;
} }
void MemArena::ReleaseView(void* view, size_t size) { void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
#if PPSSPP_PLATFORM(UWP) #if PPSSPP_PLATFORM(UWP)
#else #else
UnmapViewOfFile(view); UnmapViewOfFile(view);

View file

@ -17,6 +17,7 @@
#include "ppsspp_config.h" #include "ppsspp_config.h"
#if !PPSSPP_PLATFORM(SWITCH)
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
@ -257,7 +258,7 @@ void *AllocateAlignedMemory(size_t size, size_t alignment) {
#endif #endif
#endif #endif
_assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %llu", size); _assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %llu", (unsigned long long)size);
return ptr; return ptr;
} }
@ -355,3 +356,4 @@ int GetMemoryProtectPageSize() {
#endif #endif
return MEM_PAGE_SIZE; return MEM_PAGE_SIZE;
} }
#endif // !PPSSPP_PLATFORM(SWITCH)

View file

@ -18,7 +18,11 @@
#pragma once #pragma once
#ifndef _WIN32 #ifndef _WIN32
#ifndef __SWITCH__
#include <sys/mman.h> #include <sys/mman.h>
#else
#include <switch.h>
#endif // !__SWITCH__
#endif #endif
#include <stdint.h> #include <stdint.h>

View file

@ -0,0 +1,89 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <cstring>
#include <cstdlib>
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/MemoryUtil.h"
#include "Common/StringUtils.h"
#include "Common/SysError.h"
#include <errno.h>
#include <stdio.h>
#include <malloc.h> // memalign
#define MEM_PAGE_SIZE (0x1000)
#define MEM_PAGE_MASK ((MEM_PAGE_SIZE)-1)
#define ppsspp_round_page(x) ((((uintptr_t)(x)) + MEM_PAGE_MASK) & ~(MEM_PAGE_MASK))
// On Switch we dont support allocating executable memory here
// See CodeBlock.h
void *AllocateExecutableMemory(size_t size) {
return nullptr;
}
void *AllocateMemoryPages(size_t size, uint32_t memProtFlags) {
void* ptr = nullptr;
size = ppsspp_round_page(size);
ptr = memalign(MEM_PAGE_SIZE, size);
return ptr;
}
void *AllocateAlignedMemory(size_t size, size_t alignment) {
void* ptr = memalign(alignment, size);
_assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %lu", size);
return ptr;
}
void FreeMemoryPages(void *ptr, size_t size) {
if (!ptr)
return;
free(ptr);
return;
}
void FreeExecutableMemory(void *ptr, size_t size) {
return; // Not supported on Switch
}
void FreeAlignedMemory(void* ptr) {
if (!ptr)
return;
free(ptr);
}
bool PlatformIsWXExclusive() {
return false; // Switch technically is W^X but we use dual mappings instead of reprotecting the pages to allow a W and X mapping
}
bool ProtectMemoryPages(const void* ptr, size_t size, uint32_t memProtFlags) {
return true;
}
int GetMemoryProtectPageSize() {
return MEM_PAGE_SIZE;
}
#endif // PPSSPP_PLATFORM(SWITCH)

View file

@ -180,7 +180,7 @@ void Connection::Disconnect() {
namespace http { namespace http {
// TODO: do something sane here // TODO: do something sane here
constexpr const char *DEFAULT_USERAGENT = "NATIVEAPP 1.0"; constexpr const char *DEFAULT_USERAGENT = "PPSSPP";
Client::Client() { Client::Client() {
httpVersion_ = "1.1"; httpVersion_ = "1.1";
@ -458,8 +458,8 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
return 0; return 0;
} }
Download::Download(const std::string &url, const Path &outfile) Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile)
: progress_(&cancelled_), url_(url), outfile_(outfile) { : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) {
} }
Download::~Download() { Download::~Download() {
@ -484,13 +484,17 @@ void Download::SetFailed(int code) {
completed_ = true; completed_ = true;
} }
int Download::PerformGET(const std::string &url) { int Download::Perform(const std::string &url) {
Url fileUrl(url); Url fileUrl(url);
if (!fileUrl.Valid()) { if (!fileUrl.Valid()) {
return -1; return -1;
} }
http::Client client; http::Client client;
if (!userAgent_.empty()) {
client.SetUserAgent(userAgent_);
}
if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) { if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) {
ERROR_LOG(IO, "Failed resolving %s", url.c_str()); ERROR_LOG(IO, "Failed resolving %s", url.c_str());
return -1; return -1;
@ -510,7 +514,11 @@ int Download::PerformGET(const std::string &url) {
} }
RequestParams req(fileUrl.Resource(), acceptMime_); RequestParams req(fileUrl.Resource(), acceptMime_);
if (method_ == RequestMethod::GET) {
return client.GET(req, &buffer_, responseHeaders_, &progress_); return client.GET(req, &buffer_, responseHeaders_, &progress_);
} else {
return client.POST(req, postData_, postMime_, &buffer_, &progress_);
}
} }
std::string Download::RedirectLocation(const std::string &baseUrl) { std::string Download::RedirectLocation(const std::string &baseUrl) {
@ -533,7 +541,7 @@ void Download::Do() {
std::string downloadURL = url_; std::string downloadURL = url_;
while (resultCode_ == 0) { while (resultCode_ == 0) {
int resultCode = PerformGET(downloadURL); int resultCode = Perform(downloadURL);
if (resultCode == -1) { if (resultCode == -1) {
SetFailed(resultCode); SetFailed(resultCode);
return; return;
@ -557,12 +565,12 @@ void Download::Do() {
} }
if (resultCode == 200) { if (resultCode == 200) {
INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str()); INFO_LOG(IO, "Completed requesting %s (storing result to %s)", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str());
if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) {
ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str());
} }
} else { } else {
ERROR_LOG(IO, "Error downloading '%s' to '%s': %i", url_.c_str(), outfile_.c_str(), resultCode); ERROR_LOG(IO, "Error requesting '%s' (storing result to '%s'): %i", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str(), resultCode);
} }
resultCode_ = resultCode; resultCode_ = resultCode;
} }
@ -575,10 +583,12 @@ void Download::Do() {
} }
std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) { std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(url, outfile)); std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
if (acceptMime) if (acceptMime)
dl->SetAccept(acceptMime); dl->SetAccept(acceptMime);
downloads_.push_back(dl); newDownloads_.push_back(dl);
dl->Start(); dl->Start();
return dl; return dl;
} }
@ -588,19 +598,40 @@ std::shared_ptr<Download> Downloader::StartDownloadWithCallback(
const Path &outfile, const Path &outfile,
std::function<void(Download &)> callback, std::function<void(Download &)> callback,
const char *acceptMime) { const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(url, outfile)); std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
if (acceptMime) if (acceptMime)
dl->SetAccept(acceptMime); dl->SetAccept(acceptMime);
dl->SetCallback(callback); dl->SetCallback(callback);
downloads_.push_back(dl); newDownloads_.push_back(dl);
dl->Start();
return dl;
}
std::shared_ptr<Download> Downloader::AsyncPostWithCallback(
const std::string &url,
const std::string &postData,
const std::string &postMime,
std::function<void(Download &)> callback) {
std::shared_ptr<Download> dl(new Download(RequestMethod::POST, url, postData, postMime, Path()));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
dl->SetCallback(callback);
newDownloads_.push_back(dl);
dl->Start(); dl->Start();
return dl; return dl;
} }
void Downloader::Update() { void Downloader::Update() {
for (auto iter : newDownloads_) {
downloads_.push_back(iter);
}
newDownloads_.clear();
restart: restart:
for (size_t i = 0; i < downloads_.size(); i++) { for (size_t i = 0; i < downloads_.size(); i++) {
auto &dl = downloads_[i]; auto dl = downloads_[i];
if (dl->Done()) { if (dl->Done()) {
dl->RunCallback(); dl->RunCallback();
dl->Join(); dl->Join();
@ -610,6 +641,14 @@ void Downloader::Update() {
} }
} }
void Downloader::WaitForAll() {
// TODO: Should lock? Though, OK if called from main thread, where Update() is called from.
while (!downloads_.empty()) {
Update();
sleep_ms(10);
}
}
std::vector<float> Downloader::GetCurrentProgress() { std::vector<float> Downloader::GetCurrentProgress() {
std::vector<float> progress; std::vector<float> progress;
for (size_t i = 0; i < downloads_.size(); i++) { for (size_t i = 0; i < downloads_.size(); i++) {

View file

@ -97,10 +97,15 @@ protected:
double dataTimeout_ = 900.0; double dataTimeout_ = 900.0;
}; };
// Not particularly efficient, but hey - it's a background download, that's pretty cool :P enum class RequestMethod {
GET,
POST,
};
// Really an asynchronous request.
class Download { class Download {
public: public:
Download(const std::string &url, const Path &outfile); Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile);
~Download(); ~Download();
void Start(); void Start();
@ -151,20 +156,27 @@ public:
// Downloader::GetCurrentProgress won't return it in the results. // Downloader::GetCurrentProgress won't return it in the results.
bool IsHidden() const { return hidden_; } bool IsHidden() const { return hidden_; }
void SetHidden(bool hidden) { hidden_ = hidden; } void SetHidden(bool hidden) { hidden_ = hidden; }
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
private: private:
void Do(); // Actually does the download. Runs on thread. void Do(); // Actually does the download. Runs on thread.
int PerformGET(const std::string &url); int Perform(const std::string &url);
std::string RedirectLocation(const std::string &baseUrl); std::string RedirectLocation(const std::string &baseUrl);
void SetFailed(int code); void SetFailed(int code);
RequestProgress progress_; RequestProgress progress_;
RequestMethod method_;
std::string postData_;
std::string userAgent_;
Buffer buffer_; Buffer buffer_;
std::vector<std::string> responseHeaders_; std::vector<std::string> responseHeaders_;
std::string url_; std::string url_;
Path outfile_; Path outfile_;
std::thread thread_; std::thread thread_;
const char *acceptMime_ = "*/*"; const char *acceptMime_ = "*/*";
std::string postMime_;
int resultCode_ = 0; int resultCode_ = 0;
bool completed_ = false; bool completed_ = false;
bool failed_ = false; bool failed_ = false;
@ -190,14 +202,34 @@ public:
std::function<void(Download &)> callback, std::function<void(Download &)> callback,
const char *acceptMime = nullptr); const char *acceptMime = nullptr);
std::shared_ptr<Download> AsyncPostWithCallback(
const std::string &url,
const std::string &postData,
const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder.
std::function<void(Download &)> callback);
// Drops finished downloads from the list. // Drops finished downloads from the list.
void Update(); void Update();
void CancelAll(); void CancelAll();
void WaitForAll();
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
std::vector<float> GetCurrentProgress(); std::vector<float> GetCurrentProgress();
size_t GetActiveCount() const {
return downloads_.size();
}
private: private:
std::vector<std::shared_ptr<Download>> downloads_; std::vector<std::shared_ptr<Download>> downloads_;
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads
// while running.
std::vector<std::shared_ptr<Download>> newDownloads_;
std::string userAgent_;
}; };
} // http } // http

View file

@ -47,7 +47,7 @@ bool TextDrawerAndroid::IsReady() const {
uint32_t TextDrawerAndroid::SetFont(const char *fontName, int size, int flags) { uint32_t TextDrawerAndroid::SetFont(const char *fontName, int size, int flags) {
// We will only use the default font but just for consistency let's still involve // We will only use the default font but just for consistency let's still involve
// the font name. // the font name.
uint32_t fontHash = hash::Adler32((const uint8_t *)fontName, strlen(fontName)); uint32_t fontHash = fontName ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 1337;
fontHash ^= size; fontHash ^= size;
fontHash ^= flags << 10; fontHash ^= flags << 10;

View file

@ -134,13 +134,25 @@ void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float
SelectObject(ctx_->hDC, iter->second->hFont); SelectObject(ctx_->hDC, iter->second->hFont);
} }
std::string toMeasure = ReplaceAll(std::string(str, len), "&&", "&");
std::vector<std::string> lines;
SplitString(toMeasure, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
SIZE size; SIZE size;
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(ReplaceAll(std::string(str, len), "\n", "\r\n"), "&&", "&")); std::wstring wstr = ConvertUTF8ToWString(line);
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size); GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
if (size.cx > extW)
extW = size.cx;
extH += size.cy;
}
entry = new TextMeasureEntry(); entry = new TextMeasureEntry();
entry->width = size.cx; entry->width = extW;
entry->height = size.cy; entry->height = extH;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry); sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
} }

View file

@ -209,8 +209,8 @@ void CPUInfo::Detect()
RiscV_C = ExtensionSupported(hwcap, 'C'); RiscV_C = ExtensionSupported(hwcap, 'C');
RiscV_V = ExtensionSupported(hwcap, 'V'); RiscV_V = ExtensionSupported(hwcap, 'V');
RiscV_B = ExtensionSupported(hwcap, 'B'); RiscV_B = ExtensionSupported(hwcap, 'B');
// Let's assume for now... // We assume as in RVA20U64 that F means Zicsr is available.
RiscV_Zicsr = RiscV_M && RiscV_A && RiscV_F && RiscV_D; RiscV_Zicsr = RiscV_F;
#ifdef USE_CPU_FEATURES #ifdef USE_CPU_FEATURES
cpu_features::RiscvInfo info = cpu_features::GetRiscvInfo(); cpu_features::RiscvInfo info = cpu_features::GetRiscvInfo();
@ -220,6 +220,7 @@ void CPUInfo::Detect()
RiscV_F = info.features.F; RiscV_F = info.features.F;
RiscV_D = info.features.D; RiscV_D = info.features.D;
RiscV_C = info.features.C; RiscV_C = info.features.C;
RiscV_V = info.features.V;
RiscV_Zicsr = info.features.Zicsr; RiscV_Zicsr = info.features.Zicsr;
truncate_cpy(brand_string, info.uarch); truncate_cpy(brand_string, info.uarch);

View file

@ -302,6 +302,12 @@ public:
void NEG(RiscVReg rd, RiscVReg rs) { void NEG(RiscVReg rd, RiscVReg rs) {
SUB(rd, R_ZERO, rs); SUB(rd, R_ZERO, rs);
} }
void SNEZ(RiscVReg rd, RiscVReg rs) {
SLTU(rd, R_ZERO, rs);
}
void SEQZ(RiscVReg rd, RiscVReg rs) {
SLTIU(rd, rs, 1);
}
void FENCE(Fence predecessor, Fence successor); void FENCE(Fence predecessor, Fence successor);
void FENCE_TSO(); void FENCE_TSO();
@ -896,6 +902,9 @@ public:
void MINU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); void MINU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2);
void SEXT_B(RiscVReg rd, RiscVReg rs); void SEXT_B(RiscVReg rd, RiscVReg rs);
void SEXT_H(RiscVReg rd, RiscVReg rs); void SEXT_H(RiscVReg rd, RiscVReg rs);
void SEXT_W(RiscVReg rd, RiscVReg rs) {
ADDIW(rd, rs, 0);
}
void ZEXT_H(RiscVReg rd, RiscVReg rs); void ZEXT_H(RiscVReg rd, RiscVReg rs);
void ZEXT_W(RiscVReg rd, RiscVReg rs) { void ZEXT_W(RiscVReg rd, RiscVReg rs) {
ADD_UW(rd, rs, R_ZERO); ADD_UW(rd, rs, R_ZERO);

View file

@ -147,6 +147,11 @@ public:
void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42); void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42);
void SkipBytes(size_t bytes) {
// Should work in all modes.
*ptr += bytes;
}
size_t Offset() const { return *ptr - ptrStart_; } size_t Offset() const { return *ptr - ptrStart_; }
private: private:

View file

@ -368,3 +368,31 @@ std::string UnescapeMenuString(const char *input, char *shortcutChar) {
} }
return output; return output;
} }
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2, const std::string &string3) {
size_t formatLen = strlen(format);
std::string output;
output.reserve(formatLen + 20);
for (size_t i = 0; i < formatLen; i++) {
char c = format[i];
if (c != '%') {
output.push_back(c);
continue;
}
if (i >= formatLen - 1) {
break;
}
switch (format[i + 1]) {
case '1':
output += string1; i++;
break;
case '2':
output += string2; i++;
break;
case '3':
output += string3; i++;
break;
}
}
return output;
}

View file

@ -111,3 +111,7 @@ inline void CharArrayFromFormat(char (& out)[Count], const char* format, ...)
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, std::string* _pExtension); bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, std::string* _pExtension);
// Replaces %1, %2, %3 in format with arg1, arg2, arg3.
// Much safer than snprintf and friends.
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2 = "", const std::string &string3 = "");

View file

@ -20,9 +20,6 @@ class GraphicsContext;
// This might get called multiple times in some implementations, you must be able to handle that. // This might get called multiple times in some implementations, you must be able to handle that.
void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version); void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version);
// Generic host->C++ messaging, used for functionality like system-native popup input boxes.
void NativeMessageReceived(const char *message, const char *value);
// Easy way for the Java side to ask the C++ side for configuration options, such as // Easy way for the Java side to ask the C++ side for configuration options, such as
// the rotation lock which must be controlled from Java on Android. // the rotation lock which must be controlled from Java on Android.
// It is currently not called on non-Android platforms. // It is currently not called on non-Android platforms.
@ -87,3 +84,6 @@ void NativeShutdownGraphics();
void NativeShutdown(); void NativeShutdown();
void PostLoadConfig(); void PostLoadConfig();
void NativeSaveSecret(const char *nameOfSecret, const std::string &data);
std::string NativeLoadSecret(const char *nameOfSecret);

281
Common/System/OSD.cpp Normal file
View file

@ -0,0 +1,281 @@
#include <cstring>
#include "Common/System/OSD.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
#include "Common/Math/math_util.h"
OnScreenDisplay g_OSD;
// Effectively forever.
constexpr double forever_s = 10000000000.0;
void OnScreenDisplay::Update() {
std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d();
for (auto iter = entries_.begin(); iter != entries_.end(); ) {
if (now >= iter->endTime) {
iter = entries_.erase(iter);
} else {
iter++;
}
}
for (auto iter = sideEntries_.begin(); iter != sideEntries_.end(); ) {
if (now >= iter->endTime) {
iter = sideEntries_.erase(iter);
} else {
iter++;
}
}
for (auto iter = bars_.begin(); iter != bars_.end(); ) {
if (now >= iter->endTime) {
iter = bars_.erase(iter);
} else {
iter++;
}
}
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
std::lock_guard<std::mutex> guard(mutex_);
return entries_; // makes a copy.
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::SideEntries() {
std::lock_guard<std::mutex> guard(mutex_);
return sideEntries_; // makes a copy.
}
std::vector<OnScreenDisplay::ProgressBar> OnScreenDisplay::ProgressBars() {
std::lock_guard<std::mutex> guard(mutex_);
return bars_; // makes a copy.
}
void OnScreenDisplay::NudgeSidebar() {
sideBarShowTime_ = time_now_d();
}
float OnScreenDisplay::SidebarAlpha() const {
double timeSinceNudge = time_now_d() - sideBarShowTime_;
// Fade out in 1/4 second, 0.1s after the last nudge.
return saturatef(1.0f - ((float)timeSinceNudge - 0.1f) * 4.0f);
}
void OnScreenDisplay::Show(OSDType type, const std::string &text, const std::string &text2, const std::string &icon, float duration_s, const char *id) {
// Automatic duration based on type.
if (duration_s <= 0.0f) {
switch (type) {
case OSDType::MESSAGE_ERROR:
case OSDType::MESSAGE_WARNING:
duration_s = 4.0f;
break;
case OSDType::MESSAGE_FILE_LINK:
duration_s = 5.0f;
break;
case OSDType::MESSAGE_SUCCESS:
duration_s = 2.0f;
break;
default:
duration_s = 1.5f;
break;
}
}
double now = time_now_d();
std::lock_guard<std::mutex> guard(mutex_);
if (id) {
for (auto iter = entries_.begin(); iter != entries_.end(); ++iter) {
if (iter->id && !strcmp(iter->id, id)) {
Entry msg = *iter;
msg.endTime = now + duration_s;
msg.text = text;
msg.text2 = text2;
msg.type = type;
msg.iconName = icon;
// Move to top (should we? maybe not?)
entries_.erase(iter);
entries_.insert(entries_.begin(), msg);
return;
}
}
}
Entry msg;
msg.text = text;
msg.text2 = text2;
msg.iconName = icon;
msg.startTime = now;
msg.endTime = now + duration_s;
msg.type = type;
msg.id = id;
entries_.insert(entries_.begin(), msg);
}
void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
double now = time_now_d();
double duration_s = 5.0;
Entry msg;
msg.numericID = achievementID;
msg.type = OSDType::ACHIEVEMENT_UNLOCKED;
msg.startTime = now;
msg.endTime = now + duration_s;
entries_.insert(entries_.begin(), msg);
}
void OnScreenDisplay::ShowAchievementProgress(int achievementID, float duration_s) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
// Duplicate, let's just bump the timer.
entry.startTime = now;
entry.endTime = now + (double)duration_s;
// We're done.
return;
}
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
entry.startTime = now;
entry.endTime = now + (double)duration_s;
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowChallengeIndicator(int achievementID, bool show) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR && !show) {
// Hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR;
entry.startTime = now;
entry.endTime = now + forever_s;
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show) { // show=true is used both for create and update.
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == leaderboardTrackerID && entry.type == OSDType::LEADERBOARD_TRACKER) {
if (show) {
// Just an update.
entry.text = trackerText ? trackerText : "";
} else {
// Keep the current text, hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
}
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = leaderboardTrackerID;
entry.type = OSDType::LEADERBOARD_TRACKER;
entry.startTime = now;
entry.endTime = now + forever_s;
if (trackerText) {
entry.text = trackerText;
}
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float duration_s) {
// TODO: translate "on" and "off"? Or just get rid of this whole thing?
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
}
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) {
std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d();
bool found = false;
for (auto &bar : bars_) {
if (bar.id == id) {
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.message = message;
bar.endTime = now + 60.0; // Nudge the progress bar to keep it shown.
return;
}
}
ProgressBar bar;
bar.id = id;
bar.message = std::move(message);
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
bars_.push_back(bar);
}
void OnScreenDisplay::RemoveProgressBar(std::string id) {
std::lock_guard<std::mutex> guard(mutex_);
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
if (iter->id == id) {
iter->progress = iter->maxValue;
iter->endTime = time_now_d() + FadeoutTime();
break;
}
}
}
// Fades out everything related to achievements. Should be used on game shutdown.
void OnScreenDisplay::ClearAchievementStuff() {
double now = time_now_d();
for (auto &iter : entries_) {
switch (iter.type) {
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
case OSDType::ACHIEVEMENT_UNLOCKED:
case OSDType::ACHIEVEMENT_PROGRESS:
case OSDType::LEADERBOARD_TRACKER:
iter.endTime = now;
break;
default:
break;
}
}
for (auto &iter : sideEntries_) {
switch (iter.type) {
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
case OSDType::ACHIEVEMENT_UNLOCKED:
case OSDType::ACHIEVEMENT_PROGRESS:
case OSDType::LEADERBOARD_TRACKER:
iter.endTime = now;
break;
default:
break;
}
}
}

98
Common/System/OSD.h Normal file
View file

@ -0,0 +1,98 @@
#pragma once
#include <string>
#include <vector>
#include <mutex>
// Shows a visible message to the user.
// The default implementation in NativeApp.cpp uses our "osm" system (on screen messaging).
enum class OSDType {
MESSAGE_INFO,
MESSAGE_SUCCESS,
MESSAGE_WARNING,
MESSAGE_ERROR,
MESSAGE_ERROR_DUMP, // displays lots of text (after the first line), small size
MESSAGE_FILE_LINK,
ACHIEVEMENT_UNLOCKED,
// Side entries
ACHIEVEMENT_PROGRESS, // Achievement icon + "measured_progress" text, auto-hide after 2s
ACHIEVEMENT_CHALLENGE_INDICATOR, // Achievement icon ONLY, no auto-hide
LEADERBOARD_TRACKER,
};
// Data holder for on-screen messages.
class OnScreenDisplay {
public:
// If you specify 0.0f as duration, a duration will be chosen automatically depending on type.
void Show(OSDType type, const std::string &text, float duration_s = 0.0f, const char *id = nullptr) {
Show(type, text, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, float duration_s = 0.0f, const char *id = nullptr) {
Show(type, text, text2, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, const std::string &icon, float duration_s = 0.0f, const char *id = nullptr);
void ShowOnOff(const std::string &message, bool on, float duration_s = 0.0f);
bool IsEmpty() const { return entries_.empty(); } // Shortcut to skip rendering.
// Call this every frame, cleans up old entries.
void Update();
// Specialized achievement-related types. These go to the side notifications, not the top-middle.
void ShowAchievementUnlocked(int achievementID);
void ShowAchievementProgress(int achievementID, float duration_s);
void ShowChallengeIndicator(int achievementID, bool show); // call with show=false to hide.
void ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show); // show=true is used both for create and update.
// Progress bar controls
// Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress
// bar that doesn't show a specific amount of progress.
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress);
void RemoveProgressBar(std::string id);
// Call every frame to keep the sidebar visible. Otherwise it'll fade out.
void NudgeSidebar();
float SidebarAlpha() const;
// Fades out everything related to achievements. Should be used on game shutdown.
void ClearAchievementStuff();
struct Entry {
OSDType type;
std::string text;
std::string text2;
std::string iconName;
int numericID;
const char *id;
double startTime;
double endTime;
};
struct ProgressBar {
std::string id;
std::string message;
int minValue;
int maxValue;
int progress;
double endTime;
};
std::vector<Entry> Entries();
std::vector<Entry> SideEntries();
std::vector<ProgressBar> ProgressBars();
static float FadeoutTime() { return 0.25f; }
private:
std::vector<Entry> entries_;
std::vector<Entry> sideEntries_;
std::vector<ProgressBar> bars_;
std::mutex mutex_;
double sideBarShowTime_ = 0.0;
};
extern OnScreenDisplay g_OSD;

View file

@ -1,7 +1,10 @@
#include <cstring>
#include "Common/System/Request.h" #include "Common/System/Request.h"
#include "Common/System/System.h" #include "Common/System/System.h"
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/File/Path.h" #include "Common/File/Path.h"
#include "Common/TimeUtil.h"
RequestManager g_requestManager; RequestManager g_requestManager;

View file

@ -61,7 +61,8 @@ private:
RequestFailedCallback callback; RequestFailedCallback callback;
}; };
int idCounter_ = 0; // Let's start at 10 to get a recognizably valid ID in logs.
int idCounter_ = 10;
std::vector<PendingSuccess> pendingSuccesses_; std::vector<PendingSuccess> pendingSuccesses_;
std::vector<PendingFailure> pendingFailures_; std::vector<PendingFailure> pendingFailures_;
std::mutex responseMutex_; std::mutex responseMutex_;
@ -72,12 +73,13 @@ const char *RequestTypeAsString(SystemRequestType type);
extern RequestManager g_requestManager; extern RequestManager g_requestManager;
// Wrappers for easy requests. // Wrappers for easy requests.
// NOTE: Semantics have changed - this no longer calls the callback on cancellation. // NOTE: Semantics have changed - this no longer calls the callback on cancellation, instead you
// can specify a different callback for that.
inline void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) { inline void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::INPUT_TEXT_MODAL, callback, failedCallback, title, defaultValue, 0); g_requestManager.MakeSystemRequest(SystemRequestType::INPUT_TEXT_MODAL, callback, failedCallback, title, defaultValue, 0);
} }
// This one will pop up a special image brwoser if available. You can also pick // This one will pop up a special image browser if available. You can also pick
// images with the file browser below. // images with the file browser below.
inline void System_BrowseForImage(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) { inline void System_BrowseForImage(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, callback, failedCallback, title, "", 0); g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, callback, failedCallback, title, "", 0);
@ -88,6 +90,7 @@ enum class BrowseFileType {
IMAGE, IMAGE,
INI, INI,
DB, DB,
SOUND_EFFECT,
ANY, ANY,
}; };
@ -99,6 +102,11 @@ inline void System_BrowseForFolder(const std::string &title, RequestCallback cal
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_FOLDER, callback, failedCallback, title, "", 0); g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_FOLDER, callback, failedCallback, title, "", 0);
} }
// The returned string is username + '\n' + password.
inline void System_AskUsernamePassword(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::ASK_USERNAME_PASSWORD, callback, failedCallback, title, "", 0);
}
inline void System_CopyStringToClipboard(const std::string &string) { inline void System_CopyStringToClipboard(const std::string &string) {
g_requestManager.MakeSystemRequest(SystemRequestType::COPY_TO_CLIPBOARD, nullptr, nullptr, string, "", 0); g_requestManager.MakeSystemRequest(SystemRequestType::COPY_TO_CLIPBOARD, nullptr, nullptr, string, "", 0);
} }
@ -111,6 +119,10 @@ inline void System_RestartApp(const std::string &params) {
g_requestManager.MakeSystemRequest(SystemRequestType::RESTART_APP, nullptr, nullptr, params, "", 0); g_requestManager.MakeSystemRequest(SystemRequestType::RESTART_APP, nullptr, nullptr, params, "", 0);
} }
inline void System_RecreateActivity() {
g_requestManager.MakeSystemRequest(SystemRequestType::RECREATE_ACTIVITY, nullptr, nullptr, "", "", 0);
}
// The design is a little weird, just a holdover from the old message. Can either toggle or set to on or off. // The design is a little weird, just a holdover from the old message. Can either toggle or set to on or off.
inline void System_ToggleFullscreenState(const std::string &param) { inline void System_ToggleFullscreenState(const std::string &param) {
g_requestManager.MakeSystemRequest(SystemRequestType::TOGGLE_FULLSCREEN_STATE, nullptr, nullptr, param, "", 0); g_requestManager.MakeSystemRequest(SystemRequestType::TOGGLE_FULLSCREEN_STATE, nullptr, nullptr, param, "", 0);

View file

@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <functional> #include <functional>
#include <cstdint> #include <cstdint>
#include <mutex>
// Platform integration // Platform integration
@ -57,12 +58,14 @@ void System_LaunchUrl(LaunchUrlType urlType, const char *url);
enum class SystemRequestType { enum class SystemRequestType {
INPUT_TEXT_MODAL, INPUT_TEXT_MODAL,
ASK_USERNAME_PASSWORD,
BROWSE_FOR_IMAGE, BROWSE_FOR_IMAGE,
BROWSE_FOR_FILE, BROWSE_FOR_FILE,
BROWSE_FOR_FOLDER, BROWSE_FOR_FOLDER,
EXIT_APP, EXIT_APP,
RESTART_APP, // For graphics backend changes RESTART_APP, // For graphics backend changes
RECREATE_ACTIVITY, // Android
COPY_TO_CLIPBOARD, COPY_TO_CLIPBOARD,
SHARE_TEXT, SHARE_TEXT,
SET_WINDOW_TITLE, SET_WINDOW_TITLE,
@ -127,6 +130,8 @@ enum SystemProperty {
SYSPROP_HAS_BACK_BUTTON, SYSPROP_HAS_BACK_BUTTON,
SYSPROP_HAS_KEYBOARD, SYSPROP_HAS_KEYBOARD,
SYSPROP_HAS_OPEN_DIRECTORY, SYSPROP_HAS_OPEN_DIRECTORY,
SYSPROP_HAS_LOGIN_DIALOG,
SYSPROP_HAS_TEXT_INPUT_DIALOG, // Indicates that System_InputBoxGetString is available.
SYSPROP_CAN_CREATE_SHORTCUT, SYSPROP_CAN_CREATE_SHORTCUT,
@ -191,6 +196,7 @@ enum class SystemNotification {
SUSTAINED_PERF_CHANGE, SUSTAINED_PERF_CHANGE,
POLL_CONTROLLERS, POLL_CONTROLLERS,
TOGGLE_DEBUG_CONSOLE, // TODO: Kinda weird, just ported forward. TOGGLE_DEBUG_CONSOLE, // TODO: Kinda weird, just ported forward.
TEST_JAVA_EXCEPTION,
}; };
std::string System_GetProperty(SystemProperty prop); std::string System_GetProperty(SystemProperty prop);
@ -206,18 +212,17 @@ std::vector<std::string> System_GetCameraDeviceList();
bool System_AudioRecordingIsAvailable(); bool System_AudioRecordingIsAvailable();
bool System_AudioRecordingState(); bool System_AudioRecordingState();
// This will be changed to take an enum. Currently simply implemented by forwarding to NativeMessageReceived. // This will be changed to take an enum. Replacement for the old NativeMessageReceived.
void System_PostUIMessage(const std::string &message, const std::string &param); void System_PostUIMessage(const std::string &message, const std::string &param);
// Shows a visible message to the user.
// The default implementation in NativeApp.cpp uses our "osm" system (on screen messaging).
void System_NotifyUserMessage(const std::string &message, float duration = 1.0f, uint32_t color = 0x00FFFFFF, const char *id = nullptr);
// For these functions, most platforms will use the implementation provided in UI/AudioCommon.cpp, // For these functions, most platforms will use the implementation provided in UI/AudioCommon.cpp,
// no need to implement separately. // no need to implement separately.
void System_AudioGetDebugStats(char *buf, size_t bufSize); void System_AudioGetDebugStats(char *buf, size_t bufSize);
void System_AudioClear(); void System_AudioClear();
// These samples really have 16 bits of value, but can be a little out of range. // These samples really have 16 bits of value, but can be a little out of range.
// This is for pushing rate-controlled 44khz audio from emulation.
// If you push a little too fast, we'll pitch up to a limit, for example.
void System_AudioPushSamples(const int32_t *audio, int numSamples); void System_AudioPushSamples(const int32_t *audio, int numSamples);
inline void System_AudioResetStatCounters() { inline void System_AudioResetStatCounters() {

View file

@ -208,10 +208,12 @@ void UIContext::SetFontStyle(const UI::FontStyle &fontStyle) {
} }
void UIContext::MeasureText(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, float *x, float *y, int align) const { void UIContext::MeasureText(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
MeasureTextCount(style, scaleX, scaleY, str, (int)strlen(str), x, y, align); MeasureTextCount(style, scaleX, scaleY, str, (int)strlen(str), x, y, align);
} }
void UIContext::MeasureTextCount(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, float *x, float *y, int align) const { void UIContext::MeasureTextCount(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) { if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
float sizeFactor = (float)style.sizePts / 24.0f; float sizeFactor = (float)style.sizePts / 24.0f;
Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor); Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor);
@ -225,6 +227,7 @@ void UIContext::MeasureTextCount(const UI::FontStyle &style, float scaleX, float
} }
void UIContext::MeasureTextRect(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, const Bounds &bounds, float *x, float *y, int align) const { void UIContext::MeasureTextRect(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, const Bounds &bounds, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) { if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
float sizeFactor = (float)style.sizePts / 24.0f; float sizeFactor = (float)style.sizePts / 24.0f;
Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor); Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor);
@ -238,6 +241,7 @@ void UIContext::MeasureTextRect(const UI::FontStyle &style, float scaleX, float
} }
void UIContext::DrawText(const char *str, float x, float y, uint32_t color, int align) { void UIContext::DrawText(const char *str, float x, float y, uint32_t color, int align) {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) { if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
// Use the font texture if this font is in that texture instead. // Use the font texture if this font is in that texture instead.
bool useFontTexture = Draw()->GetFontAtlas()->getFont(fontStyle_->atlasFont) != nullptr; bool useFontTexture = Draw()->GetFontAtlas()->getFont(fontStyle_->atlasFont) != nullptr;
@ -286,6 +290,28 @@ void UIContext::DrawTextRect(const char *str, const Bounds &bounds, uint32_t col
RebindTexture(); RebindTexture();
} }
static constexpr float MIN_TEXT_SCALE = 0.7f;
float UIContext::CalculateTextScale(const char *text, float availWidth, float availHeight) const {
float actualWidth, actualHeight;
Bounds availBounds(0, 0, availWidth, availHeight);
MeasureTextRect(theme->uiFont, 1.0f, 1.0f, text, (int)strlen(text), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER);
if (actualWidth > availWidth) {
return std::max(MIN_TEXT_SCALE, availWidth / actualWidth);
}
return 1.0f;
}
void UIContext::DrawTextRectSqueeze(const char *str, const Bounds &bounds, uint32_t color, int align) {
float origScaleX = fontScaleX_;
float origScaleY = fontScaleY_;
float scale = CalculateTextScale(str, bounds.w / origScaleX, bounds.h / origScaleY);
SetFontScale(scale * origScaleX, scale * origScaleY);
Bounds textBounds(bounds.x, bounds.y, bounds.w, bounds.h);
DrawTextRect(str, textBounds, color, align);
SetFontScale(origScaleX, origScaleY);
}
void UIContext::DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align) { void UIContext::DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align) {
uint32_t alpha = (color >> 1) & 0xFF000000; uint32_t alpha = (color >> 1) & 0xFF000000;
Bounds shadowBounds(bounds.x+2, bounds.y+2, bounds.w, bounds.h); Bounds shadowBounds(bounds.x+2, bounds.y+2, bounds.w, bounds.h);
@ -313,6 +339,17 @@ void UIContext::FillRect(const UI::Drawable &drawable, const Bounds &bounds) {
} }
} }
void UIContext::DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color) {
if (alpha <= 0.0f)
return;
color = colorAlpha(color, alpha);
// Bias the shadow downwards a bit.
Bounds shadowBounds = bounds.Expand(radius, 0.5 * radius, radius, 1.5 * radius);
Draw()->DrawImage4Grid(theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y, shadowBounds.x2(), shadowBounds.y2(), color, radius * (1.0f / 24.0f) * 2.0f);
}
void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds) { void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds) {
uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2); uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2);
} }

View file

@ -77,6 +77,8 @@ public:
void SetTintSaturation(float tint, float sat); void SetTintSaturation(float tint, float sat);
// High level drawing functions. They generally assume the default texture to be bounds.
void SetFontStyle(const UI::FontStyle &style); void SetFontStyle(const UI::FontStyle &style);
const UI::FontStyle &GetFontStyle() { return *fontStyle_; } const UI::FontStyle &GetFontStyle() { return *fontStyle_; }
void SetFontScale(float scaleX, float scaleY); void SetFontScale(float scaleX, float scaleY);
@ -87,7 +89,13 @@ public:
void DrawTextShadow(const char *str, float x, float y, uint32_t color, int align = 0); void DrawTextShadow(const char *str, float x, float y, uint32_t color, int align = 0);
void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0); void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
void DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0); void DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
// Will squeeze the text into the bounds if needed.
void DrawTextRectSqueeze(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
float CalculateTextScale(const char *text, float availWidth, float availHeight) const;
void FillRect(const UI::Drawable &drawable, const Bounds &bounds); void FillRect(const UI::Drawable &drawable, const Bounds &bounds);
void DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color = 0);
void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds); void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds);
// in dps, like dp_xres and dp_yres // in dps, like dp_xres and dp_yres

340
Common/UI/IconCache.cpp Normal file
View file

@ -0,0 +1,340 @@
#include <algorithm>
#include "Common/UI/IconCache.h"
#include "Common/UI/Context.h"
#include "Common/TimeUtil.h"
#include "Common/Data/Format/PNGLoad.h"
#include "Common/Log.h"
#define ICON_CACHE_VERSION 1
#define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24))
#define MAX_RUNTIME_CACHE_SIZE (1024 * 1024 * 4)
#define MAX_SAVED_CACHE_SIZE (1024 * 1024 * 1)
const uint32_t ICON_CACHE_MAGIC = MK_FOURCC("pICN");
IconCache g_iconCache;
struct DiskCacheHeader {
uint32_t magic;
uint32_t version;
uint32_t entryCount;
};
struct DiskCacheEntry {
uint32_t keyLen;
uint32_t dataLen;
IconFormat format;
double insertedTimestamp;
};
void IconCache::SaveToFile(FILE *file) {
std::unique_lock<std::mutex> lock(lock_);
// First, compute the total size. If above a threshold, remove until under.
Decimate(MAX_SAVED_CACHE_SIZE);
DiskCacheHeader header;
header.magic = ICON_CACHE_MAGIC;
header.version = ICON_CACHE_VERSION;
header.entryCount = (uint32_t)cache_.size();
fwrite(&header, 1, sizeof(header), file);
for (auto &iter : cache_) {
DiskCacheEntry entryHeader;
entryHeader.keyLen = (uint32_t)iter.first.size();
entryHeader.dataLen = (uint32_t)iter.second.data.size();
entryHeader.format = iter.second.format;
entryHeader.insertedTimestamp = iter.second.insertedTimeStamp;
fwrite(&entryHeader, 1, sizeof(entryHeader), file);
fwrite(iter.first.c_str(), 1, iter.first.size(), file);
fwrite(iter.second.data.data(), 1, iter.second.data.size(), file);
}
}
bool IconCache::LoadFromFile(FILE *file) {
std::unique_lock<std::mutex> lock(lock_);
DiskCacheHeader header;
if (fread(&header, 1, sizeof(header), file) != sizeof(DiskCacheHeader)) {
return false;
}
if (header.magic != ICON_CACHE_MAGIC || header.version != ICON_CACHE_VERSION) {
return false;
}
double now = time_now_d();
for (uint32_t i = 0; i < header.entryCount; i++) {
DiskCacheEntry entryHeader;
if (fread(&entryHeader, 1, sizeof(entryHeader), file) != sizeof(entryHeader)) {
break;
}
std::string key;
key.resize(entryHeader.keyLen, 0);
if (entryHeader.keyLen > 0x1000) {
// Let's say this is invalid, probably a corrupted file.
break;
}
fread(&key[0], 1, entryHeader.keyLen, file);
// Check if we already have the entry somehow.
if (cache_.find(key) != cache_.end()) {
// Seek past the data and go to the next entry.
fseek(file, entryHeader.dataLen, SEEK_CUR);
continue;
}
std::string data;
data.resize(entryHeader.dataLen);
size_t len = fread(&data[0], 1, entryHeader.dataLen, file);
if (len != (size_t)entryHeader.dataLen) {
// Stop reading and don't use this entry. Seems the file is truncated, but we'll recover.
break;
}
Entry entry{};
entry.data = data;
entry.format = entryHeader.format;
entry.insertedTimeStamp = entryHeader.insertedTimestamp;
entry.usedTimeStamp = now;
cache_.insert(std::pair<std::string, Entry>(key, entry));
}
return true;
}
void IconCache::ClearTextures() {
std::unique_lock<std::mutex> lock(lock_);
for (auto &iter : cache_) {
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}
void IconCache::ClearData() {
ClearTextures();
std::unique_lock<std::mutex> lock(lock_);
cache_.clear();
}
void IconCache::FrameUpdate() {
std::unique_lock<std::mutex> lock(lock_);
// Remove old textures after a while.
double now = time_now_d();
if (now > lastUpdate_ + 2.0) {
for (auto &iter : cache_) {
double useAge = now - iter.second.usedTimeStamp;
if (useAge > 5.0) {
// Release the texture after a few seconds of no use.
// Still, keep the png data loaded, it's small.
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}
lastUpdate_ = now;
}
if (now > lastDecimate_ + 60.0) {
Decimate(MAX_RUNTIME_CACHE_SIZE);
lastDecimate_ = now;
}
}
void IconCache::Decimate(int64_t maxSize) {
// Call this under the lock.
int64_t totalSize = 0;
for (auto &iter : cache_) {
totalSize += iter.second.data.size();
}
if (totalSize <= maxSize) {
return;
}
// Create a list of all the entries, sort by date. Then delete until we reach the desired size.
struct SortEntry {
std::string key;
double usedTimestamp;
size_t size;
};
std::vector<SortEntry> sortEntries;
for (auto iter : cache_) {
sortEntries.push_back({ iter.first, iter.second.usedTimeStamp, iter.second.data.size() });
}
std::sort(sortEntries.begin(), sortEntries.end(), [](const SortEntry &a, const SortEntry &b) {
// Oldest should be last in the lsit.
return a.usedTimestamp > b.usedTimestamp;
});
while (totalSize > maxSize && !sortEntries.empty()) {
totalSize -= sortEntries.back().size;
auto iter = cache_.find(sortEntries.back().key);
if (iter != cache_.end()) {
if (iter->second.texture) {
iter->second.texture->Release();
}
cache_.erase(iter);
}
sortEntries.pop_back();
}
}
bool IconCache::GetDimensions(const std::string &key, int *width, int *height) {
std::unique_lock<std::mutex> lock(lock_);
auto iter = cache_.find(key);
if (iter == cache_.end()) {
// Don't have this entry.
return false;
}
if (iter->second.texture) {
// TODO: Store the width/height in the cache.
*width = iter->second.texture->Width();
*height = iter->second.texture->Height();
return true;
} else {
return false;
}
}
bool IconCache::Contains(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
return cache_.find(key) != cache_.end();
}
bool IconCache::MarkPending(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
if (cache_.find(key) != cache_.end()) {
return false;
}
if (pending_.find(key) != pending_.end()) {
return false;
}
pending_.insert(key);
return true;
}
void IconCache::Cancel(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
pending_.erase(key);
}
bool IconCache::InsertIcon(const std::string &key, IconFormat format, std::string &&data) {
std::unique_lock<std::mutex> lock(lock_);
if (key.empty()) {
return false;
}
if (data.empty()) {
ERROR_LOG(G3D, "Can't insert empty data into icon cache");
return false;
}
if (cache_.find(key) != cache_.end()) {
// Already have this entry.
return false;
}
if (data.size() > 1024 * 512) {
WARN_LOG(G3D, "Unusually large icon inserted in icon cache: %s (%d bytes)", key.c_str(), (int)data.size());
}
pending_.erase(key);
double now = time_now_d();
cache_.emplace(key, Entry{ std::move(data), format, nullptr, now, now, false });
return true;
}
Draw::Texture *IconCache::BindIconTexture(UIContext *context, const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
auto iter = cache_.find(key);
if (iter == cache_.end()) {
// Don't have this entry.
return nullptr;
}
if (iter->second.texture) {
context->GetDrawContext()->BindTexture(0, iter->second.texture);
iter->second.usedTimeStamp = time_now_d();
return iter->second.texture;
}
if (iter->second.badData) {
return nullptr;
}
// OK, don't have a texture. Upload it!
int width = 0;
int height = 0;
Draw::DataFormat dataFormat;
unsigned char *buffer = nullptr;
switch (iter->second.format) {
case IconFormat::PNG:
{
int result = pngLoadPtr((const unsigned char *)iter->second.data.data(), iter->second.data.size(), &width,
&height, &buffer);
if (result != 1) {
ERROR_LOG(G3D, "IconCache: Failed to load png (%d bytes) for key %s", (int)iter->second.data.size(), key.c_str());
iter->second.badData = true;
return nullptr;
}
dataFormat = Draw::DataFormat::R8G8B8A8_UNORM;
break;
}
default:
return nullptr;
}
Draw::TextureDesc iconDesc{};
iconDesc.width = width;
iconDesc.height = height;
iconDesc.depth = 1;
iconDesc.initData.push_back((const uint8_t *)buffer);
iconDesc.mipLevels = 1;
iconDesc.swizzle = Draw::TextureSwizzle::DEFAULT;
iconDesc.generateMips = false;
iconDesc.tag = key.c_str();
iconDesc.format = dataFormat;
iconDesc.type = Draw::TextureType::LINEAR2D;
Draw::Texture *texture = context->GetDrawContext()->CreateTexture(iconDesc);
iter->second.texture = texture;
iter->second.usedTimeStamp = time_now_d();
free(buffer);
return texture;
}
IconCacheStats IconCache::GetStats() {
IconCacheStats stats{};
std::unique_lock<std::mutex> lock(lock_);
for (auto &iter : cache_) {
stats.cachedCount++;
if (iter.second.texture)
stats.textureCount++;
stats.dataSize += iter.second.data.size();
}
stats.pending = pending_.size();
return stats;
}

75
Common/UI/IconCache.h Normal file
View file

@ -0,0 +1,75 @@
#pragma once
#include <map>
#include <set>
#include <string>
#include <cstdio>
#include <mutex>
#include <cstdint>
#include "Common/GPU/thin3d.h"
class UIContext;
enum class IconFormat : uint32_t {
PNG,
};
namespace Draw {
class Texture;
}
// TODO: Possibly make this smarter and use instead of ManagedTexture?
struct IconCacheStats {
size_t cachedCount;
size_t textureCount; // number of cached images that are "live" textures
size_t pending;
size_t dataSize;
};
class IconCache {
public:
// NOTE: Don't store the returned texture. Only use it to look up dimensions or other properties,
// instead call BindIconTexture every time you want to use it.
Draw::Texture *BindIconTexture(UIContext *context, const std::string &key);
// It's okay to call these from any thread.
bool MarkPending(const std::string &key); // returns false if already pending or loaded
void Cancel(const std::string &key);
bool InsertIcon(const std::string &key, IconFormat format, std::string &&pngData);
bool GetDimensions(const std::string &key, int *width, int *height);
bool Contains(const std::string &key);
void SaveToFile(FILE *file);
bool LoadFromFile(FILE *file);
void FrameUpdate();
void ClearTextures();
void ClearData();
IconCacheStats GetStats();
private:
void Decimate(int64_t maxSize);
struct Entry {
std::string data;
IconFormat format;
Draw::Texture *texture;
double insertedTimeStamp;
double usedTimeStamp;
bool badData;
};
std::map<std::string, Entry> cache_;
std::set<std::string> pending_;
std::mutex lock_;
double lastUpdate_ = 0.0;
double lastDecimate_ = 0.0;
};
extern IconCache g_iconCache;

View file

@ -1,5 +1,6 @@
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include <cstring>
#include "Common/UI/PopupScreens.h" #include "Common/UI/PopupScreens.h"
#include "Common/UI/ViewGroup.h" #include "Common/UI/ViewGroup.h"
@ -7,6 +8,8 @@
#include "Common/UI/Root.h" #include "Common/UI/Root.h"
#include "Common/StringUtils.h" #include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h" #include "Common/Data/Text/I18n.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
namespace UI { namespace UI {
@ -495,6 +498,16 @@ PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string
EventReturn PopupTextInputChoice::HandleClick(EventParams &e) { EventReturn PopupTextInputChoice::HandleClick(EventParams &e) {
restoreFocus_ = HasFocus(); restoreFocus_ = HasFocus();
// Choose method depending on platform capabilities.
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) {
System_InputBoxGetString(text_, *value_ , [=](const std::string &enteredValue, int) {
*value_ = StripSpaces(enteredValue);
EventParams params{};
OnChange.Trigger(params);
});
return EVENT_DONE;
}
TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_); TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_);
popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange); popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange);
if (e.v) if (e.v)
@ -582,7 +595,12 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
int paddingX = 12; int paddingX = 12;
dc.SetFontStyle(dc.theme->uiFont); dc.SetFontStyle(dc.theme->uiFont);
const std::string valueText = ValueText(); std::string valueText = ValueText();
if (passwordDisplay_) {
// Replace all characters with stars.
memset(&valueText[0], '*', valueText.size());
}
// If there is a label, assume we want at least 20% of the size for it, at a minimum. // If there is a label, assume we want at least 20% of the size for it, at a minimum.
@ -636,4 +654,28 @@ std::string ChoiceWithValueDisplay::ValueText() const {
return valueText.str(); return valueText.str();
} }
FileChooserChoice::FileChooserChoice(std::string *value, const std::string &text, BrowseFileType fileType, LayoutParams *layoutParams)
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), fileType_(fileType) {
OnClick.Add([=](UI::EventParams &) {
System_BrowseForFile(text_, fileType, [=](const std::string &returnValue, int) {
if (*value_ != returnValue) {
*value = returnValue;
UI::EventParams e{};
e.s = *value;
OnChange.Trigger(e);
}
});
return UI::EVENT_DONE;
});
}
std::string FileChooserChoice::ValueText() const {
if (value_->empty()) {
auto di = GetI18NCategory(I18NCat::DIALOG);
return di->T("Default");
}
Path path(*value_);
return path.GetFilename();
}
} // namespace } // namespace

Some files were not shown because too many files have changed in this diff Show more