Merge branch 'master' into master
This commit is contained in:
commit
a35c8425e6
520 changed files with 16053 additions and 8419 deletions
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
|
@ -155,14 +155,8 @@ jobs:
|
|||
extra: android
|
||||
cc: clang
|
||||
cxx: clang++
|
||||
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_QUEST=1
|
||||
id: android-vr-quest
|
||||
- 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
|
||||
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1
|
||||
id: android-vr
|
||||
- os: ubuntu-latest
|
||||
extra: android
|
||||
cc: clang
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -131,3 +131,5 @@ CMakeFiles
|
|||
.cache/
|
||||
build
|
||||
libretro/obj/local
|
||||
|
||||
ppsspp_retroachievements.dat
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -44,3 +44,6 @@
|
|||
[submodule "cpu_features"]
|
||||
path = ext/cpu_features
|
||||
url = https://github.com/google/cpu_features.git
|
||||
[submodule "ext/rcheevos"]
|
||||
path = ext/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
|
|
|
@ -102,6 +102,13 @@ if(ANDROID OR WIN32 OR (UNIX AND NOT ARM_NO_VULKAN))
|
|||
set(VULKAN ON)
|
||||
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)
|
||||
if(NOT IOS)
|
||||
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_FFMPEG "Dynamically link against system FFMPEG" ${USE_SYSTEM_FFMPEG})
|
||||
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_ZSTD "Dynamically link against system zstd" ${USE_SYSTEM_ZSTD})
|
||||
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(USING_X11_VULKAN)
|
||||
message("Using X11 for Vulkan")
|
||||
find_package(X11)
|
||||
include_directories(${X11_Xlib_INCLUDE_PATH})
|
||||
add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)
|
||||
else()
|
||||
message("NOT using X11 for Vulkan")
|
||||
|
@ -242,9 +251,13 @@ if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX)
|
|||
endif()
|
||||
|
||||
if(MACOSX AND NOT IOS)
|
||||
if(USE_SYSTEM_LIBSDL2)
|
||||
find_package(SDL2)
|
||||
else()
|
||||
find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS)
|
||||
message(STATUS "found SDL2Fwk=${SDL2Fwk}")
|
||||
add_definitions(-DHAVE_SYSCTLBYNAME)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(FindThreads)
|
||||
|
@ -393,7 +406,7 @@ if(NOT MSVC)
|
|||
add_definitions(-Wno-psabi)
|
||||
endif()
|
||||
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)
|
||||
elseif(ANDROID)
|
||||
add_definitions(-fsigned-char)
|
||||
|
@ -406,6 +419,7 @@ else()
|
|||
endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -D_NDEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:"libcmt.lib")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
|
@ -555,6 +569,7 @@ add_library(Common STATIC
|
|||
Common/Data/Collections/FixedSizeQueue.h
|
||||
Common/Data/Collections/Hashmaps.h
|
||||
Common/Data/Collections/TinySet.h
|
||||
Common/Data/Collections/FastVec.h
|
||||
Common/Data/Collections/ThreadSafeList.h
|
||||
Common/Data/Color/RGBAUtil.cpp
|
||||
Common/Data/Color/RGBAUtil.h
|
||||
|
@ -603,6 +618,8 @@ add_library(Common STATIC
|
|||
Common/File/VFS/DirectoryReader.h
|
||||
Common/File/AndroidStorage.h
|
||||
Common/File/AndroidStorage.cpp
|
||||
Common/File/AndroidContentURI.h
|
||||
Common/File/AndroidContentURI.cpp
|
||||
Common/File/DiskFree.h
|
||||
Common/File/DiskFree.cpp
|
||||
Common/File/Path.h
|
||||
|
@ -617,6 +634,8 @@ add_library(Common STATIC
|
|||
Common/File/FileDescriptor.h
|
||||
Common/GPU/DataFormat.h
|
||||
Common/GPU/MiscTypes.h
|
||||
Common/GPU/GPUBackendCommon.cpp
|
||||
Common/GPU/GPUBackendCommon.h
|
||||
Common/GPU/thin3d.cpp
|
||||
Common/GPU/thin3d.h
|
||||
Common/GPU/thin3d_create.h
|
||||
|
@ -638,6 +657,8 @@ add_library(Common STATIC
|
|||
Common/GPU/OpenGL/GLFrameData.cpp
|
||||
Common/GPU/OpenGL/GLFrameData.h
|
||||
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.h
|
||||
Common/GPU/OpenGL/GLQueueRunner.cpp
|
||||
|
@ -723,6 +744,8 @@ add_library(Common STATIC
|
|||
Common/System/NativeApp.h
|
||||
Common/System/Request.cpp
|
||||
Common/System/Request.h
|
||||
Common/System/OSD.cpp
|
||||
Common/System/OSD.h
|
||||
Common/Thread/Channel.h
|
||||
Common/Thread/ParallelLoop.cpp
|
||||
Common/Thread/ParallelLoop.h
|
||||
|
@ -741,6 +764,8 @@ add_library(Common STATIC
|
|||
Common/UI/UI.h
|
||||
Common/UI/Context.cpp
|
||||
Common/UI/Context.h
|
||||
Common/UI/IconCache.cpp
|
||||
Common/UI/IconCache.h
|
||||
Common/UI/UIScreen.cpp
|
||||
Common/UI/UIScreen.h
|
||||
Common/UI/Tween.cpp
|
||||
|
@ -778,8 +803,10 @@ add_library(Common STATIC
|
|||
Common/MemArenaDarwin.cpp
|
||||
Common/MemArenaPosix.cpp
|
||||
Common/MemArenaWin32.cpp
|
||||
Common/MemArenaHorizon.cpp
|
||||
Common/MemArena.h
|
||||
Common/MemoryUtil.cpp
|
||||
Common/MemoryUtilHorizon.cpp
|
||||
Common/MemoryUtil.h
|
||||
Common/OSVersion.cpp
|
||||
Common/OSVersion.h
|
||||
|
@ -919,6 +946,7 @@ endif()
|
|||
|
||||
find_package(LIBZIP)
|
||||
if(LIBZIP_FOUND AND USE_SYSTEM_LIBZIP)
|
||||
include_directories(${LIBZIP_INCLUDE_DIRS})
|
||||
add_definitions(-DSHARED_LIBZIP)
|
||||
else()
|
||||
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(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY})
|
||||
|
||||
if(USE_SYSTEM_LIBSDL2)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2)
|
||||
else()
|
||||
set(nativeExtraLibs ${nativeExtraLibs} ${SDL2Fwk})
|
||||
endif()
|
||||
elseif(USING_EGL)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} pthread SDL2::SDL2)
|
||||
else()
|
||||
|
@ -1345,6 +1378,8 @@ list(APPEND NativeAppSource
|
|||
UI/MiscScreens.cpp
|
||||
UI/PauseScreen.h
|
||||
UI/PauseScreen.cpp
|
||||
UI/TabbedDialogScreen.h
|
||||
UI/TabbedDialogScreen.cpp
|
||||
UI/GameScreen.h
|
||||
UI/GameScreen.cpp
|
||||
UI/GameSettingsScreen.h
|
||||
|
@ -1385,6 +1420,8 @@ list(APPEND NativeAppSource
|
|||
UI/CustomButtonMappingScreen.cpp
|
||||
UI/Theme.h
|
||||
UI/Theme.cpp
|
||||
UI/RetroAchievementScreens.cpp
|
||||
UI/RetroAchievementScreens.h
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
|
@ -1786,6 +1823,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
|||
Core/KeyMap.h
|
||||
Core/KeyMapDefaults.cpp
|
||||
Core/KeyMapDefaults.h
|
||||
Core/RetroAchievements.h
|
||||
Core/RetroAchievements.cpp
|
||||
Core/ThreadEventQueue.h
|
||||
Core/TiltEventProcessor.h
|
||||
Core/TiltEventProcessor.cpp
|
||||
|
@ -1812,6 +1851,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
|||
Core/Debugger/WebSocket/GameBroadcaster.h
|
||||
Core/Debugger/WebSocket/GameSubscriber.cpp
|
||||
Core/Debugger/WebSocket/GameSubscriber.h
|
||||
Core/Debugger/WebSocket/ClientConfigSubscriber.cpp
|
||||
Core/Debugger/WebSocket/ClientConfigSubscriber.h
|
||||
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
|
||||
Core/Debugger/WebSocket/GPUBufferSubscriber.h
|
||||
Core/Debugger/WebSocket/GPURecordSubscriber.cpp
|
||||
|
@ -2179,7 +2220,7 @@ else()
|
|||
include_directories(ext/zstd/lib)
|
||||
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})
|
||||
|
||||
target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
|
||||
|
@ -2328,7 +2369,6 @@ set(WindowsFiles
|
|||
Windows/Debugger/Debugger_MemoryDlg.h
|
||||
Windows/Debugger/Debugger_Lists.cpp
|
||||
Windows/Debugger/Debugger_Lists.h
|
||||
Windows/Debugger/Debugger_SymbolMap.h
|
||||
Windows/Debugger/Debugger_VFPUDlg.cpp
|
||||
Windows/Debugger/Debugger_VFPUDlg.h
|
||||
Windows/Debugger/WatchItemWindow.cpp
|
||||
|
@ -2376,6 +2416,13 @@ set(WindowsFiles
|
|||
Windows/W32Util/ShellUtil.h
|
||||
Windows/W32Util/TabControl.cpp
|
||||
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.h
|
||||
Windows/MainWindow.cpp
|
||||
|
@ -2399,7 +2446,7 @@ set(WindowsFiles
|
|||
list(APPEND LinkCommon ${CoreLibName} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
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)
|
||||
list(APPEND NativeAppSource ${WindowsFiles})
|
||||
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/")
|
||||
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")
|
||||
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}")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -315,6 +315,14 @@ const u8* ARM64XEmitter::AlignCodePage()
|
|||
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()
|
||||
{
|
||||
FlushIcacheSection(m_lastCacheFlushEnd, m_code);
|
||||
|
|
|
@ -94,7 +94,7 @@ enum ARM64Reg
|
|||
|
||||
// R19-R28. R29 (FP), R30 (LR) are always saved and FP updated appropriately.
|
||||
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 IsSingle(ARM64Reg reg) { return (reg & 0xC0) == 0x40; }
|
||||
|
@ -401,6 +401,7 @@ public:
|
|||
void ReserveCodeSpace(u32 bytes);
|
||||
const u8* AlignCode16();
|
||||
const u8* AlignCodePage();
|
||||
const u8 *NopAlignCode16();
|
||||
void FlushIcache();
|
||||
void FlushIcacheSection(const u8* start, const u8* end);
|
||||
u8* GetWritableCodePtr();
|
||||
|
|
|
@ -32,14 +32,15 @@
|
|||
#if defined(CPU_FEATURES_OS_LINUX)
|
||||
#define USE_CPU_FEATURES 1
|
||||
#endif
|
||||
#elif PPSSPP_ARCH(ARM64) && defined(__aarch64__)
|
||||
#elif PPSSPP_ARCH(ARM64)
|
||||
#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
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -54,7 +55,7 @@
|
|||
std::string GetCPUBrandString();
|
||||
#else
|
||||
// No CPUID on ARM, so we'll have to read the registry
|
||||
#include <windows.h>
|
||||
#include "Common/CommonWindows.h"
|
||||
std::string GetCPUBrandString() {
|
||||
std::string cpu_string;
|
||||
|
||||
|
|
|
@ -613,6 +613,14 @@ const u8 *ARMXEmitter::AlignCode16()
|
|||
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()
|
||||
{
|
||||
ReserveCodeSpace((-(intptr_t)code) & 4095);
|
||||
|
|
|
@ -446,6 +446,8 @@ public:
|
|||
void ReserveCodeSpace(u32 bytes);
|
||||
const u8 *AlignCode16();
|
||||
const u8 *AlignCodePage();
|
||||
const u8 *NopAlignCode16();
|
||||
|
||||
void FlushIcache();
|
||||
void FlushIcacheSection(u8 *start, u8 *end);
|
||||
u8 *GetWritableCodePtr();
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
#include "Common/Log.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.
|
||||
// You get memory management for free, plus, you can use all emitter functions without
|
||||
// having to prefix them with gen-> or something similar.
|
||||
|
@ -65,9 +70,20 @@ public:
|
|||
// Call this before you generate any code.
|
||||
void AllocCodeSpace(int 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.
|
||||
region = (u8 *)AllocateExecutableMemory(region_size);
|
||||
writableRegion = region;
|
||||
#endif // !PPSSPP_PLATFORM(SWITCH)
|
||||
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.
|
||||
void FreeCodeSpace() {
|
||||
#if !PPSSPP_PLATFORM(SWITCH)
|
||||
ProtectMemoryPages(region, region_size, MEM_PROT_READ | MEM_PROT_WRITE);
|
||||
FreeExecutableMemory(region, region_size);
|
||||
#else // !PPSSPP_PLATFORM(SWITCH)
|
||||
jitClose(&jitController);
|
||||
printf("[NXJIT]: Jit closed\n");
|
||||
#endif // PPSSPP_PLATFORM(SWITCH)
|
||||
region = nullptr;
|
||||
writableRegion = nullptr;
|
||||
region_size = 0;
|
||||
|
@ -176,5 +197,7 @@ private:
|
|||
const uint8_t *writeStart_ = nullptr;
|
||||
uint8_t *writableRegion = nullptr;
|
||||
size_t writeEstimated_ = 0;
|
||||
#if PPSSPP_PLATFORM(SWITCH)
|
||||
Jit jitController;
|
||||
#endif // PPSSPP_PLATFORM(SWITCH)
|
||||
};
|
||||
|
||||
|
|
|
@ -434,6 +434,7 @@
|
|||
<ClInclude Include="Data\Text\Parsers.h" />
|
||||
<ClInclude Include="Data\Text\WrapText.h" />
|
||||
<ClInclude Include="FakeEmitter.h" />
|
||||
<ClInclude Include="File\AndroidContentURI.h" />
|
||||
<ClInclude Include="File\AndroidStorage.h" />
|
||||
<ClInclude Include="File\DirListing.h" />
|
||||
<ClInclude Include="File\DiskFree.h" />
|
||||
|
@ -449,11 +450,13 @@
|
|||
<ClInclude Include="GPU\D3D9\D3D9ShaderCompiler.h" />
|
||||
<ClInclude Include="GPU\D3D9\D3D9StateCache.h" />
|
||||
<ClInclude Include="GPU\DataFormat.h" />
|
||||
<ClInclude Include="GPU\GPUBackendCommon.h" />
|
||||
<ClInclude Include="GPU\MiscTypes.h" />
|
||||
<ClInclude Include="GPU\OpenGL\DataFormatGL.h" />
|
||||
<ClInclude Include="GPU\OpenGL\gl3stub.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLFeatures.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLFrameData.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLMemory.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLQueueRunner.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLRenderManager.h" />
|
||||
<ClInclude Include="GPU\OpenGL\GLSLProgram.h" />
|
||||
|
@ -558,6 +561,7 @@
|
|||
<ClInclude Include="Swap.h" />
|
||||
<ClInclude Include="SysError.h" />
|
||||
<ClInclude Include="System\Display.h" />
|
||||
<ClInclude Include="System\OSD.h" />
|
||||
<ClInclude Include="System\Request.h" />
|
||||
<ClInclude Include="System\NativeApp.h" />
|
||||
<ClInclude Include="System\System.h" />
|
||||
|
@ -573,6 +577,7 @@
|
|||
<ClInclude Include="TimeUtil.h" />
|
||||
<ClInclude Include="UI\AsyncImageFileView.h" />
|
||||
<ClInclude Include="UI\Context.h" />
|
||||
<ClInclude Include="UI\IconCache.h" />
|
||||
<ClInclude Include="UI\PopupScreens.h" />
|
||||
<ClInclude Include="UI\Root.h" />
|
||||
<ClInclude Include="UI\Screen.h" />
|
||||
|
@ -857,6 +862,7 @@
|
|||
</ClCompile>
|
||||
<ClCompile Include="ArmEmitter.cpp" />
|
||||
<ClCompile Include="Buffer.cpp" />
|
||||
<ClCompile Include="Data\Collections\FastVec.h" />
|
||||
<ClCompile Include="Data\Color\RGBAUtil.cpp" />
|
||||
<ClCompile Include="Data\Convert\SmallDataConvert.cpp" />
|
||||
<ClCompile Include="Data\Encoding\Base64.cpp" />
|
||||
|
@ -874,6 +880,7 @@
|
|||
<ClCompile Include="Data\Text\I18n.cpp" />
|
||||
<ClCompile Include="Data\Text\Parsers.cpp" />
|
||||
<ClCompile Include="Data\Text\WrapText.cpp" />
|
||||
<ClCompile Include="File\AndroidContentURI.cpp" />
|
||||
<ClCompile Include="File\AndroidStorage.cpp" />
|
||||
<ClCompile Include="File\DirListing.cpp" />
|
||||
<ClCompile Include="File\DiskFree.cpp" />
|
||||
|
@ -890,10 +897,12 @@
|
|||
<ClCompile Include="GPU\D3D9\D3D9ShaderCompiler.cpp" />
|
||||
<ClCompile Include="GPU\D3D9\D3D9StateCache.cpp" />
|
||||
<ClCompile Include="GPU\D3D9\thin3d_d3d9.cpp" />
|
||||
<ClCompile Include="GPU\GPUBackendCommon.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\DataFormatGL.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\gl3stub.c" />
|
||||
<ClCompile Include="GPU\OpenGL\GLFeatures.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\GLFrameData.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\GLMemory.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\GLRenderManager.cpp" />
|
||||
<ClCompile Include="GPU\OpenGL\GLSLProgram.cpp" />
|
||||
|
@ -1014,6 +1023,7 @@
|
|||
<ClCompile Include="OSVersion.cpp" />
|
||||
<ClCompile Include="StringUtils.cpp" />
|
||||
<ClCompile Include="System\Display.cpp" />
|
||||
<ClCompile Include="System\OSD.cpp" />
|
||||
<ClCompile Include="System\Request.cpp" />
|
||||
<ClCompile Include="Thread\ParallelLoop.cpp" />
|
||||
<ClCompile Include="Thread\ThreadManager.cpp" />
|
||||
|
@ -1022,6 +1032,7 @@
|
|||
<ClCompile Include="TimeUtil.cpp" />
|
||||
<ClCompile Include="UI\AsyncImageFileView.cpp" />
|
||||
<ClCompile Include="UI\Context.cpp" />
|
||||
<ClCompile Include="UI\IconCache.cpp" />
|
||||
<ClCompile Include="UI\PopupScreens.cpp" />
|
||||
<ClCompile Include="UI\Root.cpp" />
|
||||
<ClCompile Include="UI\Screen.cpp" />
|
||||
|
|
|
@ -449,9 +449,6 @@
|
|||
<ClInclude Include="Render\ManagedTexture.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPU\MiscTypes.h">
|
||||
<Filter>GPU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClInclude>
|
||||
|
@ -497,6 +494,24 @@
|
|||
<ClInclude Include="System\Request.h">
|
||||
<Filter>System</Filter>
|
||||
</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>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -926,6 +941,24 @@
|
|||
<ClCompile Include="System\Request.cpp">
|
||||
<Filter>System</Filter>
|
||||
</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>
|
||||
<Filter Include="Crypto">
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
|
||||
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
#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)
|
||||
#define Crash() {asm ("bkpt #0");}
|
||||
#elif PPSSPP_ARCH(ARM64)
|
||||
|
|
|
@ -36,6 +36,39 @@ typedef signed __int64 s64;
|
|||
|
||||
#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 short u16;
|
||||
typedef unsigned int u32;
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
/*
|
||||
* MD5 context setup
|
||||
*/
|
||||
void md5_starts( md5_context *ctx )
|
||||
void ppsspp_md5_starts( md5_context *ctx )
|
||||
{
|
||||
ctx->total[0] = 0;
|
||||
ctx->total[1] = 0;
|
||||
|
@ -73,7 +73,7 @@ void md5_starts( md5_context *ctx )
|
|||
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;
|
||||
|
||||
|
@ -199,7 +199,7 @@ static void md5_process( md5_context *ctx, unsigned char data[64] )
|
|||
/*
|
||||
* 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;
|
||||
unsigned long left;
|
||||
|
@ -220,7 +220,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
|
|||
{
|
||||
memcpy( (void *) (ctx->buffer + left),
|
||||
(void *) input, fill );
|
||||
md5_process( ctx, ctx->buffer );
|
||||
ppsspp_md5_process( ctx, ctx->buffer );
|
||||
input += fill;
|
||||
ilen -= fill;
|
||||
left = 0;
|
||||
|
@ -228,7 +228,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
|
|||
|
||||
while( ilen >= 64 )
|
||||
{
|
||||
md5_process( ctx, input );
|
||||
ppsspp_md5_process( ctx, input );
|
||||
input += 64;
|
||||
ilen -= 64;
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ static const unsigned char md5_padding[64] =
|
|||
/*
|
||||
* 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 high, low;
|
||||
|
@ -267,8 +267,8 @@ void md5_finish( md5_context *ctx, unsigned char output[16] )
|
|||
last = ctx->total[0] & 0x3F;
|
||||
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
|
||||
|
||||
md5_update( ctx, (unsigned char *) md5_padding, padn );
|
||||
md5_update( ctx, msglen, 8 );
|
||||
ppsspp_md5_update( ctx, (unsigned char *) md5_padding, padn );
|
||||
ppsspp_md5_update( ctx, msglen, 8 );
|
||||
|
||||
PUT_ULONG_LE( ctx->state[0], output, 0 );
|
||||
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 )
|
||||
*/
|
||||
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_starts( &ctx );
|
||||
md5_update( &ctx, input, ilen );
|
||||
md5_finish( &ctx, output );
|
||||
ppsspp_md5_starts( &ctx );
|
||||
ppsspp_md5_update( &ctx, input, ilen );
|
||||
ppsspp_md5_finish( &ctx, output );
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
unsigned char sum[16];
|
||||
|
||||
if( keylen > 64 )
|
||||
{
|
||||
md5( key, keylen, sum );
|
||||
ppsspp_md5( key, keylen, sum );
|
||||
keylen = 16;
|
||||
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] );
|
||||
}
|
||||
|
||||
md5_starts( ctx );
|
||||
md5_update( ctx, ctx->ipad, 64 );
|
||||
ppsspp_md5_starts( ctx );
|
||||
ppsspp_md5_update( ctx, ctx->ipad, 64 );
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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];
|
||||
|
||||
md5_finish( ctx, tmpbuf );
|
||||
md5_starts( ctx );
|
||||
md5_update( ctx, ctx->opad, 64 );
|
||||
md5_update( ctx, tmpbuf, 16 );
|
||||
md5_finish( ctx, output );
|
||||
ppsspp_md5_finish( ctx, tmpbuf );
|
||||
ppsspp_md5_starts( ctx );
|
||||
ppsspp_md5_update( ctx, ctx->opad, 64 );
|
||||
ppsspp_md5_update( ctx, tmpbuf, 16 );
|
||||
ppsspp_md5_finish( ctx, output );
|
||||
|
||||
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 )
|
||||
*/
|
||||
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] )
|
||||
{
|
||||
md5_context ctx;
|
||||
|
||||
md5_hmac_starts( &ctx, key, keylen );
|
||||
md5_hmac_update( &ctx, input, ilen );
|
||||
md5_hmac_finish( &ctx, output );
|
||||
ppsspp_md5_hmac_starts( &ctx, key, keylen );
|
||||
ppsspp_md5_hmac_update( &ctx, input, ilen );
|
||||
ppsspp_md5_hmac_finish( &ctx, output );
|
||||
|
||||
memset( &ctx, 0, sizeof( md5_context ) );
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ static const unsigned char md5_hmac_test_sum[7][16] =
|
|||
/*
|
||||
* Checkup routine
|
||||
*/
|
||||
int md5_self_test( int verbose )
|
||||
int ppsspp_md5_self_test( int verbose )
|
||||
{
|
||||
int i, buflen;
|
||||
unsigned char buf[1024];
|
||||
|
|
|
@ -46,7 +46,7 @@ extern "C" {
|
|||
*
|
||||
* \param ctx context to be initialized
|
||||
*/
|
||||
void md5_starts( md5_context *ctx );
|
||||
void ppsspp_md5_starts( md5_context *ctx );
|
||||
|
||||
/**
|
||||
* \brief MD5 process buffer
|
||||
|
@ -55,7 +55,7 @@ void md5_starts( md5_context *ctx );
|
|||
* \param input buffer holding the 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
|
||||
|
@ -63,7 +63,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen );
|
|||
* \param ctx MD5 context
|
||||
* \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 )
|
||||
|
@ -72,7 +72,7 @@ void md5_finish( md5_context *ctx, unsigned char output[16] );
|
|||
* \param ilen length of the input data
|
||||
* \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 )
|
||||
|
@ -83,7 +83,7 @@ void md5( unsigned char *input, int ilen, unsigned char output[16] );
|
|||
* \return 0 if successful, 1 if fopen 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
|
||||
|
@ -92,7 +92,7 @@ int md5_file( char *path, unsigned char output[16] );
|
|||
* \param key HMAC secret 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
|
||||
|
@ -101,7 +101,7 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
|
|||
* \param input buffer holding the 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
|
||||
|
@ -109,7 +109,7 @@ void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
|
|||
* \param ctx HMAC context
|
||||
* \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 )
|
||||
|
@ -120,7 +120,7 @@ void md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
|
|||
* \param ilen length of the input data
|
||||
* \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 output[16] );
|
||||
|
||||
|
@ -129,7 +129,7 @@ void md5_hmac( unsigned char *key, int keylen,
|
|||
*
|
||||
* \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
|
||||
}
|
||||
|
|
157
Common/Data/Collections/FastVec.h
Normal file
157
Common/Data/Collections/FastVec.h
Normal 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
|
||||
};
|
|
@ -56,7 +56,7 @@ public:
|
|||
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) {
|
||||
// Check load factor, resize if necessary. We never shrink.
|
||||
if (count_ > capacity_ / 2) {
|
||||
|
|
|
@ -187,7 +187,7 @@ struct FixedTinyVec {
|
|||
bool operator == (const FixedTinyVec<T, MaxSize> &other) const {
|
||||
if (count_ != other.count_)
|
||||
return false;
|
||||
for (size_t i = 0; i < count_; i++) {
|
||||
for (int i = 0; i < count_; i++) {
|
||||
if (!(data_[i] == other.data_[i])) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
@ -192,7 +194,7 @@ void Section::Set(const char* key, uint32_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) {
|
||||
|
|
|
@ -40,6 +40,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
|
|||
"UI Elements",
|
||||
"Upgrade",
|
||||
"VR",
|
||||
"Achievements",
|
||||
};
|
||||
|
||||
I18NRepo g_i18nrepo;
|
||||
|
|
|
@ -56,6 +56,7 @@ enum class I18NCat : uint8_t {
|
|||
UI_ELEMENTS,
|
||||
UPGRADE,
|
||||
VR,
|
||||
ACHIEVEMENTS,
|
||||
CATEGORY_COUNT,
|
||||
NONE = CATEGORY_COUNT,
|
||||
};
|
||||
|
|
206
Common/File/AndroidContentURI.cpp
Normal file
206
Common/File/AndroidContentURI.cpp
Normal 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());
|
||||
}
|
||||
}
|
73
Common/File/AndroidContentURI.h
Normal file
73
Common/File/AndroidContentURI.h
Normal 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();
|
||||
}
|
||||
};
|
|
@ -25,7 +25,6 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <unistd.h>
|
||||
|
@ -33,12 +32,14 @@
|
|||
#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/SysError.h"
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
|
||||
#include "UWP/UWPHelpers/StorageManager.h"
|
||||
|
@ -161,7 +161,7 @@ std::string Path::GetFilename() const {
|
|||
return path_;
|
||||
}
|
||||
|
||||
static std::string GetExtFromString(const std::string &str) {
|
||||
std::string GetExtFromString(const std::string &str) {
|
||||
size_t pos = str.rfind(".");
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
|
@ -181,7 +181,7 @@ static std::string GetExtFromString(const std::string &str) {
|
|||
std::string Path::GetFileExtension() const {
|
||||
if (type_ == PathType::CONTENT_URI) {
|
||||
AndroidContentURI uri(path_);
|
||||
return GetExtFromString(uri.FilePath());
|
||||
return uri.GetFileExtension();
|
||||
}
|
||||
return GetExtFromString(path_);
|
||||
}
|
||||
|
@ -262,6 +262,15 @@ std::wstring Path::ToWString() const {
|
|||
}
|
||||
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
|
||||
|
||||
std::string Path::ToVisualString(const char *relativeRoot) const {
|
||||
|
|
|
@ -90,6 +90,11 @@ public:
|
|||
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
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
|
||||
|
||||
// Pass in a relative root to turn the path into a relative path - if it is one!
|
||||
|
@ -133,6 +138,8 @@ private:
|
|||
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.
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
#endif
|
||||
|
||||
bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/AndroidStorage.h"
|
||||
#include "Common/StringUtils.h"
|
||||
|
||||
VFS g_VFS;
|
||||
|
||||
|
@ -27,7 +28,7 @@ void VFS::Clear() {
|
|||
static bool IsLocalAbsolutePath(const char *path) {
|
||||
bool isUnixLocal = path[0] == '/';
|
||||
#ifdef _WIN32
|
||||
bool isWindowsLocal = isalpha(path[0]) && path[1] == ':';
|
||||
bool isWindowsLocal = (isalpha(path[0]) && path[1] == ':') || startsWith(path, "\\\\") || startsWith(path, "//");
|
||||
#else
|
||||
bool isWindowsLocal = false;
|
||||
#endif
|
||||
|
|
|
@ -92,6 +92,7 @@ public:
|
|||
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) 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;
|
||||
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;
|
||||
}
|
||||
|
||||
void EndFrame() override;
|
||||
|
||||
void Draw(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 BeginFrame() override;
|
||||
void EndFrame() override;
|
||||
|
||||
int GetFrameCount() override { return frameCount_; }
|
||||
|
||||
std::string GetInfoString(InfoField info) const override {
|
||||
switch (info) {
|
||||
|
@ -220,6 +223,7 @@ private:
|
|||
int nextIndexBufferOffset_ = 0;
|
||||
|
||||
InvalidationCallback invalidationCallback_;
|
||||
int frameCount_ = 0;
|
||||
|
||||
// Dynamic state
|
||||
float blendFactor_[4]{};
|
||||
|
@ -422,6 +426,7 @@ void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1
|
|||
|
||||
void D3D11DrawContext::EndFrame() {
|
||||
curPipeline_ = nullptr;
|
||||
frameCount_++;
|
||||
}
|
||||
|
||||
void D3D11DrawContext::SetViewport(const Viewport &viewport) {
|
||||
|
@ -795,35 +800,83 @@ public:
|
|||
width_ = desc.width;
|
||||
height_ = desc.height;
|
||||
depth_ = desc.depth;
|
||||
format_ = desc.format;
|
||||
mipLevels_ = desc.mipLevels;
|
||||
}
|
||||
~D3D11Texture() {
|
||||
if (tex)
|
||||
tex->Release();
|
||||
if (stagingTex)
|
||||
stagingTex->Release();
|
||||
if (view)
|
||||
view->Release();
|
||||
if (tex_)
|
||||
tex_->Release();
|
||||
if (stagingTex_)
|
||||
stagingTex_->Release();
|
||||
if (view_)
|
||||
view_->Release();
|
||||
}
|
||||
|
||||
ID3D11Texture2D *tex = nullptr;
|
||||
ID3D11Texture2D *stagingTex = nullptr;
|
||||
ID3D11ShaderResourceView *view = nullptr;
|
||||
bool Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips);
|
||||
|
||||
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) {
|
||||
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) {
|
||||
// D3D11 does not support this format as a texture format.
|
||||
return nullptr;
|
||||
bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback) {
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
HRESULT hr = context->Map(stagingTex_, level, D3D11_MAP_WRITE, 0, &mapped);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
tex_->Release();
|
||||
tex_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (!initDataCallback((uint8_t *)mapped.pData, data[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(format_);
|
||||
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{};
|
||||
descColor.Width = desc.width;
|
||||
descColor.Height = desc.height;
|
||||
|
@ -832,25 +885,16 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
|
|||
descColor.Format = dataFormatToD3D11(desc.format);
|
||||
descColor.SampleDesc.Count = 1;
|
||||
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.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE;
|
||||
descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 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 initData[12]{};
|
||||
std::vector<uint8_t> initDataBuffer[12];
|
||||
|
@ -870,62 +914,39 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
|
|||
initDataParam = initData;
|
||||
}
|
||||
|
||||
HRESULT hr = device_->CreateTexture2D(&descColor, initDataParam, &tex->tex);
|
||||
HRESULT hr = device->CreateTexture2D(&descColor, initDataParam, &tex_);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
delete tex;
|
||||
return nullptr;
|
||||
tex_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
hr = device_->CreateShaderResourceView(tex->tex, nullptr, &tex->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);
|
||||
hr = device->CreateShaderResourceView(tex_, nullptr, &view_);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
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 (desc.initDataCallback) {
|
||||
if (!populateLevelCallback(0, desc.width, desc.height, desc.depth)) {
|
||||
delete tex;
|
||||
return nullptr;
|
||||
if (!FillLevel(context, 0, desc.width, desc.height, desc.depth, desc.initData.data(), desc.initDataCallback)) {
|
||||
tex_->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
context_->CopyResource(tex->stagingTex, tex->stagingTex);
|
||||
tex->stagingTex->Release();
|
||||
tex->stagingTex = nullptr;
|
||||
context->CopyResource(tex_, stagingTex_);
|
||||
stagingTex_->Release();
|
||||
stagingTex_ = nullptr;
|
||||
} else {
|
||||
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) {
|
||||
int w = desc.width;
|
||||
int h = desc.height;
|
||||
int d = desc.depth;
|
||||
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) {
|
||||
delete tex;
|
||||
return nullptr;
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -936,13 +957,62 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
|
|||
d = (d + 1) / 2;
|
||||
}
|
||||
|
||||
context_->CopyResource(tex->tex, tex->stagingTex);
|
||||
tex->stagingTex->Release();
|
||||
tex->stagingTex = nullptr;
|
||||
context->CopyResource(tex_, stagingTex_);
|
||||
stagingTex_->Release();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
if (language != ShaderLanguage::HLSL_D3D11) {
|
||||
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));
|
||||
for (int i = 0; i < count; i++) {
|
||||
D3D11Texture *tex = (D3D11Texture *)textures[i];
|
||||
views[i] = tex ? tex->view : nullptr;
|
||||
views[i] = tex ? tex->View() : nullptr;
|
||||
}
|
||||
context_->PSSetShaderResources(start, count, views);
|
||||
}
|
||||
|
@ -1771,7 +1841,7 @@ uint64_t D3D11DrawContext::GetNativeObject(NativeObject obj, void *srcObject) {
|
|||
case NativeObject::FEATURE_LEVEL:
|
||||
return (uint64_t)(uintptr_t)featureLevel_;
|
||||
case NativeObject::TEXTURE_VIEW:
|
||||
return (uint64_t)(((D3D11Texture *)srcObject)->view);
|
||||
return (uint64_t)(((D3D11Texture *)srcObject)->View());
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -308,14 +308,14 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
}
|
||||
void UpdateTextureLevels(const uint8_t * const *data, int numLevels, TextureCallback initDataCallback);
|
||||
|
||||
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);
|
||||
LPDIRECT3DDEVICE9 device_;
|
||||
LPDIRECT3DDEVICE9EX deviceEx_;
|
||||
TextureType type_;
|
||||
DataFormat format_;
|
||||
D3DFORMAT d3dfmt_;
|
||||
LPDIRECT3DTEXTURE9 tex_ = nullptr;
|
||||
LPDIRECT3DVOLUMETEXTURE9 volTex_ = nullptr;
|
||||
|
@ -374,25 +374,29 @@ bool D3D9Texture::Create(const TextureDesc &desc) {
|
|||
break;
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
ERROR_LOG(G3D, "Texture creation failed");
|
||||
ERROR_LOG(G3D, "D3D9 Texture creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (desc.initData.size()) {
|
||||
// In D3D9, after setting D3DUSAGE_AUTOGENMIPS, we can only access the top layer. The rest will be
|
||||
// automatically generated.
|
||||
int maxLevel = desc.generateMips ? 1 : (int)desc.initData.size();
|
||||
int w = desc.width;
|
||||
int h = desc.height;
|
||||
int d = desc.depth;
|
||||
for (int i = 0; i < maxLevel; i++) {
|
||||
SetImageData(0, 0, 0, w, h, d, i, 0, desc.initData[i], desc.initDataCallback);
|
||||
int numLevels = desc.generateMips ? 1 : (int)desc.initData.size();
|
||||
UpdateTextureLevels(desc.initData.data(), numLevels, desc.initDataCallback);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
h = (h + 1) / 2;
|
||||
d = (d + 1) / 2;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Just switches R and G.
|
||||
|
@ -532,6 +536,7 @@ public:
|
|||
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) 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 {
|
||||
// Not implemented
|
||||
|
@ -575,6 +580,7 @@ public:
|
|||
}
|
||||
|
||||
void EndFrame() override;
|
||||
int GetFrameCount() override { return frameCount_; }
|
||||
|
||||
void UpdateDynamicUniformBuffer(const void *ub, size_t size) override;
|
||||
|
||||
|
@ -635,6 +641,7 @@ private:
|
|||
D3DCAPS9 d3dCaps_;
|
||||
char shadeLangVersion_[64]{};
|
||||
DeviceCaps caps_{};
|
||||
int frameCount_ = 0;
|
||||
|
||||
// Bound state
|
||||
AutoRef<D3D9Pipeline> curPipeline_;
|
||||
|
@ -934,6 +941,12 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
|
|||
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) {
|
||||
_assert_(start + count <= MAX_BOUND_TEXTURES);
|
||||
for (int i = start; i < start + count; i++) {
|
||||
|
@ -953,6 +966,7 @@ void D3D9Context::BindNativeTexture(int index, void *nativeTexture) {
|
|||
|
||||
void D3D9Context::EndFrame() {
|
||||
curPipeline_ = nullptr;
|
||||
frameCount_++;
|
||||
}
|
||||
|
||||
static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index) {
|
||||
|
|
28
Common/GPU/GPUBackendCommon.cpp
Normal file
28
Common/GPU/GPUBackendCommon.cpp
Normal 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);
|
||||
}
|
17
Common/GPU/GPUBackendCommon.h
Normal file
17
Common/GPU/GPUBackendCommon.h
Normal 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);
|
|
@ -55,7 +55,7 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin
|
|||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
alignment = 1;
|
||||
alignment = 3;
|
||||
break;
|
||||
|
||||
case DataFormat::R4G4B4A4_UNORM_PACK16:
|
||||
|
|
|
@ -626,11 +626,13 @@ bool CheckGLExtensions() {
|
|||
}
|
||||
|
||||
void SetGLCoreContext(bool flag) {
|
||||
_assert_msg_(!extensionsDone, "SetGLCoreContext() after CheckGLExtensions()");
|
||||
|
||||
if (!extensionsDone) {
|
||||
useCoreContext = flag;
|
||||
// For convenience, it'll get reset later.
|
||||
gl_extensions.IsCoreContext = useCoreContext;
|
||||
} else {
|
||||
_assert_(flag == useCoreContext);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetGLExtensions() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "Common/GPU/OpenGL/GLCommon.h"
|
||||
|
@ -35,6 +36,15 @@ public:
|
|||
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.
|
||||
struct GLFrameData {
|
||||
bool skipSwap = false;
|
||||
|
@ -49,4 +59,6 @@ struct GLFrameData {
|
|||
GLDeleter deleter;
|
||||
GLDeleter deleter_prev;
|
||||
std::set<GLPushBuffer *> activePushBuffers;
|
||||
|
||||
GLQueueProfileContext profile;
|
||||
};
|
||||
|
|
288
Common/GPU/OpenGL/GLMemory.cpp
Normal file
288
Common/GPU/OpenGL/GLMemory.cpp
Normal 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());
|
||||
}
|
185
Common/GPU/OpenGL/GLMemory.h
Normal file
185
Common/GPU/OpenGL/GLMemory.h
Normal 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_;
|
||||
};
|
|
@ -118,7 +118,7 @@ static std::string GetStereoBufferLayout(const char *uniformName) {
|
|||
else return "undefined";
|
||||
}
|
||||
|
||||
void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps, bool skipGLCalls) {
|
||||
void GLQueueRunner::RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls) {
|
||||
if (skipGLCalls) {
|
||||
// Some bookkeeping still needs to be done.
|
||||
for (size_t i = 0; i < steps.size(); i++) {
|
||||
|
@ -651,7 +651,7 @@ retry_depth:
|
|||
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 (keepSteps) {
|
||||
return;
|
||||
|
@ -700,7 +700,7 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
|
|||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
size_t renderCount = 0;
|
||||
for (size_t i = 0; i < steps.size(); i++) {
|
||||
const GLRStep &step = *steps[i];
|
||||
GLRStep &step = *steps[i];
|
||||
|
||||
#if !defined(USING_GLES2)
|
||||
if (useDebugGroups_)
|
||||
|
@ -711,11 +711,10 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
|
|||
case GLRStepType::RENDER:
|
||||
renderCount++;
|
||||
if (IsVREnabled()) {
|
||||
GLRStep vrStep = step;
|
||||
PreprocessStepVR(&vrStep);
|
||||
PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount);
|
||||
PreprocessStepVR(&step);
|
||||
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
|
||||
} else {
|
||||
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount);
|
||||
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
|
||||
}
|
||||
break;
|
||||
case GLRStepType::COPY:
|
||||
|
@ -741,11 +740,14 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
|
|||
if (useDebugGroups_)
|
||||
glPopDebugGroup();
|
||||
#endif
|
||||
|
||||
if (frameData.profile.enabled) {
|
||||
frameData.profile.passesString += StepToString(step);
|
||||
}
|
||||
if (!keepSteps) {
|
||||
delete steps[i];
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
PerformBindFramebufferAsRenderTarget(step);
|
||||
|
@ -823,12 +838,43 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
bool clipDistanceEnabled[8]{};
|
||||
GLuint blendEqColor = (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]{};
|
||||
|
||||
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();
|
||||
auto &commands = step.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) {
|
||||
case GLRRenderCommand::DEPTH:
|
||||
if (c.depth.enabled) {
|
||||
|
@ -849,23 +895,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
depthEnabled = false;
|
||||
}
|
||||
break;
|
||||
case GLRRenderCommand::STENCILFUNC:
|
||||
if (c.stencilFunc.enabled) {
|
||||
case GLRRenderCommand::STENCIL:
|
||||
if (c.stencil.enabled) {
|
||||
if (!stencilEnabled) {
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
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) {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
stencilEnabled = false;
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
case GLRRenderCommand::STENCILOP:
|
||||
glStencilOp(c.stencilOp.sFail, c.stencilOp.zFail, c.stencilOp.pass);
|
||||
glStencilMask(c.stencilOp.writeMask);
|
||||
break;
|
||||
case GLRRenderCommand::BLEND:
|
||||
if (c.blend.enabled) {
|
||||
if (!blendEnabled) {
|
||||
|
@ -877,7 +934,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
blendEqColor = c.blend.funcColor;
|
||||
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);
|
||||
blendSrcColor = c.blend.srcColor;
|
||||
blendDstColor = c.blend.dstColor;
|
||||
blendSrcAlpha = c.blend.srcAlpha;
|
||||
blendDstAlpha = c.blend.dstAlpha;
|
||||
}
|
||||
} else if (/* !c.blend.enabled && */ blendEnabled) {
|
||||
glDisable(GL_BLEND);
|
||||
blendEnabled = false;
|
||||
|
@ -955,7 +1018,17 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
y = curFBHeight_ - y - c.viewport.vp.h;
|
||||
|
||||
// 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);
|
||||
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 (gl_extensions.IsGLES) {
|
||||
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
|
||||
|
@ -965,6 +1038,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
#else
|
||||
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
|
||||
#endif
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
|
@ -973,7 +1047,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
int y = c.scissor.rc.y;
|
||||
if (!curFB_)
|
||||
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);
|
||||
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();
|
||||
break;
|
||||
}
|
||||
|
@ -1038,28 +1118,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
{
|
||||
_dbg_assert_(curProgram);
|
||||
if (IsMultiviewSupported()) {
|
||||
int layout = GetStereoBufferIndex(c.uniformMatrix4.name);
|
||||
int layout = GetStereoBufferIndex(c.uniformStereoMatrix4.name);
|
||||
if (layout >= 0) {
|
||||
int size = 2 * 16 * sizeof(float);
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformMatrix4.loc);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, *c.uniformMatrix4.loc);
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformStereoMatrix4.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);
|
||||
memcpy(matrices, c.uniformMatrix4.m, size);
|
||||
memcpy(matrices, c.uniformStereoMatrix4.mData, size);
|
||||
glUnmapBuffer(GL_UNIFORM_BUFFER);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
}
|
||||
delete[] c.uniformStereoMatrix4.mData; // We only playback once.
|
||||
} else {
|
||||
int loc = c.uniformMatrix4.loc ? *c.uniformMatrix4.loc : -1;
|
||||
if (c.uniformMatrix4.name) {
|
||||
loc = curProgram->GetUniformLoc(c.uniformMatrix4.name);
|
||||
int loc = c.uniformStereoMatrix4.loc ? *c.uniformStereoMatrix4.loc : -1;
|
||||
if (c.uniformStereoMatrix4.name) {
|
||||
loc = curProgram->GetUniformLoc(c.uniformStereoMatrix4.name);
|
||||
}
|
||||
if (loc >= 0) {
|
||||
if (GetVRFBOIndex() == 0) {
|
||||
glUniformMatrix4fv(loc, 1, false, c.uniformMatrix4.m);
|
||||
glUniformMatrix4fv(loc, 1, false, c.uniformStereoMatrix4.mData);
|
||||
} 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();
|
||||
break;
|
||||
|
@ -1148,52 +1234,37 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::BIND_VERTEX_BUFFER:
|
||||
case GLRRenderCommand::DRAW:
|
||||
{
|
||||
// TODO: Add fast path for glBindVertexBuffer
|
||||
GLRInputLayout *layout = c.bindVertexBuffer.inputLayout;
|
||||
GLuint buf = c.bindVertexBuffer.buffer ? c.bindVertexBuffer.buffer->buffer_ : 0;
|
||||
_dbg_assert_(!c.bindVertexBuffer.buffer || !c.bindVertexBuffer.buffer->Mapped());
|
||||
GLRInputLayout *layout = c.draw.inputLayout;
|
||||
GLuint buf = c.draw.vertexBuffer->buffer_;
|
||||
_dbg_assert_(!c.draw.vertexBuffer->Mapped());
|
||||
if (buf != curArrayBuffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buf);
|
||||
curArrayBuffer = buf;
|
||||
}
|
||||
if (attrMask != layout->semanticsMask_) {
|
||||
int enable = layout->semanticsMask_ & ~attrMask;
|
||||
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);
|
||||
}
|
||||
}
|
||||
EnableDisableVertexArrays(attrMask, layout->semanticsMask_);
|
||||
attrMask = layout->semanticsMask_;
|
||||
}
|
||||
for (size_t i = 0; i < layout->entries.size(); 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();
|
||||
break;
|
||||
}
|
||||
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 (c.draw.indexBuffer) {
|
||||
GLuint buf = c.draw.indexBuffer->buffer_;
|
||||
_dbg_assert_(!c.draw.indexBuffer->Mapped());
|
||||
if (buf != curElemArrayBuffer) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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 {
|
||||
GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer_ : 0;
|
||||
_dbg_assert_(!(c.bind_buffer.buffer && c.bind_buffer.buffer->Mapped()));
|
||||
glBindBuffer(c.bind_buffer.target, buf);
|
||||
glDrawElementsInstanced(c.draw.mode, c.draw.count, c.draw.indexType, (void *)(intptr_t)c.draw.indexOffset, c.draw.instances);
|
||||
}
|
||||
} else {
|
||||
glDrawArrays(c.draw.mode, c.draw.first, c.draw.count);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
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?
|
||||
// Also, should this not be an init command?
|
||||
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();
|
||||
break;
|
||||
case GLRRenderCommand::TEXTURESAMPLER:
|
||||
|
@ -1315,8 +1376,14 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
|
|||
glEnable(GL_CULL_FACE);
|
||||
cullEnabled = true;
|
||||
}
|
||||
if (frontFace != c.raster.frontFace) {
|
||||
glFrontFace(c.raster.frontFace);
|
||||
frontFace = c.raster.frontFace;
|
||||
}
|
||||
if (cullFace != c.raster.cullFace) {
|
||||
glCullFace(c.raster.cullFace);
|
||||
cullFace = c.raster.cullFace;
|
||||
}
|
||||
} else if (/* !c.raster.cullEnable && */ cullEnabled) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
cullEnabled = false;
|
||||
|
@ -1784,3 +1851,74 @@ GLRFramebuffer::~GLRFramebuffer() {
|
|||
glDeleteRenderbuffers(1, &stencil_buffer);
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "Common/GPU/Shader.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/Data/Collections/TinySet.h"
|
||||
|
||||
#include "Common/Data/Collections/FastVec.h"
|
||||
|
||||
struct GLRViewport {
|
||||
float x, y, w, h, minZ, maxZ;
|
||||
|
@ -25,7 +25,7 @@ struct GLOffset2D {
|
|||
int x, y;
|
||||
};
|
||||
|
||||
enum class GLRAllocType {
|
||||
enum class GLRAllocType : uint8_t {
|
||||
NONE,
|
||||
NEW,
|
||||
ALIGNED,
|
||||
|
@ -40,8 +40,7 @@ class GLRInputLayout;
|
|||
|
||||
enum class GLRRenderCommand : uint8_t {
|
||||
DEPTH,
|
||||
STENCILFUNC,
|
||||
STENCILOP,
|
||||
STENCIL,
|
||||
BLEND,
|
||||
BLENDCOLOR,
|
||||
LOGICOP,
|
||||
|
@ -61,10 +60,8 @@ enum class GLRRenderCommand : uint8_t {
|
|||
BINDTEXTURE,
|
||||
BIND_FB_TEXTURE,
|
||||
BIND_VERTEX_BUFFER,
|
||||
BIND_BUFFER,
|
||||
GENMIPS,
|
||||
DRAW,
|
||||
DRAW_INDEXED,
|
||||
TEXTURE_SUBIMAGE,
|
||||
};
|
||||
|
||||
|
@ -72,6 +69,7 @@ enum class GLRRenderCommand : uint8_t {
|
|||
// type field, smashed right after each other?)
|
||||
// Also, all GLenums are really only 16 bits.
|
||||
struct GLRRenderData {
|
||||
GLRRenderData(GLRRenderCommand _cmd) : cmd(_cmd) {}
|
||||
GLRRenderCommand cmd;
|
||||
union {
|
||||
struct {
|
||||
|
@ -101,26 +99,23 @@ struct GLRRenderData {
|
|||
GLenum func;
|
||||
uint8_t ref;
|
||||
uint8_t compareMask;
|
||||
} stencilFunc;
|
||||
struct {
|
||||
GLenum sFail;
|
||||
GLenum zFail;
|
||||
GLenum pass;
|
||||
uint8_t writeMask;
|
||||
} stencilOp; // also write mask
|
||||
} stencil;
|
||||
struct {
|
||||
GLRInputLayout *inputLayout;
|
||||
GLRBuffer *vertexBuffer;
|
||||
GLRBuffer *indexBuffer;
|
||||
uint32_t vertexOffset;
|
||||
uint32_t indexOffset;
|
||||
GLenum mode; // primitive
|
||||
GLint buffer;
|
||||
GLint first;
|
||||
GLint count;
|
||||
} draw;
|
||||
struct {
|
||||
GLenum mode; // primitive
|
||||
GLint count;
|
||||
GLint instances;
|
||||
GLint indexType;
|
||||
void *indices;
|
||||
} drawIndexed;
|
||||
GLint instances;
|
||||
} draw;
|
||||
struct {
|
||||
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.
|
||||
|
@ -130,8 +125,13 @@ struct GLRRenderData {
|
|||
struct {
|
||||
const char *name; // if null, use loc
|
||||
const GLint *loc;
|
||||
float m[32];
|
||||
float m[16];
|
||||
} uniformMatrix4;
|
||||
struct {
|
||||
const char *name; // if null, use loc
|
||||
const GLint *loc;
|
||||
float *mData; // new'd, 32 entries
|
||||
} uniformStereoMatrix4;
|
||||
struct {
|
||||
uint32_t clearColor;
|
||||
float clearZ;
|
||||
|
@ -171,11 +171,6 @@ struct GLRRenderData {
|
|||
struct {
|
||||
GLRProgram *program;
|
||||
} program;
|
||||
struct {
|
||||
GLRInputLayout *inputLayout;
|
||||
GLRBuffer *buffer;
|
||||
size_t offset;
|
||||
} bindVertexBuffer;
|
||||
struct {
|
||||
int slot;
|
||||
GLenum wrapS;
|
||||
|
@ -295,16 +290,17 @@ enum class GLRRenderPassAction {
|
|||
|
||||
class GLRFramebuffer;
|
||||
|
||||
enum {
|
||||
enum GLRAspect {
|
||||
GLR_ASPECT_COLOR = 1,
|
||||
GLR_ASPECT_DEPTH = 2,
|
||||
GLR_ASPECT_STENCIL = 3,
|
||||
};
|
||||
const char *GLRAspectToString(GLRAspect aspect);
|
||||
|
||||
struct GLRStep {
|
||||
GLRStep(GLRStepType _type) : stepType(_type) {}
|
||||
GLRStepType stepType;
|
||||
std::vector<GLRRenderData> commands;
|
||||
FastVec<GLRRenderData> commands;
|
||||
TinySet<const GLRFramebuffer *, 8> dependencies;
|
||||
const char *tag;
|
||||
union {
|
||||
|
@ -313,8 +309,6 @@ struct GLRStep {
|
|||
GLRRenderPassAction color;
|
||||
GLRRenderPassAction depth;
|
||||
GLRRenderPassAction stencil;
|
||||
// Note: not accurate.
|
||||
int numDraws;
|
||||
} render;
|
||||
struct {
|
||||
GLRFramebuffer *src;
|
||||
|
@ -358,9 +352,9 @@ public:
|
|||
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 DestroyDeviceObjects();
|
||||
|
@ -385,7 +379,7 @@ private:
|
|||
void InitCreateFramebuffer(const GLRInitStep &step);
|
||||
|
||||
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 PerformBlit(const GLRStep &pass);
|
||||
void PerformReadback(const GLRStep &pass);
|
||||
|
@ -396,6 +390,8 @@ private:
|
|||
GLenum fbo_get_fb_target(bool read, GLuint **cached);
|
||||
void fbo_unbind();
|
||||
|
||||
std::string StepToString(const GLRStep &step) const;
|
||||
|
||||
GLRFramebuffer *curFB_ = nullptr;
|
||||
|
||||
GLuint globalVAO_ = 0;
|
||||
|
@ -427,3 +423,5 @@ private:
|
|||
ErrorCallbackFn errorCallback_ = nullptr;
|
||||
void *errorCallbackUserData_ = nullptr;
|
||||
};
|
||||
|
||||
const char *RenderCommandToString(GLRRenderCommand cmd);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Common/Log.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
|
||||
#if 0 // def _DEBUG
|
||||
|
@ -16,12 +17,7 @@
|
|||
#define VLOG(...)
|
||||
#endif
|
||||
|
||||
static std::thread::id renderThreadId;
|
||||
#if MAX_LOGLEVEL >= DEBUG_LEVEL
|
||||
static bool OnRenderThread() {
|
||||
return std::this_thread::get_id() == renderThreadId;
|
||||
}
|
||||
#endif
|
||||
std::thread::id renderThreadId;
|
||||
|
||||
GLRTexture::GLRTexture(const Draw::DeviceCaps &caps, int width, int height, int depth, int numMips) {
|
||||
if (caps.textureNPOTFullySupported) {
|
||||
|
@ -41,6 +37,11 @@ GLRTexture::~GLRTexture() {
|
|||
}
|
||||
}
|
||||
|
||||
GLRenderManager::GLRenderManager() {
|
||||
// size_t sz = sizeof(GLRRenderData);
|
||||
// _dbg_assert_(sz == 88);
|
||||
}
|
||||
|
||||
GLRenderManager::~GLRenderManager() {
|
||||
_dbg_assert_(!run_);
|
||||
|
||||
|
@ -128,25 +129,24 @@ bool GLRenderManager::ThreadFrame() {
|
|||
return false;
|
||||
}
|
||||
|
||||
GLRRenderThreadTask task;
|
||||
GLRRenderThreadTask *task = nullptr;
|
||||
|
||||
// In case of syncs or other partial completion, we keep going until we complete a frame.
|
||||
while (true) {
|
||||
// Pop a task of the queue and execute it.
|
||||
// NOTE: We need to actually wait for a task, we can't just bail!
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
while (renderThreadQueue_.empty()) {
|
||||
pushCondVar_.wait(lock);
|
||||
}
|
||||
task = renderThreadQueue_.front();
|
||||
task = std::move(renderThreadQueue_.front());
|
||||
renderThreadQueue_.pop();
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (task.runType == GLRRunType::EXIT) {
|
||||
if (task->runType == GLRRunType::EXIT) {
|
||||
// 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.
|
||||
std::unique_lock<std::mutex> lock(syncMutex_);
|
||||
|
@ -156,11 +156,13 @@ bool GLRenderManager::ThreadFrame() {
|
|||
}
|
||||
|
||||
// Render the scene.
|
||||
VLOG(" PULL: Frame %d RUN (%0.3f)", task.frame, time_now_d());
|
||||
if (Run(task)) {
|
||||
VLOG(" PULL: Frame %d RUN (%0.3f)", task->frame, time_now_d());
|
||||
if (Run(*task)) {
|
||||
// Swap requested, so we just bail the loop.
|
||||
delete task;
|
||||
break;
|
||||
}
|
||||
delete task;
|
||||
};
|
||||
|
||||
return true;
|
||||
|
@ -173,15 +175,21 @@ void GLRenderManager::StopThread() {
|
|||
run_ = false;
|
||||
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
GLRRenderThreadTask exitTask{};
|
||||
exitTask.runType = GLRRunType::EXIT;
|
||||
renderThreadQueue_.push(exitTask);
|
||||
renderThreadQueue_.push(new GLRRenderThreadTask(GLRRunType::EXIT));
|
||||
pushCondVar_.notify_one();
|
||||
} else {
|
||||
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) {
|
||||
_assert_(insideFrame_);
|
||||
#ifdef _DEBUG
|
||||
|
@ -206,13 +214,11 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende
|
|||
step->render.color = color;
|
||||
step->render.depth = depth;
|
||||
step->render.stencil = stencil;
|
||||
step->render.numDraws = 0;
|
||||
step->tag = tag;
|
||||
steps_.push_back(step);
|
||||
|
||||
GLuint clearMask = 0;
|
||||
GLRRenderData data;
|
||||
data.cmd = GLRRenderCommand::CLEAR;
|
||||
GLRRenderData data(GLRRenderCommand::CLEAR);
|
||||
if (color == GLRRenderPassAction::CLEAR) {
|
||||
clearMask |= GL_COLOR_BUFFER_BIT;
|
||||
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);
|
||||
}
|
||||
|
||||
void GLRenderManager::BeginFrame() {
|
||||
void GLRenderManager::BeginFrame(bool enableProfiling) {
|
||||
#ifdef _DEBUG
|
||||
curProgram_ = nullptr;
|
||||
#endif
|
||||
|
@ -344,6 +350,8 @@ void GLRenderManager::BeginFrame() {
|
|||
int curFrame = GetCurFrame();
|
||||
|
||||
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());
|
||||
std::unique_lock<std::mutex> lock(frameData.fenceMutex);
|
||||
|
@ -369,20 +377,36 @@ void GLRenderManager::Finish() {
|
|||
frameData_[curFrame].deleter.Take(deleter_);
|
||||
|
||||
VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame);
|
||||
GLRRenderThreadTask task;
|
||||
task.frame = curFrame;
|
||||
task.runType = GLRRunType::PRESENT;
|
||||
GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::PRESENT);
|
||||
task->frame = curFrame;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back().initSteps = std::move(initSteps_);
|
||||
renderThreadQueue_.back().steps = std::move(steps_);
|
||||
renderThreadQueue_.back()->initSteps = std::move(initSteps_);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
initSteps_.clear();
|
||||
steps_.clear();
|
||||
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_++;
|
||||
if (curFrame_ >= inflightFrames_)
|
||||
curFrame_ = 0;
|
||||
|
@ -412,15 +436,23 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) {
|
|||
}
|
||||
}
|
||||
|
||||
if (frameData.profile.enabled) {
|
||||
frameData.profile.cpuStartTime = time_now_d();
|
||||
}
|
||||
|
||||
if (IsVREnabled()) {
|
||||
int passes = GetVRPassesCount();
|
||||
for (int i = 0; i < passes; i++) {
|
||||
PreVRFrameRender(i);
|
||||
queueRunner_.RunSteps(task.steps, skipGLCalls_, i < passes - 1, true);
|
||||
queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, i < passes - 1, true);
|
||||
PostVRFrameRender();
|
||||
}
|
||||
} 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_) {
|
||||
|
@ -491,14 +523,13 @@ void GLRenderManager::FlushSync() {
|
|||
{
|
||||
VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame_);
|
||||
|
||||
GLRRenderThreadTask task;
|
||||
task.frame = curFrame_;
|
||||
task.runType = GLRRunType::SYNC;
|
||||
GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::SYNC);
|
||||
task->frame = curFrame_;
|
||||
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back().initSteps = std::move(initSteps_);
|
||||
renderThreadQueue_.back().steps = std::move(steps_);
|
||||
renderThreadQueue_.back()->initSteps = std::move(initSteps_);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
steps_.clear();
|
||||
}
|
||||
|
@ -513,254 +544,3 @@ void GLRenderManager::FlushSync() {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Common/GPU/OpenGL/GLQueueRunner.h"
|
||||
#include "Common/GPU/OpenGL/GLFrameData.h"
|
||||
#include "Common/GPU/OpenGL/GLCommon.h"
|
||||
#include "Common/GPU/OpenGL/GLMemory.h"
|
||||
|
||||
class GLRInputLayout;
|
||||
class GLPushBuffer;
|
||||
|
@ -52,13 +53,14 @@ public:
|
|||
|
||||
class GLRFramebuffer {
|
||||
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),
|
||||
width(_width), height(_height), z_stencil_(z_stencil) {
|
||||
}
|
||||
|
||||
~GLRFramebuffer();
|
||||
|
||||
const char *Tag() const { return tag_.c_str(); }
|
||||
|
||||
GLuint handle = 0;
|
||||
GLRTexture color_texture;
|
||||
// Either z_stencil_texture, z_stencil_buffer, or (z_buffer and stencil_buffer) are set.
|
||||
|
@ -70,8 +72,10 @@ public:
|
|||
int width;
|
||||
int height;
|
||||
GLuint colorDepth = 0;
|
||||
|
||||
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
|
||||
|
@ -179,178 +183,6 @@ private:
|
|||
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 {
|
||||
public:
|
||||
struct Entry {
|
||||
|
@ -374,14 +206,19 @@ enum class GLRRunType {
|
|||
class GLRenderManager;
|
||||
class GLPushBuffer;
|
||||
|
||||
// These are enqueued from the main thread,
|
||||
// and the render thread pops them off
|
||||
// These are enqueued from the main thread, and the render thread pops them off
|
||||
struct GLRRenderThreadTask {
|
||||
std::vector<GLRStep *> steps;
|
||||
std::vector<GLRInitStep> initSteps;
|
||||
GLRRenderThreadTask(GLRRunType _runType) : runType(_runType) {}
|
||||
|
||||
int frame;
|
||||
std::vector<GLRStep *> steps;
|
||||
FastVec<GLRInitStep> initSteps;
|
||||
|
||||
int frame = -1;
|
||||
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
|
||||
|
@ -389,9 +226,11 @@ struct GLRRenderThreadTask {
|
|||
// directly in the destructor.
|
||||
class GLRenderManager {
|
||||
public:
|
||||
GLRenderManager() {}
|
||||
GLRenderManager();
|
||||
~GLRenderManager();
|
||||
|
||||
GLRenderManager(GLRenderManager &) = delete;
|
||||
GLRenderManager &operator=(GLRenderManager &) = delete;
|
||||
|
||||
void SetInvalidationCallback(InvalidationCallback callback) {
|
||||
invalidationCallback_ = callback;
|
||||
|
@ -409,8 +248,10 @@ public:
|
|||
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.
|
||||
void BeginFrame();
|
||||
void BeginFrame(bool enableProfiling);
|
||||
// Can run on a different thread!
|
||||
void Finish();
|
||||
|
||||
|
@ -418,37 +259,40 @@ public:
|
|||
// 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.
|
||||
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->target = target;
|
||||
initSteps_.push_back(step);
|
||||
return step.create_texture.texture;
|
||||
}
|
||||
|
||||
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.size = (int)size;
|
||||
step.create_buffer.usage = usage;
|
||||
initSteps_.push_back(step);
|
||||
return step.create_buffer.buffer;
|
||||
}
|
||||
|
||||
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->desc = desc;
|
||||
step.create_shader.stage = stage;
|
||||
step.create_shader.code = new char[code.size() + 1];
|
||||
memcpy(step.create_shader.code, code.data(), code.size() + 1);
|
||||
initSteps_.push_back(step);
|
||||
return step.create_shader.shader;
|
||||
}
|
||||
|
||||
GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil) {
|
||||
GLRInitStep step{ GLRInitStepType::CREATE_FRAMEBUFFER };
|
||||
step.create_framebuffer.framebuffer = new GLRFramebuffer(caps_, width, height, z_stencil);
|
||||
initSteps_.push_back(step);
|
||||
GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil, const char *tag) {
|
||||
_dbg_assert_(width > 0 && height > 0 && tag != nullptr);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -457,7 +301,8 @@ public:
|
|||
GLRProgram *CreateProgram(
|
||||
std::vector<GLRShader *> shaders, std::vector<GLRProgram::Semantic> semantics, std::vector<GLRProgram::UniformLocQuery> queries,
|
||||
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));
|
||||
step.create_program.program = new GLRProgram();
|
||||
step.create_program.program->semantics_ = semantics;
|
||||
|
@ -481,23 +326,22 @@ public:
|
|||
}
|
||||
#endif
|
||||
step.create_program.num_shaders = (int)shaders.size();
|
||||
initSteps_.push_back(step);
|
||||
return step.create_program.program;
|
||||
}
|
||||
|
||||
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->entries = entries;
|
||||
for (auto &iter : step.create_input_layout.inputLayout->entries) {
|
||||
step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location;
|
||||
}
|
||||
initSteps_.push_back(step);
|
||||
return step.create_input_layout.inputLayout;
|
||||
}
|
||||
|
||||
GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size) {
|
||||
GLPushBuffer *push = new GLPushBuffer(this, target, size);
|
||||
GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size, const char *tag) {
|
||||
GLPushBuffer *push = new GLPushBuffer(this, target, size, tag);
|
||||
RegisterPushBuffer(frame, push);
|
||||
return push;
|
||||
}
|
||||
|
@ -565,7 +409,8 @@ public:
|
|||
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
|
||||
// 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 <= buffer->size_ - size);
|
||||
step.buffer_subdata.buffer = buffer;
|
||||
|
@ -573,12 +418,12 @@ public:
|
|||
step.buffer_subdata.size = (int)size;
|
||||
step.buffer_subdata.data = data;
|
||||
step.buffer_subdata.deleteData = deleteData;
|
||||
initSteps_.push_back(step);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
GLRInitStep step{ GLRInitStepType::TEXTURE_IMAGE };
|
||||
GLRInitStep &step = initSteps_.push_uninitialized();
|
||||
step.stepType = GLRInitStepType::TEXTURE_IMAGE;
|
||||
step.texture_image.texture = texture;
|
||||
step.texture_image.data = data;
|
||||
step.texture_image.format = format;
|
||||
|
@ -588,12 +433,11 @@ public:
|
|||
step.texture_image.depth = depth;
|
||||
step.texture_image.allocType = allocType;
|
||||
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) {
|
||||
_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.data = data;
|
||||
_data.texture_subimage.format = format;
|
||||
|
@ -608,11 +452,11 @@ public:
|
|||
}
|
||||
|
||||
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.loadedLevels = loadedLevels;
|
||||
step.texture_finalize.genMips = genMips;
|
||||
initSteps_.push_back(step);
|
||||
}
|
||||
|
||||
void BindTexture(int slot, GLRTexture *tex) {
|
||||
|
@ -623,62 +467,44 @@ public:
|
|||
}
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
_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.texture = tex;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void BindProgram(GLRProgram *program) {
|
||||
_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);
|
||||
data.program.program = program;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
#ifdef _DEBUG
|
||||
curProgram_ = program;
|
||||
#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) {
|
||||
_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.write = write;
|
||||
data.depth.func = func;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetViewport(const GLRViewport &vp) {
|
||||
_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;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetScissor(const GLRect2D &rc) {
|
||||
_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;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformI(const GLint *loc, int count, const int *udata) {
|
||||
|
@ -686,11 +512,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = count;
|
||||
memcpy(data.uniform4.v, udata, sizeof(int) * count);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformI1(const GLint *loc, int udata) {
|
||||
|
@ -698,11 +525,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = 1;
|
||||
memcpy(data.uniform4.v, &udata, sizeof(udata));
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformUI(const GLint *loc, int count, const uint32_t *udata) {
|
||||
|
@ -710,11 +538,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = count;
|
||||
memcpy(data.uniform4.v, udata, sizeof(uint32_t) * count);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformUI1(const GLint *loc, uint32_t udata) {
|
||||
|
@ -722,11 +551,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = 1;
|
||||
memcpy(data.uniform4.v, &udata, sizeof(udata));
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformF(const GLint *loc, int count, const float *udata) {
|
||||
|
@ -734,11 +564,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = count;
|
||||
memcpy(data.uniform4.v, udata, sizeof(float) * count);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformF1(const GLint *loc, const float udata) {
|
||||
|
@ -746,11 +577,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#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.count = 1;
|
||||
memcpy(data.uniform4.v, &udata, sizeof(float));
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformF(const char *name, int count, const float *udata) {
|
||||
|
@ -758,11 +590,12 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#endif
|
||||
GLRRenderData data{ GLRRenderCommand::UNIFORM4F };
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::UNIFORM4F;
|
||||
data.uniform4.name = name;
|
||||
data.uniform4.loc = nullptr;
|
||||
data.uniform4.count = count;
|
||||
memcpy(data.uniform4.v, udata, sizeof(float) * count);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetUniformM4x4(const GLint *loc, const float *udata) {
|
||||
|
@ -770,10 +603,11 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#endif
|
||||
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX };
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
|
||||
data.uniformMatrix4.name = nullptr;
|
||||
data.uniformMatrix4.loc = loc;
|
||||
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) {
|
||||
|
@ -781,12 +615,13 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#endif
|
||||
GLRRenderData data{ GLRRenderCommand::UNIFORMSTEREOMATRIX };
|
||||
data.uniformMatrix4.name = name;
|
||||
data.uniformMatrix4.loc = loc;
|
||||
memcpy(&data.uniformMatrix4.m[0], left, sizeof(float) * 16);
|
||||
memcpy(&data.uniformMatrix4.m[16], right, sizeof(float) * 16);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::UNIFORMSTEREOMATRIX;
|
||||
data.uniformStereoMatrix4.name = name;
|
||||
data.uniformStereoMatrix4.loc = loc;
|
||||
data.uniformStereoMatrix4.mData = new float[32];
|
||||
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) {
|
||||
|
@ -794,17 +629,19 @@ public:
|
|||
#ifdef _DEBUG
|
||||
_dbg_assert_(curProgram_);
|
||||
#endif
|
||||
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX };
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
|
||||
data.uniformMatrix4.name = name;
|
||||
data.uniformMatrix4.loc = nullptr;
|
||||
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) {
|
||||
// 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.
|
||||
_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.enabled = blendEnabled;
|
||||
data.blend.srcColor = srcColor;
|
||||
|
@ -813,96 +650,88 @@ public:
|
|||
data.blend.dstAlpha = dstAlpha;
|
||||
data.blend.funcColor = funcColor;
|
||||
data.blend.funcAlpha = funcAlpha;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetNoBlendAndMask(int colorMask) {
|
||||
_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.enabled = false;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
#ifndef USING_GLES2
|
||||
void SetLogicOp(bool enabled, GLenum logicOp) {
|
||||
_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.logicOp = logicOp;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
#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);
|
||||
GLRRenderData data{ GLRRenderCommand::STENCILFUNC };
|
||||
data.stencilFunc.enabled = enabled;
|
||||
data.stencilFunc.func = func;
|
||||
data.stencilFunc.ref = refValue;
|
||||
data.stencilFunc.compareMask = compareMask;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetStencilOp(uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
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);
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::STENCIL;
|
||||
data.stencil.enabled = enabled;
|
||||
data.stencil.func = func;
|
||||
data.stencil.ref = refValue;
|
||||
data.stencil.compareMask = compareMask;
|
||||
data.stencil.writeMask = writeMask;
|
||||
data.stencil.sFail = sFail;
|
||||
data.stencil.zFail = zFail;
|
||||
data.stencil.pass = pass;
|
||||
}
|
||||
|
||||
void SetStencilDisabled() {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
GLRRenderData data;
|
||||
data.cmd = GLRRenderCommand::STENCILFUNC;
|
||||
data.stencilFunc.enabled = false;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::STENCIL;
|
||||
data.stencil.enabled = false;
|
||||
}
|
||||
|
||||
void SetBlendFactor(const float color[4]) {
|
||||
_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);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable, GLboolean depthClamp) {
|
||||
_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.frontFace = frontFace;
|
||||
data.raster.cullFace = cullFace;
|
||||
data.raster.ditherEnable = ditherEnable;
|
||||
data.raster.depthClampEnable = depthClamp;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
_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.wrapS = wrapS;
|
||||
data.textureSampler.wrapT = wrapT;
|
||||
data.textureSampler.magFilter = magFilter;
|
||||
data.textureSampler.minFilter = minFilter;
|
||||
data.textureSampler.anisotropy = anisotropy;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetTextureLod(int slot, float minLod, float maxLod, float lodBias) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
_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.minLod = minLod;
|
||||
data.textureLod.maxLod = maxLod;
|
||||
data.textureLod.lodBias = lodBias;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!clearMask)
|
||||
return;
|
||||
GLRRenderData data{ GLRRenderCommand::CLEAR };
|
||||
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = GLRRenderCommand::CLEAR;
|
||||
data.clear.clearMask = clearMask;
|
||||
data.clear.clearColor = clearColor;
|
||||
data.clear.clearZ = clearZ;
|
||||
|
@ -920,30 +750,36 @@ public:
|
|||
data.clear.scissorY = scissorY;
|
||||
data.clear.scissorW = scissorW;
|
||||
data.clear.scissorH = scissorH;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void Draw(GLenum mode, int first, int count) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
GLRRenderData data{ GLRRenderCommand::DRAW };
|
||||
void Draw(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLenum mode, int first, int count) {
|
||||
_dbg_assert_(vertexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
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.first = first;
|
||||
data.draw.count = count;
|
||||
data.draw.buffer = 0;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
curRenderStep_->render.numDraws++;
|
||||
data.draw.indexType = 0;
|
||||
}
|
||||
|
||||
void DrawIndexed(GLenum mode, int count, GLenum indexType, void *indices, int instances = 1) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
GLRRenderData data{ GLRRenderCommand::DRAW_INDEXED };
|
||||
data.drawIndexed.mode = mode;
|
||||
data.drawIndexed.count = count;
|
||||
data.drawIndexed.indexType = indexType;
|
||||
data.drawIndexed.instances = instances;
|
||||
data.drawIndexed.indices = indices;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
curRenderStep_->render.numDraws++;
|
||||
// Would really love to have a basevertex parameter, but impossible in unextended GLES, without glDrawElementsBaseVertex, unfortunately.
|
||||
void DrawIndexed(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLRBuffer *indexBuffer, uint32_t indexOffset, GLenum mode, int count, GLenum indexType, int instances = 1) {
|
||||
_dbg_assert_(vertexBuffer && indexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
|
||||
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 = indexBuffer;
|
||||
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 };
|
||||
|
@ -1026,7 +862,7 @@ private:
|
|||
|
||||
GLRStep *curRenderStep_ = nullptr;
|
||||
std::vector<GLRStep *> steps_;
|
||||
std::vector<GLRInitStep> initSteps_;
|
||||
FastVec<GLRInitStep> initSteps_;
|
||||
|
||||
// Execution time state
|
||||
bool run_ = true;
|
||||
|
@ -1038,7 +874,7 @@ private:
|
|||
std::mutex pushMutex_;
|
||||
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.
|
||||
std::mutex syncMutex_;
|
||||
|
@ -1072,5 +908,6 @@ private:
|
|||
#endif
|
||||
Draw::DeviceCaps caps_{};
|
||||
|
||||
std::string profilePassesString_;
|
||||
InvalidationCallback invalidationCallback_;
|
||||
};
|
||||
|
|
|
@ -180,8 +180,9 @@ public:
|
|||
|
||||
void Apply(GLRenderManager *render, uint8_t stencilRef, uint8_t stencilWriteMask, uint8_t stencilCompareMask) {
|
||||
render->SetDepth(depthTestEnabled, depthWriteEnabled, depthComp);
|
||||
render->SetStencilFunc(stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask);
|
||||
render->SetStencilOp(stencilWriteMask, stencilFail, stencilZFail, stencilPass);
|
||||
render->SetStencil(
|
||||
stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask,
|
||||
stencilWriteMask, stencilFail, stencilZFail, stencilPass);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -328,6 +329,9 @@ public:
|
|||
DrawContext::SetTargetSize(w, h);
|
||||
renderManager_.Resize(w, h);
|
||||
}
|
||||
void SetDebugFlags(DebugFlags flags) override {
|
||||
debugFlags_ = flags;
|
||||
}
|
||||
|
||||
const DeviceCaps &GetDeviceCaps() const override {
|
||||
return caps_;
|
||||
|
@ -366,7 +370,12 @@ public:
|
|||
void BeginFrame() 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 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;
|
||||
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;
|
||||
stencilCompareMask_ = compareMask;
|
||||
// Do we need to update on the fly here?
|
||||
renderManager_.SetStencilFunc(
|
||||
renderManager_.SetStencil(
|
||||
curPipeline_->depthStencil->stencilEnabled,
|
||||
curPipeline_->depthStencil->stencilCompareOp,
|
||||
refValue,
|
||||
compareMask);
|
||||
renderManager_.SetStencilOp(
|
||||
compareMask,
|
||||
writeMask,
|
||||
curPipeline_->depthStencil->stencilFail,
|
||||
curPipeline_->depthStencil->stencilZFail,
|
||||
|
@ -488,6 +496,7 @@ private:
|
|||
void ApplySamplers();
|
||||
|
||||
GLRenderManager renderManager_;
|
||||
int frameCount_ = 0;
|
||||
|
||||
DeviceCaps caps_{};
|
||||
|
||||
|
@ -514,6 +523,8 @@ private:
|
|||
GLPushBuffer *push;
|
||||
};
|
||||
FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]{};
|
||||
|
||||
DebugFlags debugFlags_ = DebugFlags::NONE;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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)) {
|
||||
|
@ -778,7 +789,7 @@ OpenGLContext::~OpenGLContext() {
|
|||
}
|
||||
|
||||
void OpenGLContext::BeginFrame() {
|
||||
renderManager_.BeginFrame();
|
||||
renderManager_.BeginFrame(debugFlags_ & DebugFlags::PROFILE_TIMESTAMPS);
|
||||
FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
|
||||
renderManager_.BeginPushBuffer(frameData.push);
|
||||
}
|
||||
|
@ -789,6 +800,7 @@ void OpenGLContext::EndFrame() {
|
|||
renderManager_.Finish();
|
||||
|
||||
Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
|
||||
frameCount_++;
|
||||
}
|
||||
|
||||
void OpenGLContext::Invalidate(InvalidationFlags flags) {
|
||||
|
@ -810,7 +822,7 @@ InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) {
|
|||
return fmt;
|
||||
}
|
||||
|
||||
GLuint TypeToTarget(TextureType type) {
|
||||
static GLuint TypeToTarget(TextureType type) {
|
||||
switch (type) {
|
||||
#ifndef USING_GLES2
|
||||
case TextureType::LINEAR1D: return GL_TEXTURE_1D;
|
||||
|
@ -848,25 +860,33 @@ public:
|
|||
return tex_;
|
||||
}
|
||||
|
||||
void UpdateTextureLevels(GLRenderManager *render, const uint8_t *const *data, int numLevels, TextureCallback initDataCallback);
|
||||
|
||||
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_;
|
||||
GLRTexture *tex_;
|
||||
|
||||
DataFormat format_;
|
||||
TextureType type_;
|
||||
int mipLevels_;
|
||||
bool generatedMips_;
|
||||
bool generateMips_; // Generate mips requested
|
||||
bool generatedMips_; // Has generated mips
|
||||
};
|
||||
|
||||
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;
|
||||
generateMips_ = desc.generateMips;
|
||||
width_ = desc.width;
|
||||
height_ = desc.height;
|
||||
depth_ = desc.depth;
|
||||
format_ = desc.format;
|
||||
type_ = desc.type;
|
||||
|
||||
GLenum target = TypeToTarget(desc.type);
|
||||
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())
|
||||
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 width = width_;
|
||||
int height = height_;
|
||||
int depth = depth_;
|
||||
for (auto data : desc.initData) {
|
||||
SetImageData(0, 0, 0, width, height, depth, level, 0, data, desc.initDataCallback);
|
||||
for (int i = 0; i < numLevels; i++) {
|
||||
SetImageData(0, 0, 0, width, height, depth, level, 0, data[i], initDataCallback);
|
||||
width = (width + 1) / 2;
|
||||
height = (height + 1) / 2;
|
||||
depth = (depth + 1) / 2;
|
||||
level++;
|
||||
}
|
||||
mipLevels_ = desc.generateMips ? desc.mipLevels : level;
|
||||
mipLevels_ = generateMips_ ? mipLevels_ : level;
|
||||
|
||||
bool genMips = false;
|
||||
if ((int)desc.initData.size() < desc.mipLevels && desc.generateMips) {
|
||||
if (numLevels < mipLevels_ && generateMips_) {
|
||||
// Assumes the texture is bound for editing
|
||||
genMips = true;
|
||||
generatedMips_ = true;
|
||||
|
@ -918,7 +942,7 @@ public:
|
|||
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) {
|
||||
// When switching to texStorage we need to handle this correctly.
|
||||
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)];
|
||||
|
||||
bool texDataPopulated = false;
|
||||
if (callback) {
|
||||
texDataPopulated = callback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment);
|
||||
if (initDataCallback) {
|
||||
texDataPopulated = initDataCallback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment);
|
||||
}
|
||||
if (texDataPopulated) {
|
||||
if (format_ == DataFormat::A1R5G5B5_UNORM_PACK16) {
|
||||
|
@ -1016,6 +1040,11 @@ Texture *OpenGLContext::CreateTexture(const TextureDesc &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) {
|
||||
OpenGLDepthStencilState *ds = new OpenGLDepthStencilState();
|
||||
ds->depthTestEnabled = desc.depthTestEnabled;
|
||||
|
@ -1332,38 +1361,37 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
|
|||
void OpenGLContext::Draw(int vertexCount, int offset) {
|
||||
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call Draw without a vertex buffer");
|
||||
ApplySamplers();
|
||||
if (curPipeline_->inputLayout) {
|
||||
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]);
|
||||
}
|
||||
renderManager_.Draw(curPipeline_->prim, offset, vertexCount);
|
||||
_assert_(curPipeline_->inputLayout);
|
||||
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0], curPipeline_->prim, offset, vertexCount);
|
||||
}
|
||||
|
||||
void OpenGLContext::DrawIndexed(int vertexCount, int offset) {
|
||||
_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");
|
||||
ApplySamplers();
|
||||
if (curPipeline_->inputLayout) {
|
||||
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]);
|
||||
}
|
||||
renderManager_.BindIndexBuffer(curIBuffer_->buffer_);
|
||||
renderManager_.DrawIndexed(curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT, (void *)((intptr_t)curIBufferOffset_ + offset * sizeof(uint32_t)));
|
||||
_assert_(curPipeline_->inputLayout);
|
||||
renderManager_.DrawIndexed(
|
||||
curPipeline_->inputLayout->inputLayout_,
|
||||
curVBuffers_[0]->buffer_, curVBufferOffsets_[0],
|
||||
curIBuffer_->buffer_, curIBufferOffset_ + offset * sizeof(uint32_t),
|
||||
curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT);
|
||||
}
|
||||
|
||||
void OpenGLContext::DrawUP(const void *vdata, int vertexCount) {
|
||||
_assert_(curPipeline_->inputLayout != nullptr);
|
||||
int stride = curPipeline_->inputLayout->stride;
|
||||
size_t dataSize = stride * vertexCount;
|
||||
uint32_t dataSize = stride * vertexCount;
|
||||
|
||||
FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
|
||||
|
||||
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();
|
||||
if (curPipeline_->inputLayout) {
|
||||
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset);
|
||||
}
|
||||
renderManager_.Draw(curPipeline_->prim, 0, vertexCount);
|
||||
_assert_(curPipeline_->inputLayout);
|
||||
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, buf, offset, curPipeline_->prim, 0, vertexCount);
|
||||
}
|
||||
|
||||
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).
|
||||
_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);
|
||||
return fbo;
|
||||
}
|
||||
|
|
|
@ -592,10 +592,18 @@ void VulkanContext::ChooseDevice(int physical_device) {
|
|||
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR};
|
||||
// Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs.
|
||||
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;
|
||||
multiViewFeatures.pNext = &presentWaitFeatures;
|
||||
presentWaitFeatures.pNext = &presentIdFeatures;
|
||||
|
||||
vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2);
|
||||
deviceFeatures_.available.standard = features2.features;
|
||||
deviceFeatures_.available.multiview = multiViewFeatures;
|
||||
deviceFeatures_.available.presentWait = presentWaitFeatures;
|
||||
deviceFeatures_.available.presentId = presentIdFeatures;
|
||||
} else {
|
||||
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
|
||||
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.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;
|
||||
|
||||
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_.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 };
|
||||
|
||||
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() {
|
||||
_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_);
|
||||
if (res == VK_ERROR_SURFACE_LOST_KHR) {
|
||||
// Not much to do.
|
||||
|
|
|
@ -264,6 +264,8 @@ public:
|
|||
struct AllPhysicalDeviceFeatures {
|
||||
VkPhysicalDeviceFeatures standard;
|
||||
VkPhysicalDeviceMultiviewFeatures multiview;
|
||||
VkPhysicalDevicePresentWaitFeaturesKHR presentWait;
|
||||
VkPhysicalDevicePresentIdFeaturesKHR presentId;
|
||||
};
|
||||
|
||||
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {
|
||||
|
|
|
@ -53,8 +53,14 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
|
|||
return false;
|
||||
case 1303270965:
|
||||
// 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;
|
||||
break;
|
||||
|
||||
case 606910136:
|
||||
case -392708513:
|
||||
case -384083808:
|
||||
|
|
|
@ -121,7 +121,7 @@ VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) {
|
|||
}
|
||||
|
||||
// Good spot to reset the query pool.
|
||||
if (profilingEnabled_) {
|
||||
if (profile.enabled) {
|
||||
vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES);
|
||||
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;
|
||||
|
||||
if (hasInitCommands) {
|
||||
if (profilingEnabled_) {
|
||||
if (profile.enabled) {
|
||||
// Pre-allocated query ID 1 - end of init cmdbuf.
|
||||
vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1);
|
||||
}
|
||||
|
|
|
@ -19,11 +19,14 @@ enum class VKRRunType {
|
|||
};
|
||||
|
||||
struct QueueProfileContext {
|
||||
bool enabled = false;
|
||||
bool timestampsEnabled = false;
|
||||
VkQueryPool queryPool;
|
||||
std::vector<std::string> timestampDescriptions;
|
||||
std::string profileSummary;
|
||||
double cpuStartTime;
|
||||
double cpuEndTime;
|
||||
double descWriteTime;
|
||||
};
|
||||
|
||||
class VKRFramebuffer;
|
||||
|
@ -92,8 +95,7 @@ struct FrameData {
|
|||
uint32_t curSwapchainImage = -1;
|
||||
|
||||
// Profiling.
|
||||
QueueProfileContext profile;
|
||||
bool profilingEnabled_ = false;
|
||||
QueueProfileContext profile{};
|
||||
|
||||
// Async readback cache.
|
||||
DenseHashMap<ReadbackKey, CachedReadback*, nullptr> readbacks_;
|
||||
|
|
|
@ -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
|
||||
|
||||
VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
|
||||
bool selfDependency = RenderPassTypeHasInput(rpType);
|
||||
bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
|
||||
bool hasDepth = RenderPassTypeHasDepth(rpType);
|
||||
bool multiview = RenderPassTypeHasMultiView(rpType);
|
||||
|
@ -330,7 +329,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
|
||||
VkAttachmentReference colorReference{};
|
||||
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{};
|
||||
depthReference.attachment = depthAttachmentIndex;
|
||||
|
@ -339,20 +338,15 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.flags = 0;
|
||||
if (selfDependency) {
|
||||
subpass.inputAttachmentCount = 1;
|
||||
subpass.pInputAttachments = &colorReference;
|
||||
} else {
|
||||
subpass.inputAttachmentCount = 0;
|
||||
subpass.pInputAttachments = nullptr;
|
||||
}
|
||||
subpass.colorAttachmentCount = 1;
|
||||
subpass.pColorAttachments = &colorReference;
|
||||
|
||||
VkAttachmentReference colorResolveReference;
|
||||
if (multisample) {
|
||||
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;
|
||||
} else {
|
||||
subpass.pResolveAttachments = nullptr;
|
||||
|
@ -396,17 +390,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
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) {
|
||||
rp.dependencyCount = (u32)numDeps;
|
||||
rp.pDependencies = deps;
|
||||
|
@ -463,10 +446,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
VkSubpassDescription2KHR subpass2{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR };
|
||||
subpass2.colorAttachmentCount = subpass.colorAttachmentCount;
|
||||
subpass2.flags = subpass.flags;
|
||||
if (selfDependency) {
|
||||
subpass2.inputAttachmentCount = subpass.inputAttachmentCount;
|
||||
subpass2.pInputAttachments = &colorReference2;
|
||||
}
|
||||
subpass2.pColorAttachments = &colorReference2;
|
||||
if (hasDepth) {
|
||||
subpass2.pDepthStencilAttachment = &depthReference2;
|
||||
|
@ -476,7 +455,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
if (multisample) {
|
||||
colorResolveReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
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;
|
||||
} else {
|
||||
subpass2.pResolveAttachments = nullptr;
|
||||
|
|
|
@ -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
|
||||
// they can be OR-ed together in MergeRPTypes.
|
||||
HAS_DEPTH = 1,
|
||||
COLOR_INPUT = 2, // input attachment
|
||||
MULTIVIEW = 4,
|
||||
MULTISAMPLE = 8,
|
||||
MULTIVIEW = 2,
|
||||
MULTISAMPLE = 4,
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
BACKBUFFER = 16,
|
||||
BACKBUFFER = 8,
|
||||
|
||||
TYPE_COUNT = BACKBUFFER + 1,
|
||||
};
|
||||
|
@ -107,10 +106,6 @@ inline bool RenderPassTypeHasDepth(RenderPassType type) {
|
|||
return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
|
||||
}
|
||||
|
||||
inline bool RenderPassTypeHasInput(RenderPassType type) {
|
||||
return (type & RenderPassType::COLOR_INPUT) != 0;
|
||||
}
|
||||
|
||||
inline bool RenderPassTypeHasMultiView(RenderPassType type) {
|
||||
return (type & RenderPassType::MULTIVIEW) != 0;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
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) {
|
||||
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||
view_info.image = image_;
|
||||
|
|
|
@ -37,6 +37,10 @@ public:
|
|||
void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute);
|
||||
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.
|
||||
// In addition, ignore UploadMip and GenerateMip, and instead use GetViewForMip. Make sure to delete the returned views when used.
|
||||
VkImageView CreateViewForMip(int mip);
|
||||
|
|
|
@ -253,6 +253,9 @@ struct VulkanExtensions {
|
|||
bool EXT_swapchain_colorspace;
|
||||
bool ARM_rasterization_order_attachment_access;
|
||||
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.
|
||||
};
|
||||
|
||||
|
|
|
@ -35,37 +35,15 @@ using namespace PPSSPP_VK;
|
|||
// Always keep around push buffers at least this long (seconds).
|
||||
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)
|
||||
: vulkan_(vulkan), name_(name), size_(size), usage_(usage) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
|
||||
g_pushBuffers.insert(this);
|
||||
}
|
||||
|
||||
RegisterGPUMemoryManager(this);
|
||||
bool res = AddBuffer();
|
||||
_assert_(res);
|
||||
}
|
||||
|
||||
VulkanPushBuffer::~VulkanPushBuffer() {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
|
||||
g_pushBuffers.erase(this);
|
||||
}
|
||||
|
||||
UnregisterGPUMemoryManager(this);
|
||||
_dbg_assert_(!writePtr_);
|
||||
_assert_(buffers_.empty());
|
||||
}
|
||||
|
@ -276,11 +254,7 @@ VkResult VulkanDescSetPool::Recreate(bool grow) {
|
|||
|
||||
VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage)
|
||||
: vulkan_(vulkan), name_(name), originalBlockSize_(originalBlockSize), usage_(usage) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
|
||||
g_pushBuffers.insert(this);
|
||||
}
|
||||
|
||||
RegisterGPUMemoryManager(this);
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
blocks_.push_back(CreateBlock(originalBlockSize));
|
||||
blocks_.back().original = true;
|
||||
|
@ -289,11 +263,7 @@ VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t o
|
|||
}
|
||||
|
||||
VulkanPushPool::~VulkanPushPool() {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
|
||||
g_pushBuffers.erase(this);
|
||||
}
|
||||
|
||||
UnregisterGPUMemoryManager(this);
|
||||
_dbg_assert_(blocks_.empty());
|
||||
}
|
||||
|
||||
|
@ -323,6 +293,7 @@ VulkanPushPool::Block VulkanPushPool::CreateBlock(size_t size) {
|
|||
result = vmaMapMemory(vulkan_->Allocator(), block.allocation, (void **)(&block.writePtr));
|
||||
_assert_(result == VK_SUCCESS);
|
||||
|
||||
_assert_msg_(block.writePtr != nullptr, "VulkanPushPool: Failed to map memory on block of size %d", (int)block.size);
|
||||
return block;
|
||||
}
|
||||
|
||||
|
@ -384,6 +355,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
|
|||
block.used = allocationSize;
|
||||
block.lastUsed = time_now_d();
|
||||
block.frameIndex = curFrameIndex;
|
||||
_assert_(block.writePtr != nullptr);
|
||||
return;
|
||||
}
|
||||
curBlockIndex_++;
|
||||
|
@ -391,6 +363,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
|
|||
|
||||
double start = time_now_d();
|
||||
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.
|
||||
blocks_.push_back(CreateBlock(newBlockSize));
|
||||
blocks_.back().frameIndex = curFrameIndex;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Data/Collections/FastVec.h"
|
||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
#include "Common/GPU/GPUBackendCommon.h"
|
||||
|
||||
// Forward declaration
|
||||
VK_DEFINE_HANDLE(VmaAllocation);
|
||||
|
@ -14,22 +16,13 @@ VK_DEFINE_HANDLE(VmaAllocation);
|
|||
//
|
||||
// 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
|
||||
// Simple incrementing allocator.
|
||||
// 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
|
||||
// has completed.
|
||||
// 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 {
|
||||
VkBuffer buffer;
|
||||
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 -
|
||||
// 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:
|
||||
VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage);
|
||||
~VulkanPushPool();
|
||||
|
@ -142,6 +136,8 @@ public:
|
|||
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) {
|
||||
uint32_t bindOffset;
|
||||
uint8_t *ptr = Allocate(numBytes, alignment, vkbuf, &bindOffset);
|
||||
|
@ -210,6 +206,3 @@ private:
|
|||
uint32_t usage_ = 0;
|
||||
bool grow_;
|
||||
};
|
||||
|
||||
std::vector<VulkanMemoryManager *> GetActiveVulkanMemoryManagers();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
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) {
|
||||
if (dest->offset.x > src.offset.x) {
|
||||
|
@ -261,31 +261,6 @@ VKRRenderPass *VulkanQueueRunner::GetRenderPass(const RPKey &key) {
|
|||
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) {
|
||||
// Optimizes renderpasses, then sequences them.
|
||||
// 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) {
|
||||
QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr;
|
||||
QueueProfileContext *profile = frameData.profile.enabled ? &frameData.profile : nullptr;
|
||||
|
||||
if (profile)
|
||||
profile->cpuStartTime = time_now_d();
|
||||
|
@ -437,7 +412,7 @@ void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frame
|
|||
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());
|
||||
profile->timestampDescriptions.push_back(StepToString(step));
|
||||
}
|
||||
|
@ -481,7 +456,7 @@ void VulkanQueueRunner::ApplyMGSHack(std::vector<VKRStep *> &steps) {
|
|||
last = j - 1;
|
||||
// should really also check descriptor sets...
|
||||
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)
|
||||
last = j - 1;
|
||||
}
|
||||
|
@ -918,9 +893,6 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
|
|||
case VKRRenderCommand::REMOVED:
|
||||
INFO_LOG(G3D, " (Removed)");
|
||||
break;
|
||||
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
|
||||
INFO_LOG(G3D, " SelfBarrier()");
|
||||
break;
|
||||
case VKRRenderCommand::BIND_GRAPHICS_PIPELINE:
|
||||
INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline);
|
||||
break;
|
||||
|
@ -1241,7 +1213,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
|
|||
VKRGraphicsPipeline *lastGraphicsPipeline = 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.
|
||||
// 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;
|
||||
}
|
||||
|
||||
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:
|
||||
if (pipelineOK) {
|
||||
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:
|
||||
if (pipelineOK) {
|
||||
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;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
|
||||
vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "Common/Thread/Promise.h"
|
||||
#include "Common/Data/Collections/Hashmaps.h"
|
||||
#include "Common/Data/Collections/FastVec.h"
|
||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||
#include "Common/GPU/Vulkan/VulkanBarrier.h"
|
||||
#include "Common/GPU/Vulkan/VulkanFrameData.h"
|
||||
|
@ -38,7 +39,6 @@ enum class VKRRenderCommand : uint8_t {
|
|||
DRAW,
|
||||
DRAW_INDEXED,
|
||||
PUSH_CONSTANTS,
|
||||
SELF_DEPENDENCY_BARRIER,
|
||||
DEBUG_ANNOTATION,
|
||||
NUM_RENDER_COMMANDS,
|
||||
};
|
||||
|
@ -47,10 +47,9 @@ enum class PipelineFlags : u8 {
|
|||
NONE = 0,
|
||||
USES_BLEND_CONSTANT = (1 << 1),
|
||||
USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers.
|
||||
USES_INPUT_ATTACHMENT = (1 << 3),
|
||||
USES_GEOMETRY_SHADER = (1 << 4),
|
||||
USES_MULTIVIEW = (1 << 5), // Inherited from the render pass it was created with.
|
||||
USES_DISCARD = (1 << 6),
|
||||
USES_GEOMETRY_SHADER = (1 << 3),
|
||||
USES_MULTIVIEW = (1 << 4), // Inherited from the render pass it was created with.
|
||||
USES_DISCARD = (1 << 5),
|
||||
};
|
||||
ENUM_CLASS_BITOPS(PipelineFlags);
|
||||
|
||||
|
@ -80,15 +79,14 @@ struct VkRenderData {
|
|||
} draw;
|
||||
struct {
|
||||
VkDescriptorSet ds;
|
||||
int numUboOffsets;
|
||||
uint32_t uboOffsets[3];
|
||||
uint16_t numUboOffsets;
|
||||
uint16_t instances;
|
||||
VkBuffer vbuffer;
|
||||
VkBuffer ibuffer;
|
||||
uint32_t voffset;
|
||||
uint32_t ioffset;
|
||||
uint32_t count;
|
||||
int16_t instances;
|
||||
int16_t indexType;
|
||||
} drawIndexed;
|
||||
struct {
|
||||
uint32_t clearColor;
|
||||
|
@ -153,7 +151,7 @@ struct VKRStep {
|
|||
~VKRStep() {}
|
||||
|
||||
VKRStepType stepType;
|
||||
std::vector<VkRenderData> commands;
|
||||
FastVec<VkRenderData> commands;
|
||||
TinySet<TransitionRequest, 4> preTransitions;
|
||||
TinySet<VKRFramebuffer *, 8> dependencies;
|
||||
const char *tag;
|
||||
|
@ -212,9 +210,14 @@ struct VKRStep {
|
|||
// These are enqueued from the main thread,
|
||||
// and the render thread pops them off
|
||||
struct VKRRenderThreadTask {
|
||||
VKRRenderThreadTask(VKRRunType _runType) : runType(_runType) {}
|
||||
std::vector<VKRStep *> steps;
|
||||
int frame;
|
||||
int frame = -1;
|
||||
VKRRunType runType;
|
||||
|
||||
// Avoid copying these by accident.
|
||||
VKRRenderThreadTask(VKRRenderThreadTask &) = delete;
|
||||
VKRRenderThreadTask &operator =(VKRRenderThreadTask &) = delete;
|
||||
};
|
||||
|
||||
class VulkanQueueRunner {
|
||||
|
@ -309,8 +312,6 @@ private:
|
|||
static void SetupTransitionToTransferDst(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_;
|
||||
|
||||
VkFramebuffer backbuffer_ = VK_NULL_HANDLE;
|
||||
|
|
|
@ -308,9 +308,8 @@ bool VulkanRenderManager::CreateBackbuffers() {
|
|||
void VulkanRenderManager::StopThread() {
|
||||
{
|
||||
// Tell the render thread to quit when it's done.
|
||||
VKRRenderThreadTask task;
|
||||
task.frame = vulkan_->GetCurFrame();
|
||||
task.runType = VKRRunType::EXIT;
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
|
||||
task->frame = vulkan_->GetCurFrame();
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
pushCondVar_.notify_one();
|
||||
|
@ -494,7 +493,7 @@ void VulkanRenderManager::ThreadFunc() {
|
|||
SetCurrentThreadName("RenderMan");
|
||||
while (true) {
|
||||
// Pop a task of the queue and execute it.
|
||||
VKRRenderThreadTask task;
|
||||
VKRRenderThreadTask *task = nullptr;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
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
|
||||
// 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.
|
||||
delete task;
|
||||
// In this case, there should be no more tasks.
|
||||
break;
|
||||
}
|
||||
|
||||
Run(task);
|
||||
Run(*task);
|
||||
delete task;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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];
|
||||
|
||||
if (frameData.profilingEnabled_) {
|
||||
if (enableProfiling) {
|
||||
// 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();
|
||||
VkResult res = vkGetQueryPoolResults(
|
||||
vulkan_->GetDevice(),
|
||||
|
@ -594,7 +597,12 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
|
|||
frameData.profile.profileSummary = "(error getting GPU profile - not ready?)";
|
||||
}
|
||||
} 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);
|
||||
|
||||
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,
|
||||
// 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.
|
||||
|
@ -657,10 +665,6 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe
|
|||
WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
|
||||
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.
|
||||
if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) {
|
||||
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) {
|
||||
rpType = RenderPassType::BACKBUFFER;
|
||||
} 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.
|
||||
// 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!
|
||||
|
@ -764,11 +764,6 @@ void VulkanRenderManager::EndCurRenderStep() {
|
|||
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) {
|
||||
_dbg_assert_(insideFrame_);
|
||||
// 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);
|
||||
}
|
||||
|
||||
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.
|
||||
for (auto &c : *cmds) {
|
||||
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];
|
||||
memset(lastCommand, -1, sizeof(lastCommand));
|
||||
|
||||
|
@ -1266,13 +1261,12 @@ void VulkanRenderManager::Finish() {
|
|||
FrameData &frameData = frameData_[curFrame];
|
||||
|
||||
VLOG("PUSH: Frame[%d]", curFrame);
|
||||
VKRRenderThreadTask task;
|
||||
task.frame = curFrame;
|
||||
task.runType = VKRRunType::PRESENT;
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT);
|
||||
task->frame = curFrame;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back().steps = std::move(steps_);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
}
|
||||
|
||||
|
@ -1382,12 +1376,11 @@ void VulkanRenderManager::FlushSync() {
|
|||
|
||||
{
|
||||
VLOG("PUSH: Frame[%d]", curFrame);
|
||||
VKRRenderThreadTask task;
|
||||
task.frame = curFrame;
|
||||
task.runType = VKRRunType::SYNC;
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
|
||||
task->frame = curFrame;
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back().steps = std::move(steps_);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,10 @@ struct BoundingRect {
|
|||
|
||||
// All the data needed to create a graphics pipeline.
|
||||
// TODO: Compress this down greatly.
|
||||
struct VKRGraphicsPipelineDesc : Draw::RefCountedObject {
|
||||
class VKRGraphicsPipelineDesc : public Draw::RefCountedObject {
|
||||
public:
|
||||
VKRGraphicsPipelineDesc() : Draw::RefCountedObject("VKRGraphicsPipelineDesc") {}
|
||||
|
||||
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
|
||||
VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
|
||||
VkPipelineColorBlendAttachmentState blend0{};
|
||||
|
@ -215,8 +218,6 @@ public:
|
|||
// get an array texture view.
|
||||
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);
|
||||
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) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
_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);
|
||||
data.graphics_pipeline.pipeline = pipeline;
|
||||
data.graphics_pipeline.pipelineLayout = pipelineLayout;
|
||||
|
@ -249,24 +251,24 @@ public:
|
|||
// DebugBreak();
|
||||
// }
|
||||
curPipelineFlags_ |= flags;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void BindPipeline(VKRComputePipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
_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.pipelineLayout = pipelineLayout;
|
||||
curPipelineFlags_ |= flags;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetViewport(const VkViewport &vp) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
_dbg_assert_((int)vp.width >= 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.y = vp.y;
|
||||
data.viewport.vp.width = vp.width;
|
||||
|
@ -276,7 +278,6 @@ public:
|
|||
// TODO: This should be fixed at the source.
|
||||
data.viewport.vp.minDepth = clamp_value(vp.minDepth, 0.0f, 1.0f);
|
||||
data.viewport.vp.maxDepth = clamp_value(vp.maxDepth, 0.0f, 1.0f);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
curStepHasViewport_ = true;
|
||||
}
|
||||
|
||||
|
@ -318,37 +319,37 @@ public:
|
|||
|
||||
curRenderArea_.Apply(rc);
|
||||
|
||||
VkRenderData data{ VKRRenderCommand::SCISSOR };
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::SCISSOR;
|
||||
data.scissor.scissor = rc;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
curStepHasScissor_ = true;
|
||||
}
|
||||
|
||||
void SetStencilParams(uint8_t writeMask, uint8_t compareMask, uint8_t refValue) {
|
||||
_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.stencilCompareMask = compareMask;
|
||||
data.stencil.stencilRef = refValue;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void SetBlendFactor(uint32_t color) {
|
||||
_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;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
void PushConstants(VkPipelineLayout pipelineLayout, VkShaderStageFlags stages, int offset, int size, void *constants) {
|
||||
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
|
||||
_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.offset = offset;
|
||||
data.push.size = size;
|
||||
memcpy(data.push.data, constants, size);
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
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) {
|
||||
_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.offset = offset;
|
||||
data.draw.ds = descSet;
|
||||
|
@ -390,13 +392,13 @@ public:
|
|||
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.draw.uboOffsets));
|
||||
for (int i = 0; i < numUboOffsets; i++)
|
||||
data.draw.uboOffsets[i] = uboOffsets[i];
|
||||
curRenderStep_->commands.push_back(data);
|
||||
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_);
|
||||
VkRenderData data{ VKRRenderCommand::DRAW_INDEXED };
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::DRAW_INDEXED;
|
||||
data.drawIndexed.count = count;
|
||||
data.drawIndexed.instances = numInstances;
|
||||
data.drawIndexed.ds = descSet;
|
||||
|
@ -408,8 +410,6 @@ public:
|
|||
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.drawIndexed.uboOffsets));
|
||||
for (int i = 0; i < numUboOffsets; i++)
|
||||
data.drawIndexed.uboOffsets[i] = uboOffsets[i];
|
||||
data.drawIndexed.indexType = indexType;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
curRenderStep_->render.numDraws++;
|
||||
}
|
||||
|
||||
|
@ -417,9 +417,9 @@ public:
|
|||
// in the debugger.
|
||||
void DebugAnnotate(const char *annotation) {
|
||||
_dbg_assert_(curRenderStep_);
|
||||
VkRenderData data{ VKRRenderCommand::DEBUG_ANNOTATION };
|
||||
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
|
||||
data.cmd = VKRRenderCommand::DEBUG_ANNOTATION;
|
||||
data.debugAnnotation.annotation = annotation;
|
||||
curRenderStep_->commands.push_back(data);
|
||||
}
|
||||
|
||||
VkCommandBuffer GetInitCmd();
|
||||
|
@ -453,8 +453,6 @@ public:
|
|||
return outOfDateFrames_ > VulkanContext::MAX_INFLIGHT_FRAMES;
|
||||
}
|
||||
|
||||
void Invalidate(InvalidationFlags flags);
|
||||
|
||||
void ResetStats();
|
||||
void DrainCompileQueue();
|
||||
|
||||
|
@ -509,7 +507,7 @@ private:
|
|||
std::mutex pushMutex_;
|
||||
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.
|
||||
std::mutex syncMutex_;
|
||||
|
|
|
@ -335,8 +335,11 @@ struct DescriptorSetKey {
|
|||
class VKTexture : public Texture {
|
||||
public:
|
||||
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);
|
||||
void Update(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
|
||||
|
||||
~VKTexture() {
|
||||
Destroy();
|
||||
|
@ -356,7 +359,13 @@ public:
|
|||
return VK_NULL_HANDLE; // This would be bad.
|
||||
}
|
||||
|
||||
int NumLevels() const {
|
||||
return mipLevels_;
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
|
||||
|
||||
void Destroy() {
|
||||
if (vkTex_) {
|
||||
vkTex_->Destroy();
|
||||
|
@ -369,8 +378,6 @@ private:
|
|||
VulkanTexture *vkTex_ = nullptr;
|
||||
|
||||
int mipLevels_ = 0;
|
||||
|
||||
DataFormat format_ = DataFormat::UNDEFINED;
|
||||
};
|
||||
|
||||
class VKFramebuffer;
|
||||
|
@ -421,6 +428,7 @@ public:
|
|||
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) 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;
|
||||
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.
|
||||
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
|
||||
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
|
||||
void BindCurrentFramebufferForColorInput() override;
|
||||
|
||||
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
|
||||
|
||||
|
@ -476,6 +483,10 @@ public:
|
|||
void EndFrame() override;
|
||||
void WipeQueue() override;
|
||||
|
||||
int GetFrameCount() override {
|
||||
return frameCount_;
|
||||
}
|
||||
|
||||
void FlushState() override {}
|
||||
|
||||
void ResetStats() override {
|
||||
|
@ -520,6 +531,7 @@ private:
|
|||
VulkanTexture *GetNullTexture();
|
||||
VulkanContext *vulkan_ = nullptr;
|
||||
|
||||
int frameCount_ = 0;
|
||||
VulkanRenderManager renderManager_;
|
||||
|
||||
VulkanTexture *nullTexture_ = nullptr;
|
||||
|
@ -684,6 +696,7 @@ VulkanTexture *VKContext::GetNullTexture() {
|
|||
uint32_t bindOffset;
|
||||
VkBuffer bindBuf;
|
||||
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 x = 0; x < w; x++) {
|
||||
// data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard
|
||||
|
@ -747,14 +760,14 @@ enum class TextureState {
|
|||
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.
|
||||
_assert_(desc.width * desc.height * desc.depth > 0); // remember to set depth to 1!
|
||||
if (desc.width * desc.height * desc.depth <= 0) {
|
||||
ERROR_LOG(G3D, "Bad texture dimensions %dx%dx%d", desc.width, desc.height, desc.depth);
|
||||
return false;
|
||||
}
|
||||
_assert_(push);
|
||||
_dbg_assert_(pushBuffer);
|
||||
format_ = desc.format;
|
||||
mipLevels_ = desc.mipLevels;
|
||||
width_ = desc.width;
|
||||
|
@ -762,8 +775,6 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
|
|||
depth_ = desc.depth;
|
||||
vkTex_ = new VulkanTexture(vulkan_, desc.tag);
|
||||
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;
|
||||
if (mipLevels_ > (int)desc.initData.size()) {
|
||||
// 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;
|
||||
if (desc.initData.size()) {
|
||||
int w = width_;
|
||||
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;
|
||||
}
|
||||
UpdateInternal(cmd, pushBuffer, desc.initData.data(), desc.initDataCallback, (int)desc.initData.size());
|
||||
// Generate the rest of the mips automatically.
|
||||
if (i < mipLevels_) {
|
||||
vkTex_->GenerateMips(cmd, i, false);
|
||||
if (desc.initData.size() < mipLevels_) {
|
||||
vkTex_->GenerateMips(cmd, (int)desc.initData.size(), false);
|
||||
layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
}
|
||||
|
@ -811,6 +800,44 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
|
|||
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) {
|
||||
switch (fmt) {
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
|
@ -983,9 +1010,9 @@ VKContext::VKContext(VulkanContext *vulkan)
|
|||
}
|
||||
}
|
||||
|
||||
// Limited, through input attachments and self-dependencies.
|
||||
// We turn it off here already if buggy.
|
||||
caps_.framebufferFetchSupported = !bugs_.Has(Bugs::SUBPASS_FEEDBACK_BROKEN);
|
||||
// Vulkan can support this through input attachments and various extensions, but not worth
|
||||
// the trouble.
|
||||
caps_.framebufferFetchSupported = false;
|
||||
|
||||
caps_.deviceID = deviceProps.deviceID;
|
||||
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).
|
||||
Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
|
||||
|
||||
frameCount_++;
|
||||
}
|
||||
|
||||
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) {
|
||||
dest.compareOp = compToVK[(int)src.compareOp];
|
||||
dest.failOp = stencilOpToVK[(int)src.failOp];
|
||||
|
@ -1495,7 +1538,7 @@ void VKContext::DrawIndexed(int vertexCount, int offset) {
|
|||
|
||||
BindCurrentPipeline();
|
||||
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) {
|
||||
|
@ -1505,7 +1548,12 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
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, "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));
|
||||
|
||||
|
@ -1728,10 +1778,6 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne
|
|||
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) {
|
||||
VKFramebuffer *fb = (VKFramebuffer *)fbo;
|
||||
if (fb) {
|
||||
|
|
|
@ -119,7 +119,8 @@ bool DataFormatIsBlockCompressed(DataFormat fmt, int *blockSize) {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
@ -131,6 +132,7 @@ bool RefCountedObject::Release() {
|
|||
return true;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
return false;
|
||||
|
@ -138,11 +140,10 @@ bool RefCountedObject::Release() {
|
|||
|
||||
bool RefCountedObject::ReleaseAssertLast() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// ================================== PIXEL/FRAGMENT SHADERS
|
||||
|
||||
// The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL.
|
||||
|
|
|
@ -359,7 +359,7 @@ protected:
|
|||
|
||||
class RefCountedObject {
|
||||
public:
|
||||
RefCountedObject() {
|
||||
explicit RefCountedObject(const char *name) : name_(name) {
|
||||
refcount_ = 1;
|
||||
}
|
||||
RefCountedObject(const RefCountedObject &other) = delete;
|
||||
|
@ -372,6 +372,7 @@ public:
|
|||
|
||||
private:
|
||||
std::atomic<int> refcount_;
|
||||
const char * const name_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -428,18 +429,22 @@ struct AutoRef {
|
|||
|
||||
class BlendState : public RefCountedObject {
|
||||
public:
|
||||
BlendState() : RefCountedObject("BlendState") {}
|
||||
};
|
||||
|
||||
class SamplerState : public RefCountedObject {
|
||||
public:
|
||||
SamplerState() : RefCountedObject("SamplerState") {}
|
||||
};
|
||||
|
||||
class DepthStencilState : public RefCountedObject {
|
||||
public:
|
||||
DepthStencilState() : RefCountedObject("DepthStencilState") {}
|
||||
};
|
||||
|
||||
class Framebuffer : public RefCountedObject {
|
||||
public:
|
||||
Framebuffer() : RefCountedObject("Framebuffer") {}
|
||||
int Width() { return width_; }
|
||||
int Height() { return height_; }
|
||||
int Layers() { return layers_; }
|
||||
|
@ -452,15 +457,20 @@ protected:
|
|||
|
||||
class Buffer : public RefCountedObject {
|
||||
public:
|
||||
Buffer() : RefCountedObject("Buffer") {}
|
||||
};
|
||||
|
||||
class Texture : public RefCountedObject {
|
||||
public:
|
||||
int Width() { return width_; }
|
||||
int Height() { return height_; }
|
||||
int Depth() { return depth_; }
|
||||
Texture() : RefCountedObject("Texture") {}
|
||||
int Width() const { return width_; }
|
||||
int Height() const { return height_; }
|
||||
int Depth() const { return depth_; }
|
||||
DataFormat Format() const { return format_; }
|
||||
|
||||
protected:
|
||||
int width_ = -1, height_ = -1, depth_ = -1;
|
||||
DataFormat format_ = DataFormat::UNDEFINED;
|
||||
};
|
||||
|
||||
struct BindingDesc {
|
||||
|
@ -480,18 +490,28 @@ struct InputLayoutDesc {
|
|||
std::vector<AttributeDesc> attributes;
|
||||
};
|
||||
|
||||
class InputLayout : public RefCountedObject { };
|
||||
class InputLayout : public RefCountedObject {
|
||||
public:
|
||||
InputLayout() : RefCountedObject("InputLayout") {}
|
||||
};
|
||||
|
||||
// Uniform types have moved to Shader.h.
|
||||
|
||||
class ShaderModule : public RefCountedObject {
|
||||
public:
|
||||
ShaderModule() : RefCountedObject("ShaderModule") {}
|
||||
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 {
|
||||
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.
|
||||
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 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.
|
||||
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:
|
||||
ShaderModule *vsPresets_[VS_MAX_PRESET];
|
||||
ShaderModule *fsPresets_[FS_MAX_PRESET];
|
||||
|
|
|
@ -20,7 +20,7 @@ const char *GetDeviceName(int deviceId) {
|
|||
case DEVICE_ID_PAD_7: return "pad8";
|
||||
case DEVICE_ID_PAD_8: return "pad9";
|
||||
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_2: return "x360_3";
|
||||
case DEVICE_ID_XINPUT_3: return "x360_4";
|
||||
|
@ -39,7 +39,7 @@ std::vector<InputMapping> confirmKeys;
|
|||
std::vector<InputMapping> cancelKeys;
|
||||
std::vector<InputMapping> tabLeftKeys;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId) {
|
||||
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId) {
|
||||
uiFlipAnalogY = flipYByDeviceId;
|
||||
}
|
||||
|
||||
int GetAnalogYDirection(int deviceId) {
|
||||
int GetAnalogYDirection(InputDeviceID deviceId) {
|
||||
auto configured = uiFlipAnalogY.find(deviceId);
|
||||
if (configured != uiFlipAnalogY.end())
|
||||
return configured->second;
|
||||
|
@ -84,8 +84,8 @@ int GetAnalogYDirection(int deviceId) {
|
|||
InputMapping InputMapping::FromConfigString(const std::string &str) {
|
||||
std::vector<std::string> parts;
|
||||
SplitString(str, '-', parts);
|
||||
int deviceId = atoi(parts[0].c_str());
|
||||
int keyCode = atoi(parts[1].c_str());
|
||||
InputDeviceID deviceId = (InputDeviceID)(atoi(parts[0].c_str()));
|
||||
InputKeyCode keyCode = (InputKeyCode)atoi(parts[1].c_str());
|
||||
|
||||
InputMapping mapping;
|
||||
mapping.deviceId = deviceId;
|
||||
|
@ -94,15 +94,15 @@ InputMapping InputMapping::FromConfigString(const std::string &str) {
|
|||
}
|
||||
|
||||
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 {
|
||||
if (IsAxis()) {
|
||||
int 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 {
|
||||
snprintf(buffer, bufSize, "Device: %d Key: %d", deviceId, keyCode);
|
||||
snprintf(buffer, bufSize, "Device: %d Key: %d", (int)deviceId, keyCode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
// Default device IDs
|
||||
|
||||
enum {
|
||||
enum InputDeviceID {
|
||||
DEVICE_ID_ANY = -1, // Represents any device ID
|
||||
DEVICE_ID_DEFAULT = 0, // Old Android
|
||||
DEVICE_ID_KEYBOARD = 1, // PC keyboard, android keyboards
|
||||
|
@ -38,43 +38,15 @@ enum {
|
|||
DEVICE_ID_TOUCH = 42,
|
||||
};
|
||||
|
||||
inline InputDeviceID operator +(InputDeviceID deviceID, int addend) {
|
||||
return (InputDeviceID)((int)deviceID + addend);
|
||||
}
|
||||
|
||||
//number of contiguous generic joypad IDs
|
||||
const int MAX_NUM_PADS = 10;
|
||||
|
||||
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
|
||||
#define MAX_KEYQUEUESIZE 20
|
||||
#endif
|
||||
|
@ -98,18 +70,18 @@ private:
|
|||
return AXIS_BIND_NKCODE_START + axisId * 2 + (direction < 0 ? 1 : 0);
|
||||
}
|
||||
public:
|
||||
InputMapping() : deviceId(0), keyCode(0) {}
|
||||
InputMapping() : deviceId(DEVICE_ID_DEFAULT), keyCode(0) {}
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
static InputMapping FromConfigString(const std::string &str);
|
||||
std::string ToConfigString() const;
|
||||
|
||||
int deviceId;
|
||||
InputDeviceID deviceId;
|
||||
int keyCode; // Can also represent an axis with direction, if encoded properly.
|
||||
|
||||
bool IsAxis() const {
|
||||
|
@ -195,15 +167,19 @@ enum {
|
|||
|
||||
struct KeyInput {
|
||||
KeyInput() {}
|
||||
KeyInput(int devId, int code, int fl) : deviceId(devId), keyCode(code), flags(fl) {}
|
||||
int deviceId;
|
||||
int keyCode; // Android keycodes are the canonical keycodes, everyone else map to them.
|
||||
KeyInput(InputDeviceID devId, InputKeyCode code, int fl) : deviceId(devId), keyCode(code), flags(fl) {}
|
||||
KeyInput(InputDeviceID devId, int unicode) : deviceId(devId), unicodeChar(unicode), flags(KEY_CHAR) {}
|
||||
InputDeviceID deviceId;
|
||||
union {
|
||||
InputKeyCode keyCode; // Android keycodes are the canonical keycodes, everyone else map to them.
|
||||
int unicodeChar; // for KEY_CHAR
|
||||
};
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct AxisInput {
|
||||
int deviceId;
|
||||
int axisId; // Android axis Ids are the canonical ones.
|
||||
InputDeviceID deviceId;
|
||||
InputAxis axisId;
|
||||
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);
|
||||
|
||||
// 0 means unknown (attempt autodetect), -1 means flip, 1 means original direction.
|
||||
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId);
|
||||
int GetAnalogYDirection(int deviceId);
|
||||
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId);
|
||||
int GetAnalogYDirection(InputDeviceID deviceId);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#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_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)
|
||||
|
@ -259,9 +260,10 @@ typedef enum _keycode_t {
|
|||
NKCODE_EXT_ROTATION_RIGHT = 1114,
|
||||
|
||||
NKCODE_MAX
|
||||
} keycode_t;
|
||||
};
|
||||
|
||||
enum AndroidJoystickAxis {
|
||||
// These mostly match Android axis IDs.
|
||||
enum InputAxis {
|
||||
// Field descriptor #15 I
|
||||
JOYSTICK_AXIS_X = 0,
|
||||
JOYSTICK_AXIS_Y = 1,
|
||||
|
|
|
@ -62,6 +62,9 @@ enum LOG_TYPE {
|
|||
SCEMISC,
|
||||
|
||||
NUMBER_OF_LOGS, // Must be last
|
||||
|
||||
// Temporary aliases
|
||||
ACHIEVEMENTS = HLE, // TODO: Make a real category
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Exception for Windows - enable more log levels in release mode than on other platforms.
|
||||
#if defined(_DEBUG) || defined(_WIN32)
|
||||
|
||||
#define MAX_LOGLEVEL DEBUG_LEVEL
|
||||
|
@ -128,8 +132,8 @@ void SetExtraAssertInfo(const char *info);
|
|||
#define __FILENAME__ __FILE__
|
||||
#endif
|
||||
|
||||
// If we're in "debug" assert mode
|
||||
#if MAX_LOGLEVEL >= DEBUG_LEVEL
|
||||
// If we're a debug build, _dbg_assert_ is active. Not otherwise, even on Windows.
|
||||
#if defined(_DEBUG)
|
||||
|
||||
#define _dbg_assert_(_a_) \
|
||||
if (!(_a_)) {\
|
||||
|
|
|
@ -32,6 +32,7 @@ struct SimpleStat {
|
|||
void Format(char *buffer, size_t sz);
|
||||
|
||||
private:
|
||||
SimpleStat() {}
|
||||
const char *name_;
|
||||
|
||||
// These are initialized in Reset().
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "Common/StringUtils.h"
|
||||
#include "expression_parser.h"
|
||||
#include <ctype.h>
|
||||
#include <cstring>
|
||||
|
@ -14,7 +15,7 @@ typedef enum {
|
|||
|
||||
typedef enum { EXCOMM_CONST, EXCOMM_CONST_FLOAT, EXCOMM_REF, EXCOMM_OP } ExpressionCommand;
|
||||
|
||||
static char expressionError[512];
|
||||
static std::string expressionError;
|
||||
|
||||
typedef struct {
|
||||
char Name[4];
|
||||
|
@ -219,7 +220,7 @@ bool isAlphaNum(char c)
|
|||
|
||||
bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest)
|
||||
{
|
||||
expressionError[0] = 0;
|
||||
expressionError.clear();
|
||||
|
||||
int infixPos = 0;
|
||||
int infixLen = (int)strlen(infix);
|
||||
|
@ -253,7 +254,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
|
|||
isFloat = true;
|
||||
else if (parseNumber(subStr,16,subPos,value) == false)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Invalid number \"%s\"",subStr);
|
||||
expressionError = StringFromFormat("Invalid number \"%s\"", subStr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -282,14 +283,14 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
|
|||
continue;
|
||||
}
|
||||
|
||||
snprintf(expressionError, sizeof(expressionError), "Invalid symbol \"%s\"",subStr);
|
||||
expressionError = StringFromFormat("Invalid symbol \"%s\"", subStr);
|
||||
return false;
|
||||
} else {
|
||||
int len;
|
||||
ExpressionOpcodeType type = getExpressionOpcode(&infix[infixPos],len,lastOpcode);
|
||||
if (type == EXOP_NONE)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Invalid operator at \"%s\"",&infix[infixPos]);
|
||||
expressionError = StringFromFormat("Invalid operator at \"%s\"", &infix[infixPos]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -304,7 +305,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
|
|||
{
|
||||
if (opcodeStack.empty())
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Closing parenthesis without opening one");
|
||||
expressionError = "Closing parenthesis without opening one";
|
||||
return false;
|
||||
}
|
||||
ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1];
|
||||
|
@ -318,7 +319,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
|
|||
{
|
||||
if (opcodeStack.empty())
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Closing bracket without opening one");
|
||||
expressionError = "Closing bracket without opening one";
|
||||
return false;
|
||||
}
|
||||
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
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Parenthesis not closed");
|
||||
expressionError = "Parenthesis not closed";
|
||||
return false;
|
||||
}
|
||||
dest.push_back(ExpressionPair(EXCOMM_OP,t));
|
||||
|
@ -430,7 +431,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
|
|||
opcode = exp[num++].second;
|
||||
if (valueStack.size() < ExpressionOpcodes[opcode].args)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Not enough arguments");
|
||||
expressionError = "Not enough arguments";
|
||||
return false;
|
||||
}
|
||||
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
|
||||
if (exp[num++].second != EXOP_MEM)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Invalid memsize operator");
|
||||
expressionError = "Invalid memsize operator";
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -460,7 +461,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
|
|||
case EXOP_MEM:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -490,7 +491,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
|
|||
case EXOP_DIV: // a/b
|
||||
if (arg[0] == 0)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Division by zero");
|
||||
expressionError = "Division by zero";
|
||||
return false;
|
||||
}
|
||||
if (useFloat)
|
||||
|
@ -501,7 +502,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
|
|||
case EXOP_MOD: // a%b
|
||||
if (arg[0] == 0)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Modulo by zero");
|
||||
expressionError = "Modulo by zero";
|
||||
return false;
|
||||
}
|
||||
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!
|
||||
if (exp[num++].second != EXOP_TERTIF)
|
||||
{
|
||||
snprintf(expressionError, sizeof(expressionError), "Invalid tertiary operator");
|
||||
expressionError = "Invalid tertiary operator";
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
const char* getExpressionError()
|
||||
const char *getExpressionError()
|
||||
{
|
||||
if (expressionError[0] == 0) snprintf(expressionError, sizeof(expressionError), "Invalid expression");
|
||||
return expressionError;
|
||||
if (expressionError.empty())
|
||||
expressionError = "Invalid expression";
|
||||
return expressionError.c_str();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
typedef std::pair<uint32_t, uint32_t> ExpressionPair;
|
||||
|
@ -21,7 +22,7 @@ public:
|
|||
virtual bool parseSymbol(char* str, uint32_t& symbolValue) = 0;
|
||||
virtual uint32_t getReferenceValue(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);
|
||||
|
|
|
@ -69,9 +69,22 @@ struct Bounds {
|
|||
Bounds Expand(float xAmount, float yAmount) const {
|
||||
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 {
|
||||
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 y;
|
||||
|
|
|
@ -67,6 +67,13 @@ inline T clamp_value(T val, T floor, T cap) {
|
|||
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_DOWN(x, a) ((x) & ~((a) - 1))
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
bool GrabMemSpace(size_t size);
|
||||
void ReleaseSpace();
|
||||
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
|
||||
u8 *Find4GBBase();
|
||||
|
|
|
@ -129,7 +129,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
|
|||
return retval;
|
||||
}
|
||||
|
||||
void MemArena::ReleaseView(void* view, size_t size) {
|
||||
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
|
||||
munmap(view, size);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
|
|||
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_deallocate(mach_task_self(), addr, size);
|
||||
}
|
||||
|
|
83
Common/MemArenaHorizon.cpp
Normal file
83
Common/MemArenaHorizon.cpp
Normal 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)
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#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/stat.h>
|
||||
|
@ -121,7 +121,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
|
|||
return retval;
|
||||
}
|
||||
|
||||
void MemArena::ReleaseView(void* view, size_t size) {
|
||||
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
|
||||
munmap(view, size);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *viewbase) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
void MemArena::ReleaseView(void* view, size_t size) {
|
||||
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
|
||||
#if PPSSPP_PLATFORM(UWP)
|
||||
#else
|
||||
UnmapViewOfFile(view);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#if !PPSSPP_PLATFORM(SWITCH)
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
|
@ -257,7 +258,7 @@ void *AllocateAlignedMemory(size_t size, size_t alignment) {
|
|||
#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;
|
||||
}
|
||||
|
||||
|
@ -355,3 +356,4 @@ int GetMemoryProtectPageSize() {
|
|||
#endif
|
||||
return MEM_PAGE_SIZE;
|
||||
}
|
||||
#endif // !PPSSPP_PLATFORM(SWITCH)
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifndef __SWITCH__
|
||||
#include <sys/mman.h>
|
||||
#else
|
||||
#include <switch.h>
|
||||
#endif // !__SWITCH__
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
|
||||
|
|
89
Common/MemoryUtilHorizon.cpp
Normal file
89
Common/MemoryUtilHorizon.cpp
Normal 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)
|
|
@ -180,7 +180,7 @@ void Connection::Disconnect() {
|
|||
namespace http {
|
||||
|
||||
// TODO: do something sane here
|
||||
constexpr const char *DEFAULT_USERAGENT = "NATIVEAPP 1.0";
|
||||
constexpr const char *DEFAULT_USERAGENT = "PPSSPP";
|
||||
|
||||
Client::Client() {
|
||||
httpVersion_ = "1.1";
|
||||
|
@ -458,8 +458,8 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
|
|||
return 0;
|
||||
}
|
||||
|
||||
Download::Download(const std::string &url, const Path &outfile)
|
||||
: progress_(&cancelled_), url_(url), outfile_(outfile) {
|
||||
Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile)
|
||||
: method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) {
|
||||
}
|
||||
|
||||
Download::~Download() {
|
||||
|
@ -484,13 +484,17 @@ void Download::SetFailed(int code) {
|
|||
completed_ = true;
|
||||
}
|
||||
|
||||
int Download::PerformGET(const std::string &url) {
|
||||
int Download::Perform(const std::string &url) {
|
||||
Url fileUrl(url);
|
||||
if (!fileUrl.Valid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
http::Client client;
|
||||
if (!userAgent_.empty()) {
|
||||
client.SetUserAgent(userAgent_);
|
||||
}
|
||||
|
||||
if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) {
|
||||
ERROR_LOG(IO, "Failed resolving %s", url.c_str());
|
||||
return -1;
|
||||
|
@ -510,7 +514,11 @@ int Download::PerformGET(const std::string &url) {
|
|||
}
|
||||
|
||||
RequestParams req(fileUrl.Resource(), acceptMime_);
|
||||
if (method_ == RequestMethod::GET) {
|
||||
return client.GET(req, &buffer_, responseHeaders_, &progress_);
|
||||
} else {
|
||||
return client.POST(req, postData_, postMime_, &buffer_, &progress_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Download::RedirectLocation(const std::string &baseUrl) {
|
||||
|
@ -533,7 +541,7 @@ void Download::Do() {
|
|||
|
||||
std::string downloadURL = url_;
|
||||
while (resultCode_ == 0) {
|
||||
int resultCode = PerformGET(downloadURL);
|
||||
int resultCode = Perform(downloadURL);
|
||||
if (resultCode == -1) {
|
||||
SetFailed(resultCode);
|
||||
return;
|
||||
|
@ -557,12 +565,12 @@ void Download::Do() {
|
|||
}
|
||||
|
||||
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_)) {
|
||||
ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str());
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
@ -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> dl(new Download(url, outfile));
|
||||
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
if (acceptMime)
|
||||
dl->SetAccept(acceptMime);
|
||||
downloads_.push_back(dl);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
@ -588,19 +598,40 @@ std::shared_ptr<Download> Downloader::StartDownloadWithCallback(
|
|||
const Path &outfile,
|
||||
std::function<void(Download &)> callback,
|
||||
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)
|
||||
dl->SetAccept(acceptMime);
|
||||
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();
|
||||
return dl;
|
||||
}
|
||||
|
||||
void Downloader::Update() {
|
||||
for (auto iter : newDownloads_) {
|
||||
downloads_.push_back(iter);
|
||||
}
|
||||
newDownloads_.clear();
|
||||
|
||||
restart:
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
auto &dl = downloads_[i];
|
||||
auto dl = downloads_[i];
|
||||
if (dl->Done()) {
|
||||
dl->RunCallback();
|
||||
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> progress;
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
|
|
|
@ -97,10 +97,15 @@ protected:
|
|||
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 {
|
||||
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();
|
||||
|
||||
void Start();
|
||||
|
@ -151,20 +156,27 @@ public:
|
|||
// Downloader::GetCurrentProgress won't return it in the results.
|
||||
bool IsHidden() const { return hidden_; }
|
||||
void SetHidden(bool hidden) { hidden_ = hidden; }
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
private:
|
||||
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);
|
||||
void SetFailed(int code);
|
||||
|
||||
RequestProgress progress_;
|
||||
RequestMethod method_;
|
||||
std::string postData_;
|
||||
std::string userAgent_;
|
||||
Buffer buffer_;
|
||||
std::vector<std::string> responseHeaders_;
|
||||
std::string url_;
|
||||
Path outfile_;
|
||||
std::thread thread_;
|
||||
const char *acceptMime_ = "*/*";
|
||||
std::string postMime_;
|
||||
int resultCode_ = 0;
|
||||
bool completed_ = false;
|
||||
bool failed_ = false;
|
||||
|
@ -190,14 +202,34 @@ public:
|
|||
std::function<void(Download &)> callback,
|
||||
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.
|
||||
void Update();
|
||||
void CancelAll();
|
||||
|
||||
void WaitForAll();
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
std::vector<float> GetCurrentProgress();
|
||||
|
||||
size_t GetActiveCount() const {
|
||||
return downloads_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
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
|
||||
|
|
|
@ -47,7 +47,7 @@ bool TextDrawerAndroid::IsReady() const {
|
|||
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
|
||||
// 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 ^= flags << 10;
|
||||
|
||||
|
|
|
@ -134,13 +134,25 @@ void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float
|
|||
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;
|
||||
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);
|
||||
|
||||
if (size.cx > extW)
|
||||
extW = size.cx;
|
||||
extH += size.cy;
|
||||
}
|
||||
|
||||
entry = new TextMeasureEntry();
|
||||
entry->width = size.cx;
|
||||
entry->height = size.cy;
|
||||
entry->width = extW;
|
||||
entry->height = extH;
|
||||
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
|
||||
}
|
||||
|
||||
|
|
|
@ -209,8 +209,8 @@ void CPUInfo::Detect()
|
|||
RiscV_C = ExtensionSupported(hwcap, 'C');
|
||||
RiscV_V = ExtensionSupported(hwcap, 'V');
|
||||
RiscV_B = ExtensionSupported(hwcap, 'B');
|
||||
// Let's assume for now...
|
||||
RiscV_Zicsr = RiscV_M && RiscV_A && RiscV_F && RiscV_D;
|
||||
// We assume as in RVA20U64 that F means Zicsr is available.
|
||||
RiscV_Zicsr = RiscV_F;
|
||||
|
||||
#ifdef USE_CPU_FEATURES
|
||||
cpu_features::RiscvInfo info = cpu_features::GetRiscvInfo();
|
||||
|
@ -220,6 +220,7 @@ void CPUInfo::Detect()
|
|||
RiscV_F = info.features.F;
|
||||
RiscV_D = info.features.D;
|
||||
RiscV_C = info.features.C;
|
||||
RiscV_V = info.features.V;
|
||||
RiscV_Zicsr = info.features.Zicsr;
|
||||
|
||||
truncate_cpy(brand_string, info.uarch);
|
||||
|
|
|
@ -302,6 +302,12 @@ public:
|
|||
void NEG(RiscVReg rd, RiscVReg 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_TSO();
|
||||
|
@ -896,6 +902,9 @@ public:
|
|||
void MINU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2);
|
||||
void SEXT_B(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_W(RiscVReg rd, RiscVReg rs) {
|
||||
ADD_UW(rd, rs, R_ZERO);
|
||||
|
|
|
@ -147,6 +147,11 @@ public:
|
|||
|
||||
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_; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -368,3 +368,31 @@ std::string UnescapeMenuString(const char *input, char *shortcutChar) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -111,3 +111,7 @@ inline void CharArrayFromFormat(char (& out)[Count], const char* format, ...)
|
|||
|
||||
// "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);
|
||||
|
||||
// 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 = "");
|
||||
|
|
|
@ -20,9 +20,6 @@ class GraphicsContext;
|
|||
// 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);
|
||||
|
||||
// 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
|
||||
// the rotation lock which must be controlled from Java on Android.
|
||||
// It is currently not called on non-Android platforms.
|
||||
|
@ -87,3 +84,6 @@ void NativeShutdownGraphics();
|
|||
void NativeShutdown();
|
||||
|
||||
void PostLoadConfig();
|
||||
|
||||
void NativeSaveSecret(const char *nameOfSecret, const std::string &data);
|
||||
std::string NativeLoadSecret(const char *nameOfSecret);
|
||||
|
|
281
Common/System/OSD.cpp
Normal file
281
Common/System/OSD.cpp
Normal 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
98
Common/System/OSD.h
Normal 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;
|
|
@ -1,7 +1,10 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "Common/System/Request.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
RequestManager g_requestManager;
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ private:
|
|||
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<PendingFailure> pendingFailures_;
|
||||
std::mutex responseMutex_;
|
||||
|
@ -72,12 +73,13 @@ const char *RequestTypeAsString(SystemRequestType type);
|
|||
extern RequestManager g_requestManager;
|
||||
|
||||
// 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) {
|
||||
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.
|
||||
inline void System_BrowseForImage(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, callback, failedCallback, title, "", 0);
|
||||
|
@ -88,6 +90,7 @@ enum class BrowseFileType {
|
|||
IMAGE,
|
||||
INI,
|
||||
DB,
|
||||
SOUND_EFFECT,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::COPY_TO_CLIPBOARD, nullptr, nullptr, string, "", 0);
|
||||
}
|
||||
|
@ -111,6 +119,10 @@ inline void System_RestartApp(const std::string ¶ms) {
|
|||
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.
|
||||
inline void System_ToggleFullscreenState(const std::string ¶m) {
|
||||
g_requestManager.MakeSystemRequest(SystemRequestType::TOGGLE_FULLSCREEN_STATE, nullptr, nullptr, param, "", 0);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
// Platform integration
|
||||
|
||||
|
@ -57,12 +58,14 @@ void System_LaunchUrl(LaunchUrlType urlType, const char *url);
|
|||
|
||||
enum class SystemRequestType {
|
||||
INPUT_TEXT_MODAL,
|
||||
ASK_USERNAME_PASSWORD,
|
||||
BROWSE_FOR_IMAGE,
|
||||
BROWSE_FOR_FILE,
|
||||
BROWSE_FOR_FOLDER,
|
||||
|
||||
EXIT_APP,
|
||||
RESTART_APP, // For graphics backend changes
|
||||
RECREATE_ACTIVITY, // Android
|
||||
COPY_TO_CLIPBOARD,
|
||||
SHARE_TEXT,
|
||||
SET_WINDOW_TITLE,
|
||||
|
@ -127,6 +130,8 @@ enum SystemProperty {
|
|||
SYSPROP_HAS_BACK_BUTTON,
|
||||
SYSPROP_HAS_KEYBOARD,
|
||||
SYSPROP_HAS_OPEN_DIRECTORY,
|
||||
SYSPROP_HAS_LOGIN_DIALOG,
|
||||
SYSPROP_HAS_TEXT_INPUT_DIALOG, // Indicates that System_InputBoxGetString is available.
|
||||
|
||||
SYSPROP_CAN_CREATE_SHORTCUT,
|
||||
|
||||
|
@ -191,6 +196,7 @@ enum class SystemNotification {
|
|||
SUSTAINED_PERF_CHANGE,
|
||||
POLL_CONTROLLERS,
|
||||
TOGGLE_DEBUG_CONSOLE, // TODO: Kinda weird, just ported forward.
|
||||
TEST_JAVA_EXCEPTION,
|
||||
};
|
||||
|
||||
std::string System_GetProperty(SystemProperty prop);
|
||||
|
@ -206,18 +212,17 @@ std::vector<std::string> System_GetCameraDeviceList();
|
|||
bool System_AudioRecordingIsAvailable();
|
||||
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 ¶m);
|
||||
|
||||
// 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,
|
||||
// no need to implement separately.
|
||||
void System_AudioGetDebugStats(char *buf, size_t bufSize);
|
||||
void System_AudioClear();
|
||||
|
||||
// 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);
|
||||
|
||||
inline void System_AudioResetStatCounters() {
|
||||
|
|
|
@ -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 {
|
||||
_dbg_assert_(str != nullptr);
|
||||
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 {
|
||||
_dbg_assert_(str != nullptr);
|
||||
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
|
||||
float sizeFactor = (float)style.sizePts / 24.0f;
|
||||
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 {
|
||||
_dbg_assert_(str != nullptr);
|
||||
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
|
||||
float sizeFactor = (float)style.sizePts / 24.0f;
|
||||
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) {
|
||||
_dbg_assert_(str != nullptr);
|
||||
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
|
||||
// Use the font texture if this font is in that texture instead.
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
uint32_t alpha = (color >> 1) & 0xFF000000;
|
||||
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) {
|
||||
uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2);
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@ public:
|
|||
|
||||
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);
|
||||
const UI::FontStyle &GetFontStyle() { return *fontStyle_; }
|
||||
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 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);
|
||||
// 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 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);
|
||||
|
||||
// in dps, like dp_xres and dp_yres
|
||||
|
|
340
Common/UI/IconCache.cpp
Normal file
340
Common/UI/IconCache.cpp
Normal 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
75
Common/UI/IconCache.h
Normal 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;
|
|
@ -1,5 +1,6 @@
|
|||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/UI/PopupScreens.h"
|
||||
#include "Common/UI/ViewGroup.h"
|
||||
|
@ -7,6 +8,8 @@
|
|||
#include "Common/UI/Root.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Data/Text/I18n.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/System/Request.h"
|
||||
|
||||
namespace UI {
|
||||
|
||||
|
@ -495,6 +498,16 @@ PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string
|
|||
EventReturn PopupTextInputChoice::HandleClick(EventParams &e) {
|
||||
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_);
|
||||
popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange);
|
||||
if (e.v)
|
||||
|
@ -582,7 +595,12 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
|
|||
int paddingX = 12;
|
||||
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.
|
||||
|
||||
|
@ -636,4 +654,28 @@ std::string ChoiceWithValueDisplay::ValueText() const {
|
|||
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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue