From be4af6874ffa9a53d81491a4f5f65faac983dfc0 Mon Sep 17 00:00:00 2001 From: Narugakuruga <31060534+Narugakuruga@users.noreply.github.com> Date: Sat, 18 Feb 2023 22:32:38 +0800 Subject: [PATCH 001/141] Update zh_CN.ini --- assets/lang/zh_CN.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 12b0227052..9bb0c7a322 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -91,11 +91,11 @@ Swipe = 滑动 Swipe sensitivity = 滑动灵敏度 Swipe smoothing = 滑动平滑度 Thin borders = 细边框 -Tilt control setup = 倾斜控制设置… -Tilt Input Type = 倾斜输入类型 +Tilt control setup = 设置重力感应… +Tilt Input Type = 重力操作模式 Tilt Sensitivity along X axis = 沿X轴倾斜灵敏度 Tilt Sensitivity along Y axis = 沿Y轴倾斜灵敏度 -To Calibrate = 以您喜欢的角度握住设备,然后按“校准”。 +To Calibrate = 以喜欢的角度握住设备,然后按“校准”。 Toggle mode = 开关模式(只需按一次) Touch Control Visibility = 显示的按键 Use custom right analog = 自定义右摇杆用途 @@ -1124,7 +1124,7 @@ Failed to save state. Error in the file system. = 无法保存即时存档。文 Fast (lag on slow storage) = 快速模式(存储低速时有延迟) Fast Memory = 快速内存(不稳定) Force real clock sync (slower, less lag) = 强制同步实际时钟(降低延迟,很慢) -seconds, 0:off = 秒, 0=关 +seconds, 0:off = 每n秒,0=不回档 Games list settings = 游戏列表设置 General = 常规设置 Grid icon size = 图标方格尺寸 From 37ca5d5e95ace6ba24d20447791589f84c8f6a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Moraz=C3=A1n?= <35014183+GABO1423@users.noreply.github.com> Date: Wed, 22 Feb 2023 18:03:12 -0400 Subject: [PATCH 002/141] (UWP) File Tweaks --- UWP/PackageGold.appxmanifest | 2 +- UWP/PackageNormal.appxmanifest | 2 +- UWP/cpu_features_UWP/cpu_features_UWP.vcxproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/UWP/PackageGold.appxmanifest b/UWP/PackageGold.appxmanifest index d428bcd738..216c2dfb5e 100644 --- a/UWP/PackageGold.appxmanifest +++ b/UWP/PackageGold.appxmanifest @@ -1,6 +1,6 @@ - + PPSSPP Gold - PSP emulator diff --git a/UWP/PackageNormal.appxmanifest b/UWP/PackageNormal.appxmanifest index ef79928e4c..c564357242 100644 --- a/UWP/PackageNormal.appxmanifest +++ b/UWP/PackageNormal.appxmanifest @@ -1,6 +1,6 @@  - + PPSSPP - PSP emulator diff --git a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj index f4d225d3b3..7e9cc89900 100644 --- a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj +++ b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj @@ -398,7 +398,7 @@ - + From cbc1ed4bfe44b6c36f44d570620771e4a8633fa7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 22 Feb 2023 21:14:33 -0800 Subject: [PATCH 003/141] Debugger: Lock memory during stack walk. In case of shutdown during it. --- Windows/Debugger/Debugger_Disasm.cpp | 7 +++---- Windows/Debugger/Debugger_Lists.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 584533703d..580166e407 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -291,11 +291,10 @@ void CDisasm::stepOver() UpdateDialog(); } -void CDisasm::stepOut() -{ - if (!PSP_IsInited()) { +void CDisasm::stepOut() { + auto memLock = Memory::Lock(); + if (!PSP_IsInited()) return; - } auto threads = GetThreadsInfo(); diff --git a/Windows/Debugger/Debugger_Lists.cpp b/Windows/Debugger/Debugger_Lists.cpp index 97be390f59..0c76c4d936 100644 --- a/Windows/Debugger/Debugger_Lists.cpp +++ b/Windows/Debugger/Debugger_Lists.cpp @@ -700,8 +700,11 @@ void CtrlStackTraceView::OnDoubleClick(int itemIndex, int column) SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,frames[itemIndex].pc,0); } -void CtrlStackTraceView::loadStackTrace() -{ +void CtrlStackTraceView::loadStackTrace() { + auto memLock = Memory::Lock(); + if (!PSP_IsInited()) + return; + auto threads = GetThreadsInfo(); u32 entry = 0, stackTop = 0; From cd3fc26190c4ef0897884449094a724022b4d2ae Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 22 Feb 2023 21:15:03 -0800 Subject: [PATCH 004/141] samplerjit: Prevent thread local stale cache read. If the generation count happens to match, would still get a stale pointer and crash. Let's just make the generation count static so it always increases. --- GPU/Software/DrawPixel.cpp | 2 ++ GPU/Software/DrawPixel.h | 2 +- GPU/Software/Sampler.cpp | 2 ++ GPU/Software/Sampler.h | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GPU/Software/DrawPixel.cpp b/GPU/Software/DrawPixel.cpp index 72b6e2a91a..eda04b0b41 100644 --- a/GPU/Software/DrawPixel.cpp +++ b/GPU/Software/DrawPixel.cpp @@ -802,10 +802,12 @@ SingleFunc PixelJitCache::GenericSingle(const PixelFuncID &id) { } thread_local PixelJitCache::LastCache PixelJitCache::lastSingle_; +int PixelJitCache::clearGen_ = 0; // 256k should be plenty of space for plenty of variations. PixelJitCache::PixelJitCache() : CodeBlock(1024 * 64 * 4), cache_(64) { lastSingle_.gen = -1; + clearGen_++; } void PixelJitCache::Clear() { diff --git a/GPU/Software/DrawPixel.h b/GPU/Software/DrawPixel.h index d72c04a744..b0eae46ac6 100644 --- a/GPU/Software/DrawPixel.h +++ b/GPU/Software/DrawPixel.h @@ -128,7 +128,7 @@ private: DenseHashMap cache_; std::unordered_map addresses_; std::unordered_set compileQueue_; - int clearGen_ = 0; + static int clearGen_; static thread_local LastCache lastSingle_; const u8 *constBlendHalf_11_4s_ = nullptr; diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index dd022c5efd..eef759344f 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -109,12 +109,14 @@ FetchFunc GetFetchFunc(SamplerID id, BinManager *binner) { thread_local SamplerJitCache::LastCache SamplerJitCache::lastFetch_; thread_local SamplerJitCache::LastCache SamplerJitCache::lastNearest_; thread_local SamplerJitCache::LastCache SamplerJitCache::lastLinear_; +int SamplerJitCache::clearGen_ = 0; // 256k should be enough. SamplerJitCache::SamplerJitCache() : Rasterizer::CodeBlock(1024 * 64 * 4), cache_(64) { lastFetch_.gen = -1; lastNearest_.gen = -1; lastLinear_.gen = -1; + clearGen_++; } void SamplerJitCache::Clear() { diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h index fbe606b110..ec8d4c2638 100644 --- a/GPU/Software/Sampler.h +++ b/GPU/Software/Sampler.h @@ -147,7 +147,7 @@ private: DenseHashMap cache_; std::unordered_map addresses_; std::unordered_set compileQueue_; - int clearGen_ = 0; + static int clearGen_; static thread_local LastCache lastFetch_; static thread_local LastCache lastNearest_; static thread_local LastCache lastLinear_; From bec429ed72049c8e2aba8842ac155c7cc77024d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 23 Feb 2023 20:21:41 +0100 Subject: [PATCH 005/141] MacOS buildfix for some reason --- Core/SaveState.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 2715d897b0..3f51aebbc6 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -291,10 +291,10 @@ namespace SaveState } private: - static const int BLOCK_SIZE = 8192; - static const int REWIND_NUM_STATES = 20; + const int BLOCK_SIZE = 8192; + const int REWIND_NUM_STATES = 20; // TODO: Instead, based on size of compressed state? - static const int BASE_USAGE_INTERVAL = 15; + const int BASE_USAGE_INTERVAL = 15; typedef std::vector StateBuffer; From c6b44cd5d7ddbf8973747b7a415d8ecd4007ee4f Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Fri, 24 Feb 2023 23:44:32 +0300 Subject: [PATCH 006/141] Update gitignore --- Windows/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/Windows/.gitignore b/Windows/.gitignore index 653ef6879e..8d4dba11ef 100644 --- a/Windows/.gitignore +++ b/Windows/.gitignore @@ -1,3 +1,4 @@ *.VC.VC.opendb *.VC.db +*.txt enc_temp_folder From aab4a3034c4f611d47dd7250c6ec7e6774dc2515 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Fri, 24 Feb 2023 23:13:48 +0300 Subject: [PATCH 007/141] Added new MainWindow menu item "copy PSP memory base pointer" --- Windows/MainWindowMenu.cpp | 8 ++++++++ Windows/ppsspp.rc | 1 + Windows/resource.h | 1 + 3 files changed, 10 insertions(+) diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index ee15fab63b..022e1e70cc 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -91,6 +91,7 @@ namespace MainWindow { EnableMenuItem(menu, ID_DEBUG_TAKESCREENSHOT, menuEnable); EnableMenuItem(menu, ID_DEBUG_SHOWDEBUGSTATISTICS, menuInGameEnable); EnableMenuItem(menu, ID_DEBUG_EXTRACTFILE, menuEnable); + EnableMenuItem(menu, ID_DEBUG_MEMORYBASE, menuInGameEnable); // While playing, this pop up doesn't work - and probably doesn't make sense. EnableMenuItem(menu, ID_OPTIONS_LANGUAGE, state == UISTATE_INGAME ? MF_GRAYED : MF_ENABLED); @@ -221,6 +222,7 @@ namespace MainWindow { TranslateMenuItem(menu, ID_DEBUG_GEDEBUGGER, g_Config.bSystemControls ? L"\tCtrl+G" : L""); TranslateMenuItem(menu, ID_DEBUG_EXTRACTFILE); TranslateMenuItem(menu, ID_DEBUG_LOG, g_Config.bSystemControls ? L"\tCtrl+L" : L""); + TranslateMenuItem(menu, ID_DEBUG_MEMORYBASE); TranslateMenuItem(menu, ID_DEBUG_MEMORYVIEW, g_Config.bSystemControls ? L"\tCtrl+M" : L""); // Options menu @@ -822,6 +824,12 @@ namespace MainWindow { memoryWindow->Show(true); break; + case ID_DEBUG_MEMORYBASE: + { + W32Util::CopyTextToClipboard(hWnd, ConvertUTF8ToWString(StringFromFormat("%016llx", (uintptr_t)Memory::base))); + break; + } + case ID_DEBUG_EXTRACTFILE: { std::string filename; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index b547443913..d5109b9cd0 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -537,6 +537,7 @@ BEGIN MENUITEM "GE Debugger...", ID_DEBUG_GEDEBUGGER MENUITEM "Extract File...", ID_DEBUG_EXTRACTFILE MENUITEM "Log Console", ID_DEBUG_LOG + MENUITEM "Copy PSP memory base pointer", ID_DEBUG_MEMORYBASE MENUITEM "Memory View...", ID_DEBUG_MEMORYVIEW END diff --git a/Windows/resource.h b/Windows/resource.h index 66ec982d23..fc421b7e60 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -18,6 +18,7 @@ #define ID_DEBUG_DISASSEMBLY 119 #define WHEEL_DELTA 120 #define ID_DEBUG_LOG 121 +#define ID_DEBUG_MEMORYBASE 122 #define ID_FILE_OPEN_NEW_INSTANCE 123 #define ID_FILE_LOADSTATEFILE 126 #define ID_FILE_SAVESTATEFILE 127 From 0cb051a37f717cb6eb5c0953793d0763b88a5474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 24 Feb 2023 22:45:49 +0100 Subject: [PATCH 008/141] Update SPIRV-Cross to a version that builds in debug mode on the latest VS. The old bug that used to cause a warning now fails outright, so the upgrade is necessary. --- ext/SPIRV-Cross | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/SPIRV-Cross b/ext/SPIRV-Cross index c77b09b57c..4212eef67e 160000 --- a/ext/SPIRV-Cross +++ b/ext/SPIRV-Cross @@ -1 +1 @@ -Subproject commit c77b09b57c27837dc2d41aa371ed3d236ce9ce47 +Subproject commit 4212eef67ed0ca048cb726a6767185504e7695e5 From 61c5b9e0ca5bc16675fbe8fac93f2ffb28cea8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 24 Feb 2023 22:59:32 +0100 Subject: [PATCH 009/141] Retarget UWP projects for latest VS build 19061 is the oldest SDK installed by default. Also disable "SDL" (security development lifecycle) checks in SPIRV-Cross because it doesn't build with it... --- UWP/Armips_UWP/Armips_UWP.vcxproj | 4 ++-- UWP/CommonUWP/CommonUWP.vcxproj | 4 ++-- UWP/CoreUWP/CoreUWP.vcxproj | 4 ++-- UWP/GPU_UWP/GPU_UWP.vcxproj | 4 ++-- UWP/Package.appxmanifest | 2 +- UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj | 12 ++++++------ UWP/UI_UWP/UI_UWP.vcxproj | 4 ++-- UWP/UWP.vcxproj | 9 +++------ UWP/cpu_features_UWP/cpu_features_UWP.vcxproj | 6 +++--- .../cpu_features_UWP.vcxproj.filters | 8 ++------ UWP/glslang_UWP/glslang_UWP.vcxproj | 6 +++--- UWP/glslang_UWP/glslang_UWP.vcxproj.filters | 2 ++ UWP/libkirk_UWP/libkirk_UWP.vcxproj | 6 +++--- UWP/libzstd_UWP/libzstd_UWP.vcxproj | 4 ++-- UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj | 4 ++-- UWP/zlib_UWP/zlib_UWP.vcxproj | 6 +++--- 16 files changed, 40 insertions(+), 45 deletions(-) diff --git a/UWP/Armips_UWP/Armips_UWP.vcxproj b/UWP/Armips_UWP/Armips_UWP.vcxproj index 4610681d23..f10d16ddfa 100644 --- a/UWP/Armips_UWP/Armips_UWP.vcxproj +++ b/UWP/Armips_UWP/Armips_UWP.vcxproj @@ -42,8 +42,8 @@ 14.0 true Windows Store - 10.0.17763.0 - 10.0.15063.0 + 10.0 + 10.0.19041.0 10.0 diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 99e542cc99..4a10010455 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 2d89c268b3..886dc9f29e 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 369a75da72..0b8ed0070f 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 diff --git a/UWP/Package.appxmanifest b/UWP/Package.appxmanifest index 68b9c930d7..b1e9b4db73 100644 --- a/UWP/Package.appxmanifest +++ b/UWP/Package.appxmanifest @@ -1,6 +1,6 @@  - + PPSSPP - PSP emulator diff --git a/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj b/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj index b27d1659aa..f57655dc95 100644 --- a/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj +++ b/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 @@ -202,7 +202,7 @@ Use false - true + false pch.h _UNICODE;UNICODE;%(PreprocessorDefinitions);NOMINMAX @@ -244,7 +244,7 @@ Use false - true + false pch.h _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions);NOMINMAX @@ -258,7 +258,7 @@ Use false - true + false pch.h _ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions);NOMINMAX @@ -328,7 +328,7 @@ Use false - true + false pch.h _UNICODE;UNICODE;%(PreprocessorDefinitions);NOMINMAX diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index eda2c2c994..ad66ad74dc 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 5fcdbecf25..1cd5d12174 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -58,10 +58,10 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 PPSSPP_UWP - 10.0.17763.0 + 10.0 @@ -1734,9 +1734,6 @@ true - - - @@ -1746,4 +1743,4 @@ - + \ No newline at end of file diff --git a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj index 7e9cc89900..722e0e8b16 100644 --- a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj +++ b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 @@ -439,4 +439,4 @@ - + \ No newline at end of file diff --git a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj.filters b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj.filters index 0fad65bc31..d0acee9d45 100644 --- a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj.filters +++ b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj.filters @@ -66,11 +66,6 @@ Header Files\internal - - - Resource Files - - Source Files @@ -87,6 +82,7 @@ Source Files + @@ -130,4 +126,4 @@ Source Files - + \ No newline at end of file diff --git a/UWP/glslang_UWP/glslang_UWP.vcxproj b/UWP/glslang_UWP/glslang_UWP.vcxproj index 2242d14f57..656b796b7d 100644 --- a/UWP/glslang_UWP/glslang_UWP.vcxproj +++ b/UWP/glslang_UWP/glslang_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 @@ -508,4 +508,4 @@ - + \ No newline at end of file diff --git a/UWP/glslang_UWP/glslang_UWP.vcxproj.filters b/UWP/glslang_UWP/glslang_UWP.vcxproj.filters index dc0cd80fc3..bff8201bc4 100644 --- a/UWP/glslang_UWP/glslang_UWP.vcxproj.filters +++ b/UWP/glslang_UWP/glslang_UWP.vcxproj.filters @@ -170,6 +170,7 @@ hlsl + @@ -348,5 +349,6 @@ glslang + \ No newline at end of file diff --git a/UWP/libkirk_UWP/libkirk_UWP.vcxproj b/UWP/libkirk_UWP/libkirk_UWP.vcxproj index 510ff09681..5664ad0d4a 100644 --- a/UWP/libkirk_UWP/libkirk_UWP.vcxproj +++ b/UWP/libkirk_UWP/libkirk_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 @@ -396,4 +396,4 @@ - + \ No newline at end of file diff --git a/UWP/libzstd_UWP/libzstd_UWP.vcxproj b/UWP/libzstd_UWP/libzstd_UWP.vcxproj index 78101ed883..3f063dfb71 100644 --- a/UWP/libzstd_UWP/libzstd_UWP.vcxproj +++ b/UWP/libzstd_UWP/libzstd_UWP.vcxproj @@ -42,8 +42,8 @@ 14.0 true Windows Store - 10.0.17763.0 - 10.0.15063.0 + 10.0 + 10.0.19041.0 10.0 diff --git a/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj b/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj index 5e42ecf2a4..e89bebd5f5 100644 --- a/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj +++ b/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 diff --git a/UWP/zlib_UWP/zlib_UWP.vcxproj b/UWP/zlib_UWP/zlib_UWP.vcxproj index 5ac9ab5c08..ea314b2c44 100644 --- a/UWP/zlib_UWP/zlib_UWP.vcxproj +++ b/UWP/zlib_UWP/zlib_UWP.vcxproj @@ -58,9 +58,9 @@ 14.0 true Windows Store - 10.0.15063.0 + 10.0.19041.0 10.0 - 10.0.17763.0 + 10.0 @@ -396,4 +396,4 @@ - + \ No newline at end of file From 1b5148bb6c5a32e85aea6414badb71d5065b84cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 24 Feb 2023 21:53:12 +0100 Subject: [PATCH 010/141] Remove kinda-duplicate function. --- GPU/Common/TextureCacheCommon.cpp | 8 ++++---- GPU/Common/TextureCacheCommon.h | 1 - GPU/D3D11/TextureCacheD3D11.cpp | 12 +++--------- GPU/D3D11/TextureCacheD3D11.h | 1 - GPU/Directx9/TextureCacheDX9.cpp | 6 +++--- GPU/Directx9/TextureCacheDX9.h | 5 +---- GPU/GLES/TextureCacheGLES.cpp | 6 +++--- GPU/GLES/TextureCacheGLES.h | 3 --- GPU/Vulkan/TextureCacheVulkan.cpp | 2 -- GPU/Vulkan/TextureCacheVulkan.h | 2 -- 10 files changed, 14 insertions(+), 32 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index e6c29e8264..6178df18b8 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -817,7 +817,7 @@ void TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const c gpuStats.numTextureInvalidations++; DEBUG_LOG(G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason); if (doDelete) { - InvalidateLastTexture(); + ForgetLastTexture(); ReleaseTexture(entry, true); entry->status &= ~TexCacheEntry::STATUS_IS_SCALED; } @@ -1990,7 +1990,7 @@ void TextureCacheCommon::ApplyTexture() { TexCacheEntry *entry = nextTexture_; if (!entry) { // Maybe we bound a framebuffer? - InvalidateLastTexture(); + ForgetLastTexture(); if (failedTexture_) { // Backends should handle this by binding a black texture with 0 alpha. BindTexture(nullptr); @@ -2050,7 +2050,7 @@ void TextureCacheCommon::ApplyTexture() { if (nextNeedsRebuild_) { _assert_(!entry->texturePtr); BuildTexture(entry); - InvalidateLastTexture(); + ForgetLastTexture(); } if (entry->status & TexCacheEntry::STATUS_CLUT_GPU) { @@ -2205,7 +2205,7 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer gstate_c.SetTextureFullAlpha(alphaStatus == CHECKALPHA_FULL); draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); - InvalidateLastTexture(); + ForgetLastTexture(); return; } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 6d3fcd604f..f85e09b9a4 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -329,7 +329,6 @@ public: TextureShaderCache *GetTextureShaderCache() { return textureShaderCache_; } virtual void ForgetLastTexture() = 0; - virtual void InvalidateLastTexture() = 0; virtual void Clear(bool delete_them); virtual void NotifyConfigChanged(); virtual void ApplySamplingParams(const SamplerCacheKey &key) = 0; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 31433016ee..c8d6b9194a 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -179,20 +179,16 @@ void TextureCacheD3D11::ReleaseTexture(TexCacheEntry *entry, bool delete_them) { } void TextureCacheD3D11::ForgetLastTexture() { - InvalidateLastTexture(); + lastBoundTexture = INVALID_TEX; ID3D11ShaderResourceView *nullTex[4]{}; context_->PSSetShaderResources(0, 4, nullTex); } -void TextureCacheD3D11::InvalidateLastTexture() { - lastBoundTexture = INVALID_TEX; -} - void TextureCacheD3D11::StartFrame() { TextureCacheCommon::StartFrame(); - InvalidateLastTexture(); + lastBoundTexture = INVALID_TEX; timesInvalidatedAllThisFrame_ = 0; replacementTimeThisFrame_ = 0.0; @@ -268,9 +264,7 @@ void TextureCacheD3D11::ApplySamplingParams(const SamplerCacheKey &key) { } void TextureCacheD3D11::Unbind() { - ID3D11ShaderResourceView *nullView = nullptr; - context_->PSSetShaderResources(0, 1, &nullView); - InvalidateLastTexture(); + ForgetLastTexture(); } void TextureCacheD3D11::BindAsClutTexture(Draw::Texture *tex, bool smooth) { diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 20d84d6d48..4cb01375ff 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -51,7 +51,6 @@ public: void SetFramebufferManager(FramebufferManagerD3D11 *fbManager); void ForgetLastTexture() override; - void InvalidateLastTexture() override; bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 591dc3f85c..d615c4e330 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -111,7 +111,7 @@ void TextureCacheDX9::ReleaseTexture(TexCacheEntry *entry, bool delete_them) { } } -void TextureCacheDX9::InvalidateLastTexture() { +void TextureCacheDX9::ForgetLastTexture() { lastBoundTexture = INVALID_TEX; } @@ -154,7 +154,7 @@ void TextureCacheDX9::ApplySamplingParams(const SamplerCacheKey &key) { void TextureCacheDX9::StartFrame() { TextureCacheCommon::StartFrame(); - InvalidateLastTexture(); + lastBoundTexture = nullptr; timesInvalidatedAllThisFrame_ = 0; replacementTimeThisFrame_ = 0.0; @@ -229,7 +229,7 @@ void TextureCacheDX9::BindTexture(TexCacheEntry *entry) { void TextureCacheDX9::Unbind() { device_->SetTexture(0, nullptr); - InvalidateLastTexture(); + ForgetLastTexture(); } void TextureCacheDX9::BindAsClutTexture(Draw::Texture *tex, bool smooth) { diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index 23c2e0163b..0080fbdef3 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -38,10 +38,7 @@ public: void SetFramebufferManager(FramebufferManagerDX9 *fbManager); - void ForgetLastTexture() override { - InvalidateLastTexture(); - } - void InvalidateLastTexture() override; + void ForgetLastTexture() override; bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 36ce7837d9..c4ed59221d 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -143,7 +143,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dst void TextureCacheGLES::StartFrame() { TextureCacheCommon::StartFrame(); - InvalidateLastTexture(); + ForgetLastTexture(); timesInvalidatedAllThisFrame_ = 0; replacementTimeThisFrame_ = 0.0; @@ -234,7 +234,7 @@ void TextureCacheGLES::BindTexture(TexCacheEntry *entry) { void TextureCacheGLES::Unbind() { render_->BindTexture(TEX_SLOT_PSP_TEXTURE, nullptr); - InvalidateLastTexture(); + ForgetLastTexture(); } void TextureCacheGLES::BindAsClutTexture(Draw::Texture *tex, bool smooth) { @@ -385,7 +385,7 @@ Draw::DataFormat TextureCacheGLES::GetDestFormat(GETextureFormat format, GEPalet } bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { - InvalidateLastTexture(); + ForgetLastTexture(); SetTexture(); if (!nextTexture_) { return GetCurrentFramebufferTextureDebug(buffer, isFramebuffer); diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 8f9517371b..2546c605b9 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -51,9 +51,6 @@ public: void ForgetLastTexture() override { lastBoundTexture = nullptr; } - void InvalidateLastTexture() override { - lastBoundTexture = nullptr; - } bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 1e43f8d355..349d608c3a 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -329,7 +329,6 @@ static const VkFilter MagFiltVK[2] = { void TextureCacheVulkan::StartFrame() { TextureCacheCommon::StartFrame(); - InvalidateLastTexture(); textureShaderCache_->Decimate(); timesInvalidatedAllThisFrame_ = 0; @@ -419,7 +418,6 @@ void TextureCacheVulkan::ApplySamplingParams(const SamplerCacheKey &key) { void TextureCacheVulkan::Unbind() { imageView_ = VK_NULL_HANDLE; curSampler_ = VK_NULL_HANDLE; - InvalidateLastTexture(); } void TextureCacheVulkan::BindAsClutTexture(Draw::Texture *tex, bool smooth) { diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 2c16d02a03..3654101b90 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -72,8 +72,6 @@ public: } void ForgetLastTexture() override {} - void InvalidateLastTexture() override {} - void NotifyConfigChanged() override; void GetVulkanHandles(VkImageView &imageView, VkSampler &sampler) { From 49e5460c9c3da2a0ebd99d99aaffe9bc0bb8703d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 24 Feb 2023 22:09:32 +0100 Subject: [PATCH 011/141] Remove count parameter from SetViewports. No use foreseen. --- Common/GPU/D3D11/thin3d_d3d11.cpp | 28 ++++++++++++------------- Common/GPU/D3D9/thin3d_d3d9.cpp | 14 ++++++------- Common/GPU/OpenGL/thin3d_gl.cpp | 4 ++-- Common/GPU/Vulkan/thin3d_vulkan.cpp | 24 ++++++++++----------- Common/GPU/thin3d.h | 2 +- Common/UI/UIScreen.cpp | 2 +- GPU/Common/DepthBufferCommon.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 8 +++---- GPU/Common/PresentationCommon.cpp | 4 ++-- GPU/Common/StencilCommon.cpp | 2 +- GPU/Common/TextureCacheCommon.cpp | 8 +++---- GPU/D3D11/StateMappingD3D11.cpp | 2 +- GPU/GLES/StencilBufferGLES.cpp | 2 +- UI/EmuScreen.cpp | 4 ++-- UI/MiscScreens.cpp | 2 +- UWP/PPSSPP_UWPMain.cpp | 2 +- 16 files changed, 53 insertions(+), 57 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index c0fcb9afbb..120f8db5ea 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -112,7 +112,7 @@ public: // Raster state void SetScissorRect(int left, int top, int width, int height) override; - void SetViewports(int count, Viewport *viewports) override; + void SetViewport(const Viewport &viewport) override; void SetBlendFactor(float color[4]) override { if (memcmp(blendFactor_, color, sizeof(float) * 4)) { memcpy(blendFactor_, color, sizeof(float) * 4); @@ -420,20 +420,18 @@ void D3D11DrawContext::EndFrame() { curPipeline_ = nullptr; } -void D3D11DrawContext::SetViewports(int count, Viewport *viewports) { - D3D11_VIEWPORT vp[4]; - for (int i = 0; i < count; i++) { - DisplayRect rc{ viewports[i].TopLeftX , viewports[i].TopLeftY, viewports[i].Width, viewports[i].Height }; - if (curRenderTargetView_ == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong! - RotateRectToDisplay(rc, curRTWidth_, curRTHeight_); - vp[i].TopLeftX = rc.x; - vp[i].TopLeftY = rc.y; - vp[i].Width = rc.w; - vp[i].Height = rc.h; - vp[i].MinDepth = viewports[i].MinDepth; - vp[i].MaxDepth = viewports[i].MaxDepth; - } - context_->RSSetViewports(count, vp); +void D3D11DrawContext::SetViewport(const Viewport &viewport) { + DisplayRect rc{ viewport.TopLeftX , viewport.TopLeftY, viewport.Width, viewport.Height }; + if (curRenderTargetView_ == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong! + RotateRectToDisplay(rc, curRTWidth_, curRTHeight_); + D3D11_VIEWPORT vp; + vp.TopLeftX = rc.x; + vp.TopLeftY = rc.y; + vp.Width = rc.w; + vp.Height = rc.h; + vp.MinDepth = viewport.MinDepth; + vp.MaxDepth = viewport.MaxDepth; + context_->RSSetViewports(1, &vp); } void D3D11DrawContext::SetScissorRect(int left, int top, int width, int height) { diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index b0116c875a..b130aae436 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -573,7 +573,7 @@ public: // Raster state void SetScissorRect(int left, int top, int width, int height) override; - void SetViewports(int count, Viewport *viewports) override; + void SetViewport(const Viewport &viewport) override; void SetBlendFactor(float color[4]) override; void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) override; @@ -1173,12 +1173,12 @@ void D3D9Context::SetScissorRect(int left, int top, int width, int height) { dxstate.scissorTest.set(true); } -void D3D9Context::SetViewports(int count, Viewport *viewports) { - int x = (int)viewports[0].TopLeftX; - int y = (int)viewports[0].TopLeftY; - int w = (int)viewports[0].Width; - int h = (int)viewports[0].Height; - dxstate.viewport.set(x, y, w, h, viewports[0].MinDepth, viewports[0].MaxDepth); +void D3D9Context::SetViewport(const Viewport &viewport) { + int x = (int)viewport.TopLeftX; + int y = (int)viewport.TopLeftY; + int w = (int)viewport.Width; + int h = (int)viewport.Height; + dxstate.viewport.set(x, y, w, h, viewport.MinDepth, viewport.MaxDepth); } void D3D9Context::SetBlendFactor(float color[4]) { diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index ca539f1f17..5cb85ac9c7 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -385,9 +385,9 @@ public: renderManager_.SetScissor({ left, top, width, height }); } - void SetViewports(int count, Viewport *viewports) override { + void SetViewport(const Viewport &viewport) override { // Same structure, different name. - renderManager_.SetViewport((GLRViewport &)*viewports); + renderManager_.SetViewport((GLRViewport &)viewport); } void SetBlendFactor(float color[4]) override { diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 14c1c0e502..b0c74c5c76 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -425,7 +425,7 @@ public: void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void SetScissorRect(int left, int top, int width, int height) override; - void SetViewports(int count, Viewport *viewports) override; + void SetViewport(const Viewport &viewport) override; void SetBlendFactor(float color[4]) override; void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) override; @@ -1237,18 +1237,16 @@ void VKContext::SetScissorRect(int left, int top, int width, int height) { renderManager_.SetScissor(left, top, width, height); } -void VKContext::SetViewports(int count, Viewport *viewports) { - if (count > 0) { - // Ignore viewports more than the first. - VkViewport viewport; - viewport.x = viewports[0].TopLeftX; - viewport.y = viewports[0].TopLeftY; - viewport.width = viewports[0].Width; - viewport.height = viewports[0].Height; - viewport.minDepth = viewports[0].MinDepth; - viewport.maxDepth = viewports[0].MaxDepth; - renderManager_.SetViewport(viewport); - } +void VKContext::SetViewport(const Viewport &viewport) { + // Ignore viewports more than the first. + VkViewport vkViewport; + vkViewport.x = viewport.TopLeftX; + vkViewport.y = viewport.TopLeftY; + vkViewport.width = viewport.Width; + vkViewport.height = viewport.Height; + vkViewport.minDepth = viewport.MinDepth; + vkViewport.maxDepth = viewport.MaxDepth; + renderManager_.SetViewport(vkViewport); } void VKContext::SetBlendFactor(float color[4]) { diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 3db2d7b8db..9a427027a9 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -733,7 +733,7 @@ public: // Dynamic state virtual void SetScissorRect(int left, int top, int width, int height) = 0; - virtual void SetViewports(int count, Viewport *viewports) = 0; + virtual void SetViewport(const Viewport &viewport) = 0; virtual void SetBlendFactor(float color[4]) = 0; virtual void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) = 0; diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index 768fc88b16..d1fc99595f 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -102,7 +102,7 @@ void UIScreen::preRender() { viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; - draw->SetViewports(1, &viewport); + draw->SetViewport(viewport); draw->SetTargetSize(pixel_xres, pixel_yres); } diff --git a/GPU/Common/DepthBufferCommon.cpp b/GPU/Common/DepthBufferCommon.cpp index 3c78362352..46a2ecfb6f 100644 --- a/GPU/Common/DepthBufferCommon.cpp +++ b/GPU/Common/DepthBufferCommon.cpp @@ -207,7 +207,7 @@ bool FramebufferManagerCommon::ReadbackDepthbuffer(Draw::Framebuffer *fbo, int x auto *blitFBO = GetTempFBO(TempFBO::COPY, fbo->Width() * scaleX, fbo->Height() * scaleY); draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackDepthbufferSync"); Draw::Viewport viewport = { 0.0f, 0.0f, (float)destW, (float)destH, 0.0f, 1.0f }; - draw_->SetViewports(1, &viewport); + draw_->SetViewport(viewport); draw_->SetScissorRect(0, 0, fbo->Width() * scaleX, fbo->Height() * scaleY); draw_->BindFramebufferAsTexture(fbo, TEX_SLOT_PSP_TEXTURE, FB_DEPTH_BIT, 0); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index a126bb7fb2..4c0c4dea0b 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1429,8 +1429,8 @@ void FramebufferManagerCommon::DrawFramebufferToOutput(const u8 *srcPixels, int } void FramebufferManagerCommon::SetViewport2D(int x, int y, int w, int h) { - Draw::Viewport vp{ (float)x, (float)y, (float)w, (float)h, 0.0f, 1.0f }; - draw_->SetViewports(1, &vp); + Draw::Viewport viewport{ (float)x, (float)y, (float)w, (float)h, 0.0f, 1.0f }; + draw_->SetViewport(viewport); } void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { @@ -3235,8 +3235,8 @@ void FramebufferManagerCommon::BlitUsingRaster( draw_->InvalidateFramebuffer(Draw::FB_INVALIDATION_LOAD, pipeline->info.writeChannel == RASTER_COLOR ? Draw::FB_COLOR_BIT : Draw::FB_DEPTH_BIT); } - Draw::Viewport vp{ 0.0f, 0.0f, (float)dest->Width(), (float)dest->Height(), 0.0f, 1.0f }; - draw_->SetViewports(1, &vp); + Draw::Viewport viewport{ 0.0f, 0.0f, (float)dest->Width(), (float)dest->Height(), 0.0f, 1.0f }; + draw_->SetViewport(viewport); draw_->SetScissorRect(0, 0, (int)dest->Width(), (int)dest->Height()); draw2D_.Blit(pipeline, srcX1, srcY1, srcX2, srcY2, destX1, destY1, destX2, destY2, (float)srcW, (float)srcH, (float)destW, (float)destH, linearFilter, scaleFactor); diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 4341961214..0c027ae796 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -734,7 +734,7 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u int nextWidth, nextHeight; draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight); Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f }; - draw_->SetViewports(1, &viewport); + draw_->SetViewport(viewport); draw_->SetScissorRect(0, 0, nextWidth, nextHeight); CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms); @@ -853,7 +853,7 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u auto setViewport = [&](float x, float y, float w, float h) { Draw::Viewport viewport{ x, y, w, h, 0.0f, 1.0f }; - draw_->SetViewports(1, &viewport); + draw_->SetViewport(viewport); }; CardboardSettings cardboardSettings; diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp index 9bf141756c..00e81b19d1 100644 --- a/GPU/Common/StencilCommon.cpp +++ b/GPU/Common/StencilCommon.cpp @@ -304,7 +304,7 @@ bool FramebufferManagerCommon::PerformWriteStencilFromMemory(u32 addr, int size, } Draw::Viewport viewport = { 0.0f, 0.0f, (float)w, (float)h, 0.0f, 1.0f }; - draw_->SetViewports(1, &viewport); + draw_->SetViewport(viewport); // TODO: Switch the format to a single channel format? Draw::Texture *tex = MakePixelTexture(src, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstBuffer->height); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 6178df18b8..6611c21156 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2247,8 +2247,8 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); draw_->InvalidateFramebuffer(Draw::FB_INVALIDATION_STORE, Draw::FB_DEPTH_BIT | Draw::FB_STENCIL_BIT); draw_->SetScissorRect(u1, v1, u2 - u1, v2 - v1); - Draw::Viewport vp{ 0.0f, 0.0f, (float)depalWidth, (float)framebuffer->renderHeight, 0.0f, 1.0f }; - draw_->SetViewports(1, &vp); + Draw::Viewport viewport{ 0.0f, 0.0f, (float)depalWidth, (float)framebuffer->renderHeight, 0.0f, 1.0f }; + draw_->SetViewport(viewport); draw_->BindFramebufferAsTexture(framebuffer->fbo, 0, depth ? Draw::FB_DEPTH_BIT : Draw::FB_COLOR_BIT, Draw::ALL_LAYERS); if (clutRenderAddress_ == 0xFFFFFFFF) { @@ -2352,8 +2352,8 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); draw_->InvalidateFramebuffer(Draw::FB_INVALIDATION_STORE, Draw::FB_DEPTH_BIT | Draw::FB_STENCIL_BIT); draw_->SetScissorRect(u1, v1, u2 - u1, v2 - v1); - Draw::Viewport vp{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f }; - draw_->SetViewports(1, &vp); + Draw::Viewport viewport{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f }; + draw_->SetViewport(viewport); draw_->BindNativeTexture(0, GetNativeTextureView(entry)); draw_->BindFramebufferAsTexture(dynamicClutFbo_, 1, Draw::FB_COLOR_BIT, 0); diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index ac80c9627a..1d00bfb46d 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -442,7 +442,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { void DrawEngineD3D11::ApplyDrawStateLate(bool applyStencilRef, uint8_t stencilRef) { // we go through Draw here because it automatically handles screen rotation, as needed in UWP on mobiles. if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) { - draw_->SetViewports(1, &dynState_.viewport); + draw_->SetViewport(dynState_.viewport); draw_->SetScissorRect(dynState_.scissor.left, dynState_.scissor.top, dynState_.scissor.right - dynState_.scissor.left, dynState_.scissor.bottom - dynState_.scissor.top); } if (gstate_c.IsDirty(DIRTY_RASTER_STATE)) { diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index 110d2d94f7..763e5a95fe 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -133,7 +133,7 @@ bool FramebufferManagerGLES::ReadbackStencilbuffer(Draw::Framebuffer *fbo, int x auto *blitFBO = GetTempFBO(TempFBO::COPY, fbo->Width(), fbo->Height()); draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackStencilbufferSync"); Draw::Viewport viewport = { 0.0f, 0.0f, (float)fbo->Width(), (float)fbo->Height(), 0.0f, 1.0f }; - draw_->SetViewports(1, &viewport); + draw_->SetViewport(viewport); draw_->BindFramebufferAsTexture(fbo, TEX_SLOT_PSP_TEXTURE, FB_STENCIL_BIT, 0); draw_->BindSamplerStates(TEX_SLOT_PSP_TEXTURE, 1, &stencilReadbackSampler_); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 28287cc786..b95b41de36 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1375,7 +1375,7 @@ void EmuScreen::preRender() { viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; - draw->SetViewports(1, &viewport); + draw->SetViewport(viewport); } draw->SetTargetSize(pixel_xres, pixel_yres); } @@ -1523,7 +1523,7 @@ void EmuScreen::renderUI() { viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; - thin3d->SetViewports(1, &viewport); + thin3d->SetViewport(viewport); if (root_) { UI::LayoutViewHierarchy(*ctx, root_, false); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index c1bb989b01..7a8984dfef 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -375,7 +375,7 @@ void DrawGameBackground(UIContext &dc, const Path &gamePath, float x, float y, f viewport.Height = pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; - draw->SetViewports(1, &viewport); + draw->SetViewport(viewport); dc.BeginFrame(); dc.RebindTexture(); dc.Begin(); diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index b734f6e3d2..0438575d7c 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -201,7 +201,7 @@ bool PPSSPP_UWPMain::Render() { dp_xres = pixel_xres * g_dpi_scale_x; dp_yres = pixel_yres * g_dpi_scale_y; - context->RSSetViewports(1, &viewport); + context->RSSetViewport(viewport); NativeRender(ctx_.get()); return true; From e332259e7a94554afa9d5e16c8c3682f915dee45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 24 Feb 2023 23:51:01 +0100 Subject: [PATCH 012/141] Oops (fix UWP) --- UWP/PPSSPP_UWPMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 0438575d7c..b734f6e3d2 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -201,7 +201,7 @@ bool PPSSPP_UWPMain::Render() { dp_xres = pixel_xres * g_dpi_scale_x; dp_yres = pixel_yres * g_dpi_scale_y; - context->RSSetViewport(viewport); + context->RSSetViewports(1, &viewport); NativeRender(ctx_.get()); return true; From 30120560ab73caec5d0b598d8beed578a06c420b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 11:31:59 +0100 Subject: [PATCH 013/141] Include file cleanup in framebuffer managers --- GPU/D3D11/FramebufferManagerD3D11.cpp | 4 ---- GPU/D3D11/FramebufferManagerD3D11.h | 7 ------ GPU/D3D11/GPU_D3D11.cpp | 2 +- GPU/Directx9/FramebufferManagerDX9.cpp | 24 +++----------------- GPU/Directx9/FramebufferManagerDX9.h | 8 ------- GPU/Directx9/GPU_DX9.cpp | 2 +- GPU/GLES/FramebufferManagerGLES.cpp | 18 ++------------- GPU/GLES/FramebufferManagerGLES.h | 7 +----- GPU/Vulkan/FramebufferManagerVulkan.cpp | 30 ------------------------- GPU/Vulkan/FramebufferManagerVulkan.h | 16 +++---------- 10 files changed, 11 insertions(+), 107 deletions(-) diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index f05bc499fe..3e4b5c949c 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -15,10 +15,6 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include -#include -#include - #include "Common/Common.h" #include "Common/GPU/thin3d.h" diff --git a/GPU/D3D11/FramebufferManagerD3D11.h b/GPU/D3D11/FramebufferManagerD3D11.h index fc280e9de6..c105e16682 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.h +++ b/GPU/D3D11/FramebufferManagerD3D11.h @@ -17,20 +17,13 @@ #pragma once -#include - // Keeps track of allocated FBOs. // Also provides facilities for drawing and later converting raw // pixel data. -#include "GPU/GPUCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "Common/GPU/thin3d.h" -class TextureCacheD3D11; -class DrawEngineD3D11; -class ShaderManagerD3D11; - class FramebufferManagerD3D11 : public FramebufferManagerCommon { public: FramebufferManagerD3D11(Draw::DrawContext *draw); diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 5706c45fca..2b8179e472 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -15,7 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include +#include #include "Common/Log.h" #include "Common/Serialize/Serializer.h" diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index 58d92756f0..e0eda00207 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -15,34 +15,16 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include + #include "Common/Common.h" -#include "Common/Data/Convert/ColorConv.h" #include "Common/GPU/thin3d.h" -#include "Common/Math/lin/matrix4x4.h" -#include "Core/MemMap.h" -#include "Core/Config.h" -#include "Core/ConfigValues.h" -#include "Core/System.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" -#include "GPU/Debugger/Stepping.h" -#include "Common/GPU/D3D9/D3D9StateCache.h" #include "GPU/Common/FramebufferManagerCommon.h" +#include "GPU/Common/GPUStateUtils.h" #include "GPU/Common/PresentationCommon.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Directx9/FramebufferManagerDX9.h" -#include "GPU/Directx9/ShaderManagerDX9.h" -#include "GPU/Directx9/TextureCacheDX9.h" -#include "GPU/Directx9/DrawEngineDX9.h" - -#include "Common/GPU/thin3d.h" - -#include - -#ifdef _M_SSE -#include -#endif FramebufferManagerDX9::FramebufferManagerDX9(Draw::DrawContext *draw) : FramebufferManagerCommon(draw) { diff --git a/GPU/Directx9/FramebufferManagerDX9.h b/GPU/Directx9/FramebufferManagerDX9.h index 37458526fd..5a76d05944 100644 --- a/GPU/Directx9/FramebufferManagerDX9.h +++ b/GPU/Directx9/FramebufferManagerDX9.h @@ -17,17 +17,9 @@ #pragma once -#include - -#include - #include "GPU/GPUCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" -class TextureCacheDX9; -class DrawEngineDX9; -class ShaderManagerDX9; - class FramebufferManagerDX9 : public FramebufferManagerCommon { public: FramebufferManagerDX9(Draw::DrawContext *draw); diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index af8d80e327..a85903a94c 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -15,7 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include +#include #include "Common/Serialize/Serializer.h" #include "Common/GraphicsContext.h" diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index d88800873e..b226e9a5ce 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -15,28 +15,14 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include - -#include "Common/Data/Convert/ColorConv.h" -#include "Common/Profiler/Profiler.h" -#include "Common/GPU/OpenGL/GLCommon.h" -#include "Common/GPU/OpenGL/GLDebugLog.h" -#include "Common/GPU/OpenGL/GLSLProgram.h" +#include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/GPU/thin3d.h" -#include "Core/MemMap.h" -#include "Core/Config.h" -#include "Core/ConfigValues.h" #include "Core/System.h" -#include "Core/Reporting.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/PresentationCommon.h" #include "GPU/Common/TextureDecoder.h" -#include "GPU/Debugger/Stepping.h" #include "GPU/GLES/FramebufferManagerGLES.h" -#include "GPU/GLES/TextureCacheGLES.h" -#include "GPU/GLES/ShaderManagerGLES.h" FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw) : FramebufferManagerCommon(draw) diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index bdfaa27e8d..1cbe825d60 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -18,13 +18,8 @@ #pragma once #include "Common/GPU/thin3d.h" -#include "GPU/GPUCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" - -class TextureCacheGLES; -class DrawEngineGLES; -class ShaderManagerGLES; -class GLRProgram; +#include "GPU/Common/GPUDebugInterface.h" class FramebufferManagerGLES : public FramebufferManagerCommon { public: diff --git a/GPU/Vulkan/FramebufferManagerVulkan.cpp b/GPU/Vulkan/FramebufferManagerVulkan.cpp index 7d47fda518..d5d4bdae50 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.cpp +++ b/GPU/Vulkan/FramebufferManagerVulkan.cpp @@ -15,35 +15,9 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include - -#include "Common/Profiler/Profiler.h" - -#include "Common/System/Display.h" -#include "Common/Math/lin/matrix4x4.h" -#include "Common/Data/Convert/ColorConv.h" -#include "Common/Data/Convert/SmallDataConvert.h" -#include "Common/GPU/thin3d.h" - -#include "Common/GPU/Vulkan/VulkanContext.h" -#include "Common/GPU/Vulkan/VulkanMemory.h" -#include "Common/GPU/Vulkan/VulkanImage.h" -#include "Common/GPU/Vulkan/VulkanRenderManager.h" -#include "Core/MemMap.h" -#include "Core/Config.h" -#include "Core/ConfigValues.h" -#include "Core/System.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUInterface.h" #include "GPU/GPUState.h" -#include "GPU/Common/TextureDecoder.h" -#include "GPU/Common/FramebufferManagerCommon.h" -#include "GPU/Debugger/Stepping.h" #include "GPU/Vulkan/FramebufferManagerVulkan.h" -#include "GPU/Vulkan/DrawEngineVulkan.h" -#include "GPU/Vulkan/TextureCacheVulkan.h" -#include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/VulkanUtil.h" using namespace PPSSPP_VK; @@ -53,10 +27,6 @@ FramebufferManagerVulkan::FramebufferManagerVulkan(Draw::DrawContext *draw) : presentation_->SetLanguage(GLSL_VULKAN); } -FramebufferManagerVulkan::~FramebufferManagerVulkan() { - DeviceLost(); -} - void FramebufferManagerVulkan::NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth) { int mask = 0; // The Clear detection takes care of doing a regular draw instead if separate masking diff --git a/GPU/Vulkan/FramebufferManagerVulkan.h b/GPU/Vulkan/FramebufferManagerVulkan.h index d3370fafb7..c2e575f0b2 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.h +++ b/GPU/Vulkan/FramebufferManagerVulkan.h @@ -17,24 +17,14 @@ #pragma once -#include "Common/GPU/Vulkan/VulkanLoader.h" -#include "GPU/GPUInterface.h" -#include "GPU/Common/FramebufferManagerCommon.h" -#include "GPU/Common/GPUDebugInterface.h" -#include "GPU/Common/PresentationCommon.h" -#include "GPU/Vulkan/VulkanUtil.h" +#include "Common/GPU/thin3d.h" -class TextureCacheVulkan; -class DrawEngineVulkan; -class VulkanContext; -class ShaderManagerVulkan; -class VulkanTexture; -class VulkanPushBuffer; +#include "GPU/Common/FramebufferManagerCommon.h" +#include "GPU/Common/PresentationCommon.h" class FramebufferManagerVulkan : public FramebufferManagerCommon { public: explicit FramebufferManagerVulkan(Draw::DrawContext *draw); - ~FramebufferManagerVulkan(); // If within a render pass, this will just issue a regular clear. If beginning a new render pass, // do that. From 16eded4e1c798ab043385ab52fa86055fcd6bcaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 11:46:06 +0100 Subject: [PATCH 014/141] Comment on why FinishDeferred is not unified. --- GPU/GPUCommon.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 9529be025e..a63677bfe1 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -310,7 +310,7 @@ protected: void FlushImm(); void DoBlockTransfer(u32 skipDrawReason); - // TODO: Unify this. + // TODO: Unify this. The only backend that differs is Vulkan. virtual void FinishDeferred() {} void AdvanceVerts(u32 vertType, int count, int bytesRead) { From da561aa9debed4d57934f8ebe3d600b47c567c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 11:53:37 +0100 Subject: [PATCH 015/141] Unify CheckFlushOp --- GPU/D3D11/GPU_D3D11.cpp | 10 ---------- GPU/D3D11/GPU_D3D11.h | 2 -- GPU/Directx9/GPU_DX9.cpp | 10 ---------- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 10 ---------- GPU/GLES/GPU_GLES.h | 1 - GPU/GPUCommon.cpp | 10 ++++++++++ GPU/GPUCommon.h | 2 ++ GPU/Vulkan/GPU_Vulkan.cpp | 10 ---------- GPU/Vulkan/GPU_Vulkan.h | 1 - 10 files changed, 12 insertions(+), 45 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 2b8179e472..c230723a8e 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -185,16 +185,6 @@ void GPU_D3D11::FinishDeferred() { drawEngine_.FinishDeferred(); } -inline void GPU_D3D11::CheckFlushOp(int cmd, u32 diff) { - const u8 cmdFlags = cmdInfo_[cmd].flags; - if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { - if (dumpThisFrame_) { - NOTICE_LOG(G3D, "================ FLUSH ================"); - } - drawEngine_.Flush(); - } -} - void GPU_D3D11::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 0fece6f431..bba27d040c 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -54,8 +54,6 @@ protected: void FinishDeferred() override; private: - // void ApplyDrawState(int prim); - void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo() override; void InitClear() override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index a85903a94c..cfe17016a5 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -181,16 +181,6 @@ void GPU_DX9::FinishDeferred() { drawEngine_.FinishDeferred(); } -inline void GPU_DX9::CheckFlushOp(int cmd, u32 diff) { - const u8 cmdFlags = cmdInfo_[cmd].flags; - if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { - if (dumpThisFrame_) { - NOTICE_LOG(G3D, "================ FLUSH ================"); - } - drawEngine_.Flush(); - } -} - void GPU_DX9::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 621ad79857..2e0e697b3c 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -54,7 +54,6 @@ protected: void FinishDeferred() override; private: - void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo() override; void InitClear() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 3646fc802c..5b882f32f8 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -334,16 +334,6 @@ void GPU_GLES::FinishDeferred() { drawEngine_.FinishDeferred(); } -inline void GPU_GLES::CheckFlushOp(int cmd, u32 diff) { - const u8 cmdFlags = cmdInfo_[cmd].flags; - if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { - if (dumpThisFrame_) { - NOTICE_LOG(G3D, "================ FLUSH ================"); - } - drawEngine_.Flush(); - } -} - void GPU_GLES::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index ce2b363149..94e921ca7b 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -65,7 +65,6 @@ protected: void FinishDeferred() override; private: - void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo() override; void InitClear() override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 836af1236d..bfc2123b3a 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3609,3 +3609,13 @@ void GPUCommon::UpdateMSAALevel(Draw::DrawContext *draw) { msaaLevel_ = 0; } } + +void GPUCommon::CheckFlushOp(int cmd, u32 diff) { + const u8 cmdFlags = cmdInfo_[cmd].flags; + if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { + if (dumpThisFrame_) { + NOTICE_LOG(G3D, "================ FLUSH ================"); + } + drawEngineCommon_->DispatchFlush(); + } +} diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index a63677bfe1..ff53cb6e49 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -313,6 +313,8 @@ protected: // TODO: Unify this. The only backend that differs is Vulkan. virtual void FinishDeferred() {} + void CheckFlushOp(int cmd, u32 diff); + void AdvanceVerts(u32 vertType, int count, int bytesRead) { if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { int indexShift = ((vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 8e1ad1e2d1..258b7d5821 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -416,16 +416,6 @@ void GPU_Vulkan::FinishDeferred() { drawEngine_.FinishDeferred(); } -inline void GPU_Vulkan::CheckFlushOp(int cmd, u32 diff) { - const u8 cmdFlags = cmdInfo_[cmd].flags; - if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { - if (dumpThisFrame_) { - NOTICE_LOG(G3D, "================ FLUSH ================"); - } - drawEngine_.Flush(); - } -} - void GPU_Vulkan::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 67f8301e00..6544b1de41 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -71,7 +71,6 @@ protected: void CheckRenderResized() override; private: - void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo() override; void InitClear() override; void CopyDisplayToOutput(bool reallyDirty) override; From ed03348c6570931927fbd21abe8267bbc35b22c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 11:56:30 +0100 Subject: [PATCH 016/141] Unify PreExecuteOp, keep the soft GPU as a special case --- GPU/D3D11/GPU_D3D11.cpp | 4 ---- GPU/D3D11/GPU_D3D11.h | 1 - GPU/Directx9/GPU_DX9.cpp | 4 ---- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 4 ---- GPU/GLES/GPU_GLES.h | 1 - GPU/GPUCommon.cpp | 2 +- GPU/Software/SoftGpu.h | 1 + GPU/Vulkan/GPU_Vulkan.cpp | 4 ---- GPU/Vulkan/GPU_Vulkan.h | 1 - 10 files changed, 2 insertions(+), 21 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index c230723a8e..512e155743 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -185,10 +185,6 @@ void GPU_D3D11::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_D3D11::PreExecuteOp(u32 op, u32 diff) { - CheckFlushOp(op >> 24, diff); -} - void GPU_D3D11::ExecuteOp(u32 op, u32 diff) { const u8 cmd = op >> 24; const CommandInfo info = cmdInfo_[cmd]; diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index bba27d040c..311d6044dd 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -37,7 +37,6 @@ public: ~GPU_D3D11(); u32 CheckGPUFeatures() const override; - void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; void GetStats(char *buffer, size_t bufsize) override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index cfe17016a5..2fc33297da 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -181,10 +181,6 @@ void GPU_DX9::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_DX9::PreExecuteOp(u32 op, u32 diff) { - CheckFlushOp(op >> 24, diff); -} - void GPU_DX9::ExecuteOp(u32 op, u32 diff) { const u8 cmd = op >> 24; const CommandInfo info = cmdInfo_[cmd]; diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 2e0e697b3c..ec0b759a3a 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -36,7 +36,6 @@ public: ~GPU_DX9(); u32 CheckGPUFeatures() const override; - void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; void ReapplyGfxState() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 5b882f32f8..55c1f3eb7a 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -334,10 +334,6 @@ void GPU_GLES::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_GLES::PreExecuteOp(u32 op, u32 diff) { - CheckFlushOp(op >> 24, diff); -} - void GPU_GLES::ExecuteOp(u32 op, u32 diff) { const u8 cmd = op >> 24; const CommandInfo info = cmdInfo_[cmd]; diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 94e921ca7b..9ee2d3b8f2 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -43,7 +43,6 @@ public: bool IsReady() override; void CancelReady() override; - void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; void ReapplyGfxState() override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index bfc2123b3a..636965c4ff 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1411,7 +1411,7 @@ void GPUCommon::ProcessDLQueue() { } void GPUCommon::PreExecuteOp(u32 op, u32 diff) { - // Nothing to do + CheckFlushOp(op >> 24, diff); } void GPUCommon::Execute_OffsetAddr(u32 op, u32 diff) { diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 2496c7b474..f60f26a9b1 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -210,6 +210,7 @@ protected: void ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, const uint16_t *overrideData = nullptr); void BuildReportingInfo() override {} + void PreExecuteOp(u32 op, u32 diff) override {} private: void MarkDirty(uint32_t addr, uint32_t stride, uint32_t height, GEBufferFormat fmt, SoftGPUVRAMDirty value); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 258b7d5821..dbbb731d4c 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -416,10 +416,6 @@ void GPU_Vulkan::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_Vulkan::PreExecuteOp(u32 op, u32 diff) { - CheckFlushOp(op >> 24, diff); -} - void GPU_Vulkan::ExecuteOp(u32 op, u32 diff) { const u8 cmd = op >> 24; const CommandInfo info = cmdInfo_[cmd]; diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 6544b1de41..86951f11dc 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -47,7 +47,6 @@ public: void BeginHostFrame() override; void EndHostFrame() override; - void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; void GetStats(char *buffer, size_t bufsize) override; From 3823c08771e416278f5429226697d13d1c526d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 11:58:14 +0100 Subject: [PATCH 017/141] Remove PreExecuteOp from GPUInterface --- GPU/GPUCommon.h | 2 +- GPU/GPUInterface.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index ff53cb6e49..92c8d0d0ae 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -109,7 +109,7 @@ public: void DumpNextFrame() override; void ExecuteOp(u32 op, u32 diff) override; - void PreExecuteOp(u32 op, u32 diff) override; + virtual void PreExecuteOp(u32 op, u32 diff); bool InterpretList(DisplayList &list); void ProcessDLQueue(); diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 2e1af82a66..8403d6f048 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -221,7 +221,6 @@ public: virtual void InterruptEnd(int listid) = 0; virtual void SyncEnd(GPUSyncType waitType, int listid, bool wokeThreads) = 0; - virtual void PreExecuteOp(u32 op, u32 diff) = 0; virtual void ExecuteOp(u32 op, u32 diff) = 0; // Framebuffer management From 6b91647209ea65d4da819fd9e1b1d07909533f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 12:01:11 +0100 Subject: [PATCH 018/141] Remove unused ExeuteOp in GPUCommon.cpp --- GPU/GPUCommon.cpp | 47 ----------------------------------------------- GPU/GPUCommon.h | 1 - 2 files changed, 48 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 636965c4ff..40be69be68 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2706,53 +2706,6 @@ void GPUCommon::FlushImm() { } } -void GPUCommon::ExecuteOp(u32 op, u32 diff) { - const u32 cmd = op >> 24; - - // Handle control and drawing commands here directly. The others we delegate. - switch (cmd) { - case GE_CMD_NOP: - break; - - case GE_CMD_OFFSETADDR: - Execute_OffsetAddr(op, diff); - break; - - case GE_CMD_ORIGIN: - Execute_Origin(op, diff); - break; - - case GE_CMD_JUMP: - Execute_Jump(op, diff); - break; - - case GE_CMD_BJUMP: - Execute_BJump(op, diff); - break; - - case GE_CMD_CALL: - Execute_Call(op, diff); - break; - - case GE_CMD_RET: - Execute_Ret(op, diff); - break; - - case GE_CMD_SIGNAL: - case GE_CMD_FINISH: - // Processed in GE_END. - break; - - case GE_CMD_END: - Execute_End(op, diff); - break; - - default: - DEBUG_LOG(G3D, "DL Unknown: %08x @ %08x", op, currentList == NULL ? 0 : currentList->pc); - break; - } -} - void GPUCommon::Execute_Unknown(u32 op, u32 diff) { if ((op & 0xFFFFFF) != 0) WARN_LOG_REPORT_ONCE(unknowncmd, G3D, "Unknown GE command : %08x ", op); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 92c8d0d0ae..8fccd97cd9 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -108,7 +108,6 @@ public: void DumpNextFrame() override; - void ExecuteOp(u32 op, u32 diff) override; virtual void PreExecuteOp(u32 op, u32 diff); bool InterpretList(DisplayList &list); From 609a8f6d362a763fbec740b3f3fed7bb6790c01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 12:02:52 +0100 Subject: [PATCH 019/141] Unify ExecuteOp between the accelerated backends --- GPU/D3D11/GPU_D3D11.cpp | 13 ------------- GPU/D3D11/GPU_D3D11.h | 1 - GPU/Directx9/GPU_DX9.cpp | 13 ------------- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 13 ------------- GPU/GLES/GPU_GLES.h | 2 -- GPU/GPUCommon.cpp | 13 +++++++++++++ GPU/GPUCommon.h | 1 + GPU/Vulkan/GPU_Vulkan.cpp | 13 ------------- GPU/Vulkan/GPU_Vulkan.h | 2 -- 10 files changed, 14 insertions(+), 58 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 512e155743..0c5c1312e5 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -185,19 +185,6 @@ void GPU_D3D11::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_D3D11::ExecuteOp(u32 op, u32 diff) { - const u8 cmd = op >> 24; - const CommandInfo info = cmdInfo_[cmd]; - const u8 cmdFlags = info.flags; - if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { - (this->*info.func)(op, diff); - } else if (diff) { - uint64_t dirty = info.flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } -} - void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { size_t offset = FormatGPUStatsCommon(buffer, bufsize); buffer += offset; diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 311d6044dd..619ca841b6 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -37,7 +37,6 @@ public: ~GPU_D3D11(); u32 CheckGPUFeatures() const override; - void ExecuteOp(u32 op, u32 diff) override; void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 2fc33297da..adc006efd7 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -181,19 +181,6 @@ void GPU_DX9::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_DX9::ExecuteOp(u32 op, u32 diff) { - const u8 cmd = op >> 24; - const CommandInfo info = cmdInfo_[cmd]; - const u8 cmdFlags = info.flags; - if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { - (this->*info.func)(op, diff); - } else if (diff) { - uint64_t dirty = info.flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } -} - void GPU_DX9::GetStats(char *buffer, size_t bufsize) { size_t offset = FormatGPUStatsCommon(buffer, bufsize); buffer += offset; diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index ec0b759a3a..1385ea8999 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -36,7 +36,6 @@ public: ~GPU_DX9(); u32 CheckGPUFeatures() const override; - void ExecuteOp(u32 op, u32 diff) override; void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 55c1f3eb7a..e6f67be12d 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -334,19 +334,6 @@ void GPU_GLES::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_GLES::ExecuteOp(u32 op, u32 diff) { - const u8 cmd = op >> 24; - const CommandInfo info = cmdInfo_[cmd]; - const u8 cmdFlags = info.flags; - if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { - (this->*info.func)(op, diff); - } else if (diff) { - uint64_t dirty = info.flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } -} - void GPU_GLES::GetStats(char *buffer, size_t bufsize) { size_t offset = FormatGPUStatsCommon(buffer, bufsize); buffer += offset; diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 9ee2d3b8f2..5ebba38098 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -43,8 +43,6 @@ public: bool IsReady() override; void CancelReady() override; - void ExecuteOp(u32 op, u32 diff) override; - void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 40be69be68..23d3fb7e03 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2706,6 +2706,19 @@ void GPUCommon::FlushImm() { } } +void GPUCommon::ExecuteOp(u32 op, u32 diff) { + const u8 cmd = op >> 24; + const CommandInfo info = cmdInfo_[cmd]; + const u8 cmdFlags = info.flags; + if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { + (this->*info.func)(op, diff); + } else if (diff) { + uint64_t dirty = info.flags >> 8; + if (dirty) + gstate_c.Dirty(dirty); + } +} + void GPUCommon::Execute_Unknown(u32 op, u32 diff) { if ((op & 0xFFFFFF) != 0) WARN_LOG_REPORT_ONCE(unknowncmd, G3D, "Unknown GE command : %08x ", op); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 8fccd97cd9..92c8d0d0ae 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -108,6 +108,7 @@ public: void DumpNextFrame() override; + void ExecuteOp(u32 op, u32 diff) override; virtual void PreExecuteOp(u32 op, u32 diff); bool InterpretList(DisplayList &list); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index dbbb731d4c..78afe87e2e 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -416,19 +416,6 @@ void GPU_Vulkan::FinishDeferred() { drawEngine_.FinishDeferred(); } -void GPU_Vulkan::ExecuteOp(u32 op, u32 diff) { - const u8 cmd = op >> 24; - const CommandInfo info = cmdInfo_[cmd]; - const u8 cmdFlags = info.flags; - if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { - (this->*info.func)(op, diff); - } else if (diff) { - uint64_t dirty = info.flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } -} - void GPU_Vulkan::InitDeviceObjects() { INFO_LOG(G3D, "GPU_Vulkan::InitDeviceObjects"); VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 86951f11dc..f8884ac762 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -47,8 +47,6 @@ public: void BeginHostFrame() override; void EndHostFrame() override; - void ExecuteOp(u32 op, u32 diff) override; - void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; From 0d57f3beee169c98636b71ee3407bc72e3c7a83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 12:07:28 +0100 Subject: [PATCH 020/141] Unify GPUCommon::DoState. --- GPU/Common/DrawEngineCommon.h | 3 ++- GPU/D3D11/GPU_D3D11.cpp | 14 -------------- GPU/D3D11/GPU_D3D11.h | 2 -- GPU/Directx9/GPU_DX9.cpp | 14 -------------- GPU/Directx9/GPU_DX9.h | 2 -- GPU/GLES/GPU_GLES.cpp | 15 --------------- GPU/GLES/GPU_GLES.h | 2 -- GPU/GPUCommon.cpp | 11 +++++++++++ GPU/Vulkan/GPU_Vulkan.cpp | 14 -------------- GPU/Vulkan/GPU_Vulkan.h | 2 -- 10 files changed, 13 insertions(+), 66 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 3e2891c4a3..78473c3cf3 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -123,9 +123,10 @@ public: VertexDecoder *GetVertexDecoder(u32 vtype); + virtual void ClearTrackedVertexArrays() {} + protected: virtual bool UpdateUseHWTessellation(bool enabled) { return enabled; } - virtual void ClearTrackedVertexArrays() {} int ComputeNumVertsToDecode() const; void DecodeVerts(u8 *dest); diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 0c5c1312e5..a9c6632a88 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -198,20 +198,6 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { ); } -void GPU_D3D11::DoState(PointerWrap &p) { - GPUCommon::DoState(p); - - // TODO: Some of these things may not be necessary. - // None of these are necessary when saving. - if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) { - textureCache_->Clear(true); - drawEngine_.ClearTrackedVertexArrays(); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); - framebufferManager_->DestroyAllFBOs(); - } -} - std::vector GPU_D3D11::DebugGetShaderIDs(DebugShaderType type) { switch (type) { case SHADER_TYPE_VERTEXLOADER: diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 619ca841b6..cdb3ddbfb7 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -42,8 +42,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DoState(PointerWrap &p) override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index adc006efd7..90f7e1a0dd 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -194,20 +194,6 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { ); } -void GPU_DX9::DoState(PointerWrap &p) { - GPUCommon::DoState(p); - - // TODO: Some of these things may not be necessary. - // None of these are necessary when saving. - if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) { - textureCache_->Clear(true); - drawEngine_.ClearTrackedVertexArrays(); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); - framebufferManager_->DestroyAllFBOs(); - } -} - std::vector GPU_DX9::DebugGetShaderIDs(DebugShaderType type) { switch (type) { case SHADER_TYPE_VERTEXLOADER: diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 1385ea8999..218145ab15 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -42,8 +42,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DoState(PointerWrap &p) override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index e6f67be12d..f63c61dd59 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -348,21 +348,6 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { ); } -void GPU_GLES::DoState(PointerWrap &p) { - GPUCommon::DoState(p); - - // TODO: Some of these things may not be necessary. - // None of these are necessary when saving. - // In Freeze-Frame mode, we don't want to do any of this. - if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) { - textureCache_->Clear(true); - drawEngine_.ClearTrackedVertexArrays(); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); - framebufferManager_->DestroyAllFBOs(); - } -} - std::vector GPU_GLES::DebugGetShaderIDs(DebugShaderType type) { switch (type) { case SHADER_TYPE_VERTEXLOADER: diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 5ebba38098..ff9bc1373b 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -49,8 +49,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DoState(PointerWrap &p) override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 23d3fb7e03..0725563bc0 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2871,6 +2871,17 @@ void GPUCommon::DoState(PointerWrap &p) { if (s >= 6) { Do(p, edramTranslation_); } + + // TODO: Some of these things may not be necessary. + // None of these are necessary when saving. + // The textureCache_ check avoids this getting called in SoftGPU. + if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen && textureCache_) { + textureCache_->Clear(true); + drawEngineCommon_->ClearTrackedVertexArrays(); + + gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); + framebufferManager_->DestroyAllFBOs(); + } } void GPUCommon::InterruptStart(int listid) { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 78afe87e2e..54953c879b 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -523,20 +523,6 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { ); } -void GPU_Vulkan::DoState(PointerWrap &p) { - GPUCommon::DoState(p); - - // TODO: Some of these things may not be necessary. - // None of these are necessary when saving. - // In Freeze-Frame mode, we don't want to do any of this. - if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) { - textureCache_->Clear(true); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); - framebufferManager_->DestroyAllFBOs(); - } -} - std::vector GPU_Vulkan::DebugGetShaderIDs(DebugShaderType type) { if (type == SHADER_TYPE_VERTEXLOADER) { return drawEngine_.DebugGetVertexLoaderIDs(); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index f8884ac762..58e1726f20 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -51,8 +51,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DoState(PointerWrap &p) override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; From 8b431b39babdcf6f0c6e04aae7615f680f3d5eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 13:09:44 +0100 Subject: [PATCH 021/141] Wrap the display globals in a struct (#16998) * Wrap the display globals in a struct Makes it easier to search/replace these, for future refactorings. * Some renaming * Qt buildfix, also fix the Qt build on Mac (got broken with battery changes) * Attempt at buildfixing ios * UWP buildfix --- CMakeLists.txt | 10 +++- Common/GPU/Vulkan/VulkanContext.cpp | 18 ++++---- Common/GPU/Vulkan/VulkanRenderManager.cpp | 3 +- Common/Render/DrawBuffer.cpp | 16 +++---- Common/Render/Text/draw_text.cpp | 2 +- Common/System/Display.cpp | 20 +------- Common/System/Display.h | 41 +++++++++------- Common/UI/Context.cpp | 24 +++++----- Common/UI/Screen.cpp | 2 +- Common/UI/UIScreen.cpp | 20 ++++---- Core/Core.cpp | 52 ++++++++++----------- Core/HLE/sceIo.cpp | 4 +- Core/Screenshot.cpp | 4 +- GPU/Common/FramebufferManagerCommon.cpp | 8 ++-- GPU/Common/PresentationCommon.cpp | 18 ++++---- GPU/Common/ShaderUniforms.cpp | 10 ++-- GPU/GLES/ShaderManagerGLES.cpp | 2 +- Qt/QtMain.cpp | 36 +++++++-------- Qt/mainwindow.cpp | 4 +- SDL/SDLGLGraphicsContext.cpp | 4 +- SDL/SDLMain.cpp | 48 +++++++++---------- SDL/SDLVulkanGraphicsContext.cpp | 2 +- UI/ControlMappingScreen.cpp | 12 ++--- UI/DevScreens.cpp | 6 +-- UI/DisplayLayoutScreen.cpp | 10 ++-- UI/EmuScreen.cpp | 22 ++++----- UI/GameSettingsScreen.cpp | 8 ++-- UI/GamepadEmu.cpp | 6 +-- UI/MiscScreens.cpp | 12 ++--- UI/NativeApp.cpp | 26 ++++++----- UI/ReportScreen.cpp | 2 +- UI/TouchControlLayoutScreen.cpp | 4 +- UWP/PPSSPP_UWPMain.cpp | 38 +++++++-------- Windows/Debugger/CtrlDisAsmView.cpp | 2 +- Windows/Debugger/CtrlMemView.cpp | 2 +- Windows/Debugger/CtrlRegisterList.cpp | 2 +- Windows/Debugger/Debugger_MemoryDlg.cpp | 2 +- Windows/GEDebugger/CtrlDisplayListView.cpp | 2 +- Windows/GPU/D3D11Context.cpp | 4 +- Windows/GPU/D3D9Context.cpp | 4 +- Windows/MainWindow.cpp | 12 ++--- Windows/TouchInputHandler.cpp | 4 +- Windows/WindowsHost.cpp | 4 +- android/jni/AndroidJavaGLContext.cpp | 4 +- android/jni/TestRunner.cpp | 8 ++-- android/jni/app-android.cpp | 54 +++++++++++----------- ios/DisplayManager.mm | 30 ++++++------ ios/ViewController.mm | 4 +- 48 files changed, 319 insertions(+), 313 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b0206f664..fadd2853c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1188,7 +1188,15 @@ elseif(USING_QT_UI) if(USING_GLES2) add_definitions(-DQT_OPENGL_ES -DQT_OPENGL_ES_2) endif() - + if(APPLE) + list(APPEND NativeAppSource + UI/DarwinFileSystemServices.mm + UI/DarwinFileSystemServices.h + Common/Battery/AppleBatteryClient.m) + set_source_files_properties(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(UI/DarwinFileSystemServices.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY}) + endif() include_directories(Qt) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(nativeExtraLibs ${nativeExtraLibs} Qt5::OpenGL Qt5::Gui Qt5::Core Qt5::Multimedia) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 6768602852..fe15710d70 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -1288,8 +1288,8 @@ bool VulkanContext::InitSwapchain() { VkSurfaceTransformFlagBitsKHR preTransform; std::string supportedTransforms = surface_transforms_to_string(surfCapabilities_.supportedTransforms); std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform); - g_display_rotation = DisplayRotation::ROTATE_0; - g_display_rot_matrix.setIdentity(); + g_display.rotation = DisplayRotation::ROTATE_0; + g_display.rot_matrix.setIdentity(); uint32_t allowedRotations = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; // Hack: Don't allow 270 degrees pretransform (inverse landscape), it creates bizarre issues on some devices (see #15773). @@ -1300,20 +1300,20 @@ bool VulkanContext::InitSwapchain() { } else if (surfCapabilities_.currentTransform & allowedRotations) { // Normal, sensible rotations. Let's handle it. preTransform = surfCapabilities_.currentTransform; - g_display_rot_matrix.setIdentity(); + g_display.rot_matrix.setIdentity(); switch (surfCapabilities_.currentTransform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: - g_display_rotation = DisplayRotation::ROTATE_90; - g_display_rot_matrix.setRotationZ90(); + g_display.rotation = DisplayRotation::ROTATE_90; + g_display.rot_matrix.setRotationZ90(); std::swap(swapChainExtent_.width, swapChainExtent_.height); break; case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: - g_display_rotation = DisplayRotation::ROTATE_180; - g_display_rot_matrix.setRotationZ180(); + g_display.rotation = DisplayRotation::ROTATE_180; + g_display.rot_matrix.setRotationZ180(); break; case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: - g_display_rotation = DisplayRotation::ROTATE_270; - g_display_rot_matrix.setRotationZ270(); + g_display.rotation = DisplayRotation::ROTATE_270; + g_display.rot_matrix.setRotationZ270(); std::swap(swapChainExtent_.width, swapChainExtent_.height); break; default: diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 55298b3be4..7ab778d8c2 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -884,7 +884,8 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } else { curWidthRaw_ = vulkan_->GetBackbufferWidth(); curHeightRaw_ = vulkan_->GetBackbufferHeight(); - if (g_display_rotation == DisplayRotation::ROTATE_90 || g_display_rotation == DisplayRotation::ROTATE_270) { + if (g_display.rotation == DisplayRotation::ROTATE_90 || + g_display.rotation == DisplayRotation::ROTATE_270) { curWidth_ = curHeightRaw_; curHeight_ = curWidthRaw_; } else { diff --git a/Common/Render/DrawBuffer.cpp b/Common/Render/DrawBuffer.cpp index bf49bc4774..73c0f30a68 100644 --- a/Common/Render/DrawBuffer.cpp +++ b/Common/Render/DrawBuffer.cpp @@ -111,14 +111,14 @@ void DrawBuffer::Rect(float x, float y, float w, float h, uint32_t color, int al void DrawBuffer::hLine(float x1, float y, float x2, uint32_t color) { // Round Y to the closest full pixel, since we're making it 1-pixel-thin. - y -= fmodf(y, pixel_in_dps_y); - Rect(x1, y, x2 - x1, pixel_in_dps_y, color); + y -= fmodf(y, g_display.pixel_in_dps_y); + Rect(x1, y, x2 - x1, g_display.pixel_in_dps_y, color); } void DrawBuffer::vLine(float x, float y1, float y2, uint32_t color) { // Round X to the closest full pixel, since we're making it 1-pixel-thin. - x -= fmodf(x, pixel_in_dps_x); - Rect(x, y1, pixel_in_dps_x, y2 - y1, color); + x -= fmodf(x, g_display.pixel_in_dps_x); + Rect(x, y1, g_display.pixel_in_dps_x, y2 - y1, color); } void DrawBuffer::RectVGradient(float x, float y, float w, float h, uint32_t colorTop, uint32_t colorBottom) { @@ -131,11 +131,11 @@ void DrawBuffer::RectVGradient(float x, float y, float w, float h, uint32_t colo } void DrawBuffer::RectOutline(float x, float y, float w, float h, uint32_t color, int align) { - hLine(x, y, x + w + pixel_in_dps_x, color); - hLine(x, y + h, x + w + pixel_in_dps_x, color); + hLine(x, y, x + w + g_display.pixel_in_dps_x, color); + hLine(x, y + h, x + w + g_display.pixel_in_dps_x, color); - vLine(x, y, y + h + pixel_in_dps_y, color); - vLine(x + w, y, y + h + pixel_in_dps_y, color); + vLine(x, y, y + h + g_display.pixel_in_dps_y, color); + vLine(x + w, y, y + h + g_display.pixel_in_dps_y, color); } void DrawBuffer::MultiVGradient(float x, float y, float w, float h, const GradientStop *stops, int numStops) { diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index e236065483..4d4b58e7f9 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -38,7 +38,7 @@ void TextDrawer::SetFontScale(float xscale, float yscale) { float TextDrawer::CalculateDPIScale() { if (ignoreGlobalDpi_) return dpiScale_; - float scale = g_dpi_scale_y; + float scale = g_display.dpi_scale_y; if (scale >= 1.0f) { scale = 1.0f; } diff --git a/Common/System/Display.cpp b/Common/System/Display.cpp index 0e9c1ffb62..90644a592a 100644 --- a/Common/System/Display.cpp +++ b/Common/System/Display.cpp @@ -1,27 +1,11 @@ #include "Common/System/Display.h" #include "Common/Math/math_util.h" -int dp_xres; -int dp_yres; - -int pixel_xres; -int pixel_yres; - -float g_dpi = 1.0f; // will be overwritten with a value that makes sense. -float g_dpi_scale_x = 1.0f; -float g_dpi_scale_y = 1.0f; -float g_dpi_scale_real_x = 1.0f; -float g_dpi_scale_real_y = 1.0f; -float pixel_in_dps_x = 1.0f; -float pixel_in_dps_y = 1.0f; -float display_hz = 60.0f; - -DisplayRotation g_display_rotation; -Lin::Matrix4x4 g_display_rot_matrix = Lin::Matrix4x4::identity(); +DisplayProperties g_display; template void RotateRectToDisplayImpl(DisplayRect &rect, T curRTWidth, T curRTHeight) { - switch (g_display_rotation) { + switch (g_display.rotation) { case DisplayRotation::ROTATE_180: rect.x = curRTWidth - rect.w - rect.x; rect.y = curRTHeight - rect.h - rect.y; diff --git a/Common/System/Display.h b/Common/System/Display.h index 586c346f77..c7e826394c 100644 --- a/Common/System/Display.h +++ b/Common/System/Display.h @@ -5,20 +5,6 @@ // This is meant to be a framework for handling DPI scaling etc. // For now, it just consists of these ugly globals. -extern int dp_xres; -extern int dp_yres; -extern int pixel_xres; -extern int pixel_yres; - -extern float g_dpi; -extern float g_dpi_scale_x; -extern float g_dpi_scale_y; -extern float g_dpi_scale_real_x; -extern float g_dpi_scale_real_y; -extern float pixel_in_dps_x; -extern float pixel_in_dps_y; -extern float display_hz; - // On some platforms (currently only Windows UWP) we need to manually rotate // our rendered output to match the display. Use these to do so. enum class DisplayRotation { @@ -28,8 +14,31 @@ enum class DisplayRotation { ROTATE_270, }; -extern DisplayRotation g_display_rotation; -extern Lin::Matrix4x4 g_display_rot_matrix; +struct DisplayProperties { + int dp_xres; + int dp_yres; + int pixel_xres; + int pixel_yres; + + float dpi = 1.0f; // will be overwritten with a value that makes sense. + float dpi_scale_x = 1.0f; + float dpi_scale_y = 1.0f; + + // pixel_xres/yres in dps + float pixel_in_dps_x = 1.0f; + float pixel_in_dps_y = 1.0f; + + // If DPI is overridden (like in small window mode), these are still the original DPI. + float dpi_scale_real_x = 1.0f; + float dpi_scale_real_y = 1.0f; + + float display_hz = 60.0f; + + DisplayRotation rotation; + Lin::Matrix4x4 rot_matrix; +}; + +extern DisplayProperties g_display; template struct DisplayRect { diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index 70202f44a3..cb62571bf0 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -15,7 +15,7 @@ UIContext::UIContext() { fontStyle_ = new UI::FontStyle(); - bounds_ = Bounds(0, 0, dp_xres, dp_yres); + bounds_ = Bounds(0, 0, g_display.dp_xres, g_display.dp_yres); } UIContext::~UIContext() { @@ -163,22 +163,22 @@ Bounds UIContext::GetLayoutBounds() const { void UIContext::ActivateTopScissor() { Bounds bounds; if (scissorStack_.size()) { - float scale_x = pixel_in_dps_x; - float scale_y = pixel_in_dps_y; + float scale_x = g_display.pixel_in_dps_x; + float scale_y = g_display.pixel_in_dps_y; bounds = scissorStack_.back(); int x = floorf(scale_x * bounds.x); int y = floorf(scale_y * bounds.y); int w = std::max(0.0f, ceilf(scale_x * bounds.w)); int h = std::max(0.0f, ceilf(scale_y * bounds.h)); - if (x < 0 || y < 0 || x + w > pixel_xres || y + h > pixel_yres) { + if (x < 0 || y < 0 || x + w > g_display.pixel_xres || y + h > g_display.pixel_yres) { // This won't actually report outside a game, but we can try. - DEBUG_LOG(G3D, "UI scissor out of bounds in %sScreen: %d,%d-%d,%d / %d,%d", screenTag_ ? screenTag_ : "N/A", x, y, w, h, pixel_xres, pixel_yres); + DEBUG_LOG(G3D, "UI scissor out of bounds in %sScreen: %d,%d-%d,%d / %d,%d", screenTag_ ? screenTag_ : "N/A", x, y, w, h, g_display.pixel_xres, g_display.pixel_yres); if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } - if (x >= pixel_xres) { x = pixel_xres - 1; } - if (y >= pixel_yres) { y = pixel_yres - 1; } - if (x + w > pixel_xres) { w = std::min(w, pixel_xres - x); } - if (y + w > pixel_yres) { h = std::min(h, pixel_yres - y); } + if (x >= g_display.pixel_xres) { x = g_display.pixel_xres - 1; } + if (y >= g_display.pixel_yres) { y = g_display.pixel_yres - 1; } + if (x + w > g_display.pixel_xres) { w = std::min(w, g_display.pixel_xres - x); } + if (y + w > g_display.pixel_yres) { h = std::min(h, g_display.pixel_yres - y); } if (w == 0) w = 1; if (h == 0) h = 1; draw_->SetScissorRect(x, y, w, h); @@ -190,7 +190,7 @@ void UIContext::ActivateTopScissor() { } } else { // Avoid rounding errors - draw_->SetScissorRect(0, 0, pixel_xres, pixel_yres); + draw_->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres); } } @@ -351,8 +351,8 @@ Bounds UIContext::TransformBounds(const Bounds &bounds) { Bounds translated = bounds.Offset(t.translate.x, t.translate.y); // Scale around the center as the origin. - float scaledX = (translated.x - dp_xres * 0.5f) * t.scale.x + dp_xres * 0.5f; - float scaledY = (translated.y - dp_yres * 0.5f) * t.scale.y + dp_yres * 0.5f; + float scaledX = (translated.x - g_display.dp_xres * 0.5f) * t.scale.x + g_display.dp_xres * 0.5f; + float scaledY = (translated.y - g_display.dp_yres * 0.5f) * t.scale.y + g_display.dp_yres * 0.5f; return Bounds(scaledX, scaledY, translated.w * t.scale.x, translated.h * t.scale.y); } diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp index 59ea6a320b..047ec04483 100644 --- a/Common/UI/Screen.cpp +++ b/Common/UI/Screen.cpp @@ -138,7 +138,7 @@ void ScreenManager::deviceRestored() { } void ScreenManager::resized() { - INFO_LOG(SYSTEM, "ScreenManager::resized(dp: %dx%d)", dp_xres, dp_yres); + INFO_LOG(SYSTEM, "ScreenManager::resized(dp: %dx%d)", g_display.dp_xres, g_display.dp_yres); std::lock_guard guard(inputLock_); // Have to notify the whole stack, otherwise there will be problems when going back // to non-top screens. diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index d1fc99595f..a412df9abb 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -24,7 +24,7 @@ UIScreen::~UIScreen() { } bool UIScreen::UseVerticalLayout() const { - return dp_yres > dp_xres * 1.1f; + return g_display.dp_yres > g_display.dp_xres * 1.1f; } void UIScreen::DoRecreateViews() { @@ -98,12 +98,12 @@ void UIScreen::preRender() { Draw::Viewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; - viewport.Width = pixel_xres; - viewport.Height = pixel_yres; + viewport.Width = g_display.pixel_xres; + viewport.Height = g_display.pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; draw->SetViewport(viewport); - draw->SetTargetSize(pixel_xres, pixel_yres); + draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres); } void UIScreen::postRender() { @@ -141,8 +141,8 @@ TouchInput UIScreen::transformTouch(const TouchInput &touch) { float x = touch.x - translation_.x; float y = touch.y - translation_.y; // Scale around the center as the origin. - updated.x = (x - dp_xres * 0.5f) / scale_.x + dp_xres * 0.5f; - updated.y = (y - dp_yres * 0.5f) / scale_.y + dp_yres * 0.5f; + updated.x = (x - g_display.dp_xres * 0.5f) / scale_.x + g_display.dp_xres * 0.5f; + updated.y = (y - g_display.dp_yres * 0.5f) / scale_.y + g_display.dp_yres * 0.5f; return updated; } @@ -280,14 +280,14 @@ void PopupScreen::update() { scale_.y = 0.9f + animatePos * 0.1f; if (hasPopupOrigin_) { - float xoff = popupOrigin_.x - dp_xres / 2; - float yoff = popupOrigin_.y - dp_yres / 2; + float xoff = popupOrigin_.x - g_display.dp_xres / 2; + float yoff = popupOrigin_.y - g_display.dp_yres / 2; // Pull toward the origin a bit. translation_.x = xoff * (1.0f - animatePos) * 0.2f; translation_.y = yoff * (1.0f - animatePos) * 0.2f; } else { - translation_.y = -dp_yres * (1.0f - animatePos) * 0.2f; + translation_.y = -g_display.dp_yres * (1.0f - animatePos) * 0.2f; } } else { alpha_ = 1.0f; @@ -333,7 +333,7 @@ void PopupScreen::CreateViews() { box_->SetBG(dc.theme->popupStyle.background); box_->SetHasDropShadow(hasDropShadow_); // Since we scale a bit, make the dropshadow bleed past the edges. - box_->SetDropShadowExpand(std::max(dp_xres, dp_yres)); + box_->SetDropShadowExpand(std::max(g_display.dp_xres, g_display.dp_yres)); box_->SetSpacing(0.0f); View *title = new PopupHeader(title_); diff --git a/Core/Core.cpp b/Core/Core.cpp index 611dad8457..95864b197a 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -154,8 +154,8 @@ bool Core_GetPowerSaving() { static bool IsWindowSmall(int pixelWidth, int pixelHeight) { // Can't take this from config as it will not be set if windows is maximized. - int w = (int)(pixelWidth * g_dpi_scale_x); - int h = (int)(pixelHeight * g_dpi_scale_y); + int w = (int)(pixelWidth * g_display.dpi_scale_x); + int h = (int)(pixelHeight * g_display.dpi_scale_y); return g_Config.IsPortrait() ? (h < 480 + 80) : (w < 480 + 80); } @@ -163,42 +163,42 @@ static bool IsWindowSmall(int pixelWidth, int pixelHeight) { bool UpdateScreenScale(int width, int height) { bool smallWindow; #if defined(USING_QT_UI) - g_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); + g_display.dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); float g_logical_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_LOGICAL_DPI); - g_dpi_scale_x = g_logical_dpi / g_dpi; - g_dpi_scale_y = g_logical_dpi / g_dpi; + g_display.dpi_scale_x = g_logical_dpi / g_display.dpi; + g_display.dpi_scale_y = g_logical_dpi / g_display.dpi; #elif PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - g_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); - g_dpi_scale_x = 96.0f / g_dpi; - g_dpi_scale_y = 96.0f / g_dpi; + g_display.dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); + g_display.dpi_scale_x = 96.0f / g_display.dpi; + g_display.dpi_scale_y = 96.0f / g_display.dpi; #else - g_dpi = 96.0f; - g_dpi_scale_x = 1.0f; - g_dpi_scale_y = 1.0f; + g_display.dpi = 96.0f; + g_display.dpi_scale_x = 1.0f; + g_display.dpi_scale_y = 1.0f; #endif - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; + g_display.dpi_scale_real_x = g_display.dpi_scale_x; + g_display.dpi_scale_real_y = g_display.dpi_scale_y; smallWindow = IsWindowSmall(width, height); if (smallWindow) { - g_dpi /= 2.0f; - g_dpi_scale_x *= 2.0f; - g_dpi_scale_y *= 2.0f; + g_display.dpi /= 2.0f; + g_display.dpi_scale_x *= 2.0f; + g_display.dpi_scale_y *= 2.0f; } - pixel_in_dps_x = 1.0f / g_dpi_scale_x; - pixel_in_dps_y = 1.0f / g_dpi_scale_y; + g_display.pixel_in_dps_x = 1.0f / g_display.dpi_scale_x; + g_display.pixel_in_dps_y = 1.0f / g_display.dpi_scale_y; - int new_dp_xres = (int)(width * g_dpi_scale_x); - int new_dp_yres = (int)(height * g_dpi_scale_y); + int new_dp_xres = (int)(width * g_display.dpi_scale_x); + int new_dp_yres = (int)(height * g_display.dpi_scale_y); - bool dp_changed = new_dp_xres != dp_xres || new_dp_yres != dp_yres; - bool px_changed = pixel_xres != width || pixel_yres != height; + bool dp_changed = new_dp_xres != g_display.dp_xres || new_dp_yres != g_display.dp_yres; + bool px_changed = g_display.pixel_xres != width || g_display.pixel_yres != height; if (dp_changed || px_changed) { - dp_xres = new_dp_xres; - dp_yres = new_dp_yres; - pixel_xres = width; - pixel_yres = height; + g_display.dp_xres = new_dp_xres; + g_display.dp_yres = new_dp_yres; + g_display.pixel_xres = width; + g_display.pixel_yres = height; NativeResized(); return true; } diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 9eb3a678b0..ade4158218 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -2055,7 +2055,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o // I have a planned further refactoring. float ar; if (g_Config.bDisplayStretch) { - ar = (float)dp_xres / (float)dp_yres; + ar = (float)g_display.dp_xres / (float)g_display.dp_yres; } else { ar = g_Config.fDisplayAspectRatio * (480.0f / 272.0f); } @@ -2066,7 +2066,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o if (Memory::IsValidAddress(outPtr)) { // TODO: Maybe do something more sophisticated taking the longest side and screen rotation // into account, etc. - float scale = float(dp_xres) * g_Config.fDisplayScale / 480.0f; + float scale = (float)g_display.dp_xres * g_Config.fDisplayScale / 480.0f; Memory::Write_Float(scale, outPtr); } return 0; diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index 6ad9de9316..492ffab35c 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -344,11 +344,11 @@ bool TakeGameScreenshot(const Path &filename, ScreenshotFormat fmt, ScreenshotTy // Only crop to the top left when using a render screenshot. w = maxRes > 0 ? 480 * maxRes : PSP_CoreParameter().renderWidth; h = maxRes > 0 ? 272 * maxRes : PSP_CoreParameter().renderHeight; - } else if (g_display_rotation != DisplayRotation::ROTATE_0) { + } else if (g_display.rotation != DisplayRotation::ROTATE_0) { GPUDebugBuffer temp; success = gpuDebug->GetOutputFramebuffer(temp); if (success) { - buf = ApplyRotation(temp, g_display_rotation); + buf = ApplyRotation(temp, g_display.rotation); } } else { success = gpuDebug->GetOutputFramebuffer(buf); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 4c0c4dea0b..deba2f8d39 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1205,8 +1205,8 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int // TODO: Replace with draw2D_.Blit() directly. DrawActiveTexture(dstX, dstY, width, height, - vfb ? vfb->bufferWidth : pixel_xres, - vfb ? vfb->bufferHeight : pixel_yres, + vfb ? vfb->bufferWidth : g_display.pixel_xres, + vfb ? vfb->bufferHeight : g_display.pixel_yres, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); gpuStats.numUploads++; @@ -3081,11 +3081,11 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa coord[i].y = coord[i].y * invDestH - 1.0f; } - if ((flags & DRAWTEX_TO_BACKBUFFER) && g_display_rotation != DisplayRotation::ROTATE_0) { + if ((flags & DRAWTEX_TO_BACKBUFFER) && g_display.rotation != DisplayRotation::ROTATE_0) { for (int i = 0; i < 4; i++) { // backwards notation, should fix that... Lin::Vec3 pos = Lin::Vec3(coord[i].x, coord[i].y, 0.0); - pos = pos * g_display_rot_matrix; + pos = pos * g_display.rot_matrix; coord[i].x = pos.x; coord[i].y = pos.y; } diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 0c027ae796..3305e03b73 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -55,10 +55,10 @@ FRect GetScreenFrame(float pixelWidth, float pixelHeight) { if (applyInset) { // Remove the DPI scale to get back to pixels. - float left = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT) / g_dpi_scale_x; - float right = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT) / g_dpi_scale_x; - float top = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP) / g_dpi_scale_y; - float bottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM) / g_dpi_scale_y; + float left = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT) / g_display.dpi_scale_x; + float right = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT) / g_display.dpi_scale_x; + float top = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP) / g_display.dpi_scale_y; + float bottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM) / g_display.dpi_scale_y; // Adjust left edge to compensate for cutouts (notches) if any. rc.x += left; @@ -100,8 +100,8 @@ void CenterDisplayOutputRect(FRect *rc, float origW, float origH, const FRect &f if (stretch) { // Automatically set aspect ratio to match the display, IF the rotation matches the output display ratio! Otherwise, just // sets standard aspect ratio because actually stretching will just look silly. - bool globalRotated = g_display_rotation == DisplayRotation::ROTATE_90 || g_display_rotation == DisplayRotation::ROTATE_270; - if (rotated == dp_yres > dp_xres) { + bool globalRotated = g_display.rotation == DisplayRotation::ROTATE_90 || g_display.rotation == DisplayRotation::ROTATE_270; + if (rotated == g_display.dp_yres > g_display.dp_xres) { origRatio = frameRatio; } else { origRatio *= aspectRatioAdjust; @@ -699,11 +699,11 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u if (isFinalAtOutputResolution || useStereo) { // In this mode, we ignore the g_display_rot_matrix. Apply manually. - if (g_display_rotation != DisplayRotation::ROTATE_0) { + if (g_display.rotation != DisplayRotation::ROTATE_0) { for (int i = 0; i < 4; i++) { Lin::Vec3 v(verts[i].x, verts[i].y, verts[i].z); // Backwards notation, should fix that... - v = v * g_display_rot_matrix; + v = v * g_display.rot_matrix; verts[i].x = v.x; verts[i].y = v.y; } @@ -840,7 +840,7 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms)); } else { Draw::VsTexColUB ub{}; - memcpy(ub.WorldViewProj, g_display_rot_matrix.m, sizeof(float) * 16); + memcpy(ub.WorldViewProj, g_display.rot_matrix.m, sizeof(float) * 16); draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub)); } diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 011d09a49b..207778cf56 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -135,12 +135,12 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView ConvertProjMatrixToVulkan(flippedMatrix); } - if (!useBufferedRendering && g_display_rotation != DisplayRotation::ROTATE_0) { - flippedMatrix = flippedMatrix * g_display_rot_matrix; + if (!useBufferedRendering && g_display.rotation != DisplayRotation::ROTATE_0) { + flippedMatrix = flippedMatrix * g_display.rot_matrix; } CopyMatrix4x4(ub->proj, flippedMatrix.getReadPtr()); - ub->rotation = useBufferedRendering ? 0 : (float)g_display_rotation; + ub->rotation = useBufferedRendering ? 0 : (float)g_display.rotation; } if (dirtyUniforms & DIRTY_PROJTHROUGHMATRIX) { @@ -150,8 +150,8 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView } else { proj_through.setOrthoVulkan(0.0f, gstate_c.curRTWidth, 0, gstate_c.curRTHeight, 0, 1); } - if (!useBufferedRendering && g_display_rotation != DisplayRotation::ROTATE_0) { - proj_through = proj_through * g_display_rot_matrix; + if (!useBufferedRendering && g_display.rotation != DisplayRotation::ROTATE_0) { + proj_through = proj_through * g_display.rot_matrix; } // Negative RT offsets come from split framebuffers (Killzone) diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 9df5b7f28d..d80cfb494f 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -443,7 +443,7 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin ScaleProjMatrix(flippedMatrix, useBufferedRendering); render_->SetUniformM4x4(&u_proj, flippedMatrix.m); - render_->SetUniformF1(&u_rotation, useBufferedRendering ? 0 : (float)g_display_rotation); + render_->SetUniformF1(&u_rotation, useBufferedRendering ? 0 : (float)g_display.rotation); } if (dirty & DIRTY_PROJTHROUGHMATRIX) { Matrix4x4 proj_through; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 2baf523f8e..6809e9fec9 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -423,8 +423,8 @@ void MainUI::resizeGL(int w, int h) { xscale = w / this->width(); yscale = h / this->height(); - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres; } void MainUI::timerEvent(QTimerEvent *) { @@ -453,15 +453,15 @@ bool MainUI::event(QEvent *e) { break; case Qt::TouchPointPressed: case Qt::TouchPointReleased: - input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; - input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; + input.x = touchPoint.pos().x() * g_display.dpi_scale_x * xscale; + input.y = touchPoint.pos().y() * g_display.dpi_scale_y * yscale; input.flags = (touchPoint.state() == Qt::TouchPointPressed) ? TOUCH_DOWN : TOUCH_UP; input.id = touchPoint.id(); NativeTouch(input); break; case Qt::TouchPointMoved: - input.x = touchPoint.pos().x() * g_dpi_scale_x * xscale; - input.y = touchPoint.pos().y() * g_dpi_scale_y * yscale; + input.x = touchPoint.pos().x() * g_display.dpi_scale_x * xscale; + input.y = touchPoint.pos().y() * g_display.dpi_scale_y * yscale; input.flags = TOUCH_MOVE; input.id = touchPoint.id(); NativeTouch(input); @@ -479,8 +479,8 @@ bool MainUI::event(QEvent *e) { case QEvent::MouseButtonRelease: switch(((QMouseEvent*)e)->button()) { case Qt::LeftButton: - input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; - input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; + input.x = ((QMouseEvent*)e)->pos().x() * g_display.dpi_scale_x * xscale; + input.y = ((QMouseEvent*)e)->pos().y() * g_display.dpi_scale_y * yscale; input.flags = (e->type() == QEvent::MouseButtonPress) ? TOUCH_DOWN : TOUCH_UP; input.id = 0; NativeTouch(input); @@ -502,8 +502,8 @@ bool MainUI::event(QEvent *e) { } break; case QEvent::MouseMove: - input.x = ((QMouseEvent*)e)->pos().x() * g_dpi_scale_x * xscale; - input.y = ((QMouseEvent*)e)->pos().y() * g_dpi_scale_y * yscale; + input.x = ((QMouseEvent*)e)->pos().x() * g_display.dpi_scale_x * xscale; + input.y = ((QMouseEvent*)e)->pos().y() * g_display.dpi_scale_y * yscale; input.flags = TOUCH_MOVE; input.id = 0; NativeTouch(input); @@ -738,15 +738,15 @@ int main(int argc, char *argv[]) if (res.width() < res.height()) res.transpose(); - pixel_xres = res.width(); - pixel_yres = res.height(); + g_display.pixel_xres = res.width(); + g_display.pixel_yres = res.height(); - g_dpi_scale_x = screen->logicalDotsPerInchX() / screen->physicalDotsPerInchX(); - g_dpi_scale_y = screen->logicalDotsPerInchY() / screen->physicalDotsPerInchY(); - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; - dp_xres = (int)(pixel_xres * g_dpi_scale_x); - dp_yres = (int)(pixel_yres * g_dpi_scale_y); + g_display.dpi_scale_x = screen->logicalDotsPerInchX() / screen->physicalDotsPerInchX(); + g_display.dpi_scale_y = screen->logicalDotsPerInchY() / screen->physicalDotsPerInchY(); + g_display.dpi_scale_real_x = g_display.dpi_scale_x; + g_display.dpi_scale_real_y = g_display.dpi_scale_y; + g_display.dp_xres = (int)(g_display.pixel_xres * g_display.dpi_scale_x); + g_display.dp_yres = (int)(g_display.pixel_yres * g_display.dpi_scale_y); refreshRate = screen->refreshRate(); diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 3ab5f4ac37..593fe5a0ec 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -375,7 +375,7 @@ void MainWindow::SetFullScreen(bool fullscreen) { #endif showFullScreen(); - InitPadLayout(dp_xres, dp_yres); + InitPadLayout(g_display.dp_xres, g_display.dp_yres); if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); @@ -387,7 +387,7 @@ void MainWindow::SetFullScreen(bool fullscreen) { showNormal(); SetWindowScale(-1); - InitPadLayout(dp_xres, dp_yres); + InitPadLayout(g_display.dp_xres, g_display.dp_yres); if (GetUIState() == UISTATE_INGAME && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); diff --git a/SDL/SDLGLGraphicsContext.cpp b/SDL/SDLGLGraphicsContext.cpp index 62fba14b81..b1906ac7d4 100644 --- a/SDL/SDLGLGraphicsContext.cpp +++ b/SDL/SDLGLGraphicsContext.cpp @@ -333,7 +333,7 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: SetGLCoreContext(true); #endif - window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode); + window = SDL_CreateWindow("PPSSPP", x, y, g_display.pixel_xres, g_display.pixel_yres, mode); if (!window) { // Definitely don't shutdown here: we'll keep trying more GL versions. fprintf(stderr, "SDL_CreateWindow failed for GL %d.%d: %s\n", ver.major, ver.minor, SDL_GetError()); @@ -358,7 +358,7 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SetGLCoreContext(false); - window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode); + window = SDL_CreateWindow("PPSSPP", x, y, g_display.pixel_xres, g_display.pixel_yres, mode); if (window == nullptr) { NativeShutdown(); fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 406f303a73..d1e71462a3 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -670,16 +670,16 @@ int main(int argc, char *argv[]) { #endif if (mode & SDL_WINDOW_FULLSCREEN_DESKTOP) { - pixel_xres = g_DesktopWidth; - pixel_yres = g_DesktopHeight; + g_display.pixel_xres = g_DesktopWidth; + g_display.pixel_yres = g_DesktopHeight; if (g_Config.iForceFullScreen == -1) g_Config.bFullScreen = true; } else { // set a sensible default resolution (2x) - pixel_xres = 480 * 2 * set_scale; - pixel_yres = 272 * 2 * set_scale; + g_display.pixel_xres = 480 * 2 * set_scale; + g_display.pixel_yres = 272 * 2 * set_scale; if (portrait) { - std::swap(pixel_xres, pixel_yres); + std::swap(g_display.pixel_xres, g_display.pixel_yres); } if (g_Config.iForceFullScreen == -1) g_Config.bFullScreen = false; @@ -688,26 +688,26 @@ int main(int argc, char *argv[]) { set_dpi = 1.0f / set_dpi; if (set_ipad) { - pixel_xres = 1024; - pixel_yres = 768; + g_display.pixel_xres = 1024; + g_display.pixel_yres = 768; } if (!landscape) { - std::swap(pixel_xres, pixel_yres); + std::swap(g_display.pixel_xres, g_display.pixel_yres); } if (set_xres > 0) { - pixel_xres = set_xres; + g_display.pixel_xres = set_xres; } if (set_yres > 0) { - pixel_yres = set_yres; + g_display.pixel_yres = set_yres; } float dpi_scale = 1.0f; if (set_dpi > 0) { dpi_scale = set_dpi; } - dp_xres = (float)pixel_xres * dpi_scale; - dp_yres = (float)pixel_yres * dpi_scale; + g_display.dp_xres = (float)g_display.pixel_xres * dpi_scale; + g_display.dp_yres = (float)g_display.pixel_yres * dpi_scale; // Mac / Linux char path[2048]; @@ -745,15 +745,15 @@ int main(int argc, char *argv[]) { int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber()); int y = SDL_WINDOWPOS_UNDEFINED; - pixel_in_dps_x = (float)pixel_xres / dp_xres; - pixel_in_dps_y = (float)pixel_yres / dp_yres; - g_dpi_scale_x = dp_xres / (float)pixel_xres; - g_dpi_scale_y = dp_yres / (float)pixel_yres; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; + g_display.pixel_in_dps_x = (float)g_display.pixel_xres / g_display.dp_xres; + g_display.pixel_in_dps_y = (float)g_display.pixel_yres / g_display.dp_yres; + g_display.dpi_scale_x = g_display.dp_xres / (float)g_display.pixel_xres; + g_display.dpi_scale_y = g_display.dp_yres / (float)g_display.pixel_yres; + g_display.dpi_scale_real_x = g_display.dpi_scale_x; + g_display.dpi_scale_real_y = g_display.dpi_scale_y; - printf("Pixels: %i x %i\n", pixel_xres, pixel_yres); - printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres); + printf("Pixels: %i x %i\n", g_display.pixel_xres, g_display.pixel_yres); + printf("Virtual pixels: %i x %i\n", g_display.dp_xres, g_display.dp_yres); GraphicsContext *graphicsContext = nullptr; SDL_Window *window = nullptr; @@ -873,8 +873,8 @@ int main(int argc, char *argv[]) { } SDL_Event event, touchEvent; while (SDL_PollEvent(&event)) { - float mx = event.motion.x * g_dpi_scale_x; - float my = event.motion.y * g_dpi_scale_y; + float mx = event.motion.x * g_display.dpi_scale_x; + float my = event.motion.y * g_display.dpi_scale_y; switch (event.type) { case SDL_QUIT: @@ -1182,8 +1182,8 @@ int main(int argc, char *argv[]) { // Disabled by default, needs a workaround to map to psp keys. if (g_Config.bMouseControl) { - float scaleFactor_x = g_dpi_scale_x * 0.1 * g_Config.fMouseSensitivity; - float scaleFactor_y = g_dpi_scale_y * 0.1 * g_Config.fMouseSensitivity; + float scaleFactor_x = g_display.dpi_scale_x * 0.1 * g_Config.fMouseSensitivity; + float scaleFactor_y = g_display.dpi_scale_y * 0.1 * g_Config.fMouseSensitivity; AxisInput axisX, axisY; axisX.axisId = JOYSTICK_AXIS_MOUSE_REL_X; diff --git a/SDL/SDLVulkanGraphicsContext.cpp b/SDL/SDLVulkanGraphicsContext.cpp index b01571b626..21108d18b3 100644 --- a/SDL/SDLVulkanGraphicsContext.cpp +++ b/SDL/SDLVulkanGraphicsContext.cpp @@ -33,7 +33,7 @@ static uint32_t FlagsFromConfig() { } bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) { - window = SDL_CreateWindow("Initializing Vulkan...", x, y, pixel_xres, pixel_yres, mode); + window = SDL_CreateWindow("Initializing Vulkan...", x, y, g_display.pixel_xres, g_display.pixel_yres, mode); if (!window) { fprintf(stderr, "Error creating SDL window: %s\n", SDL_GetError()); exit(1); diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index a639d29b26..bfcb9e662c 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -684,7 +684,7 @@ void TouchTestScreen::render() { if (touches_[i].id != -1) { ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0); snprintf(buffer, sizeof(buffer), "%0.1fx%0.1f", touches_[i].x, touches_[i].y); - ui_context->DrawText(buffer, touches_[i].x, touches_[i].y + (touches_[i].y > dp_yres - 100.0f ? -135.0f : 95.0f), 0xFFFFFFFF, ALIGN_HCENTER | FLAG_DYNAMIC_ASCII); + ui_context->DrawText(buffer, touches_[i].x, touches_[i].y + (touches_[i].y > g_display.dp_yres - 100.0f ? -135.0f : 95.0f), 0xFFFFFFFF, ALIGN_HCENTER | FLAG_DYNAMIC_ASCII); } } @@ -704,11 +704,11 @@ void TouchTestScreen::render() { #if PPSSPP_PLATFORM(ANDROID) display_xres, display_yres, #endif - dp_xres, dp_yres, - pixel_xres, pixel_yres, - g_dpi, - g_dpi_scale_x, g_dpi_scale_y, - g_dpi_scale_real_x, g_dpi_scale_real_y, extra_debug); + g_display.dp_xres, g_display.dp_yres, + g_display.pixel_xres, g_display.pixel_yres, + g_display.dpi, + g_display.dpi_scale_x, g_display.dpi_scale_y, + g_display.dpi_scale_real_x, g_display.dpi_scale_real_y, extra_debug); // On Android, also add joystick debug data. diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index d9fc09d7de..eb3de25579 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -585,10 +585,10 @@ void SystemInfoScreen::CreateViews() { System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)))); deviceSpecs->Add(new InfoItem(si->T("UI Resolution"), StringFromFormat("%dx%d (%s: %0.2f)", - dp_xres, - dp_yres, + g_display.dp_xres, + g_display.dp_yres, si->T("DPI"), - g_dpi))); + g_display.dpi))); #endif #if !PPSSPP_PLATFORM(WINDOWS) diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 2c661d2965..791dc8c414 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -50,10 +50,10 @@ enum Mode { static Bounds FRectToBounds(FRect rc) { Bounds b; - b.x = rc.x * g_dpi_scale_x; - b.y = rc.y * g_dpi_scale_y; - b.w = rc.w * g_dpi_scale_x; - b.h = rc.h * g_dpi_scale_y; + b.x = rc.x * g_display.dpi_scale_x; + b.y = rc.y * g_display.dpi_scale_y; + b.w = rc.w * g_display.dpi_scale_x; + b.h = rc.h * g_display.dpi_scale_y; return b; } @@ -132,7 +132,7 @@ void DisplayLayoutScreen::DrawBackground(UIContext &dc) { // we have to draw a substitute ourselves. // TODO: Clean this up a bit, this GetScreenFrame/CenterDisplay combo is too common. - FRect screenFrame = GetScreenFrame(pixel_xres, pixel_yres); + FRect screenFrame = GetScreenFrame(g_display.pixel_xres, g_display.pixel_yres); FRect rc; CenterDisplayOutputRect(&rc, 480.0f, 272.0f, screenFrame, g_Config.iInternalScreenRotation); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index b95b41de36..a86a460acb 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -311,16 +311,16 @@ void EmuScreen::bootGame(const Path &filename) { coreParam.headLess = false; if (g_Config.iInternalResolution == 0) { - coreParam.renderWidth = pixel_xres; - coreParam.renderHeight = pixel_yres; + coreParam.renderWidth = g_display.pixel_xres; + coreParam.renderHeight = g_display.pixel_yres; } else { if (g_Config.iInternalResolution < 0) g_Config.iInternalResolution = 1; coreParam.renderWidth = 480 * g_Config.iInternalResolution; coreParam.renderHeight = 272 * g_Config.iInternalResolution; } - coreParam.pixelWidth = pixel_xres; - coreParam.pixelHeight = pixel_yres; + coreParam.pixelWidth = g_display.pixel_xres; + coreParam.pixelHeight = g_display.pixel_yres; std::string error_string; if (!PSP_InitStart(coreParam, &error_string)) { @@ -1027,8 +1027,8 @@ void EmuScreen::update() { #ifndef _WIN32 const Bounds &bounds = screenManager()->getUIContext()->GetBounds(); - PSP_CoreParameter().pixelWidth = pixel_xres * bounds.w / dp_xres; - PSP_CoreParameter().pixelHeight = pixel_yres * bounds.h / dp_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres * bounds.w / g_display.dp_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres * bounds.h / g_display.dp_yres; #endif if (!invalid_) { @@ -1371,13 +1371,13 @@ void EmuScreen::preRender() { Viewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; - viewport.Width = pixel_xres; - viewport.Height = pixel_yres; + viewport.Width = g_display.pixel_xres; + viewport.Height = g_display.pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; draw->SetViewport(viewport); } - draw->SetTargetSize(pixel_xres, pixel_yres); + draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres); } void EmuScreen::postRender() { @@ -1519,8 +1519,8 @@ void EmuScreen::renderUI() { Viewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; - viewport.Width = pixel_xres; - viewport.Height = pixel_yres; + viewport.Width = g_display.pixel_xres; + viewport.Height = g_display.pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; thin3d->SetViewport(viewport); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 9e4bb43a6f..454eec0d03 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -237,8 +237,10 @@ void GameSettingsScreen::CreateViews() { if (!vertical) { leftSide += 200.0f; } - settingInfo_ = new SettingInfoMessage(ALIGN_CENTER | FLAG_WRAP_TEXT, new AnchorLayoutParams(dp_xres - leftSide - 40.0f, WRAP_CONTENT, leftSide, dp_yres - 80.0f - 40.0f, NONE, NONE)); - settingInfo_->SetBottomCutoff(dp_yres - 200.0f); + settingInfo_ = new SettingInfoMessage(ALIGN_CENTER | FLAG_WRAP_TEXT, new AnchorLayoutParams( + g_display.dp_xres - leftSide - 40.0f, WRAP_CONTENT, + leftSide, g_display.dp_yres - 80.0f - 40.0f, NONE, NONE)); + settingInfo_->SetBottomCutoff(g_display.dp_yres - 200.0f); root_->Add(settingInfo_); // Show it again if we recreated the view @@ -273,7 +275,7 @@ void GameSettingsScreen::CreateViews() { #if !defined(MOBILE_DEVICE) || PPSSPP_PLATFORM(ANDROID) // Hide search if screen is too small. - if (dp_xres < dp_yres || dp_yres >= 500) { + if (g_display.dp_xres < g_display.dp_yres || g_display.dp_yres >= 500) { auto se = GetI18NCategory("Search"); // Search LinearLayout *searchSettings = AddTab("GameSettingsSearch", ms->T("Search"), true); diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index efb5b6bc3a..5f997fca42 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -711,7 +711,7 @@ void InitPadLayout(float xres, float yres, float globalScale) { //select, start, throttle-------------------------------------------- //space between the bottom keys (space between select, start and un-throttle) float bottom_key_spacing = 100; - if (dp_xres < 750) { + if (g_display.dp_xres < 750) { bottom_key_spacing *= 0.8f; } @@ -956,8 +956,8 @@ bool GestureGamepad::Touch(const TouchInput &input) { void GestureGamepad::Update() { const float th = 1.0f; - float dx = deltaX_ * g_dpi_scale_x * g_Config.fSwipeSensitivity; - float dy = deltaY_ * g_dpi_scale_y * g_Config.fSwipeSensitivity; + float dx = deltaX_ * g_display.dpi_scale_x * g_Config.fSwipeSensitivity; + float dy = deltaY_ * g_display.dpi_scale_y * g_Config.fSwipeSensitivity; if (g_Config.iSwipeRight != 0) { if (dx > th) { controlMapper_->pspKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeRight-1], KEY_DOWN); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 7a8984dfef..6887d5ecd2 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -134,7 +134,7 @@ public: dc.BeginNoTex(); // Be sure to not overflow our vertex buffer - const float step = ceil(24*bounds.w/pixel_in_dps_x) > MAX_VERTS ? 24*bounds.w/(MAX_VERTS-48) : pixel_in_dps_x; + const float step = ceilf(24*bounds.w/ g_display.pixel_in_dps_x) > MAX_VERTS ? 24*bounds.w/(MAX_VERTS-48) : g_display.pixel_in_dps_x; t *= speed; for (float x = 0; x < bounds.w; x += step) { @@ -146,8 +146,8 @@ public: dc.Draw()->RectVGradient(x, wave1*bounds.h, step, (1.0-wave1)*bounds.h, color, 0x00000000); // Add some "antialiasing" - dc.Draw()->RectVGradient(x, wave0*bounds.h-3*pixel_in_dps_y, step, 3*pixel_in_dps_y, 0x00000000, color); - dc.Draw()->RectVGradient(x, wave1*bounds.h-3*pixel_in_dps_y, step, 3*pixel_in_dps_y, 0x00000000, color); + dc.Draw()->RectVGradient(x, wave0*bounds.h-3.0f * g_display.pixel_in_dps_y, step, 3.0f * g_display.pixel_in_dps_y, 0x00000000, color); + dc.Draw()->RectVGradient(x, wave1*bounds.h-3.0f * g_display.pixel_in_dps_y, step, 3.0f * g_display.pixel_in_dps_y, 0x00000000, color); } dc.Flush(); @@ -371,8 +371,8 @@ void DrawGameBackground(UIContext &dc, const Path &gamePath, float x, float y, f Viewport viewport; viewport.TopLeftX = 0; viewport.TopLeftY = 0; - viewport.Width = pixel_xres; - viewport.Height = pixel_yres; + viewport.Width = g_display.pixel_xres; + viewport.Height = g_display.pixel_yres; viewport.MaxDepth = 1.0; viewport.MinDepth = 0.0; draw->SetViewport(viewport); @@ -1080,7 +1080,7 @@ void SettingInfoMessage::Show(const std::string &text, const UI::View *refView) if (b.y >= cutOffY_) { ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, 80.0f, lp->right, lp->bottom, lp->center)); } else { - ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->center)); + ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, g_display.dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->center)); } } text_->SetText(text); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 50aa992c8b..65fd9fac70 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1095,8 +1095,8 @@ void NativeRender(GraphicsContext *graphicsContext) { g_BackgroundAudio.Update(); } - float xres = dp_xres; - float yres = dp_yres; + float xres = g_display.dp_xres; + float yres = g_display.dp_yres; // Apply the UIContext bounds as a 2D transformation matrix. // TODO: This should be moved into the draw context... @@ -1109,7 +1109,9 @@ void NativeRender(GraphicsContext *graphicsContext) { ortho.setOrthoD3D(0.0f, xres, yres, 0.0f, -1.0f, 1.0f); Matrix4x4 translation; // Account for the small window adjustment. - translation.setTranslation(Vec3(-0.5f * g_dpi_scale_x / g_dpi_scale_real_x, -0.5f * g_dpi_scale_y / g_dpi_scale_real_y, 0.0f)); + translation.setTranslation(Vec3( + -0.5f * g_display.dpi_scale_x / g_display.dpi_scale_real_x, + -0.5f * g_display.dpi_scale_y / g_display.dpi_scale_real_y, 0.0f)); ortho = translation * ortho; break; case GPUBackend::DIRECT3D11: @@ -1122,8 +1124,8 @@ void NativeRender(GraphicsContext *graphicsContext) { } // Compensate for rotated display if needed. - if (g_display_rotation != DisplayRotation::ROTATE_0) { - ortho = ortho * g_display_rot_matrix; + if (g_display.rotation != DisplayRotation::ROTATE_0) { + ortho = ortho * g_display.rot_matrix; } ui_draw2d.PushDrawMatrix(ortho); @@ -1151,13 +1153,13 @@ void NativeRender(GraphicsContext *graphicsContext) { if (uiContext) { // Modifying the bounds here can be used to "inset" the whole image to gain borders for TV overscan etc. // The UI now supports any offset but not the EmuScreen yet. - uiContext->SetBounds(Bounds(0, 0, dp_xres, dp_yres)); + uiContext->SetBounds(Bounds(0, 0, g_display.dp_xres, g_display.dp_yres)); // OSX 10.6 and SDL 1.2 bug. #if defined(__APPLE__) && !defined(USING_QT_UI) - static int dp_xres_old = dp_xres; - if (dp_xres != dp_xres_old) { - dp_xres_old = dp_xres; + static int dp_xres_old = g_display.dp_xres; + if (g_display.dp_xres != dp_xres_old) { + dp_xres_old = g_display.dp_xres; } #endif } @@ -1167,8 +1169,8 @@ void NativeRender(GraphicsContext *graphicsContext) { // TODO: Move this to the GraphicsContext objects for each backend. #if !PPSSPP_PLATFORM(WINDOWS) && !defined(ANDROID) - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres; NativeMessageReceived("gpu_displayResized", ""); #endif } else { @@ -1402,7 +1404,7 @@ void NativeAxis(const AxisInput &axis) { // sent with respect to the portrait coordinate system, while we // take all events in landscape. // see [http://developer.android.com/guide/topics/sensors/sensors_overview.html] for details - bool landscape = dp_yres < dp_xres; + bool landscape = g_display.dp_yres < g_display.dp_xres; // now transform out current tilt to the calibrated coordinate system ProcessTilt(landscape, tiltBaseAngleY, tiltX, tiltY, tiltZ, g_Config.bInvertTiltX, g_Config.bInvertTiltY, diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index c192a021a5..7bd0815de5 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -253,7 +253,7 @@ void ReportScreen::CreateViews() { Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); - float leftColumnWidth = dp_xres - actionMenuMargins.horiz() - contentMargins.horiz() - 300.0f; + float leftColumnWidth = g_display.dp_xres - actionMenuMargins.horiz() - contentMargins.horiz() - 300.0f; ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 0.4f, contentMargins)); LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT)); ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); diff --git a/UI/TouchControlLayoutScreen.cpp b/UI/TouchControlLayoutScreen.cpp index d3c85b63dc..32b230c93d 100644 --- a/UI/TouchControlLayoutScreen.cpp +++ b/UI/TouchControlLayoutScreen.cpp @@ -318,8 +318,8 @@ public: float xOffset = bounds_.x; float yOffset = bounds_.y; - dc.Draw()->Rect((x1+x2)/2 + xOffset - pixel_in_dps_x, y1 + yOffset, 3.0*pixel_in_dps_x, y2-y1, col); - dc.Draw()->Rect(x1 + xOffset, (y1+y2)/2 + yOffset - pixel_in_dps_y, x2-x1, 3.0*pixel_in_dps_y, col); + dc.Draw()->Rect((x1+x2)/2 + xOffset - g_display.pixel_in_dps_x, y1 + yOffset, 3.0f * g_display.pixel_in_dps_x, y2-y1, col); + dc.Draw()->Rect(x1 + xOffset, (y1+y2)/2 + yOffset - g_display.pixel_in_dps_y, x2-x1, 3.0f * g_display.pixel_in_dps_y, col); for (int x = x1 + (x1+x2)/2 % g_Config.iTouchSnapGridSize; x < x2; x += g_Config.iTouchSnapGridSize) dc.Draw()->vLine(x + xOffset, y1 + yOffset, y2 + yOffset, col); diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index b734f6e3d2..23d1801cf2 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -167,39 +167,39 @@ bool PPSSPP_UWPMain::Render() { auto context = m_deviceResources->GetD3DDeviceContext(); switch (m_deviceResources->ComputeDisplayRotation()) { - case DXGI_MODE_ROTATION_IDENTITY: g_display_rotation = DisplayRotation::ROTATE_0; break; - case DXGI_MODE_ROTATION_ROTATE90: g_display_rotation = DisplayRotation::ROTATE_90; break; - case DXGI_MODE_ROTATION_ROTATE180: g_display_rotation = DisplayRotation::ROTATE_180; break; - case DXGI_MODE_ROTATION_ROTATE270: g_display_rotation = DisplayRotation::ROTATE_270; break; + case DXGI_MODE_ROTATION_IDENTITY: g_display.rotation = DisplayRotation::ROTATE_0; break; + case DXGI_MODE_ROTATION_ROTATE90: g_display.rotation = DisplayRotation::ROTATE_90; break; + case DXGI_MODE_ROTATION_ROTATE180: g_display.rotation = DisplayRotation::ROTATE_180; break; + case DXGI_MODE_ROTATION_ROTATE270: g_display.rotation = DisplayRotation::ROTATE_270; break; } // Not super elegant but hey. - memcpy(&g_display_rot_matrix, &m_deviceResources->GetOrientationTransform3D(), sizeof(float) * 16); + memcpy(&g_display.rot_matrix, &m_deviceResources->GetOrientationTransform3D(), sizeof(float) * 16); // Reset the viewport to target the whole screen. auto viewport = m_deviceResources->GetScreenViewport(); - pixel_xres = viewport.Width; - pixel_yres = viewport.Height; + g_display.pixel_xres = viewport.Width; + g_display.pixel_yres = viewport.Height; - if (g_display_rotation == DisplayRotation::ROTATE_90 || g_display_rotation == DisplayRotation::ROTATE_270) { + if (g_display.rotation == DisplayRotation::ROTATE_90 || g_display.rotation == DisplayRotation::ROTATE_270) { // We need to swap our width/height. - std::swap(pixel_xres, pixel_yres); + std::swap(g_display.pixel_xres, g_display.pixel_yres); } - g_dpi = m_deviceResources->GetActualDpi(); + g_display.dpi = m_deviceResources->GetActualDpi(); if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { // Boost DPI a bit to look better. - g_dpi *= 96.0f / 136.0f; + g_display.dpi *= 96.0f / 136.0f; } - g_dpi_scale_x = 96.0f / g_dpi; - g_dpi_scale_y = 96.0f / g_dpi; + g_display.dpi_scale_x = 96.0f / g_display.dpi; + g_display.dpi_scale_y = 96.0f / g_display.dpi; - pixel_in_dps_x = 1.0f / g_dpi_scale_x; - pixel_in_dps_y = 1.0f / g_dpi_scale_y; + g_display.pixel_in_dps_x = 1.0f / g_display.dpi_scale_x; + g_display.pixel_in_dps_y = 1.0f / g_display.dpi_scale_y; - dp_xres = pixel_xres * g_dpi_scale_x; - dp_yres = pixel_yres * g_dpi_scale_y; + g_display.dp_xres = g_display.pixel_xres * g_display.dpi_scale_x; + g_display.dp_yres = g_display.pixel_yres * g_display.dpi_scale_y; context->RSSetViewports(1, &viewport); @@ -274,8 +274,8 @@ void PPSSPP_UWPMain::OnTouchEvent(int touchEvent, int touchId, float x, float y, // and then apply our own "dpi". float dpiFactor_x = m_deviceResources->GetActualDpi() / 96.0f; float dpiFactor_y = dpiFactor_x; - dpiFactor_x /= pixel_in_dps_x; - dpiFactor_y /= pixel_in_dps_y; + dpiFactor_x /= g_display.pixel_in_dps_x; + dpiFactor_y /= g_display.pixel_in_dps_y; TouchInput input{}; input.id = touchId; diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index a179749ecf..3f54114fc5 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -172,7 +172,7 @@ CtrlDisAsmView::CtrlDisAsmView(HWND _wnd) SetWindowLong(wnd, GWL_STYLE, GetWindowLong(wnd,GWL_STYLE) | WS_VSCROLL); SetScrollRange(wnd, SB_VERT, -1, 1, TRUE); - const float fontScale = 1.0f / g_dpi_scale_real_y; + const float fontScale = 1.0f / g_display.dpi_scale_real_y; charWidth = g_Config.iFontWidth * fontScale; rowHeight = (g_Config.iFontHeight + 2) * fontScale; int scaledFontHeight = g_Config.iFontHeight * fontScale; diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index 598763df85..f4eb61df2e 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -34,7 +34,7 @@ CtrlMemView::CtrlMemView(HWND _wnd) { SetWindowLong(wnd, GWL_STYLE, GetWindowLong(wnd,GWL_STYLE) | WS_VSCROLL); SetScrollRange(wnd, SB_VERT, -1,1,TRUE); - const float fontScale = 1.0f / g_dpi_scale_real_y; + const float fontScale = 1.0f / g_display.dpi_scale_real_y; charWidth_ = g_Config.iFontWidth * fontScale; rowHeight_ = g_Config.iFontHeight * fontScale; offsetPositionY_ = offsetLine * rowHeight_; diff --git a/Windows/Debugger/CtrlRegisterList.cpp b/Windows/Debugger/CtrlRegisterList.cpp index 8df25e6429..6fcf215b76 100644 --- a/Windows/Debugger/CtrlRegisterList.cpp +++ b/Windows/Debugger/CtrlRegisterList.cpp @@ -133,7 +133,7 @@ CtrlRegisterList::CtrlRegisterList(HWND _wnd) : wnd(_wnd) { SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)this); - const float fontScale = 1.0f / g_dpi_scale_real_y; + const float fontScale = 1.0f / g_display.dpi_scale_real_y; rowHeight = g_Config.iFontHeight * fontScale; int charWidth = g_Config.iFontWidth * fontScale; font = CreateFont(rowHeight, charWidth, 0, 0, diff --git a/Windows/Debugger/Debugger_MemoryDlg.cpp b/Windows/Debugger/Debugger_MemoryDlg.cpp index d016afaafa..5382a69b71 100644 --- a/Windows/Debugger/Debugger_MemoryDlg.cpp +++ b/Windows/Debugger/Debugger_MemoryDlg.cpp @@ -257,7 +257,7 @@ void CMemoryDlg::Goto(u32 addr) void CMemoryDlg::Size() { - const float fontScale = 1.0f / g_dpi_scale_real_y; + const float fontScale = 1.0f / g_display.dpi_scale_real_y; GetClientRect(m_hDlg,&winRect); int dlg_w = winRect.right - winRect.left; diff --git a/Windows/GEDebugger/CtrlDisplayListView.cpp b/Windows/GEDebugger/CtrlDisplayListView.cpp index eaaa8577f8..4c341c7e4b 100644 --- a/Windows/GEDebugger/CtrlDisplayListView.cpp +++ b/Windows/GEDebugger/CtrlDisplayListView.cpp @@ -45,7 +45,7 @@ CtrlDisplayListView::CtrlDisplayListView(HWND _wnd) instructionSize = 4; // In small window mode, g_dpi_scale may have been adjusted. - const float fontScale = 1.0f / g_dpi_scale_real_y; + const float fontScale = 1.0f / g_display.dpi_scale_real_y; int fontHeight = g_Config.iFontHeight * fontScale; int charWidth = g_Config.iFontWidth * fontScale; diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index 6207b8fd75..6d04c24b0c 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -51,8 +51,8 @@ void D3D11Context::SwapInterval(int interval) { HRESULT D3D11Context::CreateTheDevice(IDXGIAdapter *adapter) { bool windowed = true; // D3D11 has no need for display rotation. - g_display_rotation = DisplayRotation::ROTATE_0; - g_display_rot_matrix.setIdentity(); + g_display.rotation = DisplayRotation::ROTATE_0; + g_display.rot_matrix.setIdentity(); #if defined(_DEBUG) && !PPSSPP_ARCH(ARM) && !PPSSPP_ARCH(ARM64) UINT createDeviceFlags = D3D11_CREATE_DEVICE_DEBUG; #else diff --git a/Windows/GPU/D3D9Context.cpp b/Windows/GPU/D3D9Context.cpp index dc02dade21..7ffe7f79f0 100644 --- a/Windows/GPU/D3D9Context.cpp +++ b/Windows/GPU/D3D9Context.cpp @@ -51,8 +51,8 @@ bool D3D9Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { hWnd_ = wnd; // D3D9 has no need for display rotation. - g_display_rotation = DisplayRotation::ROTATE_0; - g_display_rot_matrix.setIdentity(); + g_display.rotation = DisplayRotation::ROTATE_0; + g_display.rot_matrix.setIdentity(); DIRECT3DCREATE9EX g_pfnCreate9ex; diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 6c30fa9d71..f02d1e4f79 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -643,8 +643,8 @@ namespace MainWindow // Hack: Take the opportunity to show the cursor. mouseButtonDown = true; - float x = GET_X_LPARAM(lParam) * g_dpi_scale_x; - float y = GET_Y_LPARAM(lParam) * g_dpi_scale_y; + float x = GET_X_LPARAM(lParam) * g_display.dpi_scale_x; + float y = GET_Y_LPARAM(lParam) * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; @@ -684,8 +684,8 @@ namespace MainWindow prevCursorX = cursorX; prevCursorY = cursorY; - float x = (float)cursorX * g_dpi_scale_x; - float y = (float)cursorY * g_dpi_scale_y; + float x = (float)cursorX * g_display.dpi_scale_x; + float y = (float)cursorY * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); if (wParam & MK_LBUTTON) { @@ -706,8 +706,8 @@ namespace MainWindow // Hack: Take the opportunity to hide the cursor. mouseButtonDown = false; - float x = (float)GET_X_LPARAM(lParam) * g_dpi_scale_x; - float y = (float)GET_Y_LPARAM(lParam) * g_dpi_scale_y; + float x = (float)GET_X_LPARAM(lParam) * g_display.dpi_scale_x; + float y = (float)GET_Y_LPARAM(lParam) * g_display.dpi_scale_y; WindowsRawInput::SetMousePos(x, y); TouchInput touch; diff --git a/Windows/TouchInputHandler.cpp b/Windows/TouchInputHandler.cpp index d0f00e59f6..63b25934de 100644 --- a/Windows/TouchInputHandler.cpp +++ b/Windows/TouchInputHandler.cpp @@ -54,8 +54,8 @@ bool TouchInputHandler::GetTouchPoint(HWND hWnd, const TOUCHINPUT &input, float point.x = (LONG)(TOUCH_COORD_TO_PIXEL(input.x)); point.y = (LONG)(TOUCH_COORD_TO_PIXEL(input.y)); if (ScreenToClient(hWnd, &point)) { - x = point.x * g_dpi_scale_x; - y = point.y * g_dpi_scale_y; + x = point.x * g_display.dpi_scale_x; + y = point.y * g_display.dpi_scale_y; return true; } diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index b6cd28286c..5f194bcd88 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -238,8 +238,8 @@ void WindowsHost::PollControllers() { // Disabled by default, needs a workaround to map to psp keys. if (g_Config.bMouseControl) { - float scaleFactor_x = g_dpi_scale_x * 0.1 * g_Config.fMouseSensitivity; - float scaleFactor_y = g_dpi_scale_y * 0.1 * g_Config.fMouseSensitivity; + float scaleFactor_x = g_display.dpi_scale_x * 0.1 * g_Config.fMouseSensitivity; + float scaleFactor_y = g_display.dpi_scale_y * 0.1 * g_Config.fMouseSensitivity; float mx = std::max(-1.0f, std::min(1.0f, g_mouseDeltaX * scaleFactor_x)); float my = std::max(-1.0f, std::min(1.0f, g_mouseDeltaY * scaleFactor_y)); diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index ebf22b143c..a2cada60f5 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -19,8 +19,8 @@ bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int } // OpenGL handles rotated rendering in the driver. - g_display_rotation = DisplayRotation::ROTATE_0; - g_display_rot_matrix.setIdentity(); + g_display.rotation = DisplayRotation::ROTATE_0; + g_display.rot_matrix.setIdentity(); draw_ = Draw::T3DCreateGLContext(); // Can't fail renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index 64e6f7761c..1b9a8acb52 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -119,8 +119,8 @@ bool RunTests() { output.clear(); if (!PSP_Init(coreParam, &error_string)) { ERROR_LOG(SYSTEM, "Failed to init unittest %s : %s", testsToRun[i], error_string.c_str()); - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres; return false; } @@ -176,8 +176,8 @@ bool RunTests() { } PSP_Shutdown(); } - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres; PSP_CoreParameter().headLess = false; PSP_CoreParameter().graphicsContext = tempCtx; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index b510942036..67e2fc9238 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -461,7 +461,7 @@ int System_GetPropertyInt(SystemProperty prop) { float System_GetPropertyFloat(SystemProperty prop) { switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: - return display_hz; + return g_display.display_hz; case SYSPROP_DISPLAY_SAFE_INSET_LEFT: return g_safeInsetLeft; case SYSPROP_DISPLAY_SAFE_INSET_RIGHT: @@ -977,35 +977,35 @@ extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, } static void recalculateDpi() { - g_dpi = display_dpi_x; - g_dpi_scale_x = 240.0f / display_dpi_x; - g_dpi_scale_y = 240.0f / display_dpi_y; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; + g_display.dpi = display_dpi_x; + g_display.dpi_scale_x = 240.0f / display_dpi_x; + g_display.dpi_scale_y = 240.0f / display_dpi_y; + g_display.dpi_scale_real_x = g_display.dpi_scale_x; + g_display.dpi_scale_real_y = g_display.dpi_scale_y; - dp_xres = display_xres * g_dpi_scale_x; - dp_yres = display_yres * g_dpi_scale_y; + g_display.dp_xres = display_xres * g_display.dpi_scale_x; + g_display.dp_yres = display_yres * g_display.dpi_scale_y; - pixel_in_dps_x = (float)pixel_xres / dp_xres; - pixel_in_dps_y = (float)pixel_yres / dp_yres; + g_display.pixel_in_dps_x = (float)g_display.pixel_xres / g_display.dp_xres; + g_display.pixel_in_dps_y = (float)g_display.pixel_yres / g_display.dp_yres; - INFO_LOG(G3D, "RecalcDPI: display_xres=%d display_yres=%d pixel_xres=%d pixel_yres=%d", display_xres, display_yres, pixel_xres, pixel_yres); - INFO_LOG(G3D, "RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f dp_xres=%d dp_yres=%d", g_dpi, g_dpi_scale_x, g_dpi_scale_y, dp_xres, dp_yres); + INFO_LOG(G3D, "RecalcDPI: display_xres=%d display_yres=%d pixel_xres=%d pixel_yres=%d", display_xres, display_yres, g_display.pixel_xres, g_display.pixel_yres); + INFO_LOG(G3D, "RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f dp_xres=%d dp_yres=%d", g_display.dpi, g_display.dpi_scale_x, g_display.dpi_scale_y, g_display.dp_xres, g_display.dp_yres); } extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint bufw, jint bufh, jint format) { INFO_LOG(SYSTEM, "NativeApp.backbufferResize(%d x %d)", bufw, bufh); - bool new_size = pixel_xres != bufw || pixel_yres != bufh; - int old_w = pixel_xres; - int old_h = pixel_yres; + bool new_size = g_display.pixel_xres != bufw || g_display.pixel_yres != bufh; + int old_w = g_display.pixel_xres; + int old_h = g_display.pixel_yres; // pixel_*res is the backbuffer resolution. - pixel_xres = bufw; - pixel_yres = bufh; + g_display.pixel_xres = bufw; + g_display.pixel_yres = bufh; backbuffer_format = format; if (IsVREnabled()) { - GetVRResolutionPerEye(&pixel_xres, &pixel_yres); + GetVRResolutionPerEye(&g_display.pixel_xres, &g_display.pixel_yres); } recalculateDpi(); @@ -1093,7 +1093,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, } if (IsVREnabled()) { - UpdateVRInput(g_Config.bHapticFeedback, g_dpi_scale_x, g_dpi_scale_y); + UpdateVRInput(g_Config.bHapticFeedback, g_display.dpi_scale_x, g_display.dpi_scale_y); FinishVRRender(); } } @@ -1117,8 +1117,8 @@ PermissionStatus System_GetPermissionStatus(SystemPermission permission) { extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch (JNIEnv *, jclass, float x, float y, int code, int pointerId) { - float scaledX = x * g_dpi_scale_x; - float scaledY = y * g_dpi_scale_y; + float scaledX = x * g_display.dpi_scale_x; + float scaledY = y * g_display.dpi_scale_y; TouchInput touch; touch.id = pointerId; @@ -1215,10 +1215,10 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env // We don't bother with supporting exact rectangular regions. Safe insets are good enough. int left, right, top, bottom; if (4 == sscanf(prm.c_str(), "%d:%d:%d:%d", &left, &right, &top, &bottom)) { - g_safeInsetLeft = (float)left * g_dpi_scale_x; - g_safeInsetRight = (float)right * g_dpi_scale_x; - g_safeInsetTop = (float)top * g_dpi_scale_y; - g_safeInsetBottom = (float)bottom * g_dpi_scale_y; + g_safeInsetLeft = (float)left * g_display.dpi_scale_x; + g_safeInsetRight = (float)right * g_display.dpi_scale_x; + g_safeInsetTop = (float)top * g_display.dpi_scale_y; + g_safeInsetBottom = (float)bottom * g_display.dpi_scale_y; } } @@ -1251,14 +1251,14 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JN bool changed = false; changed = changed || display_xres != xres || display_yres != yres; changed = changed || display_dpi_x != dpi || display_dpi_y != dpi; - changed = changed || display_hz != refreshRate; + changed = changed || g_display.display_hz != refreshRate; if (changed) { display_xres = xres; display_yres = yres; display_dpi_x = dpi; display_dpi_y = dpi; - display_hz = refreshRate; + g_display.display_hz = refreshRate; recalculateDpi(); NativeResized(); diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index 19f828d380..ba369e64e1 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -148,33 +148,33 @@ } if (screen == [UIScreen mainScreen]) { - g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; + g_display.dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; } else { float diagonal = sqrt(size.height * size.height + size.width * size.width); - g_dpi = diagonal * scale * 0.1f; + g_display.dpi = diagonal * scale * 0.1f; } - g_dpi_scale_x = 240.0f / g_dpi; - g_dpi_scale_y = 240.0f / g_dpi; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; - pixel_xres = size.width * scale; - pixel_yres = size.height * scale; + g_display.dpi_scale_x = 240.0f / g_display.dpi; + g_display.dpi_scale_y = 240.0f / g_display.dpi; + g_display.dpi_scale_real_x = g_display.dpi_scale_x; + g_display.dpi_scale_real_y = g_display.dpi_scale_y; + g_display.pixel_xres = size.width * scale; + g_display.pixel_yres = size.height * scale; - dp_xres = pixel_xres * g_dpi_scale_x; - dp_yres = pixel_yres * g_dpi_scale_y; + g_display.dp_xres = g_display.pixel_xres * g_display.dpi_scale_x; + g_display.dp_yres = g_display.pixel_yres * g_display.dpi_scale_y; - pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; - pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; + g_display.pixel_in_dps_x = (float)g_display.pixel_xres / (float)g_display.dp_xres; + g_display.pixel_in_dps_y = (float)g_display.pixel_yres / (float)g_display.dp_yres; [[sharedViewController view] setContentScaleFactor:scale]; // PSP native resize - PSP_CoreParameter().pixelWidth = pixel_xres; - PSP_CoreParameter().pixelHeight = pixel_yres; + PSP_CoreParameter().pixelWidth = g_display.pixel_xres; + PSP_CoreParameter().pixelHeight = g_display.pixel_yres; NativeResized(); - NSLog(@"Updated display resolution: (%d, %d) @%.1fx", pixel_xres, pixel_yres, scale); + NSLog(@"Updated display resolution: (%d, %d) @%.1fx", g_display.pixel_xres, g_display.pixel_yres, scale); } @end diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 74852291d4..8e30578dec 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -200,8 +200,8 @@ static LocationHelper *locationHelper; graphicsContext->ThreadStart(); - dp_xscale = (float)dp_xres / (float)pixel_xres; - dp_yscale = (float)dp_yres / (float)pixel_yres; + dp_xscale = (float)g_display.dp_xres / (float)g_display.pixel_xres; + dp_yscale = (float)g_display.dp_yres / (float)g_display.pixel_yres; /*self.iCadeView = [[iCadeReaderView alloc] init]; [self.view addSubview:self.iCadeView]; From 18999c3687d458ae5e691a7149e3e8c2073b6e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 14:24:59 +0100 Subject: [PATCH 022/141] Create the GPUCommonHW class. --- CMakeLists.txt | 2 ++ GPU/D3D11/GPU_D3D11.cpp | 2 +- GPU/D3D11/GPU_D3D11.h | 6 ++---- GPU/Directx9/GPU_DX9.cpp | 2 +- GPU/Directx9/GPU_DX9.h | 4 ++-- GPU/GLES/GPU_GLES.cpp | 2 +- GPU/GLES/GPU_GLES.h | 4 ++-- GPU/GPU.vcxproj | 2 ++ GPU/GPU.vcxproj.filters | 6 ++++++ GPU/GPUCommon.cpp | 4 ---- GPU/GPUCommon.h | 2 +- GPU/GPUCommonHW.cpp | 13 +++++++++++++ GPU/GPUCommonHW.h | 16 ++++++++++++++++ GPU/Software/SoftGpu.h | 1 - GPU/Vulkan/GPU_Vulkan.cpp | 2 +- GPU/Vulkan/GPU_Vulkan.h | 4 ++-- UWP/GPU_UWP/GPU_UWP.vcxproj | 2 ++ UWP/GPU_UWP/GPU_UWP.vcxproj.filters | 2 ++ android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 20 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 GPU/GPUCommonHW.cpp create mode 100644 GPU/GPUCommonHW.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fadd2853c7..20cd4d0e74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1659,6 +1659,8 @@ set(GPU_SOURCES GPU/GPU.h GPU/GPUCommon.cpp GPU/GPUCommon.h + GPU/GPUCommonHW.cpp + GPU/GPUCommonHW.h GPU/GPUState.cpp GPU/GPUState.h GPU/Math3D.cpp diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index a9c6632a88..dd87755570 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -42,7 +42,7 @@ #include "GPU/D3D11/D3D11Util.h" GPU_D3D11::GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw) - : GPUCommon(gfxCtx, draw), drawEngine_(draw, + : GPUCommonHW(gfxCtx, draw), drawEngine_(draw, (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE), (ID3D11DeviceContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT)) { device_ = (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE); diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index cdb3ddbfb7..7a6418bb34 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -21,17 +21,15 @@ #include #include -#include "GPU/GPUCommon.h" +#include "GPU/GPUCommonHW.h" #include "GPU/D3D11/DrawEngineD3D11.h" -#include "GPU/Common/TextureShaderCommon.h" #include "GPU/Common/VertexDecoderCommon.h" class FramebufferManagerD3D11; class ShaderManagerD3D11; -class LinkedShaderD3D11; class TextureCacheD3D11; -class GPU_D3D11 : public GPUCommon { +class GPU_D3D11 : public GPUCommonHW { public: GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_D3D11(); diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 90f7e1a0dd..d0ad42dfc5 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -44,7 +44,7 @@ #include "GPU/Directx9/TextureCacheDX9.h" GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) - : GPUCommon(gfxCtx, draw), + : GPUCommonHW(gfxCtx, draw), drawEngine_(draw) { device_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE); deviceEx_ = (LPDIRECT3DDEVICE9EX)draw->GetNativeObject(Draw::NativeObject::DEVICE_EX); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 218145ab15..a1e5c1102c 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -20,7 +20,7 @@ #include #include -#include "GPU/GPUCommon.h" +#include "GPU/GPUCommonHW.h" #include "GPU/Directx9/FramebufferManagerDX9.h" #include "GPU/Directx9/DrawEngineDX9.h" #include "GPU/Common/TextureShaderCommon.h" @@ -30,7 +30,7 @@ class ShaderManagerDX9; class LinkedShaderDX9; class TextureCacheDX9; -class GPU_DX9 : public GPUCommon { +class GPU_DX9 : public GPUCommonHW { public: GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_DX9(); diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index f63c61dd59..6cffceb53d 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -48,7 +48,7 @@ #endif GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) - : GPUCommon(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw) { + : GPUCommonHW(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw) { UpdateVsyncInterval(true); gstate_c.SetUseFlags(CheckGPUFeatures()); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index ff9bc1373b..8cb6be8fc7 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -22,7 +22,7 @@ #include "Common/File/Path.h" -#include "GPU/GPUCommon.h" +#include "GPU/GPUCommonHW.h" #include "GPU/Common/TextureShaderCommon.h" #include "GPU/GLES/FramebufferManagerGLES.h" #include "GPU/GLES/DrawEngineGLES.h" @@ -32,7 +32,7 @@ class ShaderManagerGLES; class TextureCacheGLES; class LinkedShader; -class GPU_GLES : public GPUCommon { +class GPU_GLES : public GPUCommonHW { public: GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_GLES(); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 625bfb112c..606e169859 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -427,6 +427,7 @@ + @@ -572,6 +573,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index e30366acb3..b34942e06c 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -264,6 +264,9 @@ Common + + Common + @@ -524,6 +527,9 @@ Common + + Common + diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 0725563bc0..8f8c410fa1 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1410,10 +1410,6 @@ void GPUCommon::ProcessDLQueue() { // Since the event is in CoreTiming, we're in sync. Just set 0 now. } -void GPUCommon::PreExecuteOp(u32 op, u32 diff) { - CheckFlushOp(op >> 24, diff); -} - void GPUCommon::Execute_OffsetAddr(u32 op, u32 diff) { gstate_c.offsetAddr = op << 8; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 92c8d0d0ae..ddc955a237 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -109,7 +109,7 @@ public: void DumpNextFrame() override; void ExecuteOp(u32 op, u32 diff) override; - virtual void PreExecuteOp(u32 op, u32 diff); + virtual void PreExecuteOp(u32 op, u32 diff) {} bool InterpretList(DisplayList &list); void ProcessDLQueue(); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp new file mode 100644 index 0000000000..e1438db9db --- /dev/null +++ b/GPU/GPUCommonHW.cpp @@ -0,0 +1,13 @@ +#include "Common/GPU/thin3d.h" +#include "GPU/GPUCommonHW.h" + + +GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) { + +} + +GPUCommonHW::~GPUCommonHW() {} + +void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { + CheckFlushOp(op >> 24, diff); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h new file mode 100644 index 0000000000..9efe41d80b --- /dev/null +++ b/GPU/GPUCommonHW.h @@ -0,0 +1,16 @@ +#pragma once + +#include "GPUCommon.h" + +// Shared GPUCommon implementation for the HW backends. +// Things that are irrelevant for SoftGPU should live here. +class GPUCommonHW : public GPUCommon { +public: + GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw); + ~GPUCommonHW(); + +protected: + void PreExecuteOp(u32 op, u32 diff); + +}; + diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index f60f26a9b1..2496c7b474 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -210,7 +210,6 @@ protected: void ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, const uint16_t *overrideData = nullptr); void BuildReportingInfo() override {} - void PreExecuteOp(u32 op, u32 diff) override {} private: void MarkDirty(uint32_t addr, uint32_t stride, uint32_t height, GEBufferFormat fmt, SoftGPUVRAMDirty value); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 54953c879b..513a2906d6 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -46,7 +46,7 @@ #include "Common/GPU/Vulkan/VulkanQueueRunner.h" GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw) - : GPUCommon(gfxCtx, draw), drawEngine_(draw) { + : GPUCommonHW(gfxCtx, draw), drawEngine_(draw) { gstate_c.SetUseFlags(CheckGPUFeatures()); drawEngine_.InitDeviceObjects(); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 58e1726f20..c2bb6bf73b 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -22,7 +22,7 @@ #include "Common/File/Path.h" -#include "GPU/GPUCommon.h" +#include "GPU/GPUCommonHW.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #include "GPU/Vulkan/PipelineManagerVulkan.h" #include "GPU/Common/TextureShaderCommon.h" @@ -32,7 +32,7 @@ class ShaderManagerVulkan; class LinkedShader; class TextureCacheVulkan; -class GPU_Vulkan : public GPUCommon { +class GPU_Vulkan : public GPUCommonHW { public: GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_Vulkan(); diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 0b8ed0070f..03ce6c90bb 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -423,6 +423,7 @@ + @@ -487,6 +488,7 @@ + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index 2481c765c9..dc746a6f6d 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -61,6 +61,7 @@ + @@ -123,5 +124,6 @@ + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9c7912881b..9af8d57b17 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -358,6 +358,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Math3D.cpp \ $(SRC)/GPU/GPU.cpp \ $(SRC)/GPU/GPUCommon.cpp \ + $(SRC)/GPU/GPUCommonHW.cpp \ $(SRC)/GPU/GPUState.cpp \ $(SRC)/GPU/GeConstants.cpp \ $(SRC)/GPU/GeDisasm.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index e734a43415..7e602464b4 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -406,6 +406,7 @@ SOURCES_CXX += \ $(GPUDIR)/GeConstants.cpp \ $(GPUDIR)/GeDisasm.cpp \ $(GPUDIR)/GPUCommon.cpp \ + $(GPUDIR)/GPUCommonHW.cpp \ $(GPUDIR)/GPU.cpp \ $(GPUDIR)/GPUState.cpp \ $(GPUDIR)/Math3D.cpp \ From 285ffbaa52949340cc8506701cc3cf0eee0666e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 14:28:22 +0100 Subject: [PATCH 023/141] Unify CopyDisplayToOutput --- GPU/D3D11/GPU_D3D11.cpp | 11 ----------- GPU/D3D11/GPU_D3D11.h | 1 - GPU/Directx9/GPU_DX9.cpp | 10 ---------- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 12 ------------ GPU/GLES/GPU_GLES.h | 1 - GPU/GPUCommonHW.cpp | 13 +++++++++++++ GPU/GPUCommonHW.h | 3 ++- GPU/Vulkan/GPU_Vulkan.cpp | 11 ----------- GPU/Vulkan/GPU_Vulkan.h | 1 - 10 files changed, 15 insertions(+), 49 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index dd87755570..0d03b6292d 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -169,17 +169,6 @@ void GPU_D3D11::BeginFrame() { } } -void GPU_D3D11::CopyDisplayToOutput(bool reallyDirty) { - // Flush anything left over. - drawEngine_.Flush(); - - shaderManager_->DirtyLastShader(); - - framebufferManagerD3D11_->CopyDisplayToOutput(reallyDirty); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); -} - void GPU_D3D11::FinishDeferred() { // This finishes reading any vertex data that is pending. drawEngine_.FinishDeferred(); diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 7a6418bb34..42a2ea0c24 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -52,7 +52,6 @@ private: void InitClear() override; void BeginFrame() override; - void CopyDisplayToOutput(bool reallyDirty) override; ID3D11Device *device_; ID3D11DeviceContext *context_; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index d0ad42dfc5..efe4c11fd0 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -166,16 +166,6 @@ void GPU_DX9::BeginFrame() { } } -void GPU_DX9::CopyDisplayToOutput(bool reallyDirty) { - drawEngine_.Flush(); - - shaderManager_->DirtyLastShader(); - - framebufferManagerDX9_->CopyDisplayToOutput(reallyDirty); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); -} - void GPU_DX9::FinishDeferred() { // This finishes reading any vertex data that is pending. drawEngine_.FinishDeferred(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index a1e5c1102c..d89a2bf546 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -54,7 +54,6 @@ private: void InitClear() override; void BeginFrame() override; - void CopyDisplayToOutput(bool reallyDirty) override; LPDIRECT3DDEVICE9 device_; LPDIRECT3DDEVICE9EX deviceEx_; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 6cffceb53d..2cb6a88504 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -317,18 +317,6 @@ void GPU_GLES::BeginFrame() { framebufferManagerGL_->BeginFrame(); } -void GPU_GLES::CopyDisplayToOutput(bool reallyDirty) { - // Flush anything left over. - framebufferManagerGL_->RebindFramebuffer("RebindFramebuffer - CopyDisplayToOutput"); - drawEngine_.Flush(); - - shaderManagerGL_->DirtyLastShader(); - - framebufferManagerGL_->CopyDisplayToOutput(reallyDirty); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); -} - void GPU_GLES::FinishDeferred() { // This finishes reading any vertex data that is pending. drawEngine_.FinishDeferred(); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 8cb6be8fc7..912c253188 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -64,7 +64,6 @@ private: void InitClear() override; void BeginFrame() override; - void CopyDisplayToOutput(bool reallyDirty) override; void Reinitialize() override; FramebufferManagerGLES *framebufferManagerGL_; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index e1438db9db..a2307d734b 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1,5 +1,7 @@ #include "Common/GPU/thin3d.h" #include "GPU/GPUCommonHW.h" +#include "GPU/Common/DrawEngineCommon.h" +#include "GPU/Common/FramebufferManagerCommon.h" GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) { @@ -11,3 +13,14 @@ GPUCommonHW::~GPUCommonHW() {} void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } + +void GPUCommonHW::CopyDisplayToOutput(bool reallyDirty) { + // Flush anything left over. + drawEngineCommon_->DispatchFlush(); + + shaderManager_->DirtyLastShader(); + + framebufferManager_->CopyDisplayToOutput(reallyDirty); + + gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 9efe41d80b..d0ff630437 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -9,8 +9,9 @@ public: GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPUCommonHW(); + void CopyDisplayToOutput(bool reallyDirty) override; + protected: void PreExecuteOp(u32 op, u32 diff); - }; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 513a2906d6..faed3b7e4c 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -401,17 +401,6 @@ void GPU_Vulkan::InitClear() { } } -void GPU_Vulkan::CopyDisplayToOutput(bool reallyDirty) { - // Flush anything left over. - drawEngine_.Flush(); - - shaderManagerVulkan_->DirtyLastShader(); - - framebufferManagerVulkan_->CopyDisplayToOutput(reallyDirty); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); -} - void GPU_Vulkan::FinishDeferred() { drawEngine_.FinishDeferred(); } diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index c2bb6bf73b..f468ec8b68 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -68,7 +68,6 @@ protected: private: void BuildReportingInfo() override; void InitClear() override; - void CopyDisplayToOutput(bool reallyDirty) override; void Reinitialize() override; void InitDeviceObjects(); From 9e8f1d3cb37ea50fb7c824e621c6222ca7d77282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 14:34:30 +0100 Subject: [PATCH 024/141] Make the DoState stuff less ugly --- GPU/GPUCommon.cpp | 11 ----------- GPU/GPUCommon.h | 3 ++- GPU/GPUCommonHW.cpp | 25 ++++++++++++++++++++----- GPU/GPUCommonHW.h | 2 ++ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 8f8c410fa1..2e683d73be 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2867,17 +2867,6 @@ void GPUCommon::DoState(PointerWrap &p) { if (s >= 6) { Do(p, edramTranslation_); } - - // TODO: Some of these things may not be necessary. - // None of these are necessary when saving. - // The textureCache_ check avoids this getting called in SoftGPU. - if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen && textureCache_) { - textureCache_->Clear(true); - drawEngineCommon_->ClearTrackedVertexArrays(); - - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); - framebufferManager_->DestroyAllFBOs(); - } } void GPUCommon::InterruptStart(int listid) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index ddc955a237..fd17ebc6e3 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -330,9 +330,10 @@ protected: void UpdateMSAALevel(Draw::DrawContext *draw); + DrawEngineCommon *drawEngineCommon_ = nullptr; + FramebufferManagerCommon *framebufferManager_ = nullptr; TextureCacheCommon *textureCache_ = nullptr; - DrawEngineCommon *drawEngineCommon_ = nullptr; ShaderManagerCommon *shaderManager_ = nullptr; bool flushOnParams_ = true; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index a2307d734b..27dc1981b7 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1,13 +1,14 @@ #include "Common/GPU/thin3d.h" +#include "Common/Serialize/Serializer.h" + +#include "Core/System.h" + #include "GPU/GPUCommonHW.h" #include "GPU/Common/DrawEngineCommon.h" +#include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" - -GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) { - -} - +GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) {} GPUCommonHW::~GPUCommonHW() {} void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { @@ -24,3 +25,17 @@ void GPUCommonHW::CopyDisplayToOutput(bool reallyDirty) { gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } + +void GPUCommonHW::DoState(PointerWrap &p) { + GPUCommon::DoState(p); + + // TODO: Some of these things may not be necessary. + // None of these are necessary when saving. + if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) { + textureCache_->Clear(true); + drawEngineCommon_->ClearTrackedVertexArrays(); + + gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); + framebufferManager_->DestroyAllFBOs(); + } +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index d0ff630437..aa0a189564 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -10,8 +10,10 @@ public: ~GPUCommonHW(); void CopyDisplayToOutput(bool reallyDirty) override; + void DoState(PointerWrap &p) override; protected: void PreExecuteOp(u32 op, u32 diff); + }; From b6846646af0e2f4d4c3b85bb01d6a70735aea422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 14:59:14 +0100 Subject: [PATCH 025/141] Remove some uses of gfxCtx_ --- GPU/D3D11/GPU_D3D11.cpp | 5 ++--- GPU/Directx9/GPU_DX9.cpp | 6 ++---- GPU/GPUCommon.cpp | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 0d03b6292d..e11d1c8156 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -119,10 +119,9 @@ u32 GPU_D3D11::CheckGPUFeatures() const { // Needs to be called on GPU thread, not reporting thread. void GPU_D3D11::BuildReportingInfo() { using namespace Draw; - DrawContext *thin3d = gfxCtx_->GetDrawContext(); - reportingPrimaryInfo_ = thin3d->GetInfoString(InfoField::VENDORSTRING); - reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + thin3d->GetInfoString(InfoField::SHADELANGVERSION); + reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); + reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); } void GPU_D3D11::DeviceLost() { diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index efe4c11fd0..8569a6639e 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -116,10 +116,8 @@ GPU_DX9::~GPU_DX9() { // Needs to be called on GPU thread, not reporting thread. void GPU_DX9::BuildReportingInfo() { using namespace Draw; - DrawContext *thin3d = gfxCtx_->GetDrawContext(); - - reportingPrimaryInfo_ = thin3d->GetInfoString(InfoField::VENDORSTRING); - reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + thin3d->GetInfoString(InfoField::SHADELANGVERSION); + reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); + reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); } void GPU_DX9::DeviceLost() { diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 2e683d73be..af0d7d7a3d 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -796,6 +796,7 @@ int GPUCommon::GetStack(int index, u32 stackPtr) { } static void CopyMatrix24(u32_le *result, const float *mtx, u32 count, u32 cmdbits) { + // Screams out for simple SIMD, but probably not called often enough to be worth it. for (u32 i = 0; i < count; ++i) { result[i] = toFloat24(mtx[i]) | cmdbits; } From e136ad795a9fa769d2508041d3c8fedbcebfa582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 15:07:00 +0100 Subject: [PATCH 026/141] Some slight unification --- GPU/D3D11/GPU_D3D11.cpp | 8 -------- GPU/D3D11/GPU_D3D11.h | 2 -- GPU/Directx9/GPU_DX9.cpp | 7 ------- GPU/Directx9/GPU_DX9.h | 2 -- GPU/GPUCommon.cpp | 4 ---- GPU/GPUCommon.h | 4 +++- GPU/GPUCommonHW.cpp | 13 +++++++++++++ GPU/GPUCommonHW.h | 3 +++ GPU/Software/SoftGpu.h | 1 - 9 files changed, 19 insertions(+), 25 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index e11d1c8156..d20b36edbd 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -116,14 +116,6 @@ u32 GPU_D3D11::CheckGPUFeatures() const { return CheckGPUFeaturesLate(features); } -// Needs to be called on GPU thread, not reporting thread. -void GPU_D3D11::BuildReportingInfo() { - using namespace Draw; - - reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); - reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); -} - void GPU_D3D11::DeviceLost() { draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); // Simply drop all caches and textures. diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 42a2ea0c24..3112c1b437 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -48,8 +48,6 @@ protected: void FinishDeferred() override; private: - void BuildReportingInfo() override; - void InitClear() override; void BeginFrame() override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 8569a6639e..96ea5ae435 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -113,13 +113,6 @@ GPU_DX9::~GPU_DX9() { delete shaderManagerDX9_; } -// Needs to be called on GPU thread, not reporting thread. -void GPU_DX9::BuildReportingInfo() { - using namespace Draw; - reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); - reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); -} - void GPU_DX9::DeviceLost() { // Simply drop all caches and textures. shaderManagerDX9_->ClearCache(false); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index d89a2bf546..6d1001d90c 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -50,8 +50,6 @@ protected: void FinishDeferred() override; private: - void BuildReportingInfo() override; - void InitClear() override; void BeginFrame() override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index af0d7d7a3d..6d650e3d26 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -633,10 +633,6 @@ void GPUCommon::NotifyDisplayResized() { displayResized_ = true; } -void GPUCommon::ClearCacheNextFrame() { - textureCache_->ClearNextFrame(); -} - // Called once per frame. Might also get called during the pause screen // if "transparent". void GPUCommon::CheckConfigChanged() { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index fd17ebc6e3..98905a4f0b 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -272,7 +272,7 @@ protected: void DeviceLost() override; void DeviceRestore() override; - void ClearCacheNextFrame() override; + void ClearCacheNextFrame() override {} virtual void CheckRenderResized(); @@ -332,9 +332,11 @@ protected: DrawEngineCommon *drawEngineCommon_ = nullptr; + // TODO: These should live in GPUCommonHW. FramebufferManagerCommon *framebufferManager_ = nullptr; TextureCacheCommon *textureCache_ = nullptr; ShaderManagerCommon *shaderManager_ = nullptr; + bool flushOnParams_ = true; GraphicsContext *gfxCtx_; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 27dc1981b7..6c98d9f44c 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1,5 +1,6 @@ #include "Common/GPU/thin3d.h" #include "Common/Serialize/Serializer.h" +#include "Common/System/System.h" #include "Core/System.h" @@ -39,3 +40,15 @@ void GPUCommonHW::DoState(PointerWrap &p) { framebufferManager_->DestroyAllFBOs(); } } + +void GPUCommonHW::ClearCacheNextFrame() { + textureCache_->ClearNextFrame(); +} + +// Needs to be called on GPU thread, not reporting thread. +void GPUCommonHW::BuildReportingInfo() { + using namespace Draw; + + reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); + reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index aa0a189564..924a60c5f4 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -14,6 +14,9 @@ public: protected: void PreExecuteOp(u32 op, u32 diff); + void ClearCacheNextFrame() override; + // Needs to be called on GPU thread, not reporting thread. + void BuildReportingInfo() override; }; diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 2496c7b474..ef61d59663 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -145,7 +145,6 @@ public: bool PerformReadbackToMemory(u32 dest, int size) override; bool PerformWriteColorFromMemory(u32 dest, int size) override; bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) override; - void ClearCacheNextFrame() override {} void DeviceLost() override; void DeviceRestore() override; From c74f5b21890e74992a6178cb41b8948cc7bd2e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 15:59:35 +0100 Subject: [PATCH 027/141] Prepare for unifying more stuff --- GPU/Common/ShaderCommon.h | 4 ++++ GPU/Common/TextureCacheCommon.cpp | 8 +++---- GPU/Common/TextureCacheCommon.h | 7 +++++-- GPU/D3D11/GPU_D3D11.cpp | 12 +++++------ GPU/D3D11/ShaderManagerD3D11.h | 4 +++- GPU/D3D11/TextureCacheD3D11.h | 3 +++ GPU/Directx9/GPU_DX9.cpp | 21 +++++++------------ GPU/Directx9/GPU_DX9.h | 1 - GPU/Directx9/ShaderManagerDX9.cpp | 5 ++--- GPU/Directx9/ShaderManagerDX9.h | 5 ++++- GPU/Directx9/TextureCacheDX9.h | 3 +++ GPU/GLES/GPU_GLES.cpp | 35 ++++++++++++++----------------- GPU/GLES/ShaderManagerGLES.cpp | 2 +- GPU/GLES/ShaderManagerGLES.h | 6 +++--- GPU/GLES/TextureCacheGLES.h | 4 ++-- GPU/GPUCommonHW.cpp | 6 +++++- GPU/Vulkan/GPU_Vulkan.cpp | 23 ++++++++------------ GPU/Vulkan/GPU_Vulkan.h | 1 - GPU/Vulkan/ShaderManagerVulkan.h | 4 ++-- GPU/Vulkan/TextureCacheVulkan.cpp | 8 ------- GPU/Vulkan/TextureCacheVulkan.h | 1 - GPU/Vulkan/VulkanUtil.cpp | 3 --- GPU/Vulkan/VulkanUtil.h | 1 - 23 files changed, 80 insertions(+), 87 deletions(-) diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index 73f748003e..025576ea6f 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -126,8 +126,12 @@ public: ShaderManagerCommon(Draw::DrawContext *draw) : draw_(draw) {} virtual ~ShaderManagerCommon() {} + virtual void ClearShaders() = 0; virtual void DirtyLastShader() = 0; + virtual void DeviceLost() = 0; + virtual void DeviceRestore(Draw::DrawContext *draw) = 0; // must set draw_ to draw + protected: Draw::DrawContext *draw_ = nullptr; }; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 6611c21156..5a88a718b0 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -138,6 +138,10 @@ TextureCacheCommon::~TextureCacheCommon() { FreeAlignedMemory(expandClut_); } +void TextureCacheCommon::StartFrame() { + textureShaderCache_->Decimate(); +} + // Produces a signed 1.23.8 value. static int TexLog2(float delta) { union FloatBits { @@ -2906,7 +2910,3 @@ CheckAlphaResult TextureCacheCommon::CheckCLUTAlpha(const uint8_t *pixelData, GE return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000); } } - -void TextureCacheCommon::StartFrame() { - textureShaderCache_->Decimate(); -} diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index f85e09b9a4..ec45251fa8 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -350,6 +350,11 @@ public: } virtual bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { return false; } + virtual void StartFrame(); + + virtual void DeviceLost() = 0; + virtual void DeviceRestore(Draw::DrawContext *draw) = 0; + protected: virtual void *GetNativeTextureView(const TexCacheEntry *entry) = 0; bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry); @@ -403,8 +408,6 @@ protected: virtual void BoundFramebufferTexture() {} - virtual void StartFrame(); - void DecimateVideos(); bool IsVideo(u32 texaddr) const; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index d20b36edbd..1b58db17c8 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -120,9 +120,9 @@ void GPU_D3D11::DeviceLost() { draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); // Simply drop all caches and textures. // FBOs appear to survive? Or no? - shaderManagerD3D11_->ClearShaders(); + shaderManager_->ClearShaders(); drawEngine_.ClearInputLayoutMap(); - textureCacheD3D11_->Clear(false); + textureCache_->Clear(false); GPUCommon::DeviceLost(); } @@ -141,19 +141,19 @@ void GPU_D3D11::InitClear() { void GPU_D3D11::BeginFrame() { GPUCommon::BeginFrame(); - textureCacheD3D11_->StartFrame(); + textureCache_->StartFrame(); drawEngine_.BeginFrame(); - shaderManagerD3D11_->DirtyLastShader(); + shaderManager_->DirtyLastShader(); - framebufferManagerD3D11_->BeginFrame(); + framebufferManager_->BeginFrame(); gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX); if (gstate_c.useFlagsChanged) { // TODO: It'd be better to recompile them in the background, probably? // This most likely means that saw equal depth changed. WARN_LOG(G3D, "Shader use flags changed, clearing all shaders and depth buffers"); - shaderManagerD3D11_->ClearShaders(); + shaderManager_->ClearShaders(); framebufferManager_->ClearAllDepthBuffers(); drawEngine_.ClearInputLayoutMap(); gstate_c.useFlagsChanged = false; diff --git a/GPU/D3D11/ShaderManagerD3D11.h b/GPU/D3D11/ShaderManagerD3D11.h index 75203f9533..5dacd408a5 100644 --- a/GPU/D3D11/ShaderManagerD3D11.h +++ b/GPU/D3D11/ShaderManagerD3D11.h @@ -86,9 +86,11 @@ public: ~ShaderManagerD3D11(); void GetShaders(int prim, VertexDecoder *decoder, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode); - void ClearShaders(); + void ClearShaders() override; void DirtyLastShader() override; + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 4cb01375ff..8c2ff77f62 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -54,6 +54,9 @@ public: bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } + protected: void BindTexture(TexCacheEntry *entry) override; void Unbind() override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 96ea5ae435..b857fcfa80 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -106,25 +106,20 @@ u32 GPU_DX9::CheckGPUFeatures() const { } GPU_DX9::~GPU_DX9() { - framebufferManagerDX9_->DestroyAllFBOs(); - delete framebufferManagerDX9_; + framebufferManager_->DestroyAllFBOs(); + delete framebufferManager_; delete textureCache_; - shaderManagerDX9_->ClearCache(true); - delete shaderManagerDX9_; + shaderManager_->ClearShaders(); + delete shaderManager_; } void GPU_DX9::DeviceLost() { // Simply drop all caches and textures. - shaderManagerDX9_->ClearCache(false); - textureCacheDX9_->Clear(false); + shaderManager_->ClearShaders(); + textureCache_->Clear(false); GPUCommon::DeviceLost(); } -void GPU_DX9::DeviceRestore() { - GPUCommon::DeviceRestore(); - // Nothing needed. -} - void GPU_DX9::InitClear() { if (!framebufferManager_->UseBufferedRendering()) { dxstate.depthWrite.set(true); @@ -139,7 +134,7 @@ void GPU_DX9::ReapplyGfxState() { } void GPU_DX9::BeginFrame() { - textureCacheDX9_->StartFrame(); + textureCache_->StartFrame(); drawEngine_.BeginFrame(); GPUCommon::BeginFrame(); @@ -151,7 +146,7 @@ void GPU_DX9::BeginFrame() { // TODO: It'd be better to recompile them in the background, probably? // This most likely means that saw equal depth changed. WARN_LOG(G3D, "Shader use flags changed, clearing all shaders and depth buffers"); - shaderManagerDX9_->ClearCache(true); + shaderManager_->ClearShaders(); framebufferManager_->ClearAllDepthBuffers(); gstate_c.useFlagsChanged = false; } diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 6d1001d90c..7e1132175e 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -40,7 +40,6 @@ public: void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. - void DeviceRestore() override; // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index 1571d517bd..dee68027b1 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -540,11 +540,10 @@ void ShaderManagerDX9::Clear() { DirtyShader(); } -void ShaderManagerDX9::ClearCache(bool deleteThem) { +void ShaderManagerDX9::ClearShaders() { Clear(); } - void ShaderManagerDX9::DirtyShader() { // Forget the last shader ID lastFSID_.set_invalid(); @@ -554,7 +553,7 @@ void ShaderManagerDX9::DirtyShader() { gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); } -void ShaderManagerDX9::DirtyLastShader() { // disables vertex arrays +void ShaderManagerDX9::DirtyLastShader() { lastVShader_ = nullptr; lastPShader_ = nullptr; } diff --git a/GPU/Directx9/ShaderManagerDX9.h b/GPU/Directx9/ShaderManagerDX9.h index 299eb677d2..a0ccce98ed 100644 --- a/GPU/Directx9/ShaderManagerDX9.h +++ b/GPU/Directx9/ShaderManagerDX9.h @@ -77,7 +77,7 @@ public: ShaderManagerDX9(Draw::DrawContext *draw, LPDIRECT3DDEVICE9 device); ~ShaderManagerDX9(); - void ClearCache(bool deleteThem); // TODO: deleteThem currently not respected + void ClearShaders() override; VSShader *ApplyShader(bool useHWTransform, bool useHWTessellation, VertexDecoder *decoder, bool weightsAsFloat, bool useSkinInDecode, const ComputedPipelineState &pipelineState); void DirtyShader(); void DirtyLastShader() override; @@ -85,6 +85,9 @@ public: int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } + std::vector DebugGetShaderIDs(DebugShaderType type); std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index 0080fbdef3..8b2a9eaec8 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -42,6 +42,9 @@ public: bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } + protected: void BindTexture(TexCacheEntry *entry) override; void Unbind() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 2cb6a88504..4d5abeadb7 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -139,17 +139,14 @@ GPU_GLES::~GPU_GLES() { } } - framebufferManagerGL_->DestroyAllFBOs(); - shaderManagerGL_->ClearCache(true); + framebufferManager_->DestroyAllFBOs(); + shaderManager_->ClearShaders(); fragmentTestCache_.Clear(); - delete shaderManagerGL_; - shaderManagerGL_ = nullptr; - delete framebufferManagerGL_; - delete textureCacheGL_; - - // Clear features so they're not visible in system info. - gstate_c.SetUseFlags(0); + delete shaderManager_; + shaderManager_ = nullptr; + delete framebufferManager_; + delete textureCache_; } // Take the raw GL extension and versioning data and turn into feature flags. @@ -249,8 +246,8 @@ void GPU_GLES::DeviceLost() { // FBOs appear to survive? Or no? // TransformDraw has registered as a GfxResourceHolder. CancelReady(); - shaderManagerGL_->DeviceLost(); - textureCacheGL_->DeviceLost(); + shaderManager_->DeviceLost(); + textureCache_->DeviceLost(); fragmentTestCache_.DeviceLost(); drawEngine_.DeviceLost(); @@ -263,8 +260,8 @@ void GPU_GLES::DeviceRestore() { UpdateCmdInfo(); UpdateVsyncInterval(true); - shaderManagerGL_->DeviceRestore(draw_); - textureCacheGL_->DeviceRestore(draw_); + shaderManager_->DeviceRestore(draw_); + textureCache_->DeviceRestore(draw_); drawEngine_.DeviceRestore(draw_); fragmentTestCache_.DeviceRestore(draw_); } @@ -284,7 +281,7 @@ void GPU_GLES::BeginHostFrame() { // TODO: It'd be better to recompile them in the background, probably? // This most likely means that saw equal depth changed. WARN_LOG(G3D, "Shader use flags changed, clearing all shaders and depth buffers"); - shaderManagerGL_->ClearCache(true); + shaderManager_->ClearShaders(); framebufferManager_->ClearAllDepthBuffers(); gstate_c.useFlagsChanged = false; } @@ -299,22 +296,22 @@ void GPU_GLES::ReapplyGfxState() { } void GPU_GLES::BeginFrame() { - textureCacheGL_->StartFrame(); - fragmentTestCache_.Decimate(); - GPUCommon::BeginFrame(); + textureCache_->StartFrame(); + // Save the cache from time to time. TODO: How often? We save on exit, so shouldn't need to do this all that often. if (shaderCachePath_.Valid() && (gpuStats.numFlips & 4095) == 0) { shaderManagerGL_->SaveCache(shaderCachePath_, &drawEngine_); } - shaderManagerGL_->DirtyShader(); // Not sure if this is really needed. gstate_c.Dirty(DIRTY_ALL_UNIFORMS); - framebufferManagerGL_->BeginFrame(); + framebufferManager_->BeginFrame(); + + fragmentTestCache_.Decimate(); } void GPU_GLES::FinishDeferred() { diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index d80cfb494f..e050b9381a 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -715,7 +715,7 @@ void ShaderManagerGLES::Clear() { DirtyShader(); } -void ShaderManagerGLES::ClearCache(bool deleteThem) { +void ShaderManagerGLES::ClearShaders() { // TODO: Recreate all from the diskcache when we come back. Clear(); } diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index b37604ba5d..6435b44d7d 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -165,15 +165,15 @@ public: ShaderManagerGLES(Draw::DrawContext *draw); ~ShaderManagerGLES(); - void ClearCache(bool deleteThem); // TODO: deleteThem currently not respected + void ClearShaders() override; // This is the old ApplyShader split into two parts, because of annoying information dependencies. // If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards. Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, VertexDecoder *vertexDecoder, bool weightsAsFloat, bool useSkinInDecode, VShaderID *VSID); LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useBufferedRendering); - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; void DirtyShader(); void DirtyLastShader() override; diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 2546c605b9..6f90475ed1 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -54,8 +54,8 @@ public: bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; protected: void BindTexture(TexCacheEntry *entry) override; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 6c98d9f44c..8f587d6f77 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -10,7 +10,11 @@ #include "GPU/Common/FramebufferManagerCommon.h" GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) {} -GPUCommonHW::~GPUCommonHW() {} + +GPUCommonHW::~GPUCommonHW() { + // Clear features so they're not visible in system info. + gstate_c.SetUseFlags(0); +} void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index faed3b7e4c..11d7b562cc 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -187,10 +187,10 @@ GPU_Vulkan::~GPU_Vulkan() { DestroyDeviceObjects(); framebufferManagerVulkan_->DestroyAllFBOs(); drawEngine_.DeviceLost(); - delete textureCacheVulkan_; + delete textureCache_; delete pipelineManager_; - delete shaderManagerVulkan_; - delete framebufferManagerVulkan_; + delete shaderManager_; + delete framebufferManager_; } u32 GPU_Vulkan::CheckGPUFeatures() const { @@ -292,7 +292,7 @@ void GPU_Vulkan::BeginHostFrame() { GPUCommon::BeginHostFrame(); drawEngine_.BeginFrame(); - textureCacheVulkan_->StartFrame(); + textureCache_->StartFrame(); VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); int curFrame = vulkan->GetCurFrame(); @@ -301,7 +301,7 @@ void GPU_Vulkan::BeginHostFrame() { frame.push_->Reset(); frame.push_->Begin(vulkan); - framebufferManagerVulkan_->BeginFrame(); + framebufferManager_->BeginFrame(); textureCacheVulkan_->SetPushBuffer(frameData_[curFrame].push_); shaderManagerVulkan_->DirtyShader(); @@ -313,7 +313,7 @@ void GPU_Vulkan::BeginHostFrame() { WARN_LOG(G3D, "Shader use flags changed, clearing all shaders and depth buffers"); // TODO: Not all shaders need to be recompiled. In fact, quite few? Of course, depends on // the use flag change.. This is a major frame rate hitch in the start of a race in Outrun. - shaderManagerVulkan_->ClearShaders(); + shaderManager_->ClearShaders(); pipelineManager_->Clear(); framebufferManager_->ClearAllDepthBuffers(); gstate_c.useFlagsChanged = false; @@ -335,7 +335,6 @@ void GPU_Vulkan::EndHostFrame() { frame.push_->End(); drawEngine_.EndFrame(); - textureCacheVulkan_->EndFrame(); GPUCommon::EndHostFrame(); } @@ -391,10 +390,6 @@ void GPU_Vulkan::BuildReportingInfo() { Reporting::UpdateConfig(); } -void GPU_Vulkan::Reinitialize() { - GPUCommon::Reinitialize(); -} - void GPU_Vulkan::InitClear() { if (!framebufferManager_->UseBufferedRendering()) { // TODO? @@ -450,8 +445,8 @@ void GPU_Vulkan::DestroyDeviceObjects() { } void GPU_Vulkan::CheckRenderResized() { + GPUCommon::CheckRenderResized(); if (renderResized_) { - GPUCommon::CheckRenderResized(); pipelineManager_->InvalidateMSAAPipelines(); framebufferManager_->ReleasePipelines(); } @@ -471,11 +466,11 @@ void GPU_Vulkan::DeviceLost() { textureCacheVulkan_->DeviceLost(); shaderManagerVulkan_->DeviceLost(); - GPUCommon::DeviceLost(); + GPUCommonHW::DeviceLost(); } void GPU_Vulkan::DeviceRestore() { - GPUCommon::DeviceRestore(); + GPUCommonHW::DeviceRestore(); InitDeviceObjects(); gstate_c.SetUseFlags(CheckGPUFeatures()); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index f468ec8b68..6892eafa58 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -68,7 +68,6 @@ protected: private: void BuildReportingInfo() override; void InitClear() override; - void Reinitialize() override; void InitDeviceObjects(); void DestroyDeviceObjects(); diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index 91544124f3..a29ea06be3 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -114,8 +114,8 @@ public: ShaderManagerVulkan(Draw::DrawContext *draw); ~ShaderManagerVulkan(); - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; void GetShaders(int prim, VertexDecoder *decoder, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode); void ClearShaders(); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 349d608c3a..da69158dc3 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -352,14 +352,6 @@ void TextureCacheVulkan::StartFrame() { computeShaderManager_.BeginFrame(); } -void TextureCacheVulkan::EndFrame() { - computeShaderManager_.EndFrame(); - - if (texelsScaledThisFrame_) { - VERBOSE_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } -} - void TextureCacheVulkan::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutFormat == GE_CMODE_32BIT_ABGR8888 ? (clutBase * sizeof(u32)) : (clutBase * sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 3654101b90..2c7c149ef6 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -58,7 +58,6 @@ public: ~TextureCacheVulkan(); void StartFrame() override; - void EndFrame(); void DeviceLost(); void DeviceRestore(Draw::DrawContext *draw); diff --git a/GPU/Vulkan/VulkanUtil.cpp b/GPU/Vulkan/VulkanUtil.cpp index c47fd21d9c..0d87ebc153 100644 --- a/GPU/Vulkan/VulkanUtil.cpp +++ b/GPU/Vulkan/VulkanUtil.cpp @@ -208,6 +208,3 @@ void VulkanComputeShaderManager::BeginFrame() { FrameData &frame = frameData_[curFrame]; frame.descPool.Reset(); } - -void VulkanComputeShaderManager::EndFrame() { -} diff --git a/GPU/Vulkan/VulkanUtil.h b/GPU/Vulkan/VulkanUtil.h index 8663d3ce1e..2046f6201f 100644 --- a/GPU/Vulkan/VulkanUtil.h +++ b/GPU/Vulkan/VulkanUtil.h @@ -61,7 +61,6 @@ public: VkPipelineLayout GetPipelineLayout() const { return pipelineLayout_; } void BeginFrame(); - void EndFrame(); private: void InitDeviceObjects(Draw::DrawContext *draw); From 8ba665e2586ca9b13b228eb4b0c57f88ec5c89b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:12:24 +0100 Subject: [PATCH 028/141] More unification work --- GPU/D3D11/GPU_D3D11.cpp | 13 ++++--------- GPU/Directx9/GPU_DX9.cpp | 18 ++++-------------- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 26 ++++++-------------------- GPU/GLES/GPU_GLES.h | 1 - GPU/GPUCommonHW.cpp | 14 ++++++++++++++ GPU/GPUCommonHW.h | 1 + GPU/Vulkan/GPU_Vulkan.cpp | 17 +++++++---------- GPU/Vulkan/TextureCacheVulkan.cpp | 1 - 9 files changed, 36 insertions(+), 56 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 1b58db17c8..c0b419184a 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -87,16 +87,11 @@ GPU_D3D11::GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw) } GPU_D3D11::~GPU_D3D11() { - framebufferManagerD3D11_->DestroyAllFBOs(); - delete framebufferManagerD3D11_; - shaderManagerD3D11_->ClearShaders(); - delete shaderManagerD3D11_; - delete textureCacheD3D11_; stockD3D11.Destroy(); } u32 GPU_D3D11::CheckGPUFeatures() const { - u32 features = GPUCommon::CheckGPUFeatures(); + u32 features = GPUCommonHW::CheckGPUFeatures(); // Accurate depth is required because the Direct3D API does not support inverse Z. // So we cannot incorrectly use the viewport transform as the depth range on Direct3D. @@ -124,11 +119,11 @@ void GPU_D3D11::DeviceLost() { drawEngine_.ClearInputLayoutMap(); textureCache_->Clear(false); - GPUCommon::DeviceLost(); + GPUCommonHW::DeviceLost(); } void GPU_D3D11::DeviceRestore() { - GPUCommon::DeviceRestore(); + GPUCommonHW::DeviceRestore(); // Nothing needed. } @@ -139,7 +134,7 @@ void GPU_D3D11::InitClear() { } void GPU_D3D11::BeginFrame() { - GPUCommon::BeginFrame(); + GPUCommonHW::BeginFrame(); textureCache_->StartFrame(); drawEngine_.BeginFrame(); diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index b857fcfa80..a95c6155fd 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -94,7 +94,7 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) } u32 GPU_DX9::CheckGPUFeatures() const { - u32 features = GPUCommon::CheckGPUFeatures(); + u32 features = GPUCommonHW::CheckGPUFeatures(); features |= GPU_USE_16BIT_FORMATS; features |= GPU_USE_TEXTURE_LOD_CONTROL; @@ -105,19 +105,9 @@ u32 GPU_DX9::CheckGPUFeatures() const { return CheckGPUFeaturesLate(features); } -GPU_DX9::~GPU_DX9() { - framebufferManager_->DestroyAllFBOs(); - delete framebufferManager_; - delete textureCache_; - shaderManager_->ClearShaders(); - delete shaderManager_; -} - void GPU_DX9::DeviceLost() { // Simply drop all caches and textures. - shaderManager_->ClearShaders(); - textureCache_->Clear(false); - GPUCommon::DeviceLost(); + GPUCommonHW::DeviceLost(); } void GPU_DX9::InitClear() { @@ -130,14 +120,14 @@ void GPU_DX9::InitClear() { void GPU_DX9::ReapplyGfxState() { dxstate.Restore(); - GPUCommon::ReapplyGfxState(); + GPUCommonHW::ReapplyGfxState(); } void GPU_DX9::BeginFrame() { textureCache_->StartFrame(); drawEngine_.BeginFrame(); - GPUCommon::BeginFrame(); + GPUCommonHW::BeginFrame(); shaderManagerDX9_->DirtyShader(); framebufferManager_->BeginFrame(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 7e1132175e..eda85cb387 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -33,7 +33,6 @@ class TextureCacheDX9; class GPU_DX9 : public GPUCommonHW { public: GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw); - ~GPU_DX9(); u32 CheckGPUFeatures() const override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 4d5abeadb7..6bf1046764 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -138,22 +138,14 @@ GPU_GLES::~GPU_GLES() { INFO_LOG(G3D, "Shader cache disabled. Not saving."); } } - - framebufferManager_->DestroyAllFBOs(); - shaderManager_->ClearShaders(); fragmentTestCache_.Clear(); - - delete shaderManager_; - shaderManager_ = nullptr; - delete framebufferManager_; - delete textureCache_; } // Take the raw GL extension and versioning data and turn into feature flags. // TODO: This should use DrawContext::GetDeviceCaps() more and more, and eventually // this can be shared between all the backends. u32 GPU_GLES::CheckGPUFeatures() const { - u32 features = GPUCommon::CheckGPUFeatures(); + u32 features = GPUCommonHW::CheckGPUFeatures(); features |= GPU_USE_16BIT_FORMATS; @@ -246,16 +238,14 @@ void GPU_GLES::DeviceLost() { // FBOs appear to survive? Or no? // TransformDraw has registered as a GfxResourceHolder. CancelReady(); - shaderManager_->DeviceLost(); - textureCache_->DeviceLost(); fragmentTestCache_.DeviceLost(); drawEngine_.DeviceLost(); - GPUCommon::DeviceLost(); + GPUCommonHW::DeviceLost(); } void GPU_GLES::DeviceRestore() { - GPUCommon::DeviceRestore(); + GPUCommonHW::DeviceRestore(); UpdateCmdInfo(); UpdateVsyncInterval(true); @@ -266,15 +256,11 @@ void GPU_GLES::DeviceRestore() { fragmentTestCache_.DeviceRestore(draw_); } -void GPU_GLES::Reinitialize() { - GPUCommon::Reinitialize(); -} - void GPU_GLES::InitClear() { } void GPU_GLES::BeginHostFrame() { - GPUCommon::BeginHostFrame(); + GPUCommonHW::BeginHostFrame(); drawEngine_.BeginFrame(); if (gstate_c.useFlagsChanged) { @@ -292,11 +278,11 @@ void GPU_GLES::EndHostFrame() { } void GPU_GLES::ReapplyGfxState() { - GPUCommon::ReapplyGfxState(); + GPUCommonHW::ReapplyGfxState(); } void GPU_GLES::BeginFrame() { - GPUCommon::BeginFrame(); + GPUCommonHW::BeginFrame(); textureCache_->StartFrame(); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 912c253188..4ab375840a 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -64,7 +64,6 @@ private: void InitClear() override; void BeginFrame() override; - void Reinitialize() override; FramebufferManagerGLES *framebufferManagerGL_; TextureCacheGLES *textureCacheGL_; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 8f587d6f77..0ab31d9cbd 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -14,6 +14,20 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU GPUCommonHW::~GPUCommonHW() { // Clear features so they're not visible in system info. gstate_c.SetUseFlags(0); + + // Delete the various common managers. + framebufferManager_->DestroyAllFBOs(); + delete framebufferManager_; + delete textureCache_; + shaderManager_->ClearShaders(); + delete shaderManager_; +} + +void GPUCommonHW::DeviceLost() { + textureCache_->Clear(false); + framebufferManager_->DeviceLost(); + textureCache_->DeviceLost(); + shaderManager_->DeviceLost(); } void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 924a60c5f4..a427f27963 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -11,6 +11,7 @@ public: void CopyDisplayToOutput(bool reallyDirty) override; void DoState(PointerWrap &p) override; + void DeviceLost() override; protected: void PreExecuteOp(u32 op, u32 diff); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 11d7b562cc..ffe2e61755 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -185,16 +185,15 @@ GPU_Vulkan::~GPU_Vulkan() { SaveCache(shaderCachePath_); // Note: We save the cache in DeviceLost DestroyDeviceObjects(); - framebufferManagerVulkan_->DestroyAllFBOs(); drawEngine_.DeviceLost(); - delete textureCache_; + shaderManager_->ClearShaders(); + delete pipelineManager_; - delete shaderManager_; - delete framebufferManager_; + // other managers are deleted in ~GPUCommonHW. } u32 GPU_Vulkan::CheckGPUFeatures() const { - uint32_t features = GPUCommon::CheckGPUFeatures(); + uint32_t features = GPUCommonHW::CheckGPUFeatures(); VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); switch (vulkan->GetPhysicalDeviceProperties().properties.vendorID) { @@ -289,7 +288,7 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { } void GPU_Vulkan::BeginHostFrame() { - GPUCommon::BeginHostFrame(); + GPUCommonHW::BeginHostFrame(); drawEngine_.BeginFrame(); textureCache_->StartFrame(); @@ -336,7 +335,7 @@ void GPU_Vulkan::EndHostFrame() { drawEngine_.EndFrame(); - GPUCommon::EndHostFrame(); + GPUCommonHW::EndHostFrame(); } // Needs to be called on GPU thread, not reporting thread. @@ -445,7 +444,7 @@ void GPU_Vulkan::DestroyDeviceObjects() { } void GPU_Vulkan::CheckRenderResized() { - GPUCommon::CheckRenderResized(); + GPUCommonHW::CheckRenderResized(); if (renderResized_) { pipelineManager_->InvalidateMSAAPipelines(); framebufferManager_->ReleasePipelines(); @@ -463,8 +462,6 @@ void GPU_Vulkan::DeviceLost() { DestroyDeviceObjects(); drawEngine_.DeviceLost(); pipelineManager_->DeviceLost(); - textureCacheVulkan_->DeviceLost(); - shaderManagerVulkan_->DeviceLost(); GPUCommonHW::DeviceLost(); } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index da69158dc3..d47ed1ee2d 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -217,7 +217,6 @@ void TextureCacheVulkan::DeviceLost() { Clear(true); samplerCache_.DeviceLost(); - if (samplerNearest_) vulkan->Delete().QueueDeleteSampler(samplerNearest_); From 9d6c3c329e0511a25f55cea9238c345492663461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:13:54 +0100 Subject: [PATCH 029/141] Move CheckGPUFeatures() to GPUCommonHW --- GPU/GPUCommon.cpp | 64 -------------------------------------------- GPU/GPUCommon.h | 2 +- GPU/GPUCommonHW.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++ GPU/GPUCommonHW.h | 2 ++ 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 6d650e3d26..780610ac6b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3464,70 +3464,6 @@ size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) { ); } -u32 GPUCommon::CheckGPUFeatures() const { - u32 features = 0; - if (draw_->GetDeviceCaps().logicOpSupported) { - features |= GPU_USE_LOGIC_OP; - } - if (draw_->GetDeviceCaps().anisoSupported) { - features |= GPU_USE_ANISOTROPY; - } - if (draw_->GetDeviceCaps().textureNPOTFullySupported) { - features |= GPU_USE_TEXTURE_NPOT; - } - if (draw_->GetDeviceCaps().dualSourceBlend) { - if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) { - features |= GPU_USE_DUALSOURCE_BLEND; - } - } - if (draw_->GetDeviceCaps().blendMinMaxSupported) { - features |= GPU_USE_BLEND_MINMAX; - } - - if (draw_->GetDeviceCaps().clipDistanceSupported) { - features |= GPU_USE_CLIP_DISTANCE; - } - - if (draw_->GetDeviceCaps().cullDistanceSupported) { - features |= GPU_USE_CULL_DISTANCE; - } - - if (draw_->GetDeviceCaps().textureDepthSupported) { - features |= GPU_USE_DEPTH_TEXTURE; - } - - if (draw_->GetDeviceCaps().depthClampSupported) { - // Some backends always do GPU_USE_ACCURATE_DEPTH, but it's required for depth clamp. - features |= GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH; - } - - bool canClipOrCull = draw_->GetDeviceCaps().clipDistanceSupported || draw_->GetDeviceCaps().cullDistanceSupported; - bool canDiscardVertex = !draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL); - if (canClipOrCull || canDiscardVertex) { - // We'll dynamically use the parts that are supported, to reduce artifacts as much as possible. - features |= GPU_USE_VS_RANGE_CULLING; - } - - if (draw_->GetDeviceCaps().framebufferFetchSupported) { - features |= GPU_USE_FRAMEBUFFER_FETCH; - } - - if (draw_->GetShaderLanguageDesc().bitwiseOps) { - features |= GPU_USE_LIGHT_UBERSHADER; - } - - if (PSP_CoreParameter().compat.flags().ClearToRAM) { - features |= GPU_USE_CLEAR_RAM_HACK; - } - - // Even without depth clamp, force accurate depth on for some games that break without it. - if (PSP_CoreParameter().compat.flags().DepthRangeHack) { - features |= GPU_USE_ACCURATE_DEPTH; - } - - return features; -} - u32 GPUCommon::CheckGPUFeaturesLate(u32 features) const { // If we already have a 16-bit depth buffer, we don't need to round. bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 98905a4f0b..0c614d9310 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -76,7 +76,7 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } - virtual u32 CheckGPUFeatures() const; + virtual u32 CheckGPUFeatures() const = 0; void CheckDisplayResized() override; void CheckConfigChanged() override; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 0ab31d9cbd..666f41d5db 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -3,6 +3,7 @@ #include "Common/System/System.h" #include "Core/System.h" +#include "Core/Config.h" #include "GPU/GPUCommonHW.h" #include "GPU/Common/DrawEngineCommon.h" @@ -70,3 +71,67 @@ void GPUCommonHW::BuildReportingInfo() { reportingPrimaryInfo_ = draw_->GetInfoString(InfoField::VENDORSTRING); reportingFullInfo_ = reportingPrimaryInfo_ + " - " + System_GetProperty(SYSPROP_GPUDRIVER_VERSION) + " - " + draw_->GetInfoString(InfoField::SHADELANGVERSION); } + +u32 GPUCommonHW::CheckGPUFeatures() const { + u32 features = 0; + if (draw_->GetDeviceCaps().logicOpSupported) { + features |= GPU_USE_LOGIC_OP; + } + if (draw_->GetDeviceCaps().anisoSupported) { + features |= GPU_USE_ANISOTROPY; + } + if (draw_->GetDeviceCaps().textureNPOTFullySupported) { + features |= GPU_USE_TEXTURE_NPOT; + } + if (draw_->GetDeviceCaps().dualSourceBlend) { + if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) { + features |= GPU_USE_DUALSOURCE_BLEND; + } + } + if (draw_->GetDeviceCaps().blendMinMaxSupported) { + features |= GPU_USE_BLEND_MINMAX; + } + + if (draw_->GetDeviceCaps().clipDistanceSupported) { + features |= GPU_USE_CLIP_DISTANCE; + } + + if (draw_->GetDeviceCaps().cullDistanceSupported) { + features |= GPU_USE_CULL_DISTANCE; + } + + if (draw_->GetDeviceCaps().textureDepthSupported) { + features |= GPU_USE_DEPTH_TEXTURE; + } + + if (draw_->GetDeviceCaps().depthClampSupported) { + // Some backends always do GPU_USE_ACCURATE_DEPTH, but it's required for depth clamp. + features |= GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH; + } + + bool canClipOrCull = draw_->GetDeviceCaps().clipDistanceSupported || draw_->GetDeviceCaps().cullDistanceSupported; + bool canDiscardVertex = !draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL); + if (canClipOrCull || canDiscardVertex) { + // We'll dynamically use the parts that are supported, to reduce artifacts as much as possible. + features |= GPU_USE_VS_RANGE_CULLING; + } + + if (draw_->GetDeviceCaps().framebufferFetchSupported) { + features |= GPU_USE_FRAMEBUFFER_FETCH; + } + + if (draw_->GetShaderLanguageDesc().bitwiseOps) { + features |= GPU_USE_LIGHT_UBERSHADER; + } + + if (PSP_CoreParameter().compat.flags().ClearToRAM) { + features |= GPU_USE_CLEAR_RAM_HACK; + } + + // Even without depth clamp, force accurate depth on for some games that break without it. + if (PSP_CoreParameter().compat.flags().DepthRangeHack) { + features |= GPU_USE_ACCURATE_DEPTH; + } + + return features; +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index a427f27963..5e258027e8 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -13,6 +13,8 @@ public: void DoState(PointerWrap &p) override; void DeviceLost() override; + u32 CheckGPUFeatures() const override; + protected: void PreExecuteOp(u32 op, u32 diff); void ClearCacheNextFrame() override; From 8b54a14bf2302c2d1edff5d281de0bd865a51bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:20:34 +0100 Subject: [PATCH 030/141] Move the big command table to where it belongs, GPUCommonHW --- GPU/GPUCommon.cpp | 393 +--------------------------------------- GPU/GPUCommon.h | 2 +- GPU/GPUCommonHW.cpp | 395 ++++++++++++++++++++++++++++++++++++++++- GPU/GPUCommonHW.h | 2 + GPU/Software/SoftGpu.h | 1 + 5 files changed, 399 insertions(+), 394 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 780610ac6b..f48661000f 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -49,329 +49,6 @@ #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" -const CommonCommandTableEntry commonCommandTable[] = { - // From Common. No flushing but definitely need execute. - { GE_CMD_OFFSETADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_OffsetAddr }, - { GE_CMD_ORIGIN, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommon::Execute_Origin }, - { GE_CMD_JUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Jump }, - { GE_CMD_CALL, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Call }, - { GE_CMD_RET, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Ret }, - { GE_CMD_END, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_End }, - { GE_CMD_VADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Vaddr }, - { GE_CMD_IADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Iaddr }, - { GE_CMD_BJUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BJump }, // EXECUTE - { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoundingBox }, // Shouldn't need to FLUSHBEFORE. - - { GE_CMD_PRIM, FLAG_EXECUTE, 0, &GPUCommon::Execute_Prim }, - { GE_CMD_BEZIER, FLAG_EXECUTE, 0, &GPUCommon::Execute_Bezier }, - { GE_CMD_SPLINE, FLAG_EXECUTE, 0, &GPUCommon::Execute_Spline }, - - // Changing the vertex type requires us to flush. - { GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType }, - - { GE_CMD_LOADCLUT, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_LoadClut }, - - // These two are actually processed in CMD_END. - { GE_CMD_SIGNAL }, - { GE_CMD_FINISH }, - - // Changes that dirty the framebuffer - { GE_CMD_FRAMEBUFPTR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS }, - { GE_CMD_FRAMEBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_FRAMEBUFPIXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_ZBUFPTR, FLAG_FLUSHBEFOREONCHANGE }, - { GE_CMD_ZBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE }, - - { GE_CMD_FOGCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOLOR }, - { GE_CMD_FOG1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, - { GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, - - // These affect the fragment shader so need flushing. - { GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, - { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, - { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, - { GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - // Raster state for Direct3D 9, uncommon. - { GE_CMD_SHADEMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE }, - { GE_CMD_TEXFUNC, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEX_ALPHA_MUL }, - { GE_CMD_COLORTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_ALPHATESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_COLORTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_COLORTESTMASK, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORMASK | DIRTY_FRAGMENTSHADER_STATE }, - - // These change the vertex shader so need flushing. - { GE_CMD_REVERSENORMAL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, - { GE_CMD_LIGHTENABLE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_LIGHTENABLE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_LIGHTENABLE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_LIGHTENABLE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_LIGHTTYPE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT0 }, - { GE_CMD_LIGHTTYPE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT1 }, - { GE_CMD_LIGHTTYPE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT2 }, - { GE_CMD_LIGHTTYPE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT3 }, - { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - - // These change vertex shaders (in non uber shader mode) so need flushing. - { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - - { GE_CMD_TEXFILTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXWRAP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, - - // Uniform changes. though the fragmentshader optimizes based on these sometimes. - { GE_CMD_ALPHATEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORREF | DIRTY_ALPHACOLORMASK | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_COLORREF, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORREF | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_TEXENVCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXENV }, - - // Simple render state changes. Handled in StateMapping.cpp. - { GE_CMD_CULL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, - { GE_CMD_CULLFACEENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, - { GE_CMD_DITHERENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, - { GE_CMD_STENCILOP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_STENCILTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_STENCILREPLACEVALUE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE }, - { GE_CMD_STENCILTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_ALPHABLENDENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_BLENDMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_BLENDFIXEDA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_BLENDFIXEDB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_MASKRGB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_COLORWRITEMASK }, - { GE_CMD_MASKALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_COLORWRITEMASK }, - { GE_CMD_ZTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE }, - { GE_CMD_ZTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_ZWRITEDISABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_LOGICOP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_LOGICOPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - - { GE_CMD_TEXMAPMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, - - // These are read on every SubmitPrim, no need for dirtying or flushing. - { GE_CMD_TEXSCALEU }, - { GE_CMD_TEXSCALEV }, - { GE_CMD_TEXOFFSETU }, - { GE_CMD_TEXOFFSETV }, - - { GE_CMD_TEXSIZE0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_TexSize0 }, - { GE_CMD_TEXSIZE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, - { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel }, - { GE_CMD_TEXLODSLOPE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE | DIRTY_UVSCALEOFFSET }, - { GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXADDR7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, - { GE_CMD_TEXBUFWIDTH1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - { GE_CMD_TEXBUFWIDTH7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, - - // These must flush on change, so that LoadClut doesn't have to always flush. - { GE_CMD_CLUTADDR, FLAG_FLUSHBEFOREONCHANGE }, - { GE_CMD_CLUTADDRUPPER, FLAG_FLUSHBEFOREONCHANGE }, - { GE_CMD_CLUTFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_DEPAL }, - - // Morph weights. TODO: Remove precomputation? - { GE_CMD_MORPHWEIGHT0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT3, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT4, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT5, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT6, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - { GE_CMD_MORPHWEIGHT7, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, - - // Control spline/bezier patches. Don't really require flushing as such, but meh. - { GE_CMD_PATCHDIVISION, FLAG_FLUSHBEFOREONCHANGE }, - { GE_CMD_PATCHPRIMITIVE, FLAG_FLUSHBEFOREONCHANGE }, - { GE_CMD_PATCHFACING, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - { GE_CMD_PATCHCULLENABLE, FLAG_FLUSHBEFOREONCHANGE }, - - // Can probably ignore this one as we don't support AA lines. - { GE_CMD_ANTIALIASENABLE, FLAG_FLUSHBEFOREONCHANGE }, - - // Viewport. - { GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_RASTER_STATE }, - - // Z clip - { GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - - // Region - { GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - - // Scissor - { GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - - // Lighting base colors - { GE_CMD_AMBIENTCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT }, - { GE_CMD_AMBIENTALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT }, - { GE_CMD_MATERIALDIFFUSE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATDIFFUSE }, - { GE_CMD_MATERIALEMISSIVE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATEMISSIVE }, - { GE_CMD_MATERIALAMBIENT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATAMBIENTALPHA }, - { GE_CMD_MATERIALALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATAMBIENTALPHA }, - { GE_CMD_MATERIALSPECULAR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATSPECULAR }, - { GE_CMD_MATERIALSPECULARCOEF, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATSPECULAR }, - - // Light parameters - { GE_CMD_LX0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LY0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LZ0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LX1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LY1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LZ1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LX2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LY2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LZ2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LX3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LY3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LZ3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - { GE_CMD_LDX0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LDY0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LDZ0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LDX1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LDY1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LDZ1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LDX2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LDY2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LDZ2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LDX3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LDY3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LDZ3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - { GE_CMD_LKA0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LKB0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LKC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LKA1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LKB1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LKC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LKA2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LKB2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LKC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LKA3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LKB3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LKC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - { GE_CMD_LKS0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LKS1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LKS2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LKS3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - { GE_CMD_LKO0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LKO1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LKO2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LKO3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - { GE_CMD_LAC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LDC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LSC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, - { GE_CMD_LAC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LDC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LSC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, - { GE_CMD_LAC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LDC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LSC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, - { GE_CMD_LAC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LDC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - { GE_CMD_LSC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, - - // Ignored commands - { GE_CMD_TEXFLUSH, 0 }, - { GE_CMD_TEXSYNC, 0 }, - - // These are just nop or part of other later commands. - { GE_CMD_NOP, 0 }, - { GE_CMD_BASE, 0 }, - { GE_CMD_TRANSFERSRC, 0 }, - { GE_CMD_TRANSFERSRCW, 0 }, - { GE_CMD_TRANSFERDST, 0 }, - { GE_CMD_TRANSFERDSTW, 0 }, - { GE_CMD_TRANSFERSRCPOS, 0 }, - { GE_CMD_TRANSFERDSTPOS, 0 }, - { GE_CMD_TRANSFERSIZE, 0 }, - { GE_CMD_TRANSFERSTART, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommon::Execute_BlockTransferStart }, - - // We don't use the dither table. - { GE_CMD_DITH0 }, - { GE_CMD_DITH1 }, - { GE_CMD_DITH2 }, - { GE_CMD_DITH3 }, - - // These handle their own flushing. - { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_WorldMtxNum }, - { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_WorldMtxData }, - { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ViewMtxNum }, - { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ViewMtxData }, - { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ProjMtxNum }, - { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ProjMtxData }, - { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_TgenMtxNum }, - { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_TgenMtxData }, - { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BoneMtxNum }, - { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoneMtxData }, - - // Vertex Screen/Texture/Color - { GE_CMD_VSCX }, - { GE_CMD_VSCY }, - { GE_CMD_VSCZ }, - { GE_CMD_VTCS }, - { GE_CMD_VTCT }, - { GE_CMD_VTCQ }, - { GE_CMD_VCV }, - { GE_CMD_VAP, FLAG_EXECUTE, 0, &GPUCommon::Execute_ImmVertexAlphaPrim }, - { GE_CMD_VFC }, - { GE_CMD_VSCV }, - - // "Missing" commands (gaps in the sequence) - { GE_CMD_UNKNOWN_03, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_0D, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_11, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_29, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_34, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_35, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_39, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_4E, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_4F, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_52, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_59, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_5A, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_B6, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_B7, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_D1, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_ED, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_EF, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_FA, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_FB, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_FC, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_FD, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - { GE_CMD_UNKNOWN_FE, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, - // Appears to be debugging related or something? Hit a lot in GoW. - { GE_CMD_NOP_FF, 0 }, -}; - // TODO: Make class member? GPUCommon::CommandInfo GPUCommon::cmdInfo_[256]; @@ -398,32 +75,6 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : gstate_c.Reset(); gpuStats.Reset(); - memset(cmdInfo_, 0, sizeof(cmdInfo_)); - - // Convert the command table to a faster format, and check for dupes. - std::set dupeCheck; - for (size_t i = 0; i < ARRAY_SIZE(commonCommandTable); i++) { - const u8 cmd = commonCommandTable[i].cmd; - if (dupeCheck.find(cmd) != dupeCheck.end()) { - ERROR_LOG(G3D, "Command table Dupe: %02x (%i)", (int)cmd, (int)cmd); - } else { - dupeCheck.insert(cmd); - } - cmdInfo_[cmd].flags |= (uint64_t)commonCommandTable[i].flags | (commonCommandTable[i].dirty << 8); - cmdInfo_[cmd].func = commonCommandTable[i].func; - if ((cmdInfo_[cmd].flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) && !cmdInfo_[cmd].func) { - // Can't have FLAG_EXECUTE commands without a function pointer to execute. - Crash(); - } - } - // Find commands missing from the table. - for (int i = 0; i < 0xEF; i++) { - if (dupeCheck.find((u8)i) == dupeCheck.end()) { - ERROR_LOG(G3D, "Command missing from table: %02x (%i)", i, i); - } - } - - UpdateCmdInfo(); UpdateVsyncInterval(true); ResetMatrices(); @@ -437,49 +88,6 @@ GPUCommon::~GPUCommon() { PPGeSetDrawContext(nullptr); } -void GPUCommon::UpdateCmdInfo() { - if (g_Config.bSoftwareSkinning) { - cmdInfo_[GE_CMD_VERTEXTYPE].flags &= ~FLAG_FLUSHBEFOREONCHANGE; - cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexTypeSkinning; - } else { - cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE; - cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexType; - } - - if (g_Config.bFastMemory) { - cmdInfo_[GE_CMD_JUMP].func = &GPUCommon::Execute_JumpFast; - cmdInfo_[GE_CMD_CALL].func = &GPUCommon::Execute_CallFast; - } else { - cmdInfo_[GE_CMD_JUMP].func = &GPUCommon::Execute_Jump; - cmdInfo_[GE_CMD_CALL].func = &GPUCommon::Execute_Call; - } - - // Reconfigure for light ubershader or not. - for (int i = 0; i < 4; i++) { - if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) { - cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE); - cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_LIGHT_CONTROL); - cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE); - cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_LIGHT_CONTROL); - } else { - cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL); - cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE); - cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL); - cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE); - } - } - - if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) { - cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_VERTEXSHADER_STATE); - cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_LIGHT_CONTROL); - cmdInfo_[GE_CMD_LIGHTMODE].AddDirty(DIRTY_LIGHT_CONTROL); - } else { - cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_LIGHT_CONTROL); - cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_VERTEXSHADER_STATE); - cmdInfo_[GE_CMD_LIGHTMODE].RemoveDirty(DIRTY_LIGHT_CONTROL); - } -} - void GPUCommon::BeginHostFrame() { UpdateVsyncInterval(displayResized_); ReapplyGfxState(); @@ -3464,6 +3072,7 @@ size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) { ); } + u32 GPUCommon::CheckGPUFeaturesLate(u32 features) const { // If we already have a 16-bit depth buffer, we don't need to round. bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 0c614d9310..636cf00baf 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -81,7 +81,7 @@ public: void CheckDisplayResized() override; void CheckConfigChanged() override; - void UpdateCmdInfo(); + virtual void UpdateCmdInfo() = 0; bool IsReady() override { return true; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 666f41d5db..1b251f5f57 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -10,7 +10,357 @@ #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" -GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) {} +const CommonCommandTableEntry commonCommandTable[] = { + // From Common. No flushing but definitely need execute. + { GE_CMD_OFFSETADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_OffsetAddr }, + { GE_CMD_ORIGIN, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommon::Execute_Origin }, + { GE_CMD_JUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Jump }, + { GE_CMD_CALL, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Call }, + { GE_CMD_RET, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_Ret }, + { GE_CMD_END, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_End }, + { GE_CMD_VADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Vaddr }, + { GE_CMD_IADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Iaddr }, + { GE_CMD_BJUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BJump }, // EXECUTE + { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoundingBox }, // Shouldn't need to FLUSHBEFORE. + + { GE_CMD_PRIM, FLAG_EXECUTE, 0, &GPUCommon::Execute_Prim }, + { GE_CMD_BEZIER, FLAG_EXECUTE, 0, &GPUCommon::Execute_Bezier }, + { GE_CMD_SPLINE, FLAG_EXECUTE, 0, &GPUCommon::Execute_Spline }, + + // Changing the vertex type requires us to flush. + { GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType }, + + { GE_CMD_LOADCLUT, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_LoadClut }, + + // These two are actually processed in CMD_END. + { GE_CMD_SIGNAL }, + { GE_CMD_FINISH }, + + // Changes that dirty the framebuffer + { GE_CMD_FRAMEBUFPTR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS }, + { GE_CMD_FRAMEBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_FRAMEBUFPIXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_ZBUFPTR, FLAG_FLUSHBEFOREONCHANGE }, + { GE_CMD_ZBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE }, + + { GE_CMD_FOGCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOLOR }, + { GE_CMD_FOG1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, + { GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, + + // These affect the fragment shader so need flushing. + { GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, + { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, + { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEFENABLE }, + { GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + // Raster state for Direct3D 9, uncommon. + { GE_CMD_SHADEMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE }, + { GE_CMD_TEXFUNC, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEX_ALPHA_MUL }, + { GE_CMD_COLORTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_ALPHATESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_COLORTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_COLORTESTMASK, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORMASK | DIRTY_FRAGMENTSHADER_STATE }, + + // These change the vertex shader so need flushing. + { GE_CMD_REVERSENORMAL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, + { GE_CMD_LIGHTENABLE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_LIGHTENABLE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_LIGHTENABLE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_LIGHTENABLE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_LIGHTTYPE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT0 }, + { GE_CMD_LIGHTTYPE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT1 }, + { GE_CMD_LIGHTTYPE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT2 }, + { GE_CMD_LIGHTTYPE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT3 }, + { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + + // These change vertex shaders (in non uber shader mode) so need flushing. + { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + + { GE_CMD_TEXFILTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXWRAP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, + + // Uniform changes. though the fragmentshader optimizes based on these sometimes. + { GE_CMD_ALPHATEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORREF | DIRTY_ALPHACOLORMASK | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_COLORREF, FLAG_FLUSHBEFOREONCHANGE, DIRTY_ALPHACOLORREF | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_TEXENVCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXENV }, + + // Simple render state changes. Handled in StateMapping.cpp. + { GE_CMD_CULL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, + { GE_CMD_CULLFACEENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, + { GE_CMD_DITHERENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE }, + { GE_CMD_STENCILOP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_STENCILTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_STENCILREPLACEVALUE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE }, + { GE_CMD_STENCILTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_ALPHABLENDENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_BLENDMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_BLENDFIXEDA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_BLENDFIXEDB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_MASKRGB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_COLORWRITEMASK }, + { GE_CMD_MASKALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_COLORWRITEMASK }, + { GE_CMD_ZTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE }, + { GE_CMD_ZTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_ZWRITEDISABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_LOGICOP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_LOGICOPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, + + { GE_CMD_TEXMAPMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + + // These are read on every SubmitPrim, no need for dirtying or flushing. + { GE_CMD_TEXSCALEU }, + { GE_CMD_TEXSCALEV }, + { GE_CMD_TEXOFFSETU }, + { GE_CMD_TEXOFFSETV }, + + { GE_CMD_TEXSIZE0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_TexSize0 }, + { GE_CMD_TEXSIZE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, + { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel }, + { GE_CMD_TEXLODSLOPE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE | DIRTY_UVSCALEOFFSET }, + { GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXADDR7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, + { GE_CMD_TEXBUFWIDTH1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH4, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH5, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + { GE_CMD_TEXBUFWIDTH7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, + + // These must flush on change, so that LoadClut doesn't have to always flush. + { GE_CMD_CLUTADDR, FLAG_FLUSHBEFOREONCHANGE }, + { GE_CMD_CLUTADDRUPPER, FLAG_FLUSHBEFOREONCHANGE }, + { GE_CMD_CLUTFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_DEPAL }, + + // Morph weights. TODO: Remove precomputation? + { GE_CMD_MORPHWEIGHT0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT3, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT4, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT5, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT6, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT7, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight }, + + // Control spline/bezier patches. Don't really require flushing as such, but meh. + { GE_CMD_PATCHDIVISION, FLAG_FLUSHBEFOREONCHANGE }, + { GE_CMD_PATCHPRIMITIVE, FLAG_FLUSHBEFOREONCHANGE }, + { GE_CMD_PATCHFACING, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, + { GE_CMD_PATCHCULLENABLE, FLAG_FLUSHBEFOREONCHANGE }, + + // Can probably ignore this one as we don't support AA lines. + { GE_CMD_ANTIALIASENABLE, FLAG_FLUSHBEFOREONCHANGE }, + + // Viewport. + { GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_RASTER_STATE }, + + // Z clip + { GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + + // Region + { GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + + // Scissor + { GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + + // Lighting base colors + { GE_CMD_AMBIENTCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT }, + { GE_CMD_AMBIENTALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT }, + { GE_CMD_MATERIALDIFFUSE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATDIFFUSE }, + { GE_CMD_MATERIALEMISSIVE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATEMISSIVE }, + { GE_CMD_MATERIALAMBIENT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATAMBIENTALPHA }, + { GE_CMD_MATERIALALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATAMBIENTALPHA }, + { GE_CMD_MATERIALSPECULAR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATSPECULAR }, + { GE_CMD_MATERIALSPECULARCOEF, FLAG_FLUSHBEFOREONCHANGE, DIRTY_MATSPECULAR }, + + // Light parameters + { GE_CMD_LX0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LY0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LZ0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LX1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LY1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LZ1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LX2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LY2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LZ2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LX3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LY3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LZ3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + { GE_CMD_LDX0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LDY0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LDZ0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LDX1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LDY1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LDZ1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LDX2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LDY2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LDZ2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LDX3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LDY3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LDZ3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + { GE_CMD_LKA0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LKB0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LKC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LKA1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LKB1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LKC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LKA2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LKB2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LKC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LKA3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LKB3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LKC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + { GE_CMD_LKS0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LKS1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LKS2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LKS3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + { GE_CMD_LKO0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LKO1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LKO2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LKO3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + { GE_CMD_LAC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LDC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LSC0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT0 }, + { GE_CMD_LAC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LDC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LSC1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT1 }, + { GE_CMD_LAC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LDC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LSC2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT2 }, + { GE_CMD_LAC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LDC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + { GE_CMD_LSC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, + + // Ignored commands + { GE_CMD_TEXFLUSH, 0 }, + { GE_CMD_TEXSYNC, 0 }, + + // These are just nop or part of other later commands. + { GE_CMD_NOP, 0 }, + { GE_CMD_BASE, 0 }, + { GE_CMD_TRANSFERSRC, 0 }, + { GE_CMD_TRANSFERSRCW, 0 }, + { GE_CMD_TRANSFERDST, 0 }, + { GE_CMD_TRANSFERDSTW, 0 }, + { GE_CMD_TRANSFERSRCPOS, 0 }, + { GE_CMD_TRANSFERDSTPOS, 0 }, + { GE_CMD_TRANSFERSIZE, 0 }, + { GE_CMD_TRANSFERSTART, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommon::Execute_BlockTransferStart }, + + // We don't use the dither table. + { GE_CMD_DITH0 }, + { GE_CMD_DITH1 }, + { GE_CMD_DITH2 }, + { GE_CMD_DITH3 }, + + // These handle their own flushing. + { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_WorldMtxNum }, + { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_WorldMtxData }, + { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ViewMtxNum }, + { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ViewMtxData }, + { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ProjMtxNum }, + { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ProjMtxData }, + { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_TgenMtxNum }, + { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_TgenMtxData }, + { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BoneMtxNum }, + { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoneMtxData }, + + // Vertex Screen/Texture/Color + { GE_CMD_VSCX }, + { GE_CMD_VSCY }, + { GE_CMD_VSCZ }, + { GE_CMD_VTCS }, + { GE_CMD_VTCT }, + { GE_CMD_VTCQ }, + { GE_CMD_VCV }, + { GE_CMD_VAP, FLAG_EXECUTE, 0, &GPUCommon::Execute_ImmVertexAlphaPrim }, + { GE_CMD_VFC }, + { GE_CMD_VSCV }, + + // "Missing" commands (gaps in the sequence) + { GE_CMD_UNKNOWN_03, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_0D, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_11, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_29, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_34, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_35, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_39, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_4E, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_4F, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_52, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_59, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_5A, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_B6, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_B7, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_D1, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_ED, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_EF, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_FA, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_FB, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_FC, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_FD, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + { GE_CMD_UNKNOWN_FE, FLAG_EXECUTE, 0, &GPUCommon::Execute_Unknown }, + // Appears to be debugging related or something? Hit a lot in GoW. + { GE_CMD_NOP_FF, 0 }, +}; + +GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw) { + memset(cmdInfo_, 0, sizeof(cmdInfo_)); + + // Convert the command table to a faster format, and check for dupes. + std::set dupeCheck; + for (size_t i = 0; i < ARRAY_SIZE(commonCommandTable); i++) { + const u8 cmd = commonCommandTable[i].cmd; + if (dupeCheck.find(cmd) != dupeCheck.end()) { + ERROR_LOG(G3D, "Command table Dupe: %02x (%i)", (int)cmd, (int)cmd); + } else { + dupeCheck.insert(cmd); + } + cmdInfo_[cmd].flags |= (uint64_t)commonCommandTable[i].flags | (commonCommandTable[i].dirty << 8); + cmdInfo_[cmd].func = commonCommandTable[i].func; + if ((cmdInfo_[cmd].flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) && !cmdInfo_[cmd].func) { + // Can't have FLAG_EXECUTE commands without a function pointer to execute. + Crash(); + } + } + // Find commands missing from the table. + for (int i = 0; i < 0xEF; i++) { + if (dupeCheck.find((u8)i) == dupeCheck.end()) { + ERROR_LOG(G3D, "Command missing from table: %02x (%i)", i, i); + } + } + + UpdateCmdInfo(); +} GPUCommonHW::~GPUCommonHW() { // Clear features so they're not visible in system info. @@ -31,6 +381,49 @@ void GPUCommonHW::DeviceLost() { shaderManager_->DeviceLost(); } +void GPUCommonHW::UpdateCmdInfo() { + if (g_Config.bSoftwareSkinning) { + cmdInfo_[GE_CMD_VERTEXTYPE].flags &= ~FLAG_FLUSHBEFOREONCHANGE; + cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexTypeSkinning; + } else { + cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE; + cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexType; + } + + if (g_Config.bFastMemory) { + cmdInfo_[GE_CMD_JUMP].func = &GPUCommon::Execute_JumpFast; + cmdInfo_[GE_CMD_CALL].func = &GPUCommon::Execute_CallFast; + } else { + cmdInfo_[GE_CMD_JUMP].func = &GPUCommon::Execute_Jump; + cmdInfo_[GE_CMD_CALL].func = &GPUCommon::Execute_Call; + } + + // Reconfigure for light ubershader or not. + for (int i = 0; i < 4; i++) { + if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) { + cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_LIGHT_CONTROL); + } else { + cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE); + } + } + + if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) { + cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_LIGHTMODE].AddDirty(DIRTY_LIGHT_CONTROL); + } else { + cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_LIGHTMODE].RemoveDirty(DIRTY_LIGHT_CONTROL); + } +} + void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 5e258027e8..0e2dc59f05 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -16,6 +16,8 @@ public: u32 CheckGPUFeatures() const override; protected: + void UpdateCmdInfo() override; + void PreExecuteOp(u32 op, u32 diff); void ClearCacheNextFrame() override; diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index ef61d59663..25ce1d5f1a 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -134,6 +134,7 @@ public: void FinishDeferred() override; int ListSync(int listid, int mode) override; u32 DrawSync(int mode) override; + void UpdateCmdInfo() override {} void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput(bool reallyDirty) override; From 7bcc21b0e3549b9b63abe0c5377563c19c470bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:27:00 +0100 Subject: [PATCH 031/141] Move msaaLevel_ to GPUCommonHW --- GPU/GPUCommon.cpp | 19 ------------------- GPU/GPUCommon.h | 10 ++-------- GPU/GPUCommonHW.cpp | 18 ++++++++++++++++++ GPU/GPUCommonHW.h | 5 +++++ 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index f48661000f..a59e348d4c 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -79,8 +79,6 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : ResetMatrices(); PPGeSetDrawContext(draw); - - UpdateMSAALevel(draw); } GPUCommon::~GPUCommon() { @@ -267,13 +265,6 @@ void GPUCommon::CheckDisplayResized() { } } -void GPUCommon::CheckRenderResized() { - if (renderResized_) { - framebufferManager_->NotifyRenderResized(msaaLevel_); - renderResized_ = false; - } -} - void GPUCommon::DumpNextFrame() { dumpNextFrame_ = true; } @@ -3104,16 +3095,6 @@ u32 GPUCommon::CheckGPUFeaturesLate(u32 features) const { return features; } -void GPUCommon::UpdateMSAALevel(Draw::DrawContext *draw) { - int level = g_Config.iMultiSampleLevel; - if (draw && draw->GetDeviceCaps().multiSampleLevelsMask & (1 << level)) { - msaaLevel_ = level; - } else { - // Didn't support the configured level, so revert to 0. - msaaLevel_ = 0; - } -} - void GPUCommon::CheckFlushOp(int cmd, u32 diff) { const u8 cmdFlags = cmdInfo_[cmd].flags; if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 636cf00baf..facc94da07 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -264,17 +264,13 @@ public: fullInfo = reportingFullInfo_; } - int MSAALevel() const { - return msaaLevel_; - } - protected: void DeviceLost() override; void DeviceRestore() override; void ClearCacheNextFrame() override {} - virtual void CheckRenderResized(); + virtual void CheckRenderResized() {} // Add additional common features dependent on other features, which may be backend-determined. u32 CheckGPUFeaturesLate(u32 features) const; @@ -328,7 +324,7 @@ protected: virtual void BuildReportingInfo() = 0; - void UpdateMSAALevel(Draw::DrawContext *draw); + virtual void UpdateMSAALevel(Draw::DrawContext *draw) {} DrawEngineCommon *drawEngineCommon_ = nullptr; @@ -419,8 +415,6 @@ protected: std::string reportingPrimaryInfo_; std::string reportingFullInfo_; - int msaaLevel_ = 0; - private: void CheckDepthUsage(VirtualFramebuffer *vfb); void DoExecuteCall(u32 target); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 1b251f5f57..2943a80cc5 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -360,6 +360,7 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU } UpdateCmdInfo(); + UpdateMSAALevel(draw); } GPUCommonHW::~GPUCommonHW() { @@ -374,6 +375,13 @@ GPUCommonHW::~GPUCommonHW() { delete shaderManager_; } +void GPUCommonHW::CheckRenderResized() { + if (renderResized_) { + framebufferManager_->NotifyRenderResized(msaaLevel_); + renderResized_ = false; + } +} + void GPUCommonHW::DeviceLost() { textureCache_->Clear(false); framebufferManager_->DeviceLost(); @@ -528,3 +536,13 @@ u32 GPUCommonHW::CheckGPUFeatures() const { return features; } + +void GPUCommonHW::UpdateMSAALevel(Draw::DrawContext *draw) { + int level = g_Config.iMultiSampleLevel; + if (draw && draw->GetDeviceCaps().multiSampleLevelsMask & (1 << level)) { + msaaLevel_ = level; + } else { + // Didn't support the configured level, so revert to 0. + msaaLevel_ = 0; + } +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 0e2dc59f05..fea544911e 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -23,5 +23,10 @@ protected: // Needs to be called on GPU thread, not reporting thread. void BuildReportingInfo() override; + void UpdateMSAALevel(Draw::DrawContext *draw) override; + + void CheckRenderResized() override; + + int msaaLevel_ = 0; }; From c2c479b2173a7b9fcc7e364d67e08510c6e1a85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:29:53 +0100 Subject: [PATCH 032/141] Remove function InitClear. Was only implemented for DX9, and only barely meaningful in non-buffered. --- Core/HLE/sceDisplay.cpp | 9 +-------- GPU/D3D11/GPU_D3D11.cpp | 6 ------ GPU/D3D11/GPU_D3D11.h | 1 - GPU/Directx9/GPU_DX9.cpp | 8 -------- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 3 --- GPU/GLES/GPU_GLES.h | 1 - GPU/GPUCommon.h | 1 - GPU/GPUInterface.h | 1 - GPU/Software/SoftGpu.h | 1 - GPU/Vulkan/GPU_Vulkan.cpp | 6 ------ GPU/Vulkan/GPU_Vulkan.h | 1 - 12 files changed, 1 insertion(+), 38 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 342d522627..953c2ab460 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -285,10 +285,6 @@ void __DisplayDoState(PointerWrap &p) { if (p.mode == p.MODE_READ) { gpu->ReapplyGfxState(); - - if (hasSetMode) { - gpu->InitClear(); - } gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt); } } @@ -749,10 +745,7 @@ static u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeigh return hleLogWarning(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid size"); } - if (!hasSetMode) { - gpu->InitClear(); - hasSetMode = true; - } + hasSetMode = true; mode = displayMode; width = displayWidth; height = displayHeight; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index c0b419184a..fd8085f2e8 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -127,12 +127,6 @@ void GPU_D3D11::DeviceRestore() { // Nothing needed. } -void GPU_D3D11::InitClear() { - if (!framebufferManager_->UseBufferedRendering()) { - // device_->Clear(0, NULL, D3DCLEAR_STENCIL | D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.f, 0); - } -} - void GPU_D3D11::BeginFrame() { GPUCommonHW::BeginFrame(); diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 3112c1b437..434a3c6303 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -48,7 +48,6 @@ protected: void FinishDeferred() override; private: - void InitClear() override; void BeginFrame() override; ID3D11Device *device_; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index a95c6155fd..d36b0693f7 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -110,14 +110,6 @@ void GPU_DX9::DeviceLost() { GPUCommonHW::DeviceLost(); } -void GPU_DX9::InitClear() { - if (!framebufferManager_->UseBufferedRendering()) { - dxstate.depthWrite.set(true); - dxstate.colorMask.set(0xF); - device_->Clear(0, NULL, D3DCLEAR_STENCIL|D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.f, 0); - } -} - void GPU_DX9::ReapplyGfxState() { dxstate.Restore(); GPUCommonHW::ReapplyGfxState(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index eda85cb387..5e6495b60e 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -48,7 +48,6 @@ protected: void FinishDeferred() override; private: - void InitClear() override; void BeginFrame() override; LPDIRECT3DDEVICE9 device_; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 6bf1046764..d1d4dd29e1 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -256,9 +256,6 @@ void GPU_GLES::DeviceRestore() { fragmentTestCache_.DeviceRestore(draw_); } -void GPU_GLES::InitClear() { -} - void GPU_GLES::BeginHostFrame() { GPUCommonHW::BeginHostFrame(); drawEngine_.BeginFrame(); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 4ab375840a..4dd821b9fe 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -62,7 +62,6 @@ protected: private: void BuildReportingInfo() override; - void InitClear() override; void BeginFrame() override; FramebufferManagerGLES *framebufferManagerGL_; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index facc94da07..eecfaa68da 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -131,7 +131,6 @@ public: void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput(bool reallyDirty) override = 0; - void InitClear() override = 0; bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; bool PerformReadbackToMemory(u32 dest, int size) override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 8403d6f048..6bf3856e16 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -192,7 +192,6 @@ public: virtual bool IsReady() = 0; virtual void CancelReady() = 0; virtual bool IsStarted() = 0; - virtual void InitClear() = 0; virtual void Reinitialize() = 0; // Frame managment diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 25ce1d5f1a..61104a04bd 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -129,7 +129,6 @@ public: u32 CheckGPUFeatures() const override { return 0; } bool IsStarted() override; - void InitClear() override {} void ExecuteOp(u32 op, u32 diff) override; void FinishDeferred() override; int ListSync(int listid, int mode) override; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index ffe2e61755..119414aef5 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -389,12 +389,6 @@ void GPU_Vulkan::BuildReportingInfo() { Reporting::UpdateConfig(); } -void GPU_Vulkan::InitClear() { - if (!framebufferManager_->UseBufferedRendering()) { - // TODO? - } -} - void GPU_Vulkan::FinishDeferred() { drawEngine_.FinishDeferred(); } diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 6892eafa58..1e52e8e0de 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -67,7 +67,6 @@ protected: private: void BuildReportingInfo() override; - void InitClear() override; void InitDeviceObjects(); void DestroyDeviceObjects(); From 92ee6e6a758ffc69ca22a526281b8e80cc2d6a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:33:09 +0100 Subject: [PATCH 033/141] Remove more redundant functions --- GPU/Directx9/GPU_DX9.cpp | 5 ----- GPU/Directx9/GPU_DX9.h | 1 - GPU/GLES/GPU_GLES.cpp | 4 ---- GPU/GLES/GPU_GLES.h | 1 - 4 files changed, 11 deletions(-) diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index d36b0693f7..928a399635 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -105,11 +105,6 @@ u32 GPU_DX9::CheckGPUFeatures() const { return CheckGPUFeaturesLate(features); } -void GPU_DX9::DeviceLost() { - // Simply drop all caches and textures. - GPUCommonHW::DeviceLost(); -} - void GPU_DX9::ReapplyGfxState() { dxstate.Restore(); GPUCommonHW::ReapplyGfxState(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 5e6495b60e..41e057ab74 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -38,7 +38,6 @@ public: void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; - void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index d1d4dd29e1..a8f8a9c449 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -274,10 +274,6 @@ void GPU_GLES::EndHostFrame() { drawEngine_.EndFrame(); } -void GPU_GLES::ReapplyGfxState() { - GPUCommonHW::ReapplyGfxState(); -} - void GPU_GLES::BeginFrame() { GPUCommonHW::BeginFrame(); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 4dd821b9fe..21c9adba08 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -43,7 +43,6 @@ public: bool IsReady() override; void CancelReady() override; - void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. From 8fb75e0c123ba9fe37877aa1ecfcb538311cba63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 16:40:52 +0100 Subject: [PATCH 034/141] Unify most of the DebugGetShader* functions --- GPU/Common/ShaderCommon.h | 5 +++++ GPU/D3D11/GPU_D3D11.cpp | 22 ---------------------- GPU/D3D11/GPU_D3D11.h | 4 ---- GPU/Directx9/GPU_DX9.cpp | 22 ---------------------- GPU/Directx9/GPU_DX9.h | 4 ---- GPU/GLES/GPU_GLES.cpp | 22 ---------------------- GPU/GLES/GPU_GLES.h | 4 ---- GPU/GPUCommonHW.cpp | 22 ++++++++++++++++++++++ GPU/GPUCommonHW.h | 4 ++++ GPU/Vulkan/GPU_Vulkan.cpp | 30 ++++++++++-------------------- 10 files changed, 41 insertions(+), 98 deletions(-) diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index 025576ea6f..380a9e240a 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -18,6 +18,8 @@ #pragma once #include +#include +#include namespace Draw { class DrawContext; @@ -132,6 +134,9 @@ public: virtual void DeviceLost() = 0; virtual void DeviceRestore(Draw::DrawContext *draw) = 0; // must set draw_ to draw + virtual std::vector DebugGetShaderIDs(DebugShaderType type) = 0; + virtual std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) = 0; + protected: Draw::DrawContext *draw_ = nullptr; }; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index fd8085f2e8..28043a1d68 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -166,25 +166,3 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { shaderManagerD3D11_->GetNumFragmentShaders() ); } - -std::vector GPU_D3D11::DebugGetShaderIDs(DebugShaderType type) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderIDs(); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderIDs(type); - default: - return shaderManagerD3D11_->DebugGetShaderIDs(type); - } -} - -std::string GPU_D3D11::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderString(id, stringType); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); - default: - return shaderManagerD3D11_->DebugGetShaderString(id, type, stringType); - } -} diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 434a3c6303..782ef7f029 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -40,10 +40,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. - std::vector DebugGetShaderIDs(DebugShaderType shader) override; - std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; - protected: void FinishDeferred() override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 928a399635..1c748980c0 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -146,25 +146,3 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { shaderManagerDX9_->GetNumFragmentShaders() ); } - -std::vector GPU_DX9::DebugGetShaderIDs(DebugShaderType type) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderIDs(); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderIDs(type); - default: - return shaderManagerDX9_->DebugGetShaderIDs(type); - } -} - -std::string GPU_DX9::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderString(id, stringType); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); - default: - return shaderManagerDX9_->DebugGetShaderString(id, type, stringType); - } -} diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 41e057ab74..1da17b2e3e 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -39,10 +39,6 @@ public: void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. - std::vector DebugGetShaderIDs(DebugShaderType shader) override; - std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; - protected: void FinishDeferred() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index a8f8a9c449..756cf81937 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -311,25 +311,3 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { shaderManagerGL_->GetNumPrograms() ); } - -std::vector GPU_GLES::DebugGetShaderIDs(DebugShaderType type) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderIDs(); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderIDs(type); - default: - return shaderManagerGL_->DebugGetShaderIDs(type); - } -} - -std::string GPU_GLES::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { - switch (type) { - case SHADER_TYPE_VERTEXLOADER: - return drawEngine_.DebugGetVertexLoaderString(id, stringType); - case SHADER_TYPE_TEXTURE: - return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); - default: - return shaderManagerGL_->DebugGetShaderString(id, type, stringType); - } -} diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index 21c9adba08..a56765a580 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -48,10 +48,6 @@ public: void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. - std::vector DebugGetShaderIDs(DebugShaderType shader) override; - std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; - void BeginHostFrame() override; void EndHostFrame() override; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 2943a80cc5..2f5029d5f7 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -546,3 +546,25 @@ void GPUCommonHW::UpdateMSAALevel(Draw::DrawContext *draw) { msaaLevel_ = 0; } } + +std::vector GPUCommonHW::DebugGetShaderIDs(DebugShaderType type) { + switch (type) { + case SHADER_TYPE_VERTEXLOADER: + return drawEngineCommon_->DebugGetVertexLoaderIDs(); + case SHADER_TYPE_TEXTURE: + return textureCache_->GetTextureShaderCache()->DebugGetShaderIDs(type); + default: + return shaderManager_->DebugGetShaderIDs(type); + } +} + +std::string GPUCommonHW::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { + switch (type) { + case SHADER_TYPE_VERTEXLOADER: + return drawEngineCommon_->DebugGetVertexLoaderString(id, stringType); + case SHADER_TYPE_TEXTURE: + return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); + default: + return shaderManager_->DebugGetShaderString(id, type, stringType); + } +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index fea544911e..f059d93fc0 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -15,6 +15,10 @@ public: u32 CheckGPUFeatures() const override; + // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. + std::vector DebugGetShaderIDs(DebugShaderType shader) override; + std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; + protected: void UpdateCmdInfo() override; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 119414aef5..532811f365 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -499,34 +499,24 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { } std::vector GPU_Vulkan::DebugGetShaderIDs(DebugShaderType type) { - if (type == SHADER_TYPE_VERTEXLOADER) { - return drawEngine_.DebugGetVertexLoaderIDs(); - } else if (type == SHADER_TYPE_PIPELINE) { + switch (type) { + case SHADER_TYPE_PIPELINE: return pipelineManager_->DebugGetObjectIDs(type); - } else if (type == SHADER_TYPE_TEXTURE) { - return textureCache_->GetTextureShaderCache()->DebugGetShaderIDs(type); - } else if (type == SHADER_TYPE_VERTEX || type == SHADER_TYPE_FRAGMENT || type == SHADER_TYPE_GEOMETRY) { - return shaderManagerVulkan_->DebugGetShaderIDs(type); - } else if (type == SHADER_TYPE_SAMPLER) { + case SHADER_TYPE_SAMPLER: return textureCacheVulkan_->DebugGetSamplerIDs(); - } else { - return std::vector(); + default: + return GPUCommonHW::DebugGetShaderIDs(type); } } std::string GPU_Vulkan::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { - if (type == SHADER_TYPE_VERTEXLOADER) { - return drawEngine_.DebugGetVertexLoaderString(id, stringType); - } else if (type == SHADER_TYPE_PIPELINE) { + switch (type) { + case SHADER_TYPE_PIPELINE: return pipelineManager_->DebugGetObjectString(id, type, stringType, shaderManagerVulkan_); - } else if (type == SHADER_TYPE_TEXTURE) { - return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); - } else if (type == SHADER_TYPE_SAMPLER) { + case SHADER_TYPE_SAMPLER: return textureCacheVulkan_->DebugGetSamplerString(id, stringType); - } else if (type == SHADER_TYPE_VERTEX || type == SHADER_TYPE_FRAGMENT || type == SHADER_TYPE_GEOMETRY) { - return shaderManagerVulkan_->DebugGetShaderString(id, type, stringType); - } else { - return std::string(); + default: + return GPUCommonHW::DebugGetShaderString(id, type, stringType); } } From d22a22569a72a5b44944fa3bf124eada4386f67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 17:17:09 +0100 Subject: [PATCH 035/141] Move the drawing commands to GPUCommonHW --- GPU/GPUCommon.cpp | 453 +----------------------------------------- GPU/GPUCommon.h | 12 -- GPU/GPUCommonHW.cpp | 472 +++++++++++++++++++++++++++++++++++++++++++- GPU/GPUCommonHW.h | 11 +- 4 files changed, 478 insertions(+), 470 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index a59e348d4c..7338d29d3e 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -42,6 +42,7 @@ #include "Core/HW/Display.h" #include "Core/MemMapHelpers.h" #include "Core/Util/PPGeDraw.h" +#include "GPU/GPUCommonHW.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/SplineCommon.h" @@ -50,7 +51,7 @@ #include "GPU/Debugger/Record.h" // TODO: Make class member? -GPUCommon::CommandInfo GPUCommon::cmdInfo_[256]; +GPUCommonHW::CommandInfo GPUCommon::cmdInfo_[256]; void GPUCommon::Flush() { drawEngineCommon_->DispatchFlush(); @@ -1380,444 +1381,6 @@ void GPUCommon::Execute_VertexTypeSkinning(u32 op, u32 diff) { gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE | DIRTY_FOGCOEFENABLE); } -void GPUCommon::CheckDepthUsage(VirtualFramebuffer *vfb) { - if (!gstate_c.usingDepth) { - bool isReadingDepth = false; - bool isClearingDepth = false; - bool isWritingDepth = false; - if (gstate.isModeClear()) { - isClearingDepth = gstate.isClearModeDepthMask(); - isWritingDepth = isClearingDepth; - } else if (gstate.isDepthTestEnabled()) { - isWritingDepth = gstate.isDepthWriteEnabled(); - isReadingDepth = gstate.getDepthTestFunction() > GE_COMP_ALWAYS; - } - - if (isWritingDepth || isReadingDepth) { - gstate_c.usingDepth = true; - gstate_c.clearingDepth = isClearingDepth; - vfb->last_frame_depth_render = gpuStats.numFlips; - if (isWritingDepth) { - vfb->last_frame_depth_updated = gpuStats.numFlips; - } - framebufferManager_->SetDepthFrameBuffer(isClearingDepth); - } - } -} - -void GPUCommon::Execute_Prim(u32 op, u32 diff) { - // This drives all drawing. All other state we just buffer up, then we apply it only - // when it's time to draw. As most PSP games set state redundantly ALL THE TIME, this is a huge optimization. - - PROFILE_THIS_SCOPE("execprim"); - - u32 data = op & 0xFFFFFF; - u32 count = data & 0xFFFF; - if (count == 0) - return; - FlushImm(); - - // Upper bits are ignored. - GEPrimitiveType prim = static_cast((data >> 16) & 7); - SetDrawType(DRAW_PRIM, prim); - - // Discard AA lines as we can't do anything that makes sense with these anyway. The SW plugin might, though. - if (gstate.isAntiAliasEnabled()) { - // Heuristic derived from discussions in #6483 and #12588. - // Discard AA lines in Persona 3 Portable, DOA Paradise and Summon Night 5, while still keeping AA lines in Echochrome. - if ((prim == GE_PRIM_LINE_STRIP || prim == GE_PRIM_LINES) && gstate.getTextureFunction() == GE_TEXFUNC_REPLACE) - return; - } - - // Update cached framebuffer format. - // We store it in the cache so it can be modified for blue-to-alpha, next. - gstate_c.framebufFormat = gstate.FrameBufFormat(); - - if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { - ERROR_LOG(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); - return; - } - - // See the documentation for gstate_c.blueToAlpha. - bool blueToAlpha = false; - if (PSP_CoreParameter().compat.flags().BlueToAlpha) { - if (gstate_c.framebufFormat == GEBufferFormat::GE_FORMAT_565 && gstate.getColorMask() == 0x0FFFFF && !gstate.isLogicOpEnabled()) { - blueToAlpha = true; - gstate_c.framebufFormat = GEBufferFormat::GE_FORMAT_4444; - } - if (blueToAlpha != gstate_c.blueToAlpha) { - gstate_c.blueToAlpha = blueToAlpha; - gstate_c.Dirty(DIRTY_FRAMEBUF | DIRTY_FRAGMENTSHADER_STATE | DIRTY_BLEND_STATE); - } - } - - if (PSP_CoreParameter().compat.flags().SplitFramebufferMargin) { - switch (gstate.vertType & 0xFFFFFF) { - case 0x00800102: // through, u16 uv, u16 pos (used for the framebuffer effect in-game) - case 0x0080011c: // through, 8888 color, s16 pos (used for clearing in the margin of the title screen) - case 0x00000183: // float uv, float pos (used for drawing in the margin of the title screen) - // Need to re-check the framebuffer every one of these draws, to update the split if needed. - gstate_c.Dirty(DIRTY_FRAMEBUF); - } - } - - // This also makes skipping drawing very effective. - VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); - if (blueToAlpha) { - vfb->usageFlags |= FB_USAGE_BLUE_TO_ALPHA; - } - - // Must check this after SetRenderFrameBuffer so we know SKIPDRAW_NON_DISPLAYED_FB. - if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { - // Rough estimate, not sure what's correct. - cyclesExecuted += EstimatePerVertexCost() * count; - if (gstate.isModeClear()) { - gpuStats.numClears++; - } - return; - } - - CheckDepthUsage(vfb); - - const void *verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); - const void *inds = nullptr; - u32 vertexType = gstate.vertType; - if ((vertexType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { - u32 indexAddr = gstate_c.indexAddr; - if (!Memory::IsValidAddress(indexAddr)) { - ERROR_LOG(G3D, "Bad index address %08x!", indexAddr); - return; - } - inds = Memory::GetPointerUnchecked(indexAddr); - } - - if (gstate_c.dirty & DIRTY_VERTEXSHADER_STATE) { - vertexCost_ = EstimatePerVertexCost(); - } - - int bytesRead = 0; - UpdateUVScaleOffset(); - - // cull mode - int cullMode = gstate.getCullMode(); - - uint32_t vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning); - drawEngineCommon_->SubmitPrim(verts, inds, prim, count, vertTypeID, cullMode, &bytesRead); - // After drawing, we advance the vertexAddr (when non indexed) or indexAddr (when indexed). - // Some games rely on this, they don't bother reloading VADDR and IADDR. - // The VADDR/IADDR registers are NOT updated. - AdvanceVerts(vertexType, count, bytesRead); - int totalVertCount = count; - - // PRIMs are often followed by more PRIMs. Save some work and submit them immediately. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - const u32_le *stall = currentList->stall ? (const u32_le *)Memory::GetPointerUnchecked(currentList->stall) : 0; - int cmdCount = 0; - - // Optimized submission of sequences of PRIM. Allows us to avoid going through all the mess - // above for each one. This can be expanded to support additional games that intersperse - // PRIM commands with other commands. A special case is Earth Defence Force 2 that changes culling mode - // between each prim, we just change the triangle winding right here to still be able to join draw calls. - - uint32_t vtypeCheckMask = ~GE_VTYPE_WEIGHTCOUNT_MASK; - if (!g_Config.bSoftwareSkinning) - vtypeCheckMask = 0xFFFFFFFF; - - if (debugRecording_) - goto bail; - - while (src != stall) { - uint32_t data = *src; - switch (data >> 24) { - case GE_CMD_PRIM: - { - u32 count = data & 0xFFFF; - if (count == 0) { - // Ignore. - break; - } - - GEPrimitiveType newPrim = static_cast((data >> 16) & 7); - SetDrawType(DRAW_PRIM, newPrim); - // TODO: more efficient updating of verts/inds - verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); - inds = nullptr; - if ((vertexType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { - inds = Memory::GetPointerUnchecked(gstate_c.indexAddr); - } - - drawEngineCommon_->SubmitPrim(verts, inds, newPrim, count, vertTypeID, cullMode, &bytesRead); - AdvanceVerts(vertexType, count, bytesRead); - totalVertCount += count; - break; - } - case GE_CMD_VERTEXTYPE: - { - uint32_t diff = data ^ vertexType; - // don't mask upper bits, vertexType is unmasked - if (diff & vtypeCheckMask) { - goto bail; - } else { - vertexType = data; - vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning); - } - break; - } - case GE_CMD_VADDR: - gstate.cmdmem[GE_CMD_VADDR] = data; - gstate_c.vertexAddr = gstate_c.getRelativeAddress(data & 0x00FFFFFF); - break; - case GE_CMD_IADDR: - gstate.cmdmem[GE_CMD_IADDR] = data; - gstate_c.indexAddr = gstate_c.getRelativeAddress(data & 0x00FFFFFF); - break; - case GE_CMD_OFFSETADDR: - gstate.cmdmem[GE_CMD_OFFSETADDR] = data; - gstate_c.offsetAddr = data << 8; - break; - case GE_CMD_BASE: - gstate.cmdmem[GE_CMD_BASE] = data; - break; - case GE_CMD_CULLFACEENABLE: - // Earth Defence Force 2 - if (gstate.cmdmem[GE_CMD_CULLFACEENABLE] != data) { - goto bail; - } - break; - case GE_CMD_CULL: - // flip face by indices for triangles - cullMode = data & 1; - break; - case GE_CMD_TEXFLUSH: - case GE_CMD_NOP: - case GE_CMD_NOP_FF: - gstate.cmdmem[data >> 24] = data; - break; - case GE_CMD_BONEMATRIXNUMBER: - gstate.cmdmem[GE_CMD_BONEMATRIXNUMBER] = data; - break; - case GE_CMD_TEXSCALEU: - gstate.cmdmem[GE_CMD_TEXSCALEU] = data; - gstate_c.uv.uScale = getFloat24(data); - break; - case GE_CMD_TEXSCALEV: - gstate.cmdmem[GE_CMD_TEXSCALEV] = data; - gstate_c.uv.vScale = getFloat24(data); - break; - case GE_CMD_TEXOFFSETU: - gstate.cmdmem[GE_CMD_TEXOFFSETU] = data; - gstate_c.uv.uOff = getFloat24(data); - break; - case GE_CMD_TEXOFFSETV: - gstate.cmdmem[GE_CMD_TEXOFFSETV] = data; - gstate_c.uv.vOff = getFloat24(data); - break; - case GE_CMD_TEXLEVEL: - // Same Gran Turismo hack from Execute_TexLevel - if ((data & 3) != GE_TEXLEVEL_MODE_AUTO && (0x00FF0000 & data) != 0) { - goto bail; - } - gstate.cmdmem[GE_CMD_TEXLEVEL] = data; - break; - case GE_CMD_CALL: - { - // A bone matrix probably. If not we bail. - const u32 target = gstate_c.getRelativeAddress(data & 0x00FFFFFC); - if ((Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA && - (Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA && - (Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET && - (target > currentList->stall || target + 12 * 4 < currentList->stall) && - (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) { - FastLoadBoneMatrix(target); - } else { - goto bail; - } - break; - } - - case GE_CMD_TEXBUFWIDTH0: - case GE_CMD_TEXADDR0: - if (data != gstate.cmdmem[data >> 24]) - goto bail; - break; - - default: - // All other commands might need a flush or something, stop this inner loop. - goto bail; - } - cmdCount++; - src++; - } - -bail: - gstate.cmdmem[GE_CMD_VERTEXTYPE] = vertexType; - // Skip over the commands we just read out manually. - if (cmdCount > 0) { - UpdatePC(currentList->pc, currentList->pc + cmdCount * 4); - currentList->pc += cmdCount * 4; - // flush back cull mode - if (cullMode != gstate.getCullMode()) { - // We rewrote everything to the old cull mode, so flush first. - drawEngineCommon_->DispatchFlush(); - - // Now update things for next time. - gstate.cmdmem[GE_CMD_CULL] ^= 1; - gstate_c.Dirty(DIRTY_RASTER_STATE); - } - } - - gpuStats.vertexGPUCycles += vertexCost_ * totalVertCount; - cyclesExecuted += vertexCost_ * totalVertCount; -} - -void GPUCommon::Execute_Bezier(u32 op, u32 diff) { - // We don't dirty on normal changes anymore as we prescale, but it's needed for splines/bezier. - gstate_c.Dirty(DIRTY_UVSCALEOFFSET); - - gstate_c.framebufFormat = gstate.FrameBufFormat(); - - // This also make skipping drawing very effective. - VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); - if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { - // TODO: Should this eat some cycles? Probably yes. Not sure if important. - return; - } - - CheckDepthUsage(vfb); - - if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { - ERROR_LOG_REPORT(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); - return; - } - - const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); - const void *indices = NULL; - if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { - if (!Memory::IsValidAddress(gstate_c.indexAddr)) { - ERROR_LOG_REPORT(G3D, "Bad index address %08x!", gstate_c.indexAddr); - return; - } - indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); - } - - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); - } - - // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. - if (flushOnParams_) - drawEngineCommon_->DispatchFlush(); - - Spline::BezierSurface surface; - surface.tess_u = gstate.getPatchDivisionU(); - surface.tess_v = gstate.getPatchDivisionV(); - surface.num_points_u = op & 0xFF; - surface.num_points_v = (op >> 8) & 0xFF; - surface.num_patches_u = (surface.num_points_u - 1) / 3; - surface.num_patches_v = (surface.num_points_v - 1) / 3; - surface.primType = gstate.getPatchPrimitiveType(); - surface.patchFacing = gstate.patchfacing & 1; - - SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType)); - - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); - if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { - gstate_c.submitType = SubmitType::HW_BEZIER; - if (gstate_c.spline_num_points_u != surface.num_points_u) { - gstate_c.Dirty(DIRTY_BEZIERSPLINE); - gstate_c.spline_num_points_u = surface.num_points_u; - } - } else { - gstate_c.submitType = SubmitType::BEZIER; - } - - int bytesRead = 0; - UpdateUVScaleOffset(); - drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "bezier"); - - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); - gstate_c.submitType = SubmitType::DRAW; - - // After drawing, we advance pointers - see SubmitPrim which does the same. - int count = surface.num_points_u * surface.num_points_v; - AdvanceVerts(gstate.vertType, count, bytesRead); -} - -void GPUCommon::Execute_Spline(u32 op, u32 diff) { - // We don't dirty on normal changes anymore as we prescale, but it's needed for splines/bezier. - gstate_c.Dirty(DIRTY_UVSCALEOFFSET); - - gstate_c.framebufFormat = gstate.FrameBufFormat(); - - // This also make skipping drawing very effective. - VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); - if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { - // TODO: Should this eat some cycles? Probably yes. Not sure if important. - return; - } - - CheckDepthUsage(vfb); - - if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { - ERROR_LOG_REPORT(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); - return; - } - - const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); - const void *indices = NULL; - if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { - if (!Memory::IsValidAddress(gstate_c.indexAddr)) { - ERROR_LOG_REPORT(G3D, "Bad index address %08x!", gstate_c.indexAddr); - return; - } - indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); - } - - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); - } - - // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. - if (flushOnParams_) - drawEngineCommon_->DispatchFlush(); - - Spline::SplineSurface surface; - surface.tess_u = gstate.getPatchDivisionU(); - surface.tess_v = gstate.getPatchDivisionV(); - surface.type_u = (op >> 16) & 0x3; - surface.type_v = (op >> 18) & 0x3; - surface.num_points_u = op & 0xFF; - surface.num_points_v = (op >> 8) & 0xFF; - surface.num_patches_u = surface.num_points_u - 3; - surface.num_patches_v = surface.num_points_v - 3; - surface.primType = gstate.getPatchPrimitiveType(); - surface.patchFacing = gstate.patchfacing & 1; - - SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType)); - - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); - if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { - gstate_c.submitType = SubmitType::HW_SPLINE; - if (gstate_c.spline_num_points_u != surface.num_points_u) { - gstate_c.Dirty(DIRTY_BEZIERSPLINE); - gstate_c.spline_num_points_u = surface.num_points_u; - } - } else { - gstate_c.submitType = SubmitType::SPLINE; - } - - int bytesRead = 0; - UpdateUVScaleOffset(); - drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "spline"); - - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); - gstate_c.submitType = SubmitType::DRAW; - - // After drawing, we advance pointers - see SubmitPrim which does the same. - int count = surface.num_points_u * surface.num_points_v; - AdvanceVerts(gstate.vertType, count, bytesRead); -} - void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { // Just resetting, nothing to check bounds for. const u32 count = op & 0xFFFF; @@ -1872,18 +1435,6 @@ void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { } } -void GPUCommon::Execute_BlockTransferStart(u32 op, u32 diff) { - Flush(); - - PROFILE_THIS_SCOPE("block"); // don't include the flush in the profile, would be misleading. - - gstate_c.framebufFormat = gstate.FrameBufFormat(); - - // and take appropriate action. This is a block transfer between RAM and VRAM, or vice versa. - // Can we skip this on SkipDraw? - DoBlockTransfer(gstate_c.skipDrawReason); -} - void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { if (!currentList) { gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index eecfaa68da..f234f2faa3 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -155,11 +155,7 @@ public: void Execute_VertexType(u32 op, u32 diff); void Execute_VertexTypeSkinning(u32 op, u32 diff); - void Execute_Prim(u32 op, u32 diff); - void Execute_Bezier(u32 op, u32 diff); - void Execute_Spline(u32 op, u32 diff); void Execute_BoundingBox(u32 op, u32 diff); - void Execute_BlockTransferStart(u32 op, u32 diff); void Execute_LoadClut(u32 op, u32 diff); @@ -415,7 +411,6 @@ protected: std::string reportingFullInfo_; private: - void CheckDepthUsage(VirtualFramebuffer *vfb); void DoExecuteCall(u32 target); void PopDLQueue(); void CheckDrawSync(); @@ -427,10 +422,3 @@ private: int lastVsync_ = -1; }; - -struct CommonCommandTableEntry { - uint8_t cmd; - uint8_t flags; - uint64_t dirty; - GPUCommon::CmdFunc func; -}; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 2f5029d5f7..eb62e1ff60 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1,3 +1,5 @@ +#include "Common/Profiler/Profiler.h" + #include "Common/GPU/thin3d.h" #include "Common/Serialize/Serializer.h" #include "Common/System/System.h" @@ -6,10 +8,18 @@ #include "Core/Config.h" #include "GPU/GPUCommonHW.h" +#include "GPU/Common/SplineCommon.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" +struct CommonCommandTableEntry { + uint8_t cmd; + uint8_t flags; + uint64_t dirty; + GPUCommonHW::CmdFunc func; +}; + const CommonCommandTableEntry commonCommandTable[] = { // From Common. No flushing but definitely need execute. { GE_CMD_OFFSETADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_OffsetAddr }, @@ -21,11 +31,11 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_VADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Vaddr }, { GE_CMD_IADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_Iaddr }, { GE_CMD_BJUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BJump }, // EXECUTE - { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoundingBox }, // Shouldn't need to FLUSHBEFORE. + { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_BoundingBox }, // Shouldn't need to FLUSHBEFORE. - { GE_CMD_PRIM, FLAG_EXECUTE, 0, &GPUCommon::Execute_Prim }, - { GE_CMD_BEZIER, FLAG_EXECUTE, 0, &GPUCommon::Execute_Bezier }, - { GE_CMD_SPLINE, FLAG_EXECUTE, 0, &GPUCommon::Execute_Spline }, + { GE_CMD_PRIM, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_Prim }, + { GE_CMD_BEZIER, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_Bezier }, + { GE_CMD_SPLINE, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_Spline }, // Changing the vertex type requires us to flush. { GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType }, @@ -274,7 +284,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_TRANSFERSRCPOS, 0 }, { GE_CMD_TRANSFERDSTPOS, 0 }, { GE_CMD_TRANSFERSIZE, 0 }, - { GE_CMD_TRANSFERSTART, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommon::Execute_BlockTransferStart }, + { GE_CMD_TRANSFERSTART, FLAG_EXECUTE | FLAG_READS_PC, 0, &GPUCommonHW::Execute_BlockTransferStart }, // We don't use the dither table. { GE_CMD_DITH0 }, @@ -346,7 +356,7 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU dupeCheck.insert(cmd); } cmdInfo_[cmd].flags |= (uint64_t)commonCommandTable[i].flags | (commonCommandTable[i].dirty << 8); - cmdInfo_[cmd].func = commonCommandTable[i].func; + cmdInfo_[cmd].func = (GPUCommon::CmdFunc)commonCommandTable[i].func; if ((cmdInfo_[cmd].flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) && !cmdInfo_[cmd].func) { // Can't have FLAG_EXECUTE commands without a function pointer to execute. Crash(); @@ -568,3 +578,453 @@ std::string GPUCommonHW::DebugGetShaderString(std::string id, DebugShaderType ty return shaderManager_->DebugGetShaderString(id, type, stringType); } } + +void GPUCommonHW::CheckDepthUsage(VirtualFramebuffer *vfb) { + if (!gstate_c.usingDepth) { + bool isReadingDepth = false; + bool isClearingDepth = false; + bool isWritingDepth = false; + if (gstate.isModeClear()) { + isClearingDepth = gstate.isClearModeDepthMask(); + isWritingDepth = isClearingDepth; + } else if (gstate.isDepthTestEnabled()) { + isWritingDepth = gstate.isDepthWriteEnabled(); + isReadingDepth = gstate.getDepthTestFunction() > GE_COMP_ALWAYS; + } + + if (isWritingDepth || isReadingDepth) { + gstate_c.usingDepth = true; + gstate_c.clearingDepth = isClearingDepth; + vfb->last_frame_depth_render = gpuStats.numFlips; + if (isWritingDepth) { + vfb->last_frame_depth_updated = gpuStats.numFlips; + } + framebufferManager_->SetDepthFrameBuffer(isClearingDepth); + } + } +} + +void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { + // This drives all drawing. All other state we just buffer up, then we apply it only + // when it's time to draw. As most PSP games set state redundantly ALL THE TIME, this is a huge optimization. + + PROFILE_THIS_SCOPE("execprim"); + + u32 data = op & 0xFFFFFF; + u32 count = data & 0xFFFF; + if (count == 0) + return; + FlushImm(); + + // Upper bits are ignored. + GEPrimitiveType prim = static_cast((data >> 16) & 7); + SetDrawType(DRAW_PRIM, prim); + + // Discard AA lines as we can't do anything that makes sense with these anyway. The SW plugin might, though. + if (gstate.isAntiAliasEnabled()) { + // Heuristic derived from discussions in #6483 and #12588. + // Discard AA lines in Persona 3 Portable, DOA Paradise and Summon Night 5, while still keeping AA lines in Echochrome. + if ((prim == GE_PRIM_LINE_STRIP || prim == GE_PRIM_LINES) && gstate.getTextureFunction() == GE_TEXFUNC_REPLACE) + return; + } + + // Update cached framebuffer format. + // We store it in the cache so it can be modified for blue-to-alpha, next. + gstate_c.framebufFormat = gstate.FrameBufFormat(); + + if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { + ERROR_LOG(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); + return; + } + + // See the documentation for gstate_c.blueToAlpha. + bool blueToAlpha = false; + if (PSP_CoreParameter().compat.flags().BlueToAlpha) { + if (gstate_c.framebufFormat == GEBufferFormat::GE_FORMAT_565 && gstate.getColorMask() == 0x0FFFFF && !gstate.isLogicOpEnabled()) { + blueToAlpha = true; + gstate_c.framebufFormat = GEBufferFormat::GE_FORMAT_4444; + } + if (blueToAlpha != gstate_c.blueToAlpha) { + gstate_c.blueToAlpha = blueToAlpha; + gstate_c.Dirty(DIRTY_FRAMEBUF | DIRTY_FRAGMENTSHADER_STATE | DIRTY_BLEND_STATE); + } + } + + if (PSP_CoreParameter().compat.flags().SplitFramebufferMargin) { + switch (gstate.vertType & 0xFFFFFF) { + case 0x00800102: // through, u16 uv, u16 pos (used for the framebuffer effect in-game) + case 0x0080011c: // through, 8888 color, s16 pos (used for clearing in the margin of the title screen) + case 0x00000183: // float uv, float pos (used for drawing in the margin of the title screen) + // Need to re-check the framebuffer every one of these draws, to update the split if needed. + gstate_c.Dirty(DIRTY_FRAMEBUF); + } + } + + // This also makes skipping drawing very effective. + VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); + if (blueToAlpha) { + vfb->usageFlags |= FB_USAGE_BLUE_TO_ALPHA; + } + + // Must check this after SetRenderFrameBuffer so we know SKIPDRAW_NON_DISPLAYED_FB. + if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { + // Rough estimate, not sure what's correct. + cyclesExecuted += EstimatePerVertexCost() * count; + if (gstate.isModeClear()) { + gpuStats.numClears++; + } + return; + } + + CheckDepthUsage(vfb); + + const void *verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + const void *inds = nullptr; + u32 vertexType = gstate.vertType; + if ((vertexType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + u32 indexAddr = gstate_c.indexAddr; + if (!Memory::IsValidAddress(indexAddr)) { + ERROR_LOG(G3D, "Bad index address %08x!", indexAddr); + return; + } + inds = Memory::GetPointerUnchecked(indexAddr); + } + + if (gstate_c.dirty & DIRTY_VERTEXSHADER_STATE) { + vertexCost_ = EstimatePerVertexCost(); + } + + int bytesRead = 0; + UpdateUVScaleOffset(); + + // cull mode + int cullMode = gstate.getCullMode(); + + uint32_t vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning); + drawEngineCommon_->SubmitPrim(verts, inds, prim, count, vertTypeID, cullMode, &bytesRead); + // After drawing, we advance the vertexAddr (when non indexed) or indexAddr (when indexed). + // Some games rely on this, they don't bother reloading VADDR and IADDR. + // The VADDR/IADDR registers are NOT updated. + AdvanceVerts(vertexType, count, bytesRead); + int totalVertCount = count; + + // PRIMs are often followed by more PRIMs. Save some work and submit them immediately. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + const u32_le *stall = currentList->stall ? (const u32_le *)Memory::GetPointerUnchecked(currentList->stall) : 0; + int cmdCount = 0; + + // Optimized submission of sequences of PRIM. Allows us to avoid going through all the mess + // above for each one. This can be expanded to support additional games that intersperse + // PRIM commands with other commands. A special case is Earth Defence Force 2 that changes culling mode + // between each prim, we just change the triangle winding right here to still be able to join draw calls. + + uint32_t vtypeCheckMask = ~GE_VTYPE_WEIGHTCOUNT_MASK; + if (!g_Config.bSoftwareSkinning) + vtypeCheckMask = 0xFFFFFFFF; + + if (debugRecording_) + goto bail; + + while (src != stall) { + uint32_t data = *src; + switch (data >> 24) { + case GE_CMD_PRIM: + { + u32 count = data & 0xFFFF; + if (count == 0) { + // Ignore. + break; + } + + GEPrimitiveType newPrim = static_cast((data >> 16) & 7); + SetDrawType(DRAW_PRIM, newPrim); + // TODO: more efficient updating of verts/inds + verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + inds = nullptr; + if ((vertexType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + inds = Memory::GetPointerUnchecked(gstate_c.indexAddr); + } + + drawEngineCommon_->SubmitPrim(verts, inds, newPrim, count, vertTypeID, cullMode, &bytesRead); + AdvanceVerts(vertexType, count, bytesRead); + totalVertCount += count; + break; + } + case GE_CMD_VERTEXTYPE: + { + uint32_t diff = data ^ vertexType; + // don't mask upper bits, vertexType is unmasked + if (diff & vtypeCheckMask) { + goto bail; + } else { + vertexType = data; + vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning); + } + break; + } + case GE_CMD_VADDR: + gstate.cmdmem[GE_CMD_VADDR] = data; + gstate_c.vertexAddr = gstate_c.getRelativeAddress(data & 0x00FFFFFF); + break; + case GE_CMD_IADDR: + gstate.cmdmem[GE_CMD_IADDR] = data; + gstate_c.indexAddr = gstate_c.getRelativeAddress(data & 0x00FFFFFF); + break; + case GE_CMD_OFFSETADDR: + gstate.cmdmem[GE_CMD_OFFSETADDR] = data; + gstate_c.offsetAddr = data << 8; + break; + case GE_CMD_BASE: + gstate.cmdmem[GE_CMD_BASE] = data; + break; + case GE_CMD_CULLFACEENABLE: + // Earth Defence Force 2 + if (gstate.cmdmem[GE_CMD_CULLFACEENABLE] != data) { + goto bail; + } + break; + case GE_CMD_CULL: + // flip face by indices for triangles + cullMode = data & 1; + break; + case GE_CMD_TEXFLUSH: + case GE_CMD_NOP: + case GE_CMD_NOP_FF: + gstate.cmdmem[data >> 24] = data; + break; + case GE_CMD_BONEMATRIXNUMBER: + gstate.cmdmem[GE_CMD_BONEMATRIXNUMBER] = data; + break; + case GE_CMD_TEXSCALEU: + gstate.cmdmem[GE_CMD_TEXSCALEU] = data; + gstate_c.uv.uScale = getFloat24(data); + break; + case GE_CMD_TEXSCALEV: + gstate.cmdmem[GE_CMD_TEXSCALEV] = data; + gstate_c.uv.vScale = getFloat24(data); + break; + case GE_CMD_TEXOFFSETU: + gstate.cmdmem[GE_CMD_TEXOFFSETU] = data; + gstate_c.uv.uOff = getFloat24(data); + break; + case GE_CMD_TEXOFFSETV: + gstate.cmdmem[GE_CMD_TEXOFFSETV] = data; + gstate_c.uv.vOff = getFloat24(data); + break; + case GE_CMD_TEXLEVEL: + // Same Gran Turismo hack from Execute_TexLevel + if ((data & 3) != GE_TEXLEVEL_MODE_AUTO && (0x00FF0000 & data) != 0) { + goto bail; + } + gstate.cmdmem[GE_CMD_TEXLEVEL] = data; + break; + case GE_CMD_CALL: + { + // A bone matrix probably. If not we bail. + const u32 target = gstate_c.getRelativeAddress(data & 0x00FFFFFC); + if ((Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA && + (Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA && + (Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET && + (target > currentList->stall || target + 12 * 4 < currentList->stall) && + (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) { + FastLoadBoneMatrix(target); + } else { + goto bail; + } + break; + } + + case GE_CMD_TEXBUFWIDTH0: + case GE_CMD_TEXADDR0: + if (data != gstate.cmdmem[data >> 24]) + goto bail; + break; + + default: + // All other commands might need a flush or something, stop this inner loop. + goto bail; + } + cmdCount++; + src++; + } + +bail: + gstate.cmdmem[GE_CMD_VERTEXTYPE] = vertexType; + // Skip over the commands we just read out manually. + if (cmdCount > 0) { + UpdatePC(currentList->pc, currentList->pc + cmdCount * 4); + currentList->pc += cmdCount * 4; + // flush back cull mode + if (cullMode != gstate.getCullMode()) { + // We rewrote everything to the old cull mode, so flush first. + drawEngineCommon_->DispatchFlush(); + + // Now update things for next time. + gstate.cmdmem[GE_CMD_CULL] ^= 1; + gstate_c.Dirty(DIRTY_RASTER_STATE); + } + } + + gpuStats.vertexGPUCycles += vertexCost_ * totalVertCount; + cyclesExecuted += vertexCost_ * totalVertCount; +} + +void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) { + // We don't dirty on normal changes anymore as we prescale, but it's needed for splines/bezier. + gstate_c.Dirty(DIRTY_UVSCALEOFFSET); + + gstate_c.framebufFormat = gstate.FrameBufFormat(); + + // This also make skipping drawing very effective. + VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); + if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { + // TODO: Should this eat some cycles? Probably yes. Not sure if important. + return; + } + + CheckDepthUsage(vfb); + + if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { + ERROR_LOG_REPORT(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); + return; + } + + const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + const void *indices = NULL; + if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + if (!Memory::IsValidAddress(gstate_c.indexAddr)) { + ERROR_LOG_REPORT(G3D, "Bad index address %08x!", gstate_c.indexAddr); + return; + } + indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); + } + + if (vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); + } + + // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. + if (flushOnParams_) + drawEngineCommon_->DispatchFlush(); + + Spline::BezierSurface surface; + surface.tess_u = gstate.getPatchDivisionU(); + surface.tess_v = gstate.getPatchDivisionV(); + surface.num_points_u = op & 0xFF; + surface.num_points_v = (op >> 8) & 0xFF; + surface.num_patches_u = (surface.num_points_u - 1) / 3; + surface.num_patches_v = (surface.num_points_v - 1) / 3; + surface.primType = gstate.getPatchPrimitiveType(); + surface.patchFacing = gstate.patchfacing & 1; + + SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType)); + + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); + if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { + gstate_c.submitType = SubmitType::HW_BEZIER; + if (gstate_c.spline_num_points_u != surface.num_points_u) { + gstate_c.Dirty(DIRTY_BEZIERSPLINE); + gstate_c.spline_num_points_u = surface.num_points_u; + } + } else { + gstate_c.submitType = SubmitType::BEZIER; + } + + int bytesRead = 0; + UpdateUVScaleOffset(); + drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "bezier"); + + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); + gstate_c.submitType = SubmitType::DRAW; + + // After drawing, we advance pointers - see SubmitPrim which does the same. + int count = surface.num_points_u * surface.num_points_v; + AdvanceVerts(gstate.vertType, count, bytesRead); +} + +void GPUCommonHW::Execute_Spline(u32 op, u32 diff) { + // We don't dirty on normal changes anymore as we prescale, but it's needed for splines/bezier. + gstate_c.Dirty(DIRTY_UVSCALEOFFSET); + + gstate_c.framebufFormat = gstate.FrameBufFormat(); + + // This also make skipping drawing very effective. + VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); + if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { + // TODO: Should this eat some cycles? Probably yes. Not sure if important. + return; + } + + CheckDepthUsage(vfb); + + if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { + ERROR_LOG_REPORT(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); + return; + } + + const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + const void *indices = NULL; + if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + if (!Memory::IsValidAddress(gstate_c.indexAddr)) { + ERROR_LOG_REPORT(G3D, "Bad index address %08x!", gstate_c.indexAddr); + return; + } + indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); + } + + if (vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); + } + + // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. + if (flushOnParams_) + drawEngineCommon_->DispatchFlush(); + + Spline::SplineSurface surface; + surface.tess_u = gstate.getPatchDivisionU(); + surface.tess_v = gstate.getPatchDivisionV(); + surface.type_u = (op >> 16) & 0x3; + surface.type_v = (op >> 18) & 0x3; + surface.num_points_u = op & 0xFF; + surface.num_points_v = (op >> 8) & 0xFF; + surface.num_patches_u = surface.num_points_u - 3; + surface.num_patches_v = surface.num_points_v - 3; + surface.primType = gstate.getPatchPrimitiveType(); + surface.patchFacing = gstate.patchfacing & 1; + + SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType)); + + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); + if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { + gstate_c.submitType = SubmitType::HW_SPLINE; + if (gstate_c.spline_num_points_u != surface.num_points_u) { + gstate_c.Dirty(DIRTY_BEZIERSPLINE); + gstate_c.spline_num_points_u = surface.num_points_u; + } + } else { + gstate_c.submitType = SubmitType::SPLINE; + } + + int bytesRead = 0; + UpdateUVScaleOffset(); + drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "spline"); + + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); + gstate_c.submitType = SubmitType::DRAW; + + // After drawing, we advance pointers - see SubmitPrim which does the same. + int count = surface.num_points_u * surface.num_points_v; + AdvanceVerts(gstate.vertType, count, bytesRead); +} + +void GPUCommonHW::Execute_BlockTransferStart(u32 op, u32 diff) { + Flush(); + + PROFILE_THIS_SCOPE("block"); // don't include the flush in the profile, would be misleading. + + gstate_c.framebufFormat = gstate.FrameBufFormat(); + + // and take appropriate action. This is a block transfer between RAM and VRAM, or vice versa. + // Can we skip this on SkipDraw? + DoBlockTransfer(gstate_c.skipDrawReason); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index f059d93fc0..11cadb0d0a 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -19,6 +19,13 @@ public: std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; + void Execute_Prim(u32 op, u32 diff); + void Execute_Bezier(u32 op, u32 diff); + void Execute_Spline(u32 op, u32 diff); + void Execute_BlockTransferStart(u32 op, u32 diff); + + typedef void (GPUCommonHW::*CmdFunc)(u32 op, u32 diff); + protected: void UpdateCmdInfo() override; @@ -32,5 +39,7 @@ protected: void CheckRenderResized() override; int msaaLevel_ = 0; -}; +private: + void CheckDepthUsage(VirtualFramebuffer *vfb); +}; From bcd38bc9e8bfbb1c1996424e4ccc096b15313a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 17:37:57 +0100 Subject: [PATCH 036/141] Move some more commands to GPUCommonHW. Get rid of ugly cast. --- GPU/GPUCommon.cpp | 180 +--------------------------------------- GPU/GPUCommon.h | 32 +------ GPU/GPUCommonHW.cpp | 198 ++++++++++++++++++++++++++++++++++++++++++-- GPU/GPUCommonHW.h | 14 +++- 4 files changed, 207 insertions(+), 217 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 7338d29d3e..976392b818 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -50,9 +50,6 @@ #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" -// TODO: Make class member? -GPUCommonHW::CommandInfo GPUCommon::cmdInfo_[256]; - void GPUCommon::Flush() { drawEngineCommon_->DispatchFlush(); } @@ -797,53 +794,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { return gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR; } -// Maybe should write this in ASM... -void GPUCommon::FastRunLoop(DisplayList &list) { - PROFILE_THIS_SCOPE("gpuloop"); - - if (!Memory::IsValidAddress(list.pc)) { - // We're having some serious problems here, just bail and try to limp along and not crash the app. - downcount = 0; - return; - } - - const CommandInfo *cmdInfo = cmdInfo_; - int dc = downcount; - for (; dc > 0; --dc) { - // We know that display list PCs have the upper nibble == 0 - no need to mask the pointer - const u32 op = *(const u32_le *)(Memory::base + list.pc); - const u32 cmd = op >> 24; - const CommandInfo &info = cmdInfo[cmd]; - const u32 diff = op ^ gstate.cmdmem[cmd]; - if (diff == 0) { - if (info.flags & FLAG_EXECUTE) { - downcount = dc; - (this->*info.func)(op, diff); - dc = downcount; - } - } else { - uint64_t flags = info.flags; - if (flags & FLAG_FLUSHBEFOREONCHANGE) { - if (drawEngineCommon_->GetNumDrawCalls()) { - drawEngineCommon_->DispatchFlush(); - } - } - gstate.cmdmem[cmd] = op; - if (flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) { - downcount = dc; - (this->*info.func)(op, diff); - dc = downcount; - } else { - uint64_t dirty = flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } - } - list.pc += 4; - } - downcount = 0; -} - void GPUCommon::BeginFrame() { immCount_ = 0; if (dumpNextFrame_) { @@ -862,11 +812,9 @@ void GPUCommon::BeginFrame() { } } -void GPUCommon::SlowRunLoop(DisplayList &list) -{ +void GPUCommon::SlowRunLoop(DisplayList &list) { const bool dumpThisFrame = dumpThisFrame_; - while (downcount > 0) - { + while (downcount > 0) { bool process = GPUDebug::NotifyCommand(list.pc); if (process) { GPURecord::NotifyCommand(list.pc); @@ -1312,75 +1260,6 @@ void GPUCommon::Execute_End(u32 op, u32 diff) { } } -void GPUCommon::Execute_TexLevel(u32 op, u32 diff) { - // TODO: If you change the rules here, don't forget to update the inner interpreter in Execute_Prim. - if (diff == 0xFFFFFFFF) - return; - - gstate.texlevel ^= diff; - - if (diff & 0xFF0000) { - // Piggyback on this flag for 3D textures. - gstate_c.Dirty(DIRTY_MIPBIAS); - } - if (gstate.getTexLevelMode() != GE_TEXLEVEL_MODE_AUTO && (0x00FF0000 & gstate.texlevel) != 0) { - Flush(); - } - - gstate.texlevel ^= diff; - - gstate_c.Dirty(DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE); -} - -void GPUCommon::Execute_TexSize0(u32 op, u32 diff) { - // Render to texture may have overridden the width/height. - // Don't reset it unless the size is different / the texture has changed. - if (diff || gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS)) { - gstate_c.curTextureWidth = gstate.getTextureWidth(0); - gstate_c.curTextureHeight = gstate.getTextureHeight(0); - gstate_c.Dirty(DIRTY_UVSCALEOFFSET); - // We will need to reset the texture now. - gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); - } -} - -void GPUCommon::Execute_VertexType(u32 op, u32 diff) { - if (diff) - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); - if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) { - gstate_c.Dirty(DIRTY_UVSCALEOFFSET); - // Switching between through and non-through, we need to invalidate a bunch of stuff. - if (diff & GE_VTYPE_THROUGH_MASK) - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE | DIRTY_FOGCOEFENABLE); - } -} - -void GPUCommon::Execute_LoadClut(u32 op, u32 diff) { - gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); - textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes()); -} - -void GPUCommon::Execute_VertexTypeSkinning(u32 op, u32 diff) { - // Don't flush when weight count changes. - if (diff & ~GE_VTYPE_WEIGHTCOUNT_MASK) { - // Restore and flush - gstate.vertType ^= diff; - Flush(); - gstate.vertType ^= diff; - if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) - gstate_c.Dirty(DIRTY_UVSCALEOFFSET); - // In this case, we may be doing weights and morphs. - // Update any bone matrix uniforms so it uses them correctly. - if ((op & GE_VTYPE_MORPHCOUNT_MASK) != 0) { - gstate_c.Dirty(gstate_c.deferredVertTypeDirty); - gstate_c.deferredVertTypeDirty = 0; - } - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); - } - if (diff & GE_VTYPE_THROUGH_MASK) - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE | DIRTY_FOGCOEFENABLE); -} - void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { // Just resetting, nothing to check bounds for. const u32 count = op & 0xFFFF; @@ -1849,19 +1728,6 @@ void GPUCommon::FlushImm() { } } -void GPUCommon::ExecuteOp(u32 op, u32 diff) { - const u8 cmd = op >> 24; - const CommandInfo info = cmdInfo_[cmd]; - const u8 cmdFlags = info.flags; - if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { - (this->*info.func)(op, diff); - } else if (diff) { - uint64_t dirty = info.flags >> 8; - if (dirty) - gstate_c.Dirty(dirty); - } -} - void GPUCommon::Execute_Unknown(u32 op, u32 diff) { if ((op & 0xFFFFFF) != 0) WARN_LOG_REPORT_ONCE(unknowncmd, G3D, "Unknown GE command : %08x ", op); @@ -2613,45 +2479,3 @@ size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) { vertexAverageCycles ); } - - -u32 GPUCommon::CheckGPUFeaturesLate(u32 features) const { - // If we already have a 16-bit depth buffer, we don't need to round. - bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8; - bool prefer16 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D16; - if (!prefer16) { - if (sawExactEqualDepth_ && (features & GPU_USE_ACCURATE_DEPTH) != 0) { - // Exact equal tests tend to have issues unless we use the PSP's depth range. - // We use 24-bit depth virtually everwhere, the fallback is just for safety. - if (prefer24) - features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; - else - features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT; - } else if (!g_Config.bHighQualityDepth && (features & GPU_USE_ACCURATE_DEPTH) != 0) { - features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; - } else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) { - if (prefer24 && (features & GPU_USE_ACCURATE_DEPTH) != 0) { - // Here we can simulate a 16 bit depth buffer by scaling. - // Note that the depth buffer is fixed point, not floating, so dividing by 256 is pretty good. - features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; - } else { - // Use fragment rounding on where available otherwise. - features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT; - } - } else if (PSP_CoreParameter().compat.flags().VertexDepthRounding) { - features |= GPU_ROUND_DEPTH_TO_16BIT; - } - } - - return features; -} - -void GPUCommon::CheckFlushOp(int cmd, u32 diff) { - const u8 cmdFlags = cmdInfo_[cmd].flags; - if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { - if (dumpThisFrame_) { - NOTICE_LOG(G3D, "================ FLUSH ================"); - } - drawEngineCommon_->DispatchFlush(); - } -} diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index f234f2faa3..8407c6a520 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -60,7 +60,6 @@ struct TransformedVertex { u32 color1_32; }; - void CopyFromWithOffset(const TransformedVertex &other, float xoff, float yoff) { this->x = other.x + xoff; this->y = other.y + yoff; @@ -108,7 +107,6 @@ public: void DumpNextFrame() override; - void ExecuteOp(u32 op, u32 diff) override; virtual void PreExecuteOp(u32 op, u32 diff) {} bool InterpretList(DisplayList &list); @@ -152,16 +150,8 @@ public: void Execute_Ret(u32 op, u32 diff); void Execute_End(u32 op, u32 diff); - void Execute_VertexType(u32 op, u32 diff); - void Execute_VertexTypeSkinning(u32 op, u32 diff); - void Execute_BoundingBox(u32 op, u32 diff); - void Execute_LoadClut(u32 op, u32 diff); - - void Execute_TexSize0(u32 op, u32 diff); - void Execute_TexLevel(u32 op, u32 diff); - void Execute_WorldMtxNum(u32 op, u32 diff); void Execute_WorldMtxData(u32 op, u32 diff); void Execute_ViewMtxNum(u32 op, u32 diff); @@ -267,9 +257,6 @@ protected: virtual void CheckRenderResized() {} - // Add additional common features dependent on other features, which may be backend-determined. - u32 CheckGPUFeaturesLate(u32 features) const; - inline bool IsTrianglePrim(GEPrimitiveType prim) const { return prim != GE_PRIM_RECTANGLES && prim > GE_PRIM_LINE_STRIP; } @@ -292,7 +279,7 @@ protected: void BeginFrame() override; void UpdateVsyncInterval(bool force); - virtual void FastRunLoop(DisplayList &list); + virtual void FastRunLoop(DisplayList &list) = 0; void SlowRunLoop(DisplayList &list); void UpdatePC(u32 currentPC, u32 newPC); @@ -304,8 +291,6 @@ protected: // TODO: Unify this. The only backend that differs is Vulkan. virtual void FinishDeferred() {} - void CheckFlushOp(int cmd, u32 diff); - void AdvanceVerts(u32 vertType, int count, int bytesRead) { if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { int indexShift = ((vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1; @@ -333,21 +318,6 @@ protected: GraphicsContext *gfxCtx_; Draw::DrawContext *draw_; - struct CommandInfo { - uint64_t flags; - GPUCommon::CmdFunc func; - - // Dirty flags are mashed into the regular flags by a left shift of 8. - void AddDirty(u64 dirty) { - flags |= dirty << 8; - } - void RemoveDirty(u64 dirty) { - flags &= ~(dirty << 8); - } - }; - - static CommandInfo cmdInfo_[256]; - typedef std::list DisplayListQueue; int nextListID; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index eb62e1ff60..41f0a34c44 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -20,6 +20,21 @@ struct CommonCommandTableEntry { GPUCommonHW::CmdFunc func; }; +struct CommandInfo { + uint64_t flags; + GPUCommonHW::CmdFunc func; + + // Dirty flags are mashed into the regular flags by a left shift of 8. + void AddDirty(u64 dirty) { + flags |= dirty << 8; + } + void RemoveDirty(u64 dirty) { + flags &= ~(dirty << 8); + } +}; + +static CommandInfo cmdInfo_[256]; + const CommonCommandTableEntry commonCommandTable[] = { // From Common. No flushing but definitely need execute. { GE_CMD_OFFSETADDR, FLAG_EXECUTE, 0, &GPUCommon::Execute_OffsetAddr }, @@ -38,9 +53,9 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_SPLINE, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_Spline }, // Changing the vertex type requires us to flush. - { GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType }, + { GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommonHW::Execute_VertexType }, - { GE_CMD_LOADCLUT, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_LoadClut }, + { GE_CMD_LOADCLUT, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommonHW::Execute_LoadClut}, // These two are actually processed in CMD_END. { GE_CMD_SIGNAL }, @@ -122,7 +137,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_TEXOFFSETU }, { GE_CMD_TEXOFFSETV }, - { GE_CMD_TEXSIZE0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_TexSize0 }, + { GE_CMD_TEXSIZE0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommonHW::Execute_TexSize0 }, { GE_CMD_TEXSIZE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXSIZE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXSIZE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, @@ -131,7 +146,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_TEXSIZE6, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXSIZE7, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE }, - { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommon::Execute_TexLevel }, + { GE_CMD_TEXLEVEL, FLAG_EXECUTEONCHANGE, DIRTY_TEXTURE_PARAMS, &GPUCommonHW::Execute_TexLevel }, { GE_CMD_TEXLODSLOPE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXADDR0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_IMAGE | DIRTY_UVSCALEOFFSET }, { GE_CMD_TEXADDR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, @@ -356,7 +371,7 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU dupeCheck.insert(cmd); } cmdInfo_[cmd].flags |= (uint64_t)commonCommandTable[i].flags | (commonCommandTable[i].dirty << 8); - cmdInfo_[cmd].func = (GPUCommon::CmdFunc)commonCommandTable[i].func; + cmdInfo_[cmd].func = commonCommandTable[i].func; if ((cmdInfo_[cmd].flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) && !cmdInfo_[cmd].func) { // Can't have FLAG_EXECUTE commands without a function pointer to execute. Crash(); @@ -402,10 +417,10 @@ void GPUCommonHW::DeviceLost() { void GPUCommonHW::UpdateCmdInfo() { if (g_Config.bSoftwareSkinning) { cmdInfo_[GE_CMD_VERTEXTYPE].flags &= ~FLAG_FLUSHBEFOREONCHANGE; - cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexTypeSkinning; + cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommonHW::Execute_VertexTypeSkinning; } else { cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE; - cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexType; + cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommonHW::Execute_VertexType; } if (g_Config.bFastMemory) { @@ -442,6 +457,16 @@ void GPUCommonHW::UpdateCmdInfo() { } } +void GPUCommonHW::CheckFlushOp(int cmd, u32 diff) { + const u8 cmdFlags = cmdInfo_[cmd].flags; + if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { + if (dumpThisFrame_) { + NOTICE_LOG(G3D, "================ FLUSH ================"); + } + drawEngineCommon_->DispatchFlush(); + } +} + void GPUCommonHW::PreExecuteOp(u32 op, u32 diff) { CheckFlushOp(op >> 24, diff); } @@ -547,6 +572,37 @@ u32 GPUCommonHW::CheckGPUFeatures() const { return features; } +u32 GPUCommonHW::CheckGPUFeaturesLate(u32 features) const { + // If we already have a 16-bit depth buffer, we don't need to round. + bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8; + bool prefer16 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D16; + if (!prefer16) { + if (sawExactEqualDepth_ && (features & GPU_USE_ACCURATE_DEPTH) != 0) { + // Exact equal tests tend to have issues unless we use the PSP's depth range. + // We use 24-bit depth virtually everwhere, the fallback is just for safety. + if (prefer24) + features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; + else + features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT; + } else if (!g_Config.bHighQualityDepth && (features & GPU_USE_ACCURATE_DEPTH) != 0) { + features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; + } else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) { + if (prefer24 && (features & GPU_USE_ACCURATE_DEPTH) != 0) { + // Here we can simulate a 16 bit depth buffer by scaling. + // Note that the depth buffer is fixed point, not floating, so dividing by 256 is pretty good. + features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; + } else { + // Use fragment rounding on where available otherwise. + features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT; + } + } else if (PSP_CoreParameter().compat.flags().VertexDepthRounding) { + features |= GPU_ROUND_DEPTH_TO_16BIT; + } + } + + return features; +} + void GPUCommonHW::UpdateMSAALevel(Draw::DrawContext *draw) { int level = g_Config.iMultiSampleLevel; if (draw && draw->GetDeviceCaps().multiSampleLevelsMask & (1 << level)) { @@ -604,6 +660,97 @@ void GPUCommonHW::CheckDepthUsage(VirtualFramebuffer *vfb) { } } +void GPUCommonHW::ExecuteOp(u32 op, u32 diff) { + const u8 cmd = op >> 24; + const CommandInfo info = cmdInfo_[cmd]; + const u8 cmdFlags = info.flags; + if ((cmdFlags & FLAG_EXECUTE) || (diff && (cmdFlags & FLAG_EXECUTEONCHANGE))) { + (this->*info.func)(op, diff); + } else if (diff) { + uint64_t dirty = info.flags >> 8; + if (dirty) + gstate_c.Dirty(dirty); + } +} + +void GPUCommonHW::FastRunLoop(DisplayList &list) { + PROFILE_THIS_SCOPE("gpuloop"); + + if (!Memory::IsValidAddress(list.pc)) { + // We're having some serious problems here, just bail and try to limp along and not crash the app. + downcount = 0; + return; + } + + const CommandInfo *cmdInfo = cmdInfo_; + int dc = downcount; + for (; dc > 0; --dc) { + // We know that display list PCs have the upper nibble == 0 - no need to mask the pointer + const u32 op = *(const u32_le *)(Memory::base + list.pc); + const u32 cmd = op >> 24; + const CommandInfo &info = cmdInfo[cmd]; + const u32 diff = op ^ gstate.cmdmem[cmd]; + if (diff == 0) { + if (info.flags & FLAG_EXECUTE) { + downcount = dc; + (this->*info.func)(op, diff); + dc = downcount; + } + } else { + uint64_t flags = info.flags; + if (flags & FLAG_FLUSHBEFOREONCHANGE) { + if (drawEngineCommon_->GetNumDrawCalls()) { + drawEngineCommon_->DispatchFlush(); + } + } + gstate.cmdmem[cmd] = op; + if (flags & (FLAG_EXECUTE | FLAG_EXECUTEONCHANGE)) { + downcount = dc; + (this->*info.func)(op, diff); + dc = downcount; + } else { + uint64_t dirty = flags >> 8; + if (dirty) + gstate_c.Dirty(dirty); + } + } + list.pc += 4; + } + downcount = 0; +} + +void GPUCommonHW::Execute_VertexType(u32 op, u32 diff) { + if (diff) + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); + if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) { + gstate_c.Dirty(DIRTY_UVSCALEOFFSET); + // Switching between through and non-through, we need to invalidate a bunch of stuff. + if (diff & GE_VTYPE_THROUGH_MASK) + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE | DIRTY_FOGCOEFENABLE); + } +} + +void GPUCommonHW::Execute_VertexTypeSkinning(u32 op, u32 diff) { + // Don't flush when weight count changes. + if (diff & ~GE_VTYPE_WEIGHTCOUNT_MASK) { + // Restore and flush + gstate.vertType ^= diff; + Flush(); + gstate.vertType ^= diff; + if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) + gstate_c.Dirty(DIRTY_UVSCALEOFFSET); + // In this case, we may be doing weights and morphs. + // Update any bone matrix uniforms so it uses them correctly. + if ((op & GE_VTYPE_MORPHCOUNT_MASK) != 0) { + gstate_c.Dirty(gstate_c.deferredVertTypeDirty); + gstate_c.deferredVertTypeDirty = 0; + } + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); + } + if (diff & GE_VTYPE_THROUGH_MASK) + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE | DIRTY_FOGCOEFENABLE); +} + void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { // This drives all drawing. All other state we just buffer up, then we apply it only // when it's time to draw. As most PSP games set state redundantly ALL THE TIME, this is a huge optimization. @@ -1028,3 +1175,40 @@ void GPUCommonHW::Execute_BlockTransferStart(u32 op, u32 diff) { // Can we skip this on SkipDraw? DoBlockTransfer(gstate_c.skipDrawReason); } + +void GPUCommonHW::Execute_TexSize0(u32 op, u32 diff) { + // Render to texture may have overridden the width/height. + // Don't reset it unless the size is different / the texture has changed. + if (diff || gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS)) { + gstate_c.curTextureWidth = gstate.getTextureWidth(0); + gstate_c.curTextureHeight = gstate.getTextureHeight(0); + gstate_c.Dirty(DIRTY_UVSCALEOFFSET); + // We will need to reset the texture now. + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + } +} + +void GPUCommonHW::Execute_TexLevel(u32 op, u32 diff) { + // TODO: If you change the rules here, don't forget to update the inner interpreter in Execute_Prim. + if (diff == 0xFFFFFFFF) + return; + + gstate.texlevel ^= diff; + + if (diff & 0xFF0000) { + // Piggyback on this flag for 3D textures. + gstate_c.Dirty(DIRTY_MIPBIAS); + } + if (gstate.getTexLevelMode() != GE_TEXLEVEL_MODE_AUTO && (0x00FF0000 & gstate.texlevel) != 0) { + Flush(); + } + + gstate.texlevel ^= diff; + + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE); +} + +void GPUCommonHW::Execute_LoadClut(u32 op, u32 diff) { + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes()); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 11cadb0d0a..4f02ab15ee 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -19,17 +19,27 @@ public: std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; + void Execute_VertexType(u32 op, u32 diff); + void Execute_VertexTypeSkinning(u32 op, u32 diff); + void Execute_Prim(u32 op, u32 diff); void Execute_Bezier(u32 op, u32 diff); void Execute_Spline(u32 op, u32 diff); void Execute_BlockTransferStart(u32 op, u32 diff); + void Execute_TexSize0(u32 op, u32 diff); + void Execute_TexLevel(u32 op, u32 diff); + void Execute_LoadClut(u32 op, u32 diff); + typedef void (GPUCommonHW::*CmdFunc)(u32 op, u32 diff); + void FastRunLoop(DisplayList &list) override; + void ExecuteOp(u32 op, u32 diff) override; + protected: void UpdateCmdInfo() override; - void PreExecuteOp(u32 op, u32 diff); + void PreExecuteOp(u32 op, u32 diff) override; void ClearCacheNextFrame() override; // Needs to be called on GPU thread, not reporting thread. @@ -37,9 +47,11 @@ protected: void UpdateMSAALevel(Draw::DrawContext *draw) override; void CheckRenderResized() override; + u32 CheckGPUFeaturesLate(u32 features) const; int msaaLevel_ = 0; private: void CheckDepthUsage(VirtualFramebuffer *vfb); + void CheckFlushOp(int cmd, u32 diff); }; From 71ed265c576c87e4fa8cf56668115d8c717272e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 17:40:11 +0100 Subject: [PATCH 037/141] Move sawExactEqualDepth to GPUCommonHW --- GPU/GPUCommon.cpp | 5 ----- GPU/GPUCommon.h | 3 +-- GPU/GPUCommonHW.cpp | 9 +++++++++ GPU/GPUCommonHW.h | 3 +++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 976392b818..f2f8c3c335 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -805,11 +805,6 @@ void GPUCommon::BeginFrame() { } GPUDebug::NotifyBeginFrame(); GPURecord::NotifyBeginFrame(); - - if (drawEngineCommon_->EverUsedExactEqualDepth() && !sawExactEqualDepth_) { - sawExactEqualDepth_ = true; - gstate_c.SetUseFlags(CheckGPUFeatures()); - } } void GPUCommon::SlowRunLoop(DisplayList &list) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 8407c6a520..c4d0a97a35 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -343,7 +343,6 @@ protected: bool displayResized_ = false; bool renderResized_ = false; bool configChanged_ = false; - bool sawExactEqualDepth_ = false; DrawType lastDraw_ = DRAW_UNKNOWN; GEPrimitiveType lastPrim_ = GE_PRIM_INVALID; @@ -362,7 +361,7 @@ protected: uint32_t edramTranslation_ = 0x400; - // Whe matrix data overflows, the CPU visible values wrap and bleed between matrices. + // When matrix data overflows, the CPU visible values wrap and bleed between matrices. // But this doesn't actually change the values used by rendering. // The CPU visible values affect the GPU when list contexts are restored. // Note: not maintained by all backends, here for save stating. diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 41f0a34c44..6ae48c3c5b 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -457,6 +457,15 @@ void GPUCommonHW::UpdateCmdInfo() { } } +void GPUCommonHW::BeginFrame() { + GPUCommon::BeginFrame(); + + if (drawEngineCommon_->EverUsedExactEqualDepth() && !sawExactEqualDepth_) { + sawExactEqualDepth_ = true; + gstate_c.SetUseFlags(CheckGPUFeatures()); + } +} + void GPUCommonHW::CheckFlushOp(int cmd, u32 diff) { const u8 cmdFlags = cmdInfo_[cmd].flags; if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 4f02ab15ee..a51d349868 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -13,6 +13,8 @@ public: void DoState(PointerWrap &p) override; void DeviceLost() override; + void BeginFrame() override; + u32 CheckGPUFeatures() const override; // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. @@ -50,6 +52,7 @@ protected: u32 CheckGPUFeaturesLate(u32 features) const; int msaaLevel_ = 0; + bool sawExactEqualDepth_ = false; private: void CheckDepthUsage(VirtualFramebuffer *vfb); From 8520e1dec59ca217c9e72387fc7615482ee9ce46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 17:42:46 +0100 Subject: [PATCH 038/141] Move SetDisplayFramebuffer to GPUCommonHW --- GPU/GPUCommon.cpp | 4 ---- GPU/GPUCommon.h | 1 - GPU/GPUCommonHW.cpp | 4 ++++ GPU/GPUCommonHW.h | 2 ++ 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index f2f8c3c335..f7e5864cf3 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2031,10 +2031,6 @@ void GPUCommon::SetCmdValue(u32 op) { downcount = 0; } -void GPUCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { - framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format); -} - void GPUCommon::DoBlockTransfer(u32 skipDrawReason) { u32 srcBasePtr = gstate.getTransferSrcAddress(); u32 srcStride = gstate.getTransferSrcStride(); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index c4d0a97a35..b1367275d4 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -127,7 +127,6 @@ public: uint32_t SetAddrTranslation(uint32_t value) override; uint32_t GetAddrTranslation() override; - void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput(bool reallyDirty) override = 0; bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 6ae48c3c5b..b51e552543 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -466,6 +466,10 @@ void GPUCommonHW::BeginFrame() { } } +void GPUCommonHW::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { + framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format); +} + void GPUCommonHW::CheckFlushOp(int cmd, u32 diff) { const u8 cmdFlags = cmdInfo_[cmd].flags; if (diff && (cmdFlags & FLAG_FLUSHBEFOREONCHANGE)) { diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index a51d349868..d57f8619b4 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -17,6 +17,8 @@ public: u32 CheckGPUFeatures() const override; + void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; + // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; From 807d40e09531d27a4a9b268f3b2ccd0830e954fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 17:48:37 +0100 Subject: [PATCH 039/141] Move the matrix loading exec functions too --- GPU/GPUCommon.cpp | 293 ----------------------------------------- GPU/GPUCommon.h | 11 -- GPU/GPUCommonHW.cpp | 314 ++++++++++++++++++++++++++++++++++++++++++-- GPU/GPUCommonHW.h | 11 ++ 4 files changed, 315 insertions(+), 314 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index f7e5864cf3..09b4278c7e 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1309,299 +1309,6 @@ void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { } } -void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_WORLDMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.worldMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - // We must record the individual data commands while debugRecording_. - bool fastLoad = !debugRecording_ && end > 0; - // Stalling in the middle of a matrix would be stupid, I doubt this check is necessary. - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - UpdatePC(currentList->pc, currentList->pc + count * 4); - currentList->pc += count * 4; -} - -void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.worldmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) { - Flush(); - ((u32 *)gstate.worldMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX); - } - num++; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; -} - -void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_VIEWMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.viewMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - UpdatePC(currentList->pc, currentList->pc + count * 4); - currentList->pc += count * 4; -} - -void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.viewmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) { - Flush(); - ((u32 *)gstate.viewMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX); - } - num++; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; -} - -void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_PROJMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.projMatrix + (op & 0xF)); - const int end = 16 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - UpdatePC(currentList->pc, currentList->pc + count * 4); - currentList->pc += count * 4; -} - -void GPUCommon::Execute_ProjMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.projmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 16 && newVal != ((const u32 *)gstate.projMatrix)[num]) { - Flush(); - ((u32 *)gstate.projMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX); - } - num++; - if (num <= 16) - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; -} - -void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_TGENMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.tgenMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - // We check the matrix to see if we need projection. - gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - UpdatePC(currentList->pc, currentList->pc + count * 4); - currentList->pc += count * 4; -} - -void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.texmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { - Flush(); - ((u32 *)gstate.tgenMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); // We check the matrix to see if we need projection - } - num++; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; -} - -void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F); - return; - } - - // This is almost always followed by GE_CMD_BONEMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.boneMatrix + (op & 0x7F)); - const int end = 12 * 8 - (op & 0x7F); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - // If we can't use software skinning, we have to flush and dirty. - if (!g_Config.bSoftwareSkinning) { - while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - } - if (++i >= end) { - break; - } - } - - const unsigned int numPlusCount = (op & 0x7F) + i; - for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) { - gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); - } - } else { - while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { - dst[i] = src[i] << 8; - if (++i >= end) { - break; - } - } - - const unsigned int numPlusCount = (op & 0x7F) + i; - for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) { - gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); - } - } - } - - const int count = i; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op & 0x7F) + count); - - // Skip over the loaded data, it's done now. - UpdatePC(currentList->pc, currentList->pc + count * 4); - currentList->pc += count * 4; -} - -void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.boneMatrixNumber & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) { - // Bone matrices should NOT flush when software skinning is enabled! - if (!g_Config.bSoftwareSkinning) { - Flush(); - gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); - } else { - gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); - } - ((u32 *)gstate.boneMatrix)[num] = newVal; - } - num++; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; -} - void GPUCommon::Execute_MorphWeight(u32 op, u32 diff) { gstate_c.morphWeights[(op >> 24) - GE_CMD_MORPHWEIGHT0] = getFloat24(op); } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index b1367275d4..cb84369d93 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -151,17 +151,6 @@ public: void Execute_BoundingBox(u32 op, u32 diff); - void Execute_WorldMtxNum(u32 op, u32 diff); - void Execute_WorldMtxData(u32 op, u32 diff); - void Execute_ViewMtxNum(u32 op, u32 diff); - void Execute_ViewMtxData(u32 op, u32 diff); - void Execute_ProjMtxNum(u32 op, u32 diff); - void Execute_ProjMtxData(u32 op, u32 diff); - void Execute_TgenMtxNum(u32 op, u32 diff); - void Execute_TgenMtxData(u32 op, u32 diff); - void Execute_BoneMtxNum(u32 op, u32 diff); - void Execute_BoneMtxData(u32 op, u32 diff); - void Execute_MorphWeight(u32 op, u32 diff); void Execute_ImmVertexAlphaPrim(u32 op, u32 diff); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index b51e552543..319ca86dcc 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -308,16 +308,16 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_DITH3 }, // These handle their own flushing. - { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_WorldMtxNum }, - { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_WorldMtxData }, - { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ViewMtxNum }, - { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ViewMtxData }, - { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_ProjMtxNum }, - { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_ProjMtxData }, - { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_TgenMtxNum }, - { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_TgenMtxData }, - { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommon::Execute_BoneMtxNum }, - { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommon::Execute_BoneMtxData }, + { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommonHW::Execute_WorldMtxNum }, + { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_WorldMtxData }, + { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommonHW::Execute_ViewMtxNum }, + { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_ViewMtxData }, + { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommonHW::Execute_ProjMtxNum }, + { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_ProjMtxData }, + { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommonHW::Execute_TgenMtxNum }, + { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_TgenMtxData }, + { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, 0, &GPUCommonHW::Execute_BoneMtxNum }, + { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_BoneMtxData }, // Vertex Screen/Texture/Color { GE_CMD_VSCX }, @@ -1225,3 +1225,297 @@ void GPUCommonHW::Execute_LoadClut(u32 op, u32 diff) { gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes()); } + + +void GPUCommonHW::Execute_WorldMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_WORLDMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.worldMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + // We must record the individual data commands while debugRecording_. + bool fastLoad = !debugRecording_ && end > 0; + // Stalling in the middle of a matrix would be stupid, I doubt this check is necessary. + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_WORLDMATRIX); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + UpdatePC(currentList->pc, currentList->pc + count * 4); + currentList->pc += count * 4; +} + +void GPUCommonHW::Execute_WorldMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.worldmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) { + Flush(); + ((u32 *)gstate.worldMatrix)[num] = newVal; + gstate_c.Dirty(DIRTY_WORLDMATRIX); + } + num++; + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; +} + +void GPUCommonHW::Execute_ViewMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_VIEWMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.viewMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_VIEWMATRIX); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + UpdatePC(currentList->pc, currentList->pc + count * 4); + currentList->pc += count * 4; +} + +void GPUCommonHW::Execute_ViewMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.viewmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) { + Flush(); + ((u32 *)gstate.viewMatrix)[num] = newVal; + gstate_c.Dirty(DIRTY_VIEWMATRIX); + } + num++; + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; +} + +void GPUCommonHW::Execute_ProjMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_PROJMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.projMatrix + (op & 0xF)); + const int end = 16 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_PROJMATRIX); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + UpdatePC(currentList->pc, currentList->pc + count * 4); + currentList->pc += count * 4; +} + +void GPUCommonHW::Execute_ProjMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.projmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 16 && newVal != ((const u32 *)gstate.projMatrix)[num]) { + Flush(); + ((u32 *)gstate.projMatrix)[num] = newVal; + gstate_c.Dirty(DIRTY_PROJMATRIX); + } + num++; + if (num <= 16) + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; +} + +void GPUCommonHW::Execute_TgenMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_TGENMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.tgenMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + // We check the matrix to see if we need projection. + gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + UpdatePC(currentList->pc, currentList->pc + count * 4); + currentList->pc += count * 4; +} + +void GPUCommonHW::Execute_TgenMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.texmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { + Flush(); + ((u32 *)gstate.tgenMatrix)[num] = newVal; + gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); // We check the matrix to see if we need projection + } + num++; + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; +} + +void GPUCommonHW::Execute_BoneMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F); + return; + } + + // This is almost always followed by GE_CMD_BONEMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.boneMatrix + (op & 0x7F)); + const int end = 12 * 8 - (op & 0x7F); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + // If we can't use software skinning, we have to flush and dirty. + if (!g_Config.bSoftwareSkinning) { + while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + } + if (++i >= end) { + break; + } + } + + const unsigned int numPlusCount = (op & 0x7F) + i; + for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) { + gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); + } + } else { + while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { + dst[i] = src[i] << 8; + if (++i >= end) { + break; + } + } + + const unsigned int numPlusCount = (op & 0x7F) + i; + for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) { + gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); + } + } + } + + const int count = i; + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op & 0x7F) + count); + + // Skip over the loaded data, it's done now. + UpdatePC(currentList->pc, currentList->pc + count * 4); + currentList->pc += count * 4; +} + +void GPUCommonHW::Execute_BoneMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.boneMatrixNumber & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) { + // Bone matrices should NOT flush when software skinning is enabled! + if (!g_Config.bSoftwareSkinning) { + Flush(); + gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); + } else { + gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); + } + ((u32 *)gstate.boneMatrix)[num] = newVal; + } + num++; + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index d57f8619b4..983712f48f 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -35,6 +35,17 @@ public: void Execute_TexLevel(u32 op, u32 diff); void Execute_LoadClut(u32 op, u32 diff); + void Execute_WorldMtxNum(u32 op, u32 diff); + void Execute_WorldMtxData(u32 op, u32 diff); + void Execute_ViewMtxNum(u32 op, u32 diff); + void Execute_ViewMtxData(u32 op, u32 diff); + void Execute_ProjMtxNum(u32 op, u32 diff); + void Execute_ProjMtxData(u32 op, u32 diff); + void Execute_TgenMtxNum(u32 op, u32 diff); + void Execute_TgenMtxData(u32 op, u32 diff); + void Execute_BoneMtxNum(u32 op, u32 diff); + void Execute_BoneMtxData(u32 op, u32 diff); + typedef void (GPUCommonHW::*CmdFunc)(u32 op, u32 diff); void FastRunLoop(DisplayList &list) override; From c3ceb9bf708827df404722dce05a68baec6dc44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 18:17:28 +0100 Subject: [PATCH 040/141] Move assorted debug functions and similar to GPUCommonHW --- GPU/GPUCommon.cpp | 77 --------------------------------------------- GPU/GPUCommon.h | 12 ------- GPU/GPUCommonHW.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++ GPU/GPUCommonHW.h | 14 ++++++++- 4 files changed, 89 insertions(+), 90 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 09b4278c7e..562c93e52d 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -42,7 +42,6 @@ #include "Core/HW/Display.h" #include "Core/MemMapHelpers.h" #include "Core/Util/PPGeDraw.h" -#include "GPU/GPUCommonHW.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/SplineCommon.h" @@ -2011,21 +2010,6 @@ bool GPUCommon::PerformWriteColorFromMemory(u32 dest, int size) { return false; } -void GPUCommon::InvalidateCache(u32 addr, int size, GPUInvalidationType type) { - if (size > 0) - textureCache_->Invalidate(addr, size, type); - else - textureCache_->InvalidateAll(type); - - if (type != GPU_INVALIDATE_ALL && framebufferManager_->MayIntersectFramebuffer(addr)) { - // Vempire invalidates (with writeback) after drawing, but before blitting. - // TODO: Investigate whether we can get this to work some other way. - if (type == GPU_INVALIDATE_SAFE) { - framebufferManager_->UpdateFromMemory(addr, size); - } - } -} - void GPUCommon::PerformWriteFormattedFromMemory(u32 addr, int size, int frameWidth, GEBufferFormat format) { if (Memory::IsVRAMAddress(addr)) { framebufferManager_->PerformWriteFormattedFromMemory(addr, size, frameWidth, format); @@ -2042,36 +2026,6 @@ bool GPUCommon::PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil f return false; } -bool GPUCommon::GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes) { - u32 fb_address = type == GPU_DBG_FRAMEBUF_RENDER ? (gstate.getFrameBufRawAddress() | 0x04000000) : framebufferManager_->DisplayFramebufAddr(); - int fb_stride = type == GPU_DBG_FRAMEBUF_RENDER ? gstate.FrameBufStride() : framebufferManager_->DisplayFramebufStride(); - GEBufferFormat format = type == GPU_DBG_FRAMEBUF_RENDER ? gstate_c.framebufFormat : framebufferManager_->DisplayFramebufFormat(); - return framebufferManager_->GetFramebuffer(fb_address, fb_stride, format, buffer, maxRes); -} - -bool GPUCommon::GetCurrentDepthbuffer(GPUDebugBuffer &buffer) { - u32 fb_address = gstate.getFrameBufRawAddress() | 0x04000000; - int fb_stride = gstate.FrameBufStride(); - - u32 z_address = gstate.getDepthBufRawAddress() | 0x04000000; - int z_stride = gstate.DepthBufStride(); - - return framebufferManager_->GetDepthbuffer(fb_address, fb_stride, z_address, z_stride, buffer); -} - -bool GPUCommon::GetCurrentStencilbuffer(GPUDebugBuffer &buffer) { - u32 fb_address = gstate.getFrameBufRawAddress() | 0x04000000; - int fb_stride = gstate.FrameBufStride(); - - return framebufferManager_->GetStencilbuffer(fb_address, fb_stride, buffer); -} - -bool GPUCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) { - // framebufferManager_ can be null here when taking screens in software rendering mode. - // TODO: Actually grab the framebuffer anyway. - return framebufferManager_ ? framebufferManager_->GetOutputFramebuffer(buffer) : false; -} - std::vector GPUCommon::GetFramebufferList() const { return framebufferManager_->GetFramebufferList(); } @@ -2081,17 +2035,6 @@ bool GPUCommon::GetCurrentSimpleVertices(int count, std::vector return drawEngineCommon_->GetCurrentSimpleVertices(count, vertices, indices); } -bool GPUCommon::GetCurrentClut(GPUDebugBuffer &buffer) { - return textureCache_->GetCurrentClutBuffer(buffer); -} - -bool GPUCommon::GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { - if (!gstate.isTextureMapEnabled()) { - return false; - } - return textureCache_->GetCurrentTextureDebug(buffer, level, isFramebuffer); -} - bool GPUCommon::DescribeCodePtr(const u8 *ptr, std::string &name) { if (drawEngineCommon_->IsCodePtrVertexDecoder(ptr)) { name = "VertexDecoderJit"; @@ -2100,26 +2043,6 @@ bool GPUCommon::DescribeCodePtr(const u8 *ptr, std::string &name) { return false; } -bool GPUCommon::FramebufferDirty() { - VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); - if (vfb) { - bool dirty = vfb->dirtyAfterDisplay; - vfb->dirtyAfterDisplay = false; - return dirty; - } - return true; -} - -bool GPUCommon::FramebufferReallyDirty() { - VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); - if (vfb) { - bool dirty = vfb->reallyDirtyAfterDisplay; - vfb->reallyDirtyAfterDisplay = false; - return dirty; - } - return true; -} - void GPUCommon::UpdateUVScaleOffset() { #ifdef _M_SSE __m128i values = _mm_slli_epi32(_mm_load_si128((const __m128i *) & gstate.texscaleu), 8); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index cb84369d93..0927339ab7 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -133,7 +133,6 @@ public: bool PerformReadbackToMemory(u32 dest, int size) override; bool PerformWriteColorFromMemory(u32 dest, int size) override; - void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format) override; bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) override; @@ -178,13 +177,7 @@ public: // From GPUDebugInterface. bool GetCurrentDisplayList(DisplayList &list) override; - bool GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes) override; - bool GetCurrentDepthbuffer(GPUDebugBuffer &buffer) override; - bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer) override; - bool GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; - bool GetCurrentClut(GPUDebugBuffer &buffer) override; bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) override; - bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override; std::vector DebugGetShaderIDs(DebugShaderType shader) override { return std::vector(); }; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override { @@ -227,11 +220,6 @@ public: return -1; } - bool FramebufferDirty() override; - bool FramebufferReallyDirty() override; - - typedef void (GPUCommon::*CmdFunc)(u32 op, u32 diff); - void GetReportingInfo(std::string &primaryInfo, std::string &fullInfo) override { primaryInfo = reportingPrimaryInfo_; fullInfo = reportingFullInfo_; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 319ca86dcc..7bea5851be 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -648,6 +648,47 @@ std::string GPUCommonHW::DebugGetShaderString(std::string id, DebugShaderType ty } } +bool GPUCommonHW::GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes) { + u32 fb_address = type == GPU_DBG_FRAMEBUF_RENDER ? (gstate.getFrameBufRawAddress() | 0x04000000) : framebufferManager_->DisplayFramebufAddr(); + int fb_stride = type == GPU_DBG_FRAMEBUF_RENDER ? gstate.FrameBufStride() : framebufferManager_->DisplayFramebufStride(); + GEBufferFormat format = type == GPU_DBG_FRAMEBUF_RENDER ? gstate_c.framebufFormat : framebufferManager_->DisplayFramebufFormat(); + return framebufferManager_->GetFramebuffer(fb_address, fb_stride, format, buffer, maxRes); +} + +bool GPUCommonHW::GetCurrentDepthbuffer(GPUDebugBuffer &buffer) { + u32 fb_address = gstate.getFrameBufRawAddress() | 0x04000000; + int fb_stride = gstate.FrameBufStride(); + + u32 z_address = gstate.getDepthBufRawAddress() | 0x04000000; + int z_stride = gstate.DepthBufStride(); + + return framebufferManager_->GetDepthbuffer(fb_address, fb_stride, z_address, z_stride, buffer); +} + +bool GPUCommonHW::GetCurrentStencilbuffer(GPUDebugBuffer &buffer) { + u32 fb_address = gstate.getFrameBufRawAddress() | 0x04000000; + int fb_stride = gstate.FrameBufStride(); + + return framebufferManager_->GetStencilbuffer(fb_address, fb_stride, buffer); +} + +bool GPUCommonHW::GetOutputFramebuffer(GPUDebugBuffer &buffer) { + // framebufferManager_ can be null here when taking screens in software rendering mode. + // TODO: Actually grab the framebuffer anyway. + return framebufferManager_ ? framebufferManager_->GetOutputFramebuffer(buffer) : false; +} + +bool GPUCommonHW::GetCurrentClut(GPUDebugBuffer &buffer) { + return textureCache_->GetCurrentClutBuffer(buffer); +} + +bool GPUCommonHW::GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { + if (!gstate.isTextureMapEnabled()) { + return false; + } + return textureCache_->GetCurrentTextureDebug(buffer, level, isFramebuffer); +} + void GPUCommonHW::CheckDepthUsage(VirtualFramebuffer *vfb) { if (!gstate_c.usingDepth) { bool isReadingDepth = false; @@ -673,6 +714,41 @@ void GPUCommonHW::CheckDepthUsage(VirtualFramebuffer *vfb) { } } +void GPUCommonHW::InvalidateCache(u32 addr, int size, GPUInvalidationType type) { + if (size > 0) + textureCache_->Invalidate(addr, size, type); + else + textureCache_->InvalidateAll(type); + + if (type != GPU_INVALIDATE_ALL && framebufferManager_->MayIntersectFramebuffer(addr)) { + // Vempire invalidates (with writeback) after drawing, but before blitting. + // TODO: Investigate whether we can get this to work some other way. + if (type == GPU_INVALIDATE_SAFE) { + framebufferManager_->UpdateFromMemory(addr, size); + } + } +} + +bool GPUCommonHW::FramebufferDirty() { + VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); + if (vfb) { + bool dirty = vfb->dirtyAfterDisplay; + vfb->dirtyAfterDisplay = false; + return dirty; + } + return true; +} + +bool GPUCommonHW::FramebufferReallyDirty() { + VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); + if (vfb) { + bool dirty = vfb->reallyDirtyAfterDisplay; + vfb->reallyDirtyAfterDisplay = false; + return dirty; + } + return true; +} + void GPUCommonHW::ExecuteOp(u32 op, u32 diff) { const u8 cmd = op >> 24; const CommandInfo info = cmdInfo_[cmd]; diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 983712f48f..97276e0116 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -17,12 +17,24 @@ public: u32 CheckGPUFeatures() const override; - void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; + // From GPUDebugInterface. + bool GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes) override; + bool GetCurrentDepthbuffer(GPUDebugBuffer &buffer) override; + bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer) override; + bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override; + bool GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; + bool GetCurrentClut(GPUDebugBuffer &buffer) override; // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override; + void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; + void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; + + bool FramebufferDirty() override; + bool FramebufferReallyDirty() override; + void Execute_VertexType(u32 op, u32 diff); void Execute_VertexTypeSkinning(u32 op, u32 diff); From 526ba74d059a8a21d50d18e9e94624518281d9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 18:27:16 +0100 Subject: [PATCH 041/141] Move the shaderManager_ to GPUCommonHW --- GPU/GPUCommon.h | 1 - GPU/GPUCommonHW.h | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 0927339ab7..05fedca897 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -287,7 +287,6 @@ protected: // TODO: These should live in GPUCommonHW. FramebufferManagerCommon *framebufferManager_ = nullptr; TextureCacheCommon *textureCache_ = nullptr; - ShaderManagerCommon *shaderManager_ = nullptr; bool flushOnParams_ = true; diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 97276e0116..b6e1454c19 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -63,6 +63,10 @@ public: void FastRunLoop(DisplayList &list) override; void ExecuteOp(u32 op, u32 diff) override; +private: + void CheckDepthUsage(VirtualFramebuffer *vfb); + void CheckFlushOp(int cmd, u32 diff); + protected: void UpdateCmdInfo() override; @@ -78,8 +82,5 @@ protected: int msaaLevel_ = 0; bool sawExactEqualDepth_ = false; - -private: - void CheckDepthUsage(VirtualFramebuffer *vfb); - void CheckFlushOp(int cmd, u32 diff); + ShaderManagerCommon *shaderManager_ = nullptr; }; From af2fd7b038aa58c297b9176752d2b6f2758a0544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 18:31:13 +0100 Subject: [PATCH 042/141] Move Vulkan clear detection logic into DrawEngine like the other backends. --- GPU/Vulkan/DrawEngineVulkan.cpp | 20 +++++++++++++++----- GPU/Vulkan/FramebufferManagerVulkan.cpp | 19 ------------------- GPU/Vulkan/FramebufferManagerVulkan.h | 4 ---- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index c4a3be72de..78f561376e 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -983,11 +983,21 @@ void DrawEngineVulkan::DoFlush() { } } else if (result.action == SW_CLEAR) { // Note: we won't get here if the clear is alpha but not color, or color but not alpha. - - // We let the framebuffer manager handle the clear. It can use renderpasses to optimize on tilers. - // If non-buffered though, it'll just do a plain clear. - framebufferManager_->NotifyClear(gstate.isClearModeColorMask(), gstate.isClearModeAlphaMask(), gstate.isClearModeDepthMask(), result.color, result.depth); - + bool clearColor = gstate.isClearModeColorMask(); + bool clearAlpha = gstate.isClearModeAlphaMask(); // and stencil + bool clearDepth = gstate.isClearModeDepthMask(); + int mask = 0; + // The Clear detection takes care of doing a regular draw instead if separate masking + // of color and alpha is needed, so we can just treat them as the same. + if (clearColor || clearAlpha) mask |= Draw::FBChannel::FB_COLOR_BIT; + if (clearDepth) mask |= Draw::FBChannel::FB_DEPTH_BIT; + if (clearAlpha) mask |= Draw::FBChannel::FB_STENCIL_BIT; + // Note that since the alpha channel and the stencil channel are shared on the PSP, + // when we clear alpha, we also clear stencil to the same value. + draw_->Clear(mask, result.color, result.depth, result.color >> 24); + if (clearColor || clearAlpha) { + framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); + } if (gstate_c.Use(GPU_USE_CLEAR_RAM_HACK) && gstate.isClearModeColorMask() && (gstate.isClearModeAlphaMask() || gstate.FrameBufFormat() == GE_FORMAT_565)) { int scissorX1 = gstate.getScissorX1(); int scissorY1 = gstate.getScissorY1(); diff --git a/GPU/Vulkan/FramebufferManagerVulkan.cpp b/GPU/Vulkan/FramebufferManagerVulkan.cpp index d5d4bdae50..0cc7c5c9ac 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.cpp +++ b/GPU/Vulkan/FramebufferManagerVulkan.cpp @@ -26,22 +26,3 @@ FramebufferManagerVulkan::FramebufferManagerVulkan(Draw::DrawContext *draw) : FramebufferManagerCommon(draw) { presentation_->SetLanguage(GLSL_VULKAN); } - -void FramebufferManagerVulkan::NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth) { - int mask = 0; - // The Clear detection takes care of doing a regular draw instead if separate masking - // of color and alpha is needed, so we can just treat them as the same. - if (clearColor || clearAlpha) - mask |= Draw::FBChannel::FB_COLOR_BIT; - if (clearDepth) - mask |= Draw::FBChannel::FB_DEPTH_BIT; - if (clearAlpha) - mask |= Draw::FBChannel::FB_STENCIL_BIT; - - // Note that since the alpha channel and the stencil channel are shared on the PSP, - // when we clear alpha, we also clear stencil to the same value. - draw_->Clear(mask, color, depth, color >> 24); - if (clearColor || clearAlpha) { - SetColorUpdated(gstate_c.skipDrawReason); - } -} diff --git a/GPU/Vulkan/FramebufferManagerVulkan.h b/GPU/Vulkan/FramebufferManagerVulkan.h index c2e575f0b2..2abb6008ce 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.h +++ b/GPU/Vulkan/FramebufferManagerVulkan.h @@ -25,8 +25,4 @@ class FramebufferManagerVulkan : public FramebufferManagerCommon { public: explicit FramebufferManagerVulkan(Draw::DrawContext *draw); - - // If within a render pass, this will just issue a regular clear. If beginning a new render pass, - // do that. - void NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth); }; From 4c45f8a4b006c6bf5ef2d18ffbd27d3875a971b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 23:04:27 +0100 Subject: [PATCH 043/141] Pass in draw directly in GPUCommon::DeviceRestore, instead of awkwardly fetching it --- GPU/D3D11/GPU_D3D11.cpp | 4 ++-- GPU/D3D11/GPU_D3D11.h | 2 +- GPU/GLES/GPU_GLES.cpp | 4 ++-- GPU/GLES/GPU_GLES.h | 2 +- GPU/GPUCommon.cpp | 8 ++------ GPU/GPUCommon.h | 3 +-- GPU/GPUCommonHW.cpp | 4 ++++ GPU/GPUCommonHW.h | 1 + GPU/GPUInterface.h | 2 +- GPU/Software/SoftGpu.cpp | 5 ++--- GPU/Software/SoftGpu.h | 3 ++- GPU/Vulkan/GPU_Vulkan.cpp | 4 ++-- GPU/Vulkan/GPU_Vulkan.h | 2 +- GPU/Vulkan/TextureCacheVulkan.h | 4 ++-- UI/NativeApp.cpp | 2 +- libretro/LibretroGraphicsContext.cpp | 2 +- 16 files changed, 26 insertions(+), 26 deletions(-) diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 28043a1d68..c92b8ad2f5 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -122,8 +122,8 @@ void GPU_D3D11::DeviceLost() { GPUCommonHW::DeviceLost(); } -void GPU_D3D11::DeviceRestore() { - GPUCommonHW::DeviceRestore(); +void GPU_D3D11::DeviceRestore(Draw::DrawContext *draw) { + GPUCommonHW::DeviceRestore(draw); // Nothing needed. } diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index 782ef7f029..a2e374c6f0 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -38,7 +38,7 @@ public: void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. - void DeviceRestore() override; + void DeviceRestore(Draw::DrawContext *draw) override; protected: void FinishDeferred() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 756cf81937..7e362b0697 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -244,8 +244,8 @@ void GPU_GLES::DeviceLost() { GPUCommonHW::DeviceLost(); } -void GPU_GLES::DeviceRestore() { - GPUCommonHW::DeviceRestore(); +void GPU_GLES::DeviceRestore(Draw::DrawContext *draw) { + GPUCommonHW::DeviceRestore(draw); UpdateCmdInfo(); UpdateVsyncInterval(true); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index a56765a580..9e03285ee9 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -46,7 +46,7 @@ public: void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. - void DeviceRestore() override; + void DeviceRestore(Draw::DrawContext *draw) override; void BeginHostFrame() override; void EndHostFrame() override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 562c93e52d..40990eb41b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -133,8 +133,8 @@ void GPUCommon::DeviceLost() { } // Call at the start of the GPU implementation's DeviceRestore -void GPUCommon::DeviceRestore() { - draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext(); +void GPUCommon::DeviceRestore(Draw::DrawContext *draw) { + draw_ = draw; framebufferManager_->DeviceRestore(draw_); PPGeSetDrawContext(draw_); } @@ -2026,10 +2026,6 @@ bool GPUCommon::PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil f return false; } -std::vector GPUCommon::GetFramebufferList() const { - return framebufferManager_->GetFramebufferList(); -} - bool GPUCommon::GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) { UpdateUVScaleOffset(); return drawEngineCommon_->GetCurrentSimpleVertices(count, vertices, indices); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 05fedca897..b58923013e 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -211,7 +211,6 @@ public: const std::list& GetDisplayLists() override { return dlQueue; } - std::vector GetFramebufferList() const override; s64 GetListTicks(int listid) const override { if (listid >= 0 && listid < DisplayListMaxCount) { @@ -227,7 +226,7 @@ public: protected: void DeviceLost() override; - void DeviceRestore() override; + void DeviceRestore(Draw::DrawContext *draw) override; void ClearCacheNextFrame() override {} diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 7bea5851be..6a1771875d 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -678,6 +678,10 @@ bool GPUCommonHW::GetOutputFramebuffer(GPUDebugBuffer &buffer) { return framebufferManager_ ? framebufferManager_->GetOutputFramebuffer(buffer) : false; } +std::vector GPUCommonHW::GetFramebufferList() const { + return framebufferManager_->GetFramebufferList(); +} + bool GPUCommonHW::GetCurrentClut(GPUDebugBuffer &buffer) { return textureCache_->GetCurrentClutBuffer(buffer); } diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index b6e1454c19..ca3fe1780d 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -22,6 +22,7 @@ public: bool GetCurrentDepthbuffer(GPUDebugBuffer &buffer) override; bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer) override; bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override; + std::vector GetFramebufferList() const override; bool GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; bool GetCurrentClut(GPUDebugBuffer &buffer) override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 6bf3856e16..6788e595d7 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -251,7 +251,7 @@ public: virtual void EnableInterrupts(bool enable) = 0; virtual void DeviceLost() = 0; - virtual void DeviceRestore() = 0; + virtual void DeviceRestore(Draw::DrawContext *draw) = 0; virtual void ReapplyGfxState() = 0; virtual void DoState(PointerWrap &p) = 0; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 56e9a3b60a..2a3bf685f7 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -466,9 +466,8 @@ void SoftGPU::DeviceLost() { } } -void SoftGPU::DeviceRestore() { - if (PSP_CoreParameter().graphicsContext) - draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext(); +void SoftGPU::DeviceRestore(Draw::DrawContext *draw) { + draw_ = draw; if (presentation_) presentation_->DeviceRestore(draw_); PPGeSetDrawContext(draw_); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 61104a04bd..0377ca7c1b 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -138,6 +138,7 @@ public: void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput(bool reallyDirty) override; void GetStats(char *buffer, size_t bufsize) override; + std::vector GetFramebufferList() const override { return std::vector(); } void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format) override; bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; @@ -147,7 +148,7 @@ public: bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) override; void DeviceLost() override; - void DeviceRestore() override; + void DeviceRestore(Draw::DrawContext *draw) override; void NotifyRenderResized() override; void NotifyDisplayResized() override; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 532811f365..ee7500ea96 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -460,8 +460,8 @@ void GPU_Vulkan::DeviceLost() { GPUCommonHW::DeviceLost(); } -void GPU_Vulkan::DeviceRestore() { - GPUCommonHW::DeviceRestore(); +void GPU_Vulkan::DeviceRestore(Draw::DrawContext *draw) { + GPUCommonHW::DeviceRestore(draw); InitDeviceObjects(); gstate_c.SetUseFlags(CheckGPUFeatures()); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 1e52e8e0de..1fad919ab1 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -49,7 +49,7 @@ public: void GetStats(char *buffer, size_t bufsize) override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. - void DeviceRestore() override; + void DeviceRestore(Draw::DrawContext *draw) override; // Using string because it's generic - makes no assumptions on the size of the shader IDs of this backend. std::vector DebugGetShaderIDs(DebugShaderType shader) override; diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 2c7c149ef6..2a6fb5d2a9 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -59,8 +59,8 @@ public: void StartFrame() override; - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; void SetFramebufferManager(FramebufferManagerVulkan *fbManager); void SetDrawEngine(DrawEngineVulkan *td) { diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 65fd9fac70..c89c7ec122 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -914,7 +914,7 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { g_gameInfoCache = new GameInfoCache(); if (gpu) { - gpu->DeviceRestore(); + gpu->DeviceRestore(g_draw); } INFO_LOG(SYSTEM, "NativeInitGraphics completed"); diff --git a/libretro/LibretroGraphicsContext.cpp b/libretro/LibretroGraphicsContext.cpp index ca11400491..afd93be889 100644 --- a/libretro/LibretroGraphicsContext.cpp +++ b/libretro/LibretroGraphicsContext.cpp @@ -58,7 +58,7 @@ void LibretroHWRenderContext::ContextReset() { GotBackbuffer(); if (gpu) { - gpu->DeviceRestore(); + gpu->DeviceRestore(draw_); } } From 3dc47c7fef858b5ffd30872b1636f5564ddee741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 25 Feb 2023 23:20:41 +0100 Subject: [PATCH 044/141] Unify TextureCache ::StartFrame as much as possible. Tiny optimization avoiding a Vulkan pool reset on most frames. --- GPU/Common/TextureCacheCommon.cpp | 15 +++++++++++++++ GPU/D3D11/TextureCacheD3D11.cpp | 19 ------------------- GPU/D3D11/TextureCacheD3D11.h | 2 -- GPU/Directx9/TextureCacheDX9.cpp | 16 +--------------- GPU/GLES/TextureCacheGLES.cpp | 15 --------------- GPU/Vulkan/TextureCacheVulkan.cpp | 23 ++--------------------- GPU/Vulkan/VulkanUtil.cpp | 10 +++++++--- GPU/Vulkan/VulkanUtil.h | 1 + 8 files changed, 26 insertions(+), 75 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 5a88a718b0..c7985e82fa 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -139,7 +139,22 @@ TextureCacheCommon::~TextureCacheCommon() { } void TextureCacheCommon::StartFrame() { + ForgetLastTexture(); textureShaderCache_->Decimate(); + timesInvalidatedAllThisFrame_ = 0; + replacementTimeThisFrame_ = 0.0; + + if (texelsScaledThisFrame_) { + VERBOSE_LOG(G3D, "Scaled %d texels", texelsScaledThisFrame_); + } + texelsScaledThisFrame_ = 0; + + if (clearCacheNextFrame_) { + Clear(true); + clearCacheNextFrame_ = false; + } else { + Decimate(false); + } } // Produces a signed 1.23.8 value. diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index c8d6b9194a..047a3504f9 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -185,25 +185,6 @@ void TextureCacheD3D11::ForgetLastTexture() { context_->PSSetShaderResources(0, 4, nullTex); } -void TextureCacheD3D11::StartFrame() { - TextureCacheCommon::StartFrame(); - - lastBoundTexture = INVALID_TEX; - timesInvalidatedAllThisFrame_ = 0; - replacementTimeThisFrame_ = 0.0; - - if (texelsScaledThisFrame_) { - // INFO_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } - texelsScaledThisFrame_ = 0; - if (clearCacheNextFrame_) { - Clear(true); - clearCacheNextFrame_ = false; - } else { - Decimate(); - } -} - void TextureCacheD3D11::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { const u32 clutBaseBytes = clutBase * (clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16)); // Technically, these extra bytes weren't loaded, but hopefully it was loaded earlier. diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 8c2ff77f62..35f14d5a6f 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -46,8 +46,6 @@ public: TextureCacheD3D11(Draw::DrawContext *draw, Draw2D *draw2D); ~TextureCacheD3D11(); - void StartFrame() override; - void SetFramebufferManager(FramebufferManagerD3D11 *fbManager); void ForgetLastTexture() override; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index d615c4e330..4e349fbe73 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -154,22 +154,8 @@ void TextureCacheDX9::ApplySamplingParams(const SamplerCacheKey &key) { void TextureCacheDX9::StartFrame() { TextureCacheCommon::StartFrame(); - lastBoundTexture = nullptr; - timesInvalidatedAllThisFrame_ = 0; - replacementTimeThisFrame_ = 0.0; - - if (texelsScaledThisFrame_) { - VERBOSE_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } - texelsScaledThisFrame_ = 0; - if (clearCacheNextFrame_) { - Clear(true); - clearCacheNextFrame_ = false; - } else { - Decimate(); - } - if (gstate_c.Use(GPU_USE_ANISOTROPY)) { + // Just take the opportunity to set the global aniso level here, once per frame. DWORD aniso = 1 << g_Config.iAnisotropyLevel; DWORD anisotropyLevel = aniso > maxAnisotropyLevel ? maxAnisotropyLevel : aniso; device_->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, anisotropyLevel); diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index c4ed59221d..aacba8e3d6 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -143,10 +143,6 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, Draw::DataFormat dst void TextureCacheGLES::StartFrame() { TextureCacheCommon::StartFrame(); - ForgetLastTexture(); - timesInvalidatedAllThisFrame_ = 0; - replacementTimeThisFrame_ = 0.0; - GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); if (!lowMemoryMode_ && renderManager->SawOutOfMemory()) { lowMemoryMode_ = true; @@ -159,17 +155,6 @@ void TextureCacheGLES::StartFrame() { host->NotifyUserMessage(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f); } } - - if (texelsScaledThisFrame_) { - VERBOSE_LOG(G3D, "Scaled %i texels", texelsScaledThisFrame_); - } - texelsScaledThisFrame_ = 0; - if (clearCacheNextFrame_) { - Clear(true); - clearCacheNextFrame_ = false; - } else { - Decimate(); - } } void TextureCacheGLES::UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) { diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index d47ed1ee2d..1444c6abca 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -327,27 +327,8 @@ static const VkFilter MagFiltVK[2] = { void TextureCacheVulkan::StartFrame() { TextureCacheCommon::StartFrame(); - - textureShaderCache_->Decimate(); - - timesInvalidatedAllThisFrame_ = 0; - texelsScaledThisFrame_ = 0; - replacementTimeThisFrame_ = 0.0; - - if (clearCacheNextFrame_) { - Clear(true); - clearCacheNextFrame_ = false; - } else { - int slabPressureLimit = TEXCACHE_SLAB_PRESSURE; - if (g_Config.iTexScalingLevel > 1) { - // Since textures are 2D maybe we should square this, but might get too non-aggressive. - slabPressureLimit *= g_Config.iTexScalingLevel; - } - // TODO: Use some indication from VMA. - // Maybe see https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/staying_within_budget.html#staying_within_budget_querying_for_budget . - Decimate(false); - } - + // TODO: For low memory detection, maybe use some indication from VMA. + // Maybe see https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/staying_within_budget.html#staying_within_budget_querying_for_budget . computeShaderManager_.BeginFrame(); } diff --git a/GPU/Vulkan/VulkanUtil.cpp b/GPU/Vulkan/VulkanUtil.cpp index 0d87ebc153..d10749b187 100644 --- a/GPU/Vulkan/VulkanUtil.cpp +++ b/GPU/Vulkan/VulkanUtil.cpp @@ -52,8 +52,7 @@ VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits } } -VulkanComputeShaderManager::VulkanComputeShaderManager(VulkanContext *vulkan) : vulkan_(vulkan), pipelines_(8) { -} +VulkanComputeShaderManager::VulkanComputeShaderManager(VulkanContext *vulkan) : vulkan_(vulkan), pipelines_(8) {} VulkanComputeShaderManager::~VulkanComputeShaderManager() {} void VulkanComputeShaderManager::InitDeviceObjects(Draw::DrawContext *draw) { @@ -96,6 +95,7 @@ void VulkanComputeShaderManager::InitDeviceObjects(Draw::DrawContext *draw) { for (int i = 0; i < ARRAY_SIZE(frameData_); i++) { frameData_[i].descPool.Create(vulkan_, dp, dpTypes); + frameData_[i].descPoolUsed = false; } VkPushConstantRange push = {}; @@ -137,6 +137,7 @@ void VulkanComputeShaderManager::DestroyDeviceObjects() { VkDescriptorSet VulkanComputeShaderManager::GetDescriptorSet(VkImageView image, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, VkBuffer buffer2, VkDeviceSize offset2, VkDeviceSize range2) { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; + frameData.descPoolUsed = true; VkDescriptorSet desc = frameData.descPool.Allocate(1, &descriptorSetLayout_, "compute_descset"); _assert_(desc != VK_NULL_HANDLE); @@ -206,5 +207,8 @@ VkPipeline VulkanComputeShaderManager::GetPipeline(VkShaderModule cs) { void VulkanComputeShaderManager::BeginFrame() { int curFrame = vulkan_->GetCurFrame(); FrameData &frame = frameData_[curFrame]; - frame.descPool.Reset(); + if (frame.descPoolUsed) { + frame.descPool.Reset(); + frame.descPoolUsed = false; + } } diff --git a/GPU/Vulkan/VulkanUtil.h b/GPU/Vulkan/VulkanUtil.h index 2046f6201f..75ed153ad3 100644 --- a/GPU/Vulkan/VulkanUtil.h +++ b/GPU/Vulkan/VulkanUtil.h @@ -77,6 +77,7 @@ private: } VulkanDescSetPool descPool; + bool descPoolUsed = false; }; FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; From 231f4efbbb4d59088dbbde51b87377332ede35ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 26 Feb 2023 10:33:11 +0100 Subject: [PATCH 045/141] Move some more stuff to GPUCommonHW --- GPU/GPUCommon.cpp | 68 ---------------------------------------- GPU/GPUCommon.h | 6 ---- GPU/GPUCommonHW.cpp | 57 ++++++++++++++++++++++++++++++++- GPU/GPUCommonHW.h | 2 ++ GPU/Software/SoftGpu.cpp | 2 ++ 5 files changed, 60 insertions(+), 75 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 40990eb41b..884b06816b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -12,12 +12,9 @@ #endif #include -#include -#include #include "Common/Profiler/Profiler.h" -#include "Common/Data/Convert/ColorConv.h" #include "Common/GraphicsContext.h" #include "Common/LogReporting.h" #include "Common/Serialize/Serializer.h" @@ -41,10 +38,8 @@ #include "Core/HLE/sceGe.h" #include "Core/HW/Display.h" #include "Core/MemMapHelpers.h" -#include "Core/Util/PPGeDraw.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" -#include "GPU/Common/SplineCommon.h" #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" @@ -74,13 +69,6 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : UpdateVsyncInterval(true); ResetMatrices(); - - PPGeSetDrawContext(draw); -} - -GPUCommon::~GPUCommon() { - // Probably not necessary. - PPGeSetDrawContext(nullptr); } void GPUCommon::BeginHostFrame() { @@ -126,19 +114,6 @@ void GPUCommon::Reinitialize() { framebufferManager_->DestroyAllFBOs(); } -// Call at the END of the GPU implementation's DeviceLost -void GPUCommon::DeviceLost() { - framebufferManager_->DeviceLost(); - draw_ = nullptr; -} - -// Call at the start of the GPU implementation's DeviceRestore -void GPUCommon::DeviceRestore(Draw::DrawContext *draw) { - draw_ = draw; - framebufferManager_->DeviceRestore(draw_); - PPGeSetDrawContext(draw_); -} - void GPUCommon::UpdateVsyncInterval(bool force) { #if !(PPSSPP_PLATFORM(ANDROID) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) || PPSSPP_PLATFORM(IOS)) int desiredVSyncInterval = g_Config.bVSync ? 1 : 0; @@ -2053,46 +2028,3 @@ void GPUCommon::UpdateUVScaleOffset() { gstate_c.uv.vOff = getFloat24(gstate.texoffsetv); #endif } - -size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) { - float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f; - return snprintf(buffer, size, - "DL processing time: %0.2f ms, %d drawsync, %d listsync\n" - "Draw calls: %d, flushes %d, clears %d (cached: %d)\n" - "Num Tracked Vertex Arrays: %d\n" - "Vertices: %d cached: %d uncached: %d\n" - "FBOs active: %d (evaluations: %d)\n" - "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n" - "readbacks %d (%d non-block), uploads %d, depal %d\n" - "Copies: depth %d, color %d, reint %d, blend %d, selftex %d\n" - "GPU cycles executed: %d (%f per vertex)\n", - gpuStats.msProcessingDisplayLists * 1000.0f, - gpuStats.numDrawSyncs, - gpuStats.numListSyncs, - gpuStats.numDrawCalls, - gpuStats.numFlushes, - gpuStats.numClears, - gpuStats.numCachedDrawCalls, - gpuStats.numTrackedVertexArrays, - gpuStats.numVertsSubmitted, - gpuStats.numCachedVertsDrawn, - gpuStats.numUncachedVertsDrawn, - (int)framebufferManager_->NumVFBs(), - gpuStats.numFramebufferEvaluations, - (int)textureCache_->NumLoadedTextures(), - gpuStats.numTexturesDecoded, - gpuStats.numTextureInvalidations, - gpuStats.numTextureDataBytesHashed / 1024, - gpuStats.numBlockingReadbacks, - gpuStats.numReadbacks, - gpuStats.numUploads, - gpuStats.numDepal, - gpuStats.numDepthCopies, - gpuStats.numColorCopies, - gpuStats.numReinterpretCopies, - gpuStats.numCopiesForShaderBlend, - gpuStats.numCopiesForSelfTex, - gpuStats.vertexGPUCycles + gpuStats.otherGPUCycles, - vertexAverageCycles - ); -} diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index b58923013e..367da7c85b 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -70,7 +70,6 @@ struct TransformedVertex { class GPUCommon : public GPUInterface, public GPUDebugInterface { public: GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw); - ~GPUCommon(); Draw::DrawContext *GetDrawContext() override { return draw_; @@ -225,9 +224,6 @@ public: } protected: - void DeviceLost() override; - void DeviceRestore(Draw::DrawContext *draw) override; - void ClearCacheNextFrame() override {} virtual void CheckRenderResized() {} @@ -275,8 +271,6 @@ protected: } } - size_t FormatGPUStatsCommon(char *buf, size_t size); - virtual void BuildReportingInfo() = 0; virtual void UpdateMSAALevel(Draw::DrawContext *draw) {} diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 6a1771875d..2278ceadb2 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -6,6 +6,7 @@ #include "Core/System.h" #include "Core/Config.h" +#include "Core/Util/PPGeDraw.h" #include "GPU/GPUCommonHW.h" #include "GPU/Common/SplineCommon.h" @@ -386,6 +387,8 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU UpdateCmdInfo(); UpdateMSAALevel(draw); + + PPGeSetDrawContext(draw); } GPUCommonHW::~GPUCommonHW() { @@ -407,13 +410,22 @@ void GPUCommonHW::CheckRenderResized() { } } +// Call at the END of the GPU implementation's DeviceLost void GPUCommonHW::DeviceLost() { - textureCache_->Clear(false); framebufferManager_->DeviceLost(); + draw_ = nullptr; + textureCache_->Clear(false); textureCache_->DeviceLost(); shaderManager_->DeviceLost(); } +// Call at the start of the GPU implementation's DeviceRestore +void GPUCommonHW::DeviceRestore(Draw::DrawContext *draw) { + draw_ = draw; + framebufferManager_->DeviceRestore(draw_); + PPGeSetDrawContext(draw_); +} + void GPUCommonHW::UpdateCmdInfo() { if (g_Config.bSoftwareSkinning) { cmdInfo_[GE_CMD_VERTEXTYPE].flags &= ~FLAG_FLUSHBEFOREONCHANGE; @@ -1599,3 +1611,46 @@ void GPUCommonHW::Execute_BoneMtxData(u32 op, u32 diff) { gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; } + +size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { + float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f; + return snprintf(buffer, size, + "DL processing time: %0.2f ms, %d drawsync, %d listsync\n" + "Draw calls: %d, flushes %d, clears %d (cached: %d)\n" + "Num Tracked Vertex Arrays: %d\n" + "Vertices: %d cached: %d uncached: %d\n" + "FBOs active: %d (evaluations: %d)\n" + "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n" + "readbacks %d (%d non-block), uploads %d, depal %d\n" + "Copies: depth %d, color %d, reint %d, blend %d, selftex %d\n" + "GPU cycles executed: %d (%f per vertex)\n", + gpuStats.msProcessingDisplayLists * 1000.0f, + gpuStats.numDrawSyncs, + gpuStats.numListSyncs, + gpuStats.numDrawCalls, + gpuStats.numFlushes, + gpuStats.numClears, + gpuStats.numCachedDrawCalls, + gpuStats.numTrackedVertexArrays, + gpuStats.numVertsSubmitted, + gpuStats.numCachedVertsDrawn, + gpuStats.numUncachedVertsDrawn, + (int)framebufferManager_->NumVFBs(), + gpuStats.numFramebufferEvaluations, + (int)textureCache_->NumLoadedTextures(), + gpuStats.numTexturesDecoded, + gpuStats.numTextureInvalidations, + gpuStats.numTextureDataBytesHashed / 1024, + gpuStats.numBlockingReadbacks, + gpuStats.numReadbacks, + gpuStats.numUploads, + gpuStats.numDepal, + gpuStats.numDepthCopies, + gpuStats.numColorCopies, + gpuStats.numReinterpretCopies, + gpuStats.numCopiesForShaderBlend, + gpuStats.numCopiesForSelfTex, + gpuStats.vertexGPUCycles + gpuStats.otherGPUCycles, + vertexAverageCycles + ); +} diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index ca3fe1780d..2e13a11738 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -12,6 +12,7 @@ public: void CopyDisplayToOutput(bool reallyDirty) override; void DoState(PointerWrap &p) override; void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; void BeginFrame() override; @@ -69,6 +70,7 @@ private: void CheckFlushOp(int cmd, u32 diff); protected: + size_t FormatGPUStatsCommon(char *buf, size_t size); void UpdateCmdInfo() override; void PreExecuteOp(u32 op, u32 diff) override; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 2a3bf685f7..bf54743958 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -454,6 +454,8 @@ SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw) NotifyConfigChanged(); NotifyRenderResized(); NotifyDisplayResized(); + + PPGeSetDrawContext(draw); } void SoftGPU::DeviceLost() { From 72bed6f2b596b90f7ddd31584b65fa5440567c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 26 Feb 2023 11:05:52 +0100 Subject: [PATCH 046/141] Some DeviceLost/DeviceRestore cleanup --- GPU/Common/DrawEngineCommon.h | 2 ++ GPU/D3D11/DrawEngineD3D11.h | 3 +++ GPU/D3D11/ShaderManagerD3D11.h | 2 +- GPU/D3D11/TextureCacheD3D11.h | 2 +- GPU/Directx9/DrawEngineDX9.h | 3 +++ GPU/Directx9/GPU_DX9.cpp | 8 ++++++++ GPU/Directx9/GPU_DX9.h | 3 +++ GPU/Directx9/ShaderManagerDX9.h | 2 +- GPU/Directx9/TextureCacheDX9.h | 2 +- GPU/GLES/DrawEngineGLES.h | 4 ++-- GPU/GLES/GPU_GLES.cpp | 8 +------- GPU/GPUCommonHW.cpp | 9 +++++++++ GPU/Software/TransformUnit.h | 3 +++ GPU/Vulkan/DrawEngineVulkan.cpp | 1 - GPU/Vulkan/DrawEngineVulkan.h | 4 ++-- GPU/Vulkan/GPU_Vulkan.cpp | 11 ++--------- 16 files changed, 42 insertions(+), 25 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 78473c3cf3..d5f8056417 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -74,6 +74,8 @@ public: virtual ~DrawEngineCommon(); void Init(); + virtual void DeviceLost() = 0; + virtual void DeviceRestore(Draw::DrawContext *draw) = 0; bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices); diff --git a/GPU/D3D11/DrawEngineD3D11.h b/GPU/D3D11/DrawEngineD3D11.h index 2703828516..7c9b46ac5e 100644 --- a/GPU/D3D11/DrawEngineD3D11.h +++ b/GPU/D3D11/DrawEngineD3D11.h @@ -119,6 +119,9 @@ public: DrawEngineD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context); ~DrawEngineD3D11(); + void DeviceLost() override { draw_ = nullptr; } + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } + void SetShaderManager(ShaderManagerD3D11 *shaderManager) { shaderManager_ = shaderManager; } diff --git a/GPU/D3D11/ShaderManagerD3D11.h b/GPU/D3D11/ShaderManagerD3D11.h index 5dacd408a5..9f8de499a9 100644 --- a/GPU/D3D11/ShaderManagerD3D11.h +++ b/GPU/D3D11/ShaderManagerD3D11.h @@ -89,7 +89,7 @@ public: void ClearShaders() override; void DirtyLastShader() override; - void DeviceLost() override {} + void DeviceLost() override { draw_ = nullptr; } void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 35f14d5a6f..53c150a9e4 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -52,7 +52,7 @@ public: bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; - void DeviceLost() override {} + void DeviceLost() override { draw_ = nullptr; } void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } protected: diff --git a/GPU/Directx9/DrawEngineDX9.h b/GPU/Directx9/DrawEngineDX9.h index e8bb1fbf42..1cea91376b 100644 --- a/GPU/Directx9/DrawEngineDX9.h +++ b/GPU/Directx9/DrawEngineDX9.h @@ -107,6 +107,9 @@ public: DrawEngineDX9(Draw::DrawContext *draw); ~DrawEngineDX9(); + void DeviceLost() override { draw_ = nullptr; } + void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } + void SetShaderManager(ShaderManagerDX9 *shaderManager) { shaderManager_ = shaderManager; } diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 1c748980c0..cc099aa52a 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -105,6 +105,14 @@ u32 GPU_DX9::CheckGPUFeatures() const { return CheckGPUFeaturesLate(features); } +void GPU_DX9::DeviceLost() { + GPUCommonHW::DeviceLost(); +} + +void GPU_DX9::DeviceRestore(Draw::DrawContext *draw) { + GPUCommonHW::DeviceRestore(draw); +} + void GPU_DX9::ReapplyGfxState() { dxstate.Restore(); GPUCommonHW::ReapplyGfxState(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 1da17b2e3e..f18ffeb77e 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -36,6 +36,9 @@ public: u32 CheckGPUFeatures() const override; + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; + void ReapplyGfxState() override; void GetStats(char *buffer, size_t bufsize) override; diff --git a/GPU/Directx9/ShaderManagerDX9.h b/GPU/Directx9/ShaderManagerDX9.h index a0ccce98ed..99773c69dc 100644 --- a/GPU/Directx9/ShaderManagerDX9.h +++ b/GPU/Directx9/ShaderManagerDX9.h @@ -85,7 +85,7 @@ public: int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } - void DeviceLost() override {} + void DeviceLost() override { draw_ = nullptr; } void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } std::vector DebugGetShaderIDs(DebugShaderType type); diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index 8b2a9eaec8..0b41c92a71 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -42,7 +42,7 @@ public: bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; - void DeviceLost() override {} + void DeviceLost() override { draw_ = nullptr; } void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } protected: diff --git a/GPU/GLES/DrawEngineGLES.h b/GPU/GLES/DrawEngineGLES.h index 9352291c29..17f773c0b2 100644 --- a/GPU/GLES/DrawEngineGLES.h +++ b/GPU/GLES/DrawEngineGLES.h @@ -76,8 +76,8 @@ public: fragmentTestCache_ = testCache; } - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; void ClearTrackedVertexArrays() override {} diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 7e362b0697..20ac03129b 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -239,7 +239,6 @@ void GPU_GLES::DeviceLost() { // TransformDraw has registered as a GfxResourceHolder. CancelReady(); fragmentTestCache_.DeviceLost(); - drawEngine_.DeviceLost(); GPUCommonHW::DeviceLost(); } @@ -247,13 +246,8 @@ void GPU_GLES::DeviceLost() { void GPU_GLES::DeviceRestore(Draw::DrawContext *draw) { GPUCommonHW::DeviceRestore(draw); - UpdateCmdInfo(); - UpdateVsyncInterval(true); - - shaderManager_->DeviceRestore(draw_); - textureCache_->DeviceRestore(draw_); - drawEngine_.DeviceRestore(draw_); fragmentTestCache_.DeviceRestore(draw_); + UpdateVsyncInterval(true); } void GPU_GLES::BeginHostFrame() { diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 2278ceadb2..1dbf9c10df 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -417,13 +417,22 @@ void GPUCommonHW::DeviceLost() { textureCache_->Clear(false); textureCache_->DeviceLost(); shaderManager_->DeviceLost(); + drawEngineCommon_->DeviceLost(); } // Call at the start of the GPU implementation's DeviceRestore void GPUCommonHW::DeviceRestore(Draw::DrawContext *draw) { draw_ = draw; framebufferManager_->DeviceRestore(draw_); + textureCache_->DeviceRestore(draw_); + shaderManager_->DeviceRestore(draw_); + drawEngineCommon_->DeviceRestore(draw_); + PPGeSetDrawContext(draw_); + + gstate_c.SetUseFlags(CheckGPUFeatures()); + BuildReportingInfo(); + UpdateCmdInfo(); } void GPUCommonHW::UpdateCmdInfo() { diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index b1dd318540..895c023a92 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -173,6 +173,9 @@ public: SoftwareDrawEngine(); ~SoftwareDrawEngine(); + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override {} + void NotifyConfigChanged() override; void DispatchFlush() override; void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int cullMode, int *bytesRead) override; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 78f561376e..11907c13b7 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -287,7 +287,6 @@ void DrawEngineVulkan::DeviceLost() { void DrawEngineVulkan::DeviceRestore(Draw::DrawContext *draw) { draw_ = draw; - InitDeviceObjects(); } diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 6c21b7609d..b0e99a7c24 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -158,8 +158,8 @@ public: framebufferManager_ = fbManager; } - void DeviceLost(); - void DeviceRestore(Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; // So that this can be inlined void Flush() { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index ee7500ea96..ed9a15c99d 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -454,7 +454,6 @@ void GPU_Vulkan::DeviceLost() { SaveCache(shaderCachePath_); } DestroyDeviceObjects(); - drawEngine_.DeviceLost(); pipelineManager_->DeviceLost(); GPUCommonHW::DeviceLost(); @@ -462,17 +461,11 @@ void GPU_Vulkan::DeviceLost() { void GPU_Vulkan::DeviceRestore(Draw::DrawContext *draw) { GPUCommonHW::DeviceRestore(draw); - InitDeviceObjects(); - - gstate_c.SetUseFlags(CheckGPUFeatures()); - BuildReportingInfo(); - UpdateCmdInfo(); VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); - drawEngine_.DeviceRestore(draw_); pipelineManager_->DeviceRestore(vulkan); - textureCacheVulkan_->DeviceRestore(draw_); - shaderManagerVulkan_->DeviceRestore(draw_); + + InitDeviceObjects(); } void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { From b3ce31c61eee3d8ac8996115e0f013a89ded7d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 26 Feb 2023 19:54:30 +0100 Subject: [PATCH 047/141] Address feedback --- GPU/GPUCommon.cpp | 2 ++ GPU/GPUCommonHW.cpp | 2 -- GPU/Software/SoftGpu.cpp | 2 -- GPU/Vulkan/GPU_Vulkan.cpp | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 884b06816b..802219472c 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -37,6 +37,7 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceGe.h" #include "Core/HW/Display.h" +#include "Core/Util/PPGeDraw.h" #include "Core/MemMapHelpers.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" @@ -68,6 +69,7 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : gpuStats.Reset(); UpdateVsyncInterval(true); + PPGeSetDrawContext(draw); ResetMatrices(); } diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 1dbf9c10df..5c51cf5e8a 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -387,8 +387,6 @@ GPUCommonHW::GPUCommonHW(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPU UpdateCmdInfo(); UpdateMSAALevel(draw); - - PPGeSetDrawContext(draw); } GPUCommonHW::~GPUCommonHW() { diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index bf54743958..2a3bf685f7 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -454,8 +454,6 @@ SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw) NotifyConfigChanged(); NotifyRenderResized(); NotifyDisplayResized(); - - PPGeSetDrawContext(draw); } void SoftGPU::DeviceLost() { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index ed9a15c99d..065dc5b1c5 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -438,8 +438,8 @@ void GPU_Vulkan::DestroyDeviceObjects() { } void GPU_Vulkan::CheckRenderResized() { - GPUCommonHW::CheckRenderResized(); if (renderResized_) { + GPUCommonHW::CheckRenderResized(); pipelineManager_->InvalidateMSAAPipelines(); framebufferManager_->ReleasePipelines(); } From 0943bebc481854f31f7a7c225beecb8013f2344b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 Feb 2023 11:18:56 -0800 Subject: [PATCH 048/141] Build: Fix an MSVC warning, normalize a bit. --- ext/cpu_features.vcxproj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ext/cpu_features.vcxproj b/ext/cpu_features.vcxproj index 0588cb55a9..5481dad5db 100644 --- a/ext/cpu_features.vcxproj +++ b/ext/cpu_features.vcxproj @@ -198,6 +198,9 @@ stdcpp17 cpu_features/include STACK_LINE_READER_BUFFER_SIZE=1024;%(PreprocessorDefinitions) + false + Size + true Windows @@ -216,6 +219,9 @@ stdcpp17 cpu_features/include STACK_LINE_READER_BUFFER_SIZE=1024;%(PreprocessorDefinitions) + false + Size + true Windows @@ -234,6 +240,9 @@ stdcpp17 cpu_features/include STACK_LINE_READER_BUFFER_SIZE=1024;%(PreprocessorDefinitions) + false + Size + true Windows @@ -308,6 +317,10 @@ stdcpp17 cpu_features/include STACK_LINE_READER_BUFFER_SIZE=1024;%(PreprocessorDefinitions) + false + Size + StreamingSIMDExtensions2 + true Windows From c062b78b556586baa45cac9051bbb4ef48227bab Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 Feb 2023 11:27:46 -0800 Subject: [PATCH 049/141] Build: Cleanup some MSVC optimization settings. --- UI/UI.vcxproj | 1 + ext/SPIRV-Cross.vcxproj | 1 + ext/cpu_features.vcxproj | 8 ++++---- ext/discord-rpc-build/discord-rpc.vcxproj | 13 +++++++++---- ext/glslang.vcxproj | 5 +++++ ext/libarmips.vcxproj | 13 +++++++++---- ext/libzstd.vcxproj | 1 + ext/miniupnpc.vcxproj | 3 +++ unittest/UnitTests.vcxproj | 1 + 9 files changed, 34 insertions(+), 12 deletions(-) diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index be370e4ef1..f4c85a7185 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -302,6 +302,7 @@ MultiThreaded $(EXTERNAL_COMPILE_OPTIONS) ProgramDatabase + StreamingSIMDExtensions2 Windows diff --git a/ext/SPIRV-Cross.vcxproj b/ext/SPIRV-Cross.vcxproj index b6369489d7..467cb84cf1 100644 --- a/ext/SPIRV-Cross.vcxproj +++ b/ext/SPIRV-Cross.vcxproj @@ -240,6 +240,7 @@ false true ProgramDatabase + StreamingSIMDExtensions2 Windows diff --git a/ext/cpu_features.vcxproj b/ext/cpu_features.vcxproj index 5481dad5db..a0eddbd869 100644 --- a/ext/cpu_features.vcxproj +++ b/ext/cpu_features.vcxproj @@ -190,7 +190,7 @@ Level3 - MaxSpeed + MinSpace true true MultiThreaded @@ -211,7 +211,7 @@ Level3 - MaxSpeed + MinSpace true true MultiThreaded @@ -232,7 +232,7 @@ Level3 - MaxSpeed + MinSpace true true MultiThreaded @@ -309,7 +309,7 @@ Level3 - MaxSpeed + MinSpace true true MultiThreaded diff --git a/ext/discord-rpc-build/discord-rpc.vcxproj b/ext/discord-rpc-build/discord-rpc.vcxproj index 5fc9b0f697..e1b7c7d435 100644 --- a/ext/discord-rpc-build/discord-rpc.vcxproj +++ b/ext/discord-rpc-build/discord-rpc.vcxproj @@ -178,7 +178,7 @@ Level3 - MaxSpeed + MinSpace true true NDEBUG;_LIB;%(PreprocessorDefinitions) @@ -189,6 +189,7 @@ false false ProgramDatabase + Size Windows @@ -200,7 +201,7 @@ Level3 - MaxSpeed + MinSpace true true NDEBUG;_LIB;%(PreprocessorDefinitions) @@ -211,6 +212,7 @@ false false ProgramDatabase + Size Windows @@ -222,7 +224,7 @@ Level3 - MaxSpeed + MinSpace true true NDEBUG;_LIB;%(PreprocessorDefinitions) @@ -233,6 +235,7 @@ false false ProgramDatabase + Size Windows @@ -308,7 +311,7 @@ Level3 - MaxSpeed + MinSpace true true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) @@ -318,6 +321,8 @@ true false ProgramDatabase + Size + StreamingSIMDExtensions2 Windows diff --git a/ext/glslang.vcxproj b/ext/glslang.vcxproj index 6d8e742a70..2accb51d6e 100644 --- a/ext/glslang.vcxproj +++ b/ext/glslang.vcxproj @@ -222,6 +222,8 @@ false true ProgramDatabase + true + StreamingSIMDExtensions2 Windows @@ -245,6 +247,7 @@ true false ProgramDatabase + true Windows @@ -268,6 +271,7 @@ true false ProgramDatabase + true Windows @@ -291,6 +295,7 @@ true false ProgramDatabase + true Windows diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index fa5fa59c2c..47ed57ae3c 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -395,7 +395,7 @@ Level3 - MaxSpeed + MinSpace true true WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) @@ -405,6 +405,8 @@ false ProgramDatabase stdcpp17 + Size + StreamingSIMDExtensions2 Windows @@ -416,7 +418,7 @@ Level3 - MaxSpeed + MinSpace true true WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) @@ -428,6 +430,7 @@ false ProgramDatabase stdcpp17 + Size Windows @@ -439,7 +442,7 @@ Level3 - MaxSpeed + MinSpace true true WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) @@ -451,6 +454,7 @@ false ProgramDatabase stdcpp17 + Size Windows @@ -462,7 +466,7 @@ Level3 - MaxSpeed + MinSpace true true WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) @@ -474,6 +478,7 @@ false ProgramDatabase stdcpp17 + Size Windows diff --git a/ext/libzstd.vcxproj b/ext/libzstd.vcxproj index 68dadeded9..bb93a9b3f8 100644 --- a/ext/libzstd.vcxproj +++ b/ext/libzstd.vcxproj @@ -313,6 +313,7 @@ Speed true false + StreamingSIMDExtensions2 Windows diff --git a/ext/miniupnpc.vcxproj b/ext/miniupnpc.vcxproj index ee83b4495c..f69e726242 100644 --- a/ext/miniupnpc.vcxproj +++ b/ext/miniupnpc.vcxproj @@ -287,6 +287,7 @@ cd..\..\.. true false MultiThreaded + StreamingSIMDExtensions2 Windows @@ -320,6 +321,7 @@ cd..\..\.. MultiThreaded false false + false Windows @@ -412,6 +414,7 @@ cd..\..\.. true false false + true iphlpapi.lib diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index 057c5fe52b..bd29c69cd0 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -271,6 +271,7 @@ false MultiThreaded ProgramDatabase + StreamingSIMDExtensions2 Console From 0981c28224ee1a1e5c1e727c54bc59e80183e312 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 Feb 2023 15:58:40 -0800 Subject: [PATCH 050/141] Build: Skip install of list_cpu_features. --- ext/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 8205fe4054..e2b0f44a80 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -22,7 +22,7 @@ endif() # This works around a bug in cpu_features when ${CMAKE_BINARY_DIR} != ${CMAKE_CURRENT_BINRARY_DIR}. # We also disable the list_cpu_features binary. -add_subdirectory(cmake/cpu_features) +add_subdirectory(cmake/cpu_features EXCLUDE_FROM_ALL) set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "" FORCE) set(ENABLE_GLSLANG_INSTALL OFF) From 9e46a30c131ae22d06f641ccec6276faf40c83c5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 Feb 2023 15:58:58 -0800 Subject: [PATCH 051/141] GPU: Correct some missing override specifiers. --- GPU/D3D11/ShaderManagerD3D11.h | 4 ++-- GPU/Directx9/ShaderManagerDX9.h | 4 ++-- GPU/GLES/ShaderManagerGLES.h | 4 ++-- GPU/Vulkan/ShaderManagerVulkan.h | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/GPU/D3D11/ShaderManagerD3D11.h b/GPU/D3D11/ShaderManagerD3D11.h index 9f8de499a9..ffdd65ffe7 100644 --- a/GPU/D3D11/ShaderManagerD3D11.h +++ b/GPU/D3D11/ShaderManagerD3D11.h @@ -94,8 +94,8 @@ public: int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } - std::vector DebugGetShaderIDs(DebugShaderType type); - std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + std::vector DebugGetShaderIDs(DebugShaderType type) override; + std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) override; uint64_t UpdateUniforms(bool useBufferedRendering); void BindUniforms(); diff --git a/GPU/Directx9/ShaderManagerDX9.h b/GPU/Directx9/ShaderManagerDX9.h index 99773c69dc..68337503b4 100644 --- a/GPU/Directx9/ShaderManagerDX9.h +++ b/GPU/Directx9/ShaderManagerDX9.h @@ -88,8 +88,8 @@ public: void DeviceLost() override { draw_ = nullptr; } void DeviceRestore(Draw::DrawContext *draw) override { draw_ = draw; } - std::vector DebugGetShaderIDs(DebugShaderType type); - std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + std::vector DebugGetShaderIDs(DebugShaderType type) override; + std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) override; private: void PSUpdateUniforms(u64 dirtyUniforms); diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index 6435b44d7d..027c7def9f 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -182,8 +182,8 @@ public: int GetNumFragmentShaders() const { return (int)fsCache_.size(); } int GetNumPrograms() const { return (int)linkedShaderCache_.size(); } - std::vector DebugGetShaderIDs(DebugShaderType type); - std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + std::vector DebugGetShaderIDs(DebugShaderType type) override; + std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) override; bool LoadCacheFlags(File::IOFile &f, DrawEngineGLES *drawEngine); bool LoadCache(File::IOFile &f); diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index a29ea06be3..38b4b2e1e1 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -118,7 +118,7 @@ public: void DeviceRestore(Draw::DrawContext *draw) override; void GetShaders(int prim, VertexDecoder *decoder, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode); - void ClearShaders(); + void ClearShaders() override; void DirtyShader(); void DirtyLastShader() override; @@ -134,8 +134,8 @@ public: VulkanFragmentShader *GetFragmentShaderFromModule(VkShaderModule module); VulkanGeometryShader *GetGeometryShaderFromModule(VkShaderModule module); - std::vector DebugGetShaderIDs(DebugShaderType type); - std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); + std::vector DebugGetShaderIDs(DebugShaderType type) override; + std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) override; uint64_t UpdateUniforms(bool useBufferedRendering); From ef4f17e9985c6315cb66fde0e50b1c2342ac2e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 26 Feb 2023 15:12:46 +0100 Subject: [PATCH 052/141] Resurrect the Vulkan memory visualizer, but now it's global stats and pushbuffer stats. --- Common/GPU/Vulkan/VulkanMemory.cpp | 31 +++++++++++++++ Common/GPU/Vulkan/VulkanMemory.h | 7 +++- GPU/Vulkan/DebugVisVulkan.cpp | 62 +++++++++++++++++++++++++++++- UI/DevScreens.cpp | 3 +- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanMemory.cpp b/Common/GPU/Vulkan/VulkanMemory.cpp index d8184098a5..9a25e5cbb0 100644 --- a/Common/GPU/Vulkan/VulkanMemory.cpp +++ b/Common/GPU/Vulkan/VulkanMemory.cpp @@ -18,6 +18,9 @@ // Additionally, Common/Vulkan/* , including this file, are also licensed // under the public domain. +#include +#include + #include "Common/Math/math_util.h" #include "Common/Log.h" @@ -26,17 +29,41 @@ using namespace PPSSPP_VK; +// 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 g_pushBuffers; + VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage, PushBufferType type) : vulkan_(vulkan), name_(name), size_(size), usage_(usage), type_(type) { + { + std::lock_guard guard(g_pushBufferListMutex); + g_pushBuffers.insert(this); + } + bool res = AddBuffer(); _assert_(res); } VulkanPushBuffer::~VulkanPushBuffer() { + { + std::lock_guard guard(g_pushBufferListMutex); + g_pushBuffers.erase(this); + } + _dbg_assert_(!writePtr_); _assert_(buffers_.empty()); } +std::vector VulkanPushBuffer::GetAllActive() { + std::vector buffers; + std::lock_guard guard(g_pushBufferListMutex); + for (auto iter : g_pushBuffers) { + buffers.push_back(iter); + } + return buffers; +} + bool VulkanPushBuffer::AddBuffer() { BufInfo info; VkDevice device = vulkan_->GetDevice(); @@ -122,6 +149,10 @@ size_t VulkanPushBuffer::GetTotalSize() const { return sum; } +size_t VulkanPushBuffer::GetTotalCapacity() const { + return size_ * buffers_.size(); +} + void VulkanPushBuffer::Map() { _dbg_assert_(!writePtr_); VkResult res = vmaMapMemory(vulkan_->Allocator(), buffers_[buf_].allocation, (void **)(&writePtr_)); diff --git a/Common/GPU/Vulkan/VulkanMemory.h b/Common/GPU/Vulkan/VulkanMemory.h index 275ca4bb78..863e4f965e 100644 --- a/Common/GPU/Vulkan/VulkanMemory.h +++ b/Common/GPU/Vulkan/VulkanMemory.h @@ -131,7 +131,10 @@ public: info->range = sizeof(T); } - size_t GetTotalSize() const; + size_t GetTotalSize() const; // Used size + size_t GetTotalCapacity() const; + + static std::vector GetAllActive(); private: bool AddBuffer(); @@ -173,7 +176,7 @@ private: const char *tag_; VulkanContext *vulkan_ = nullptr; VkDescriptorPool descPool_ = VK_NULL_HANDLE; - VkDescriptorPoolCreateInfo info_; + VkDescriptorPoolCreateInfo info_{}; std::vector sizes_; std::function clear_; uint32_t usage_ = 0; diff --git a/GPU/Vulkan/DebugVisVulkan.cpp b/GPU/Vulkan/DebugVisVulkan.cpp index 82acb8c0fd..ff03218efd 100644 --- a/GPU/Vulkan/DebugVisVulkan.cpp +++ b/GPU/Vulkan/DebugVisVulkan.cpp @@ -15,24 +15,84 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include +#include + #include "Common/Render/DrawBuffer.h" #include "Common/GPU/thin3d.h" +#include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" #include "Common/System/Display.h" #include "Common/System/System.h" +#include "ext/vma/vk_mem_alloc.h" + #include "DebugVisVulkan.h" #include "Common/GPU/Vulkan/VulkanMemory.h" #include "Common/GPU/Vulkan/VulkanImage.h" +#include "Common/Data/Text/Parsers.h" #include "GPU/Vulkan/GPU_Vulkan.h" #include "GPU/Vulkan/VulkanUtil.h" #include "GPU/Vulkan/TextureCacheVulkan.h" #undef DrawText +bool comparePushBufferNames(const VulkanPushBuffer *a, const VulkanPushBuffer *b) { + return strcmp(a->Name(), b->Name()) < 0; +} + void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu) { - // TODO: Make a new allocator visualizer for VMA. + // This one will simply display stats. + Draw::DrawContext *draw = ui->GetDrawContext(); + + VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); + if (!vulkan) { + return; + } + + VmaTotalStatistics vmaStats; + vmaCalculateStatistics(vulkan->Allocator(), &vmaStats); + + std::vector budgets; + budgets.resize(vulkan->GetMemoryProperties().memoryHeapCount); + vmaGetHeapBudgets(vulkan->Allocator(), &budgets[0]); + + size_t totalBudget = 0; + size_t totalUsedBytes = 0; + for (auto &budget : budgets) { + totalBudget += budget.budget; + totalUsedBytes += budget.usage; + } + + std::stringstream str; + str << vulkan->GetPhysicalDeviceProperties().properties.deviceName << std::endl; + str << "Allocated " << NiceSizeFormat(vmaStats.total.statistics.allocationBytes) << " in " << vmaStats.total.statistics.allocationCount << " allocs" << std::endl; + // Note: The overall number includes stuff like descriptor sets pools and other things that are not directly visible as allocations. + str << "Overall " << NiceSizeFormat(totalUsedBytes) << " used out of " << NiceSizeFormat(totalBudget) << " available" << std::endl; + + str << "Push buffers:" << std::endl; + + // Now list the various push buffers. + auto pushBuffers = VulkanPushBuffer::GetAllActive(); + std::sort(pushBuffers.begin(), pushBuffers.end(), comparePushBufferNames); + + for (auto push : pushBuffers) { + str << " " << push->Name() << " " + << NiceSizeFormat(push->GetTotalCapacity()) << ", used: " + << NiceSizeFormat(push->GetTotalSize()) << std::endl; + } + + const int padding = 10 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT); + const int starty = 50 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP); + int x = padding; + int y = starty; + + ui->SetFontScale(0.7f, 0.7f); + ui->DrawTextShadow(str.str().c_str(), x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + ui->SetFontScale(1.0f, 1.0f); + ui->Flush(); } void DrawGPUProfilerVis(UIContext *ui, GPUInterface *gpu) { diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index eb3de25579..82cb3c159e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -101,8 +101,7 @@ void DevMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { items->Add(new Choice(dev->T("Jit Compare")))->OnClick.Handle(this, &DevMenuScreen::OnJitCompare); items->Add(new Choice(dev->T("Shader Viewer")))->OnClick.Handle(this, &DevMenuScreen::OnShaderView); if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { - // TODO: Make a new allocator visualizer for VMA. - // items->Add(new CheckBox(&g_Config.bShowAllocatorDebug, dev->T("Allocator Viewer"))); + items->Add(new CheckBox(&g_Config.bShowAllocatorDebug, dev->T("Allocator Viewer"))); items->Add(new CheckBox(&g_Config.bShowGpuProfile, dev->T("GPU Profile"))); } items->Add(new Choice(dev->T("Toggle Freeze")))->OnClick.Handle(this, &DevMenuScreen::OnFreezeFrame); From 5d5e02d94958dfdb81bd31287bc30f0ccef27af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 01:10:30 +0100 Subject: [PATCH 053/141] Attempted buildfix --- Common/GPU/Vulkan/VulkanLoader.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h index 85b4bb5510..f85a09f558 100644 --- a/Common/GPU/Vulkan/VulkanLoader.h +++ b/Common/GPU/Vulkan/VulkanLoader.h @@ -33,6 +33,11 @@ #include "ext/vulkan/vulkan.h" +// Hacky X11 header workaround +#ifdef Opposite +#undef Opposite +#endif + namespace PPSSPP_VK { // Putting our own Vulkan function pointers in a namespace ensures that ppsspp_libretro.so doesn't collide with libvulkan.so. extern PFN_vkCreateInstance vkCreateInstance; From c5383151349ba2107789399daa2c9fde53af3d97 Mon Sep 17 00:00:00 2001 From: GABO1423 <35014183+GABO1423@users.noreply.github.com> Date: Sun, 26 Feb 2023 19:19:05 -0400 Subject: [PATCH 054/141] (UWP) Small Build Overhaul --- UWP/UWP.vcxproj | 1180 +++++++-------------------------------- UWP/UWP.vcxproj.filters | 187 ++++++- 2 files changed, 377 insertions(+), 990 deletions(-) diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 1cd5d12174..d3f35bd6d8 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -212,7 +212,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -237,7 +237,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -262,7 +262,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -287,7 +287,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -312,7 +312,7 @@ copy PackageGold.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsGold\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -337,7 +337,7 @@ copy PackageGold.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsGold\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -362,7 +362,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -387,7 +387,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -412,7 +412,7 @@ copy PackageGold.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsGold\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -437,7 +437,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -462,7 +462,7 @@ copy PackageNormal.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsNormal\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -487,7 +487,7 @@ copy PackageGold.appxmanifest Package.appxmanifest /Y mkdir Assets copy AssetsGold\*.* Assets /Y - robocopy "..\Assets\." "Content\." /xf *.vsh /E /R:0 /W:0 + robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 @@ -521,6 +521,9 @@ true + + true + true @@ -544,18 +547,7 @@ - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing @@ -564,18 +556,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create @@ -596,283 +577,123 @@ true Document + + true + Document + true - + true - - true - true - true - true - true - true - true - true - true - true - true - true + + true - + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + true true + + true + + + true + + + true + + + true + + + true + - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true true @@ -881,819 +702,247 @@ true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true true - true - true - true - true - true - true - true - true - true - true - true - true + true - - true - true - true - true - true - true - true - true - true - true - true - true + + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true + + + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true + + + true - true - true - true - true - true - true - true - true - true - true - true - true + true - true - true - true - true - true - true - true - true - true - true - true - true + true @@ -1730,6 +979,9 @@ + + true + true diff --git a/UWP/UWP.vcxproj.filters b/UWP/UWP.vcxproj.filters index 15c471b062..b03d9592a8 100644 --- a/UWP/UWP.vcxproj.filters +++ b/UWP/UWP.vcxproj.filters @@ -53,6 +53,21 @@ {da2113a6-e616-4fc9-8be3-ff75b963664b} + + {55147387-68fc-487e-abed-7a763f850ef1} + + + {e5d2759b-3511-4ab9-a9ca-070dd9715f3b} + + + {0fb972a9-20a9-4eba-be70-c408f9306939} + + + {47d54a48-ca53-4d57-8bfd-9c86fd97d84b} + + + {83476056-57b8-409f-b729-754d6401e3d5} + @@ -102,6 +117,12 @@ Content + + Content + + + Content\debugger + @@ -171,9 +192,15 @@ Content\lang + + Content\lang + Content\lang + + Content\lang + Content\lang @@ -246,21 +273,33 @@ Content\shaders + + Content\shaders + Content\shaders - + Content\shaders Content\shaders + + Content\shaders + Content\shaders Content\shaders + + Content\shaders + + + Content\shaders + Content\shaders @@ -270,9 +309,72 @@ Content\shaders + + Content\shaders + Content\shaders + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + + + Content\shaders + Content\flash0\font @@ -327,48 +429,81 @@ Content\flash0\font - - Content\shaders - - - Content\shaders - - - Content\shaders - - - Content\shaders - - - Content\shaders - - - Content\shaders - - - Content\shaders - - + Content - + Content - + Content\themes Content + + Content + + + Content + + + Content + + + Content + + + Content + + + Content + + + Content\shaders + + + Content\shaders + + + Content\debugger + + + Content\debugger + + + Content\debugger + + + Content\debugger + + + Content\debugger\static\css + + + Content\debugger\static\css + + + Content\debugger\static\js + + + Content\debugger\static\js + + + Content\debugger\static\media + Content + + Content\debugger\static\js + Content - + \ No newline at end of file From 15f087b71abb41b4bd9030cf462b08f770226086 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 26 Feb 2023 23:59:48 -0800 Subject: [PATCH 055/141] D3D11: Correct equal clear check. --- GPU/D3D11/DrawEngineD3D11.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index 81692d3ec5..df5eeb0284 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -634,8 +634,8 @@ rotateVBO: swTransform.Decode(prim, dec_->VertexType(), dec_->GetDecVtxFmt(), maxIndex, &result); // Non-zero depth clears are unusual, but some drivers don't match drawn depth values to cleared values. // Games sometimes expect exact matches (see #12626, for example) for equal comparisons. - if (result.action == SW_NOT_READY && everUsedEqualDepth_ && gstate.isClearModeDepthMask() && result.depth > 0.0f && result.depth < 1.0f) - result.action = SW_DRAW_PRIMITIVES; + if (result.action == SW_CLEAR && everUsedEqualDepth_ && gstate.isClearModeDepthMask() && result.depth > 0.0f && result.depth < 1.0f) + result.action = SW_NOT_READY; if (result.action == SW_NOT_READY) { swTransform.DetectOffsetTexture(maxIndex); } From ca8af6f4c7076f0c253e2afc0886328e1a5e6d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 10:39:49 +0100 Subject: [PATCH 056/141] Fix "DebugAnnotate" (not used, but I used it temporarily to investigate #17014) See #17014 --- Common/GPU/Vulkan/VulkanRenderManager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 812128b906..3cad859e8d 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -416,8 +416,10 @@ public: // These can be useful both when inspecting in RenderDoc, and when manually inspecting recorded commands // in the debugger. void DebugAnnotate(const char *annotation) { + _dbg_assert_(curRenderStep_); VkRenderData data{ VKRRenderCommand::DEBUG_ANNOTATION }; data.debugAnnotation.annotation = annotation; + curRenderStep_->commands.push_back(data); } VkCommandBuffer GetInitCmd(); From 1071d0808c5b0bb6578f44b767070d100a591182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 11:10:47 +0100 Subject: [PATCH 057/141] Undo Daxter's rotation of the analog input. See #17015 I think this is quite a bit better actually. Appears to be exactly 15 degrees. --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + Core/HLE/sceCtrl.cpp | 16 ++++++++++++++++ assets/compat.ini | 7 +++++++ 4 files changed, 25 insertions(+) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index f6cf4a644c..a40a62bf4d 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -127,6 +127,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "RockmanDash2SoundFix", &flags_.RockmanDash2SoundFix); CheckSetting(iniFile, gameID, "ReadbackDepth", &flags_.ReadbackDepth); CheckSetting(iniFile, gameID, "BlockTransferDepth", &flags_.BlockTransferDepth); + CheckSetting(iniFile, gameID, "DaxterRotatedAnalogStick", &flags_.DaxterRotatedAnalogStick); } void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index d34722f4fa..021518ddf6 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -98,6 +98,7 @@ struct CompatFlags { bool RockmanDash2SoundFix; bool ReadbackDepth; bool BlockTransferDepth; + bool DaxterRotatedAnalogStick; }; struct VRCompat { diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index ab9d15bf9d..898ba6e37b 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -20,6 +20,7 @@ #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" +#include "Common/Math/math_util.h" #include "Core/CoreTiming.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" @@ -28,6 +29,7 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HW/Display.h" +#include "Core/System.h" #include "Core/MemMapHelpers.h" #include "Core/MIPS/MIPS.h" #include "Core/Replay.h" @@ -118,6 +120,20 @@ static void __CtrlUpdateLatch() // Copy in the current data to the current buffer. ctrlBufs[ctrlBuf] = ctrlCurrent; + + if (PSP_CoreParameter().compat.flags().DaxterRotatedAnalogStick) { + // For some reason, Daxter rotates the analog input. See #17015 + float angle = (15.0f / 360.0f) * (2.0f * M_PI); + float cosAngle = cosf(angle); + float sinAngle = sinf(angle); + float x = ctrlBufs[ctrlBuf].analog[0][0] - 128; + float y = ctrlBufs[ctrlBuf].analog[0][1] - 128; + float rX = x * cosAngle - y * sinAngle; + float rY = x * sinAngle + y * cosAngle; + ctrlBufs[ctrlBuf].analog[0][0] = (u8)Clamp(rX + 128, 0.0f, 255.0f); + ctrlBufs[ctrlBuf].analog[0][1] = (u8)Clamp(rY + 128, 0.0f, 255.0f); + } + ctrlBufs[ctrlBuf].buttons = buttons; u32 changed = buttons ^ ctrlOldButtons; diff --git a/assets/compat.ini b/assets/compat.ini index 91b673fb7a..1828319a3b 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1479,3 +1479,10 @@ NPEG00004 = true ULES01070 = true ULES01071 = true ULUS10347 = true + +[DaxterRotatedAnalogStick] +# Daxter (see issue #17015) +UCUS98618 = true +UCES00044 = true +NPUG80329 = true +NPEG00025 = true From 7b06ec7469b576124a0e5a641af5d3f142a377c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 11:15:36 +0100 Subject: [PATCH 058/141] Add more Daxter game IDs --- assets/compat.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index 1828319a3b..14ee5a4bbb 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -328,9 +328,11 @@ UCED00971 = true UCUS98713 = true # Daxter UCUS98618 = true +UCUS98654 = true UCES00044 = true NPUG80329 = true NPEG00025 = true +UCKS45025 = true # Ys Seven ULUS10551 = true ULJM05475 = true @@ -1483,6 +1485,8 @@ ULUS10347 = true [DaxterRotatedAnalogStick] # Daxter (see issue #17015) UCUS98618 = true +UCUS98654 = true UCES00044 = true NPUG80329 = true NPEG00025 = true +UCKS45025 = true From 3aa0a9e17b6c9ad6a4ae0bed18bc6062c7d68439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 15:21:58 +0100 Subject: [PATCH 059/141] Fix blackscreen, better debug print of display parameters --- Common/System/Display.cpp | 20 ++++++++++++++++++++ Common/System/Display.h | 3 +++ SDL/SDLMain.cpp | 4 +--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Common/System/Display.cpp b/Common/System/Display.cpp index 90644a592a..7f79cc5041 100644 --- a/Common/System/Display.cpp +++ b/Common/System/Display.cpp @@ -1,3 +1,5 @@ +#include + #include "Common/System/Display.h" #include "Common/Math/math_util.h" @@ -46,3 +48,21 @@ void RotateRectToDisplay(DisplayRect &rect, int curRTWidth, int curRTHeight void RotateRectToDisplay(DisplayRect &rect, float curRTWidth, float curRTHeight) { RotateRectToDisplayImpl(rect, curRTWidth, curRTHeight); } + +DisplayProperties::DisplayProperties() { + rot_matrix.setIdentity(); +} + +void DisplayProperties::Print() { + printf("dp_xres/yres: %d, %d\n", dp_xres, dp_yres); + printf("pixel_xres/yres: %d, %d\n", pixel_xres, pixel_yres); + + printf("dpi, x, y: %f, %f, %f\n", dpi, dpi_scale_x, dpi_scale_y); + printf("pixel_in_dps: %f, %f\n", pixel_in_dps_x, pixel_in_dps_y); + + printf("dpi_real: %f, %f\n", dpi_scale_real_x, dpi_scale_real_y); + printf("display_hz: %f\n", display_hz); + + printf("rotation: %d\n", rotation); + rot_matrix.print(); +} diff --git a/Common/System/Display.h b/Common/System/Display.h index c7e826394c..0bea49542a 100644 --- a/Common/System/Display.h +++ b/Common/System/Display.h @@ -36,6 +36,9 @@ struct DisplayProperties { DisplayRotation rotation; Lin::Matrix4x4 rot_matrix; + + DisplayProperties(); + void Print(); }; extern DisplayProperties g_display; diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index d1e71462a3..1acd96e77a 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -751,9 +751,7 @@ int main(int argc, char *argv[]) { g_display.dpi_scale_y = g_display.dp_yres / (float)g_display.pixel_yres; g_display.dpi_scale_real_x = g_display.dpi_scale_x; g_display.dpi_scale_real_y = g_display.dpi_scale_y; - - printf("Pixels: %i x %i\n", g_display.pixel_xres, g_display.pixel_yres); - printf("Virtual pixels: %i x %i\n", g_display.dp_xres, g_display.dp_yres); + g_display.Print(); GraphicsContext *graphicsContext = nullptr; SDL_Window *window = nullptr; From 970f7993df79518c4ca5c7f3dd20212720dc3c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Feb 2023 23:59:57 +0100 Subject: [PATCH 060/141] Android: Make font rendering work even absent support for R4G4B4A4 textures. This shouldn't normally happen as conforming drivers are required to support that texture format, but the software driver that we accidentally choose on Poco C40 (see issue #16391) doesn't. That we choose that driver will be fixed separately. This fix on its own at least lets the user comfortably navigate to settings and switch to OpenGL. --- Common/GPU/Vulkan/VulkanContext.cpp | 4 ++-- Common/GPU/Vulkan/VulkanLoader.cpp | 1 + Common/GPU/Vulkan/thin3d_vulkan.cpp | 4 +++- Common/GPU/thin3d.h | 6 ++++++ Common/Render/Text/draw_text_android.cpp | 9 +++++++-- Common/Render/Text/draw_text_android.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 1 + 7 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index fe15710d70..a8e2a1846c 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -535,7 +535,7 @@ int VulkanContext::GetBestPhysicalDevice() { void VulkanContext::ChooseDevice(int physical_device) { physical_device_ = physical_device; - INFO_LOG(G3D, "Chose physical device %d: %p", physical_device, physical_devices_[physical_device]); + INFO_LOG(G3D, "Chose physical device %d: %s", physical_device, physicalDeviceProperties_[physical_device].properties.deviceName); GetDeviceLayerProperties(); if (!CheckLayers(device_layer_properties_, device_layer_names_)) { @@ -711,7 +711,7 @@ VkResult VulkanContext::CreateDevice() { } else { VulkanLoadDeviceFunctions(device_, extensionsLookup_); } - INFO_LOG(G3D, "Vulkan Device created"); + INFO_LOG(G3D, "Vulkan Device created: %s", physicalDeviceProperties_[physical_device_].properties.deviceName); VulkanSetAvailable(true); VmaAllocatorCreateInfo allocatorInfo = {}; diff --git a/Common/GPU/Vulkan/VulkanLoader.cpp b/Common/GPU/Vulkan/VulkanLoader.cpp index 2a00ed9622..2a37785544 100644 --- a/Common/GPU/Vulkan/VulkanLoader.cpp +++ b/Common/GPU/Vulkan/VulkanLoader.cpp @@ -470,6 +470,7 @@ bool VulkanMayBeAvailable() { case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: anyGood = true; + INFO_LOG(G3D, "VulkanMayBeAvailable: Eligible device found: '%s'", props.deviceName); break; default: INFO_LOG(G3D, "VulkanMayBeAvailable: Ineligible device found and ignored: '%s'", props.deviceName); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index b0c74c5c76..2d58bebdc8 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -742,7 +742,9 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur usageBits |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } - if (!vkTex_->CreateDirect(cmd, width_, height_, 1, mipLevels_, vulkanFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, usageBits)) { + VkComponentMapping r8AsAlpha[4] = { VK_COMPONENT_SWIZZLE_ONE, VK_COMPONENT_SWIZZLE_ONE, VK_COMPONENT_SWIZZLE_ONE, VK_COMPONENT_SWIZZLE_R }; + + if (!vkTex_->CreateDirect(cmd, width_, height_, 1, mipLevels_, vulkanFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, usageBits, desc.swizzle == TextureSwizzle::R8_AS_ALPHA ? r8AsAlpha : nullptr)) { ERROR_LOG(G3D, "Failed to create VulkanTexture: %dx%dx%d fmt %d, %d levels", width_, height_, depth_, (int)vulkanFormat, mipLevels_); return false; } diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 9a427027a9..3013470e95 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -598,6 +598,11 @@ struct DeviceCaps { // Important: only write to the provided pointer, don't read from it. typedef std::function TextureCallback; +enum class TextureSwizzle { + DEFAULT, + R8_AS_ALPHA, +}; + struct TextureDesc { TextureType type; DataFormat format; @@ -607,6 +612,7 @@ struct TextureDesc { int depth; int mipLevels; bool generateMips; + TextureSwizzle swizzle; // Optional, for tracking memory usage and graphcis debuggers. const char *tag; // Does not take ownership over pointed-to data. diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index b6ada98a94..1cc3dc9d1e 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -27,7 +27,10 @@ TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) ERROR_LOG(G3D, "Failed to find class: '%s'", textRendererClassName); } dpiScale_ = CalculateDPIScale(); - INFO_LOG(G3D, "Initializing TextDrawerAndroid with DPI scale %f", dpiScale_); + + use4444Format_ = (draw->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & Draw::FMT_TEXTURE) != 0; + + INFO_LOG(G3D, "Initializing TextDrawerAndroid with DPI scale %f, use4444=%d", dpiScale_, (int)use4444Format_); } TextDrawerAndroid::~TextDrawerAndroid() { @@ -244,7 +247,8 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, entry = iter->second.get(); entry->lastUsedFrame = frameCount_; } else { - DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; + // Actually, I don't know why we don't always use R8_UNORM.. + DataFormat texFormat = use4444Format_ ? Draw::DataFormat::R4G4B4A4_UNORM_PACK16 : Draw::DataFormat::R8_UNORM; entry = new TextStringEntry(); @@ -260,6 +264,7 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, desc.depth = 1; desc.mipLevels = 1; desc.generateMips = false; + desc.swizzle = use4444Format_ ? Draw::TextureSwizzle::DEFAULT : Draw::TextureSwizzle::R8_AS_ALPHA, desc.tag = "TextDrawer"; entry->texture = draw_->CreateTexture(desc); cache_[key] = std::unique_ptr(entry); diff --git a/Common/Render/Text/draw_text_android.h b/Common/Render/Text/draw_text_android.h index 4b95bdeb75..f4b7e431e6 100644 --- a/Common/Render/Text/draw_text_android.h +++ b/Common/Render/Text/draw_text_android.h @@ -40,6 +40,7 @@ private: jmethodID method_renderText; uint32_t fontHash_; + bool use4444Format_ = false; std::map fontMap_; diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index deba2f8d39..91005fd71c 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1384,6 +1384,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G 1, 1, false, + Draw::TextureSwizzle::DEFAULT, "DrawPixels", { (uint8_t *)srcPixels }, generateTexture, From 051039b69641147d215ec354611e1c155540f751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Moraz=C3=A1n?= <35014183+GABO1423@users.noreply.github.com> Date: Tue, 28 Feb 2023 00:01:26 -0400 Subject: [PATCH 061/141] (UWP) Fix Skipped Asset Files --- UWP/UWP.vcxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index d3f35bd6d8..561080d0eb 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -598,18 +598,23 @@ true + Content\debugger\static\css\%(Filename)%(Extension) true + Content\debugger\static\css\%(Filename)%(Extension) true + Content\debugger\static\js\%(Filename)%(Extension) true + Content\debugger\static\js\%(Filename)%(Extension) true + Content\debugger\static\media\%(Filename)%(Extension) true @@ -981,6 +986,7 @@ true + Content\debugger\static\js\%(Filename)%(Extension) true From ea5d4a5671a8369ca46e2909478b336cca39dc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 28 Feb 2023 08:31:25 +0100 Subject: [PATCH 062/141] Rotate the analog stick by 15 degrees in the GoW games too, they do the same thing. --- assets/compat.ini | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index 14ee5a4bbb..58dff15e54 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1490,3 +1490,36 @@ UCES00044 = true NPUG80329 = true NPEG00025 = true UCKS45025 = true + +# GOW : Ghost of Sparta +UCUS98737 = true +UCAS40323 = true +NPHG00092 = true +NPEG00044 = true +NPEG00045 = true +NPJG00120 = true +NPUG80508 = true +UCJS10114 = true +UCES01401 = true +UCES01473 = true +# GOW : Ghost of Sparta Demo +NPJG90095 = true +NPEG90035 = true +NPUG70125 = true +# GOW : Chains Of Olympus +UCAS40198 = true +UCUS98653 = true +UCES00842 = true +ULJM05438 = true +ULJM05348 = true +UCKS45084 = true +NPUG80325 = true +NPEG00023 = true +NPHG00027 = true +NPHG00028 = true +NPJH50170 = true +UCET00844 = true +# GOW: Chains of Olympus Demo +UCUS98705 = true +UCED00971 = true +UCUS98713 = true From 8f1b4d6740eaca9b43a3f4928e7c92494dc406d1 Mon Sep 17 00:00:00 2001 From: Halo-Michael <45062120+Halo-Michael@users.noreply.github.com> Date: Tue, 28 Feb 2023 20:47:26 +0800 Subject: [PATCH 063/141] Update PPSSPP-Info.plist First, we are no longer support armv7 then specific CFBundleDevelopmentRegion key will make https://github.com/hrydgard/ppsspp/blob/a11ca4cb9010b6c6ca33d5ec46b369045a43f6f3/ios/main.mm#L109 always return en_XX --- ios/PPSSPP-Info.plist | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist index 55d219357c..6e72de13ff 100644 --- a/ios/PPSSPP-Info.plist +++ b/ios/PPSSPP-Info.plist @@ -10,8 +10,6 @@ Your camera may be used to emulate Go!Cam, a camera accessory NSMicrophoneUsageDescription Your microphone may be used to emulate Go!Cam/Talkman, a microphone accessory - CFBundleDevelopmentRegion - en CFBundleDisplayName PPSSPP CFBundleExecutable @@ -36,7 +34,7 @@ UIRequiredDeviceCapabilities - armv7 + arm64 UIStatusBarHidden From 891cc49beb71f0ebffcc2e1df3505c8f8aebd07e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 28 Feb 2023 06:57:47 -0800 Subject: [PATCH 064/141] iOS: Allow saving interp when jit was selected. --- Core/Config.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 3cf4d788b6..b82cf41ff3 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1589,15 +1589,16 @@ void Config::PostLoadCleanup(bool gameSpecific) { void Config::PreSaveCleanup(bool gameSpecific) { if (jitForcedOff) { - // if JIT has been forced off, we don't want to screw up the user's ppsspp.ini - g_Config.iCpuCore = (int)CPUCore::JIT; + // If we forced jit off and it's still set to IR, change it back to jit. + if (g_Config.iCpuCore == (int)CPUCore::IR_JIT) + g_Config.iCpuCore = (int)CPUCore::JIT; } } void Config::PostSaveCleanup(bool gameSpecific) { if (jitForcedOff) { - // force JIT off again just in case Config::Save() is called without exiting PPSSPP - if (g_Config.iCpuCore != (int)CPUCore::INTERPRETER) + // Force JIT off again just in case Config::Save() is called without exiting PPSSPP. + if (g_Config.iCpuCore == (int)CPUCore::JIT) g_Config.iCpuCore = (int)CPUCore::IR_JIT; } } From f199a0fee35e9d9720ccbbf5cd3542e2985e2a97 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 28 Feb 2023 06:58:30 -0800 Subject: [PATCH 065/141] UI: Forget jit override on user selection. --- Core/Config.cpp | 7 +++++++ Core/Config.h | 2 ++ UI/GameSettingsScreen.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/Core/Config.cpp b/Core/Config.cpp index b82cf41ff3..8b4cbe05ab 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1603,6 +1603,13 @@ void Config::PostSaveCleanup(bool gameSpecific) { } } +void Config::NotifyUpdatedCpuCore() { + if (jitForcedOff && g_Config.iCpuCore == (int)CPUCore::IR_JIT) { + // No longer forced off, the user set it to IR jit. + jitForcedOff = false; + } +} + // Use for debugging the version check without messing with the server #if 0 #define PPSSPP_GIT_VERSION "v0.0.1-gaaaaaaaaa" diff --git a/Core/Config.h b/Core/Config.h index a87fbc005d..17f198f920 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -568,6 +568,8 @@ public: bool LoadAppendedConfig(); void SetAppendedConfigIni(const Path &path); + void NotifyUpdatedCpuCore(); + protected: void LoadStandardControllerIni(); void LoadLangValuesMapping(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 454eec0d03..6c8f8469f3 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1774,6 +1774,10 @@ void DeveloperToolsScreen::CreateViews() { static const char *cpuCores[] = {"Interpreter", "Dynarec (JIT)", "IR Interpreter"}; PopupMultiChoice *core = list->Add(new PopupMultiChoice(&g_Config.iCpuCore, gr->T("CPU Core"), cpuCores, 0, ARRAY_SIZE(cpuCores), sy->GetName(), screenManager())); core->OnChoice.Handle(this, &DeveloperToolsScreen::OnJitAffectingSetting); + core->OnChoice.Add([](UI::EventParams &) { + g_Config.NotifyUpdatedCpuCore(); + return UI::EVENT_DONE; + }); if (!canUseJit) { core->HideChoice(1); } From 9c21184352de57807d5abdf2c967b5c4d9fcbe54 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 28 Feb 2023 07:03:12 -0800 Subject: [PATCH 066/141] vertexjit: Simplify CPU core check. This also avoids allocating the memory we won't use if it's off. --- GPU/Common/DrawEngineCommon.cpp | 7 +++++-- GPU/Common/DrawEngineCommon.h | 4 +++- GPU/Common/VertexDecoderCommon.cpp | 2 +- unittest/TestVertexJit.cpp | 3 --- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 139d47613c..59083f5352 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -36,7 +36,9 @@ enum { }; DrawEngineCommon::DrawEngineCommon() : decoderMap_(16) { - decJitCache_ = new VertexDecoderJitCache(); + if (g_Config.bVertexDecoderJit && g_Config.iCpuCore == (int)CPUCore::JIT) { + decJitCache_ = new VertexDecoderJitCache(); + } transformed = (TransformedVertex *)AllocateMemoryPages(TRANSFORMED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE); transformedExpanded = (TransformedVertex *)AllocateMemoryPages(3 * TRANSFORMED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE); } @@ -169,7 +171,8 @@ static Vec3f ScreenToDrawing(const Vec3f& coords) { } void DrawEngineCommon::NotifyConfigChanged() { - decJitCache_->Clear(); + if (decJitCache_) + decJitCache_->Clear(); lastVType_ = -1; dec_ = nullptr; decoderMap_.Iterate([&](const uint32_t vtype, VertexDecoder *decoder) { diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index d5f8056417..94ce1f35e7 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -117,7 +117,9 @@ public: } bool IsCodePtrVertexDecoder(const u8 *ptr) const { - return decJitCache_->IsInSpace(ptr); + if (decJitCache_) + return decJitCache_->IsInSpace(ptr); + return false; } int GetNumDrawCalls() const { return numDrawCalls; diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 488c1736e9..21b3e76a6f 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -1274,7 +1274,7 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options, // Attempt to JIT as well. But only do that if the main CPU JIT is enabled, in order to aid // debugging attempts - if the main JIT doesn't work, this one won't do any better, probably. - if (jitCache && g_Config.bVertexDecoderJit && g_Config.iCpuCore == (int)CPUCore::JIT) { + if (jitCache) { jitted_ = jitCache->Compile(*this, &jittedSize_); if (!jitted_) { WARN_LOG(G3D, "Vertex decoder JIT failed! fmt = %08x (%s)", fmt_, GetString(SHADER_STRING_SHORT_DESC).c_str()); diff --git a/unittest/TestVertexJit.cpp b/unittest/TestVertexJit.cpp index 12dad2f5aa..e759d64444 100644 --- a/unittest/TestVertexJit.cpp +++ b/unittest/TestVertexJit.cpp @@ -38,9 +38,6 @@ public: dst_ = new u8[BUFFER_SIZE]; cache_ = new VertexDecoderJitCache(); - g_Config.bVertexDecoderJit = true; - // Required for jit to be enabled. - g_Config.iCpuCore = (int)CPUCore::JIT; gstate_c.uv.uScale = 1.0f; gstate_c.uv.vScale = 1.0f; } From 31d796e769139c2379be19e3fb3a80f5e0efc75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 28 Feb 2023 18:18:19 +0100 Subject: [PATCH 067/141] Vulkan on Android: In non-debug mode, avoid devices that were rejected by VulkanMayBeAvailable. Fixes #16931 Can still use these devices in debug builds by modifying the ini. --- Common/GPU/Vulkan/VulkanContext.cpp | 2 ++ Common/GPU/Vulkan/VulkanLoader.cpp | 6 ++++-- android/jni/app-android.cpp | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index a8e2a1846c..032fc128f6 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -712,6 +712,8 @@ VkResult VulkanContext::CreateDevice() { VulkanLoadDeviceFunctions(device_, extensionsLookup_); } INFO_LOG(G3D, "Vulkan Device created: %s", physicalDeviceProperties_[physical_device_].properties.deviceName); + + // Since we successfully created a device (however we got here, might be interesting in debug), we force the choice to be visible in the menu. VulkanSetAvailable(true); VmaAllocatorCreateInfo allocatorInfo = {}; diff --git a/Common/GPU/Vulkan/VulkanLoader.cpp b/Common/GPU/Vulkan/VulkanLoader.cpp index 2a37785544..5dd7baf4f6 100644 --- a/Common/GPU/Vulkan/VulkanLoader.cpp +++ b/Common/GPU/Vulkan/VulkanLoader.cpp @@ -310,6 +310,7 @@ static void VulkanFreeLibrary(VulkanLibraryHandle &h) { } void VulkanSetAvailable(bool available) { + INFO_LOG(G3D, "Forcing Vulkan availability to true"); g_vulkanAvailabilityChecked = true; g_vulkanMayBeAvailable = available; } @@ -495,8 +496,9 @@ bail: } if (lib) { VulkanFreeLibrary(lib); - } else { - ERROR_LOG(G3D, "Vulkan with working device not detected."); + } + if (!g_vulkanMayBeAvailable) { + WARN_LOG(G3D, "Vulkan with working device not detected."); } return g_vulkanMayBeAvailable; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 67e2fc9238..42d46cae43 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -73,6 +73,7 @@ struct JNIEnv {}; #include "Common/Math/math_util.h" #include "Common/Data/Text/Parsers.h" #include "Common/VR/PPSSPPVR.h" +#include "Common/GPU/Vulkan/VulkanLoader.h" #include "Common/GraphicsContext.h" #include "Common/StringUtils.h" @@ -677,9 +678,9 @@ static void parse_args(std::vector &args, const std::string value) #define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__) extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init - (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, - jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jadditionalStorageDirs, jstring jcacheDir, jstring jshortcutParam, - jint jAndroidVersion, jstring jboard) { +(JNIEnv * env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, + jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jadditionalStorageDirs, jstring jcacheDir, jstring jshortcutParam, + jint jAndroidVersion, jstring jboard) { SetCurrentThreadName("androidInit"); // Makes sure we get early permission grants. @@ -754,6 +755,15 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str()); + // In debug mode, don't allow creating software Vulkan devices (reject by VulkaMaybeAvailable). + // Needed for #16931. +#ifdef NDEBUG + if (!VulkanMayBeAvailable()) { + // If VulkanLoader decided on no viable backend, let's force Vulkan off in release builds at least. + g_Config.iGPUBackend = 0; + } +#endif + // No need to use EARLY_LOG anymore. retry: From c6352a262d272468e867138f3d3310234302210b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 28 Feb 2023 23:21:36 +0100 Subject: [PATCH 068/141] Fix crash in SoftGPU when frameskipping, noticed by sum2012 in Daxter Fixes #17021 --- GPU/Software/SoftGpu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 2a3bf685f7..7fe082b859 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -670,6 +670,9 @@ void SoftGPU::MarkDirty(uint32_t addr, uint32_t bytes, SoftGPUVRAMDirty value) { uint32_t start = ((addr - PSP_GetVidMemBase()) & 0x001FFFFF) >> 10; uint32_t end = start + ((bytes + 1023) >> 10); + if (end > sizeof(vramDirty_)) { + end = sizeof(vramDirty_); + } if (value == SoftGPUVRAMDirty::CLEAR || value == (SoftGPUVRAMDirty::DIRTY | SoftGPUVRAMDirty::REALLY_DIRTY)) { memset(vramDirty_ + start, (uint8_t)value, end - start); } else { From 7a7117f2f70b715df6b3cc0e1829de3e9247b571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 1 Mar 2023 22:38:46 +0100 Subject: [PATCH 069/141] Cache framebuffer copies (for self-texturing) until the next TexFlush instruction. Fixes #17030 , or at least improves on it - for optimal performance that big framebuffer used for bloom should be split like in Killzone, but it's not trivial. The regression in 1.14 is fixed with this, at least. I tried it with a few other games with no issues - it seems games are using TexFlush when needed. But let's see if it really is safe to rely on that... There might also be other places we should call DiscardFramebufferCopy in. --- GPU/Common/FramebufferManagerCommon.cpp | 11 +++++++++++ GPU/Common/FramebufferManagerCommon.h | 6 ++++++ GPU/GPUCommonHW.cpp | 9 ++++++++- GPU/GPUCommonHW.h | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 91005fd71c..cf96977c42 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -546,6 +546,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer // TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?) } + DiscardFramebufferCopy(); + // We already have it! } else if (vfb != currentRenderVfb_) { // Use it as a render target. @@ -562,6 +564,8 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer NotifyRenderFramebufferSwitched(prev, vfb, params.isClearingDepth); CopyToColorFromOverlappingFramebuffers(vfb); gstate_c.usingDepth = false; // reset depth buffer tracking + + DiscardFramebufferCopy(); } else { // Something changed, but we still got the same framebuffer we were already rendering to. // Might not be a lot to do here, we check in NotifyRenderFramebufferUpdated @@ -1233,6 +1237,12 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF // Self-texturing, need a copy currently (some backends can potentially support it though). WARN_LOG_ONCE(selfTextureCopy, G3D, "Attempting to texture from current render target (src=%08x / target=%08x / flags=%d), making a copy", framebuffer->fb_address, currentRenderVfb_->fb_address, flags); // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. + if (currentFramebufferCopy_) { + // We have a copy already that hasn't been invalidated, let's keep using it. + draw_->BindFramebufferAsTexture(currentFramebufferCopy_, stage, Draw::FB_COLOR_BIT, layer); + return true; + } + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; @@ -1240,6 +1250,7 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF CopyFramebufferForColorTexture(©Info, framebuffer, flags, layer); RebindFramebuffer("After BindFramebufferAsColorTexture"); draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, layer); + currentFramebufferCopy_ = renderCopy; gpuStats.numCopiesForSelfTex++; } else { // Failed to get temp FBO? Weird. diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 672130f7aa..bda9bdc389 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -466,6 +466,10 @@ public: return msaaLevel_; } + void DiscardFramebufferCopy() { + currentFramebufferCopy_ = nullptr; + } + protected: virtual void ReadbackFramebuffer(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel, Draw::ReadbackMode mode); // Used for when a shader is required, such as GLES. @@ -552,6 +556,8 @@ protected: VirtualFramebuffer *currentRenderVfb_ = nullptr; + Draw::Framebuffer *currentFramebufferCopy_ = nullptr; + // The range of PSP memory that may contain FBOs. So we can skip iterating. u32 framebufRangeEnd_ = 0; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 5c51cf5e8a..01d6dd02c2 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -287,7 +287,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_LSC3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_LIGHT3 }, // Ignored commands - { GE_CMD_TEXFLUSH, 0 }, + { GE_CMD_TEXFLUSH, FLAG_EXECUTE, 0, &GPUCommonHW::Execute_TexFlush }, { GE_CMD_TEXSYNC, 0 }, // These are just nop or part of other later commands. @@ -1619,6 +1619,13 @@ void GPUCommonHW::Execute_BoneMtxData(u32 op, u32 diff) { gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; } +void GPUCommonHW::Execute_TexFlush(u32 op, u32 diff) { + // Games call this when they need the effect of drawing to be visible to texturing. + // And for a bunch of other reasons, but either way, this is what we need to do. + // It's possible we could also use this as a hint for the texture cache somehow. + framebufferManager_->DiscardFramebufferCopy(); +} + size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f; return snprintf(buffer, size, diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 2e13a11738..000993be79 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -60,6 +60,8 @@ public: void Execute_BoneMtxNum(u32 op, u32 diff); void Execute_BoneMtxData(u32 op, u32 diff); + void Execute_TexFlush(u32 op, u32 diff); + typedef void (GPUCommonHW::*CmdFunc)(u32 op, u32 diff); void FastRunLoop(DisplayList &list) override; From 4d0fbdb96f34affa43af8070fd3c9cadc44be6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 1 Mar 2023 22:51:23 +0100 Subject: [PATCH 070/141] Add a safety check - don't cache partial copies. --- GPU/Common/FramebufferManagerCommon.cpp | 19 ++++++++++++++++--- GPU/Common/FramebufferManagerCommon.h | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index cf96977c42..e4f3bc0d94 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1247,10 +1247,17 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; copyInfo.fbo = renderCopy; - CopyFramebufferForColorTexture(©Info, framebuffer, flags, layer); + + bool partial = false; + CopyFramebufferForColorTexture(©Info, framebuffer, flags, layer, &partial); RebindFramebuffer("After BindFramebufferAsColorTexture"); draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, layer); - currentFramebufferCopy_ = renderCopy; + + // Only cache the copy if it wasn't a partial copy. + // TODO: Improve on this. + if (!partial) { + currentFramebufferCopy_ = renderCopy; + } gpuStats.numCopiesForSelfTex++; } else { // Failed to get temp FBO? Weird. @@ -1273,14 +1280,17 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF } } -void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags, int layer) { +void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags, int layer, bool *partial) { int x = 0; int y = 0; int w = src->drawnWidth; int h = src->drawnHeight; + *partial = false; + // If max is not > min, we probably could not detect it. Skip. // See the vertex decoder, where this is updated. + // TODO: We're currently not hitting this path in Dante. See #17032 if ((flags & BINDFBCOLOR_MAY_COPY_WITH_UV) == BINDFBCOLOR_MAY_COPY_WITH_UV && gstate_c.vertBounds.maxU > gstate_c.vertBounds.minU) { x = std::max(gstate_c.vertBounds.minU, (u16)0); y = std::max(gstate_c.vertBounds.minV, (u16)0); @@ -1298,6 +1308,9 @@ void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer } if (x < src->drawnWidth && y < src->drawnHeight && w > 0 && h > 0) { + if (x != 0 || y != 0 || w < src->drawnWidth || h < src->drawnHeight) { + *partial = true; + } BlitFramebuffer(dst, x, y, src, x, y, w, h, 0, RASTER_COLOR, "CopyFBForColorTexture"); } } diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index bda9bdc389..394e5d0593 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -490,7 +490,7 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag); - void CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags, int layer); + void CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags, int layer, bool *partial); void EstimateDrawingSize(u32 fb_address, int fb_stride, GEBufferFormat fb_format, int viewport_width, int viewport_height, int region_width, int region_height, int scissor_width, int scissor_height, int &drawing_width, int &drawing_height); From 624e443b5b494b08bbff60de4ad646079b960602 Mon Sep 17 00:00:00 2001 From: huhongwei Date: Thu, 2 Mar 2023 09:13:31 +0800 Subject: [PATCH 071/141] Fix logic error: assign EGLSurface to EGLConfig --- Common/VR/VRBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index 284c33aaff..3ada93d754 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -182,7 +182,7 @@ void VR_EnterVR( engine_t* engine, XrGraphicsBindingVulkanKHR* graphicsBindingVu graphicsBindingGL.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR; graphicsBindingGL.next = NULL; graphicsBindingGL.display = eglGetCurrentDisplay(); - graphicsBindingGL.config = eglGetCurrentSurface(EGL_DRAW); + graphicsBindingGL.config = NULL; graphicsBindingGL.context = eglGetCurrentContext(); sessionCreateInfo.next = &graphicsBindingGL; #else From 92a18eed01988db7ea6a55f1549fc919476f0524 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 1 Mar 2023 22:03:56 -0800 Subject: [PATCH 072/141] GPU: Discard framebuffer copy when clearing. This avoids retaining the framebuffer copy any longer than the current framebuffer target. --- GPU/Common/DepthBufferCommon.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 6 ++++++ GPU/Common/FramebufferManagerCommon.h | 2 ++ GPU/GLES/StencilBufferGLES.cpp | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/GPU/Common/DepthBufferCommon.cpp b/GPU/Common/DepthBufferCommon.cpp index 46a2ecfb6f..a29c995775 100644 --- a/GPU/Common/DepthBufferCommon.cpp +++ b/GPU/Common/DepthBufferCommon.cpp @@ -204,7 +204,7 @@ bool FramebufferManagerCommon::ReadbackDepthbuffer(Draw::Framebuffer *fbo, int x } shaderManager_->DirtyLastShader(); - auto *blitFBO = GetTempFBO(TempFBO::COPY, fbo->Width() * scaleX, fbo->Height() * scaleY); + auto *blitFBO = GetTempFBO(TempFBO::Z_COPY, fbo->Width() * scaleX, fbo->Height() * scaleY); draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackDepthbufferSync"); Draw::Viewport viewport = { 0.0f, 0.0f, (float)destW, (float)destH, 0.0f, 1.0f }; draw_->SetViewport(viewport); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index e4f3bc0d94..e21bc35e93 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -932,6 +932,7 @@ void FramebufferManagerCommon::DestroyFramebuf(VirtualFramebuffer *v) { } // Wipe some pointers + DiscardFramebufferCopy(); if (currentRenderVfb_ == v) currentRenderVfb_ = nullptr; if (displayFramebuf_ == v) @@ -1450,6 +1451,7 @@ void FramebufferManagerCommon::DrawFramebufferToOutput(const u8 *srcPixels, int // PresentationCommon sets all kinds of state, we can't rely on anything. gstate_c.Dirty(DIRTY_ALL); + DiscardFramebufferCopy(); currentRenderVfb_ = nullptr; } @@ -1607,10 +1609,12 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { // This may get called mid-draw if the game uses an immediate flip. // PresentationCommon sets all kinds of state, we can't rely on anything. gstate_c.Dirty(DIRTY_ALL); + DiscardFramebufferCopy(); currentRenderVfb_ = nullptr; } void FramebufferManagerCommon::DecimateFBOs() { + DiscardFramebufferCopy(); currentRenderVfb_ = nullptr; for (auto iter : fbosToDelete_) { @@ -1767,6 +1771,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, } else { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "ResizeFramebufFBO"); } + DiscardFramebufferCopy(); currentRenderVfb_ = vfb; if (!vfb->fbo) { @@ -2568,6 +2573,7 @@ void FramebufferManagerCommon::NotifyConfigChanged() { } void FramebufferManagerCommon::DestroyAllFBOs() { + DiscardFramebufferCopy(); currentRenderVfb_ = nullptr; displayFramebuf_ = nullptr; prevDisplayFramebuf_ = nullptr; diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 394e5d0593..d0c83b4d6c 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -217,6 +217,8 @@ enum class TempFBO { BLIT, // For copies of framebuffers (e.g. shader blending.) COPY, + // Used for copies when setting color to depth. + Z_COPY, // Used to copy stencil data, means we need a stencil backing. STENCIL, }; diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index 763e5a95fe..845cd2b3ac 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -130,7 +130,7 @@ bool FramebufferManagerGLES::ReadbackStencilbuffer(Draw::Framebuffer *fbo, int x } shaderManager_->DirtyLastShader(); - auto *blitFBO = GetTempFBO(TempFBO::COPY, fbo->Width(), fbo->Height()); + auto *blitFBO = GetTempFBO(TempFBO::Z_COPY, fbo->Width(), fbo->Height()); draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackStencilbufferSync"); Draw::Viewport viewport = { 0.0f, 0.0f, (float)fbo->Width(), (float)fbo->Height(), 0.0f, 1.0f }; draw_->SetViewport(viewport); From 4960c8a07a4f182b31305e1a31faa236e667a629 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 2 Mar 2023 06:20:49 +0000 Subject: [PATCH 073/141] riscv: Add libatomic. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20cd4d0e74..ebd1b151c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1354,7 +1354,7 @@ if(LINUX AND NOT ANDROID) endif() set(ATOMIC_LIB) -if(ANDROID OR (LINUX AND ARM_DEVICE)) +if(ANDROID OR (LINUX AND ARM_DEVICE) OR (LINUX AND RISCV64)) set(ATOMIC_LIB atomic) endif() From 38bbd4b8a7cc21acbda7a63bf5b24bd1feb17950 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 2 Mar 2023 06:21:06 +0000 Subject: [PATCH 074/141] Common: Fix printf format warning. --- Common/System/Display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/System/Display.cpp b/Common/System/Display.cpp index 7f79cc5041..15af7de4e3 100644 --- a/Common/System/Display.cpp +++ b/Common/System/Display.cpp @@ -63,6 +63,6 @@ void DisplayProperties::Print() { printf("dpi_real: %f, %f\n", dpi_scale_real_x, dpi_scale_real_y); printf("display_hz: %f\n", display_hz); - printf("rotation: %d\n", rotation); + printf("rotation: %d\n", (int)rotation); rot_matrix.print(); } From 055ac2b697132379a4f4281af5d3758e077cbb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 2 Mar 2023 11:45:47 +0100 Subject: [PATCH 075/141] Add new default icon --- android/AndroidManifest.xml | 3 +-- android/res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++++ .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++++ android/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4218 bytes .../res/mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 8456 bytes android/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6731 bytes android/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2581 bytes .../res/mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 5094 bytes android/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3971 bytes android/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6191 bytes .../res/mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 12254 bytes android/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 9908 bytes android/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 10154 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 21824 bytes android/res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 16345 bytes android/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 14281 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 31261 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 23394 bytes android/res/values/ic_launcher_background.xml | 4 ++++ 19 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 android/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 android/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 android/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 android/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 android/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 android/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 android/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 android/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/res/values/ic_launcher_background.xml diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index ab8cf74a3b..0d67ad89e4 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,6 @@ @@ -43,7 +42,7 @@ + + + + \ No newline at end of file diff --git a/android/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..036d09bc5f --- /dev/null +++ b/android/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/res/mipmap-hdpi/ic_launcher.png b/android/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e4861f3566c7c069c9adc66de2d033f443da3142 GIT binary patch literal 4218 zcmV-=5QXoFP)jUuBa` zKmivt?#3~qag9sj9!+M+OeQn`M1DAPo^$&`(@m7EBUrpukJ#O}-+Rw<&+?peVaAN< zHNB?S^qO8%e!b@G?EKSL39!lB$g!=QoS&G#^~$Vq+h(%C9QQ!)Ia)*h9Bp8wt-GO| zUsp^e$Tjn|iw$hca5j!^8NcZ09Qqb-c!@y*ZNSur*b#W?LucQ&Ol=*_8i>z1?g5vT z4fWr@Qec~GT>Y-Fi7GiG&q>B4HeX7SP1&*EN9;)bu-Hoj1Tr+Av9mVmX@yBPTDPbq}x?X z{yEJQSlB^^-CY8gjTtnX!@&Hz69VxdCTOwyKG-@of;7H+l?}&7kgZ1$&DDjIlVKxi z)}@eVVhQ=C*ONZ4O^8@ke1IaW4^v$8NlHI*iOT+bhgMe{B(I(2{Cz7`P9s4dE_CmJ ztz#l+rbgGSY&d#?Sa;B(Rh!5)HcP@~WmXe=M<)dpbx~M(FGbh(QT)Dhlz;L%)m?c+ z9bf#F4nO;jj(+_g!KRi|_drt_A1iimV+X-8Le1h0(d{U~#LqN#7x?_~w zdyz`nJNDjuN*zi#$_dGH|J4kX%E$0x=(#y z{o8_s=b^g)9t9Wgr{z&QIS;*p1kHB!r^TzcaY*f_@=rduAmBx4zw*q4wHS9dZ^!mfEVHL)9+|Q!$;(sR!4T8!It}v7cZS41`LCNFw~p? zpbz-!KXin{@5nRYJ4w`so&x`(o!u8mpW90FRz_LuJ#I_}f}mj#B1qDm_x>ivs{|Zm z0)XBxfp4kj$+y%EaPmID*#GDsRB+~|B>BZ*Tg4;I_a7G~1wouUmPaRXvOPeR{a;uR z0CYVBzM;+s-%y9(@3im!->Lon->8CS#+wq* z&R*39um);nw0sWyg_;?A?|x0S*B?`4%@OiRtr>A8v-L2LgKjk~2wYE^=w$MU-_0#d zgvD`_f*`BMNGb&-P zTg{csKYK3=#xRS$)&1xChD#6U5|b(F8=}-8qIAF9oF!aMEkbm%5Cq_&L(6*D=%-YF z+epBzr?lrY1+_Q76x7`KlB#b!5ma7%L~(6r#6a)lDrLyUMk4VeX)lXRB<%C0;Vq_Tm?UFg@KrfpnTl7r7`X+J<3_Jfd&;2tiq>pT^5LT}`KdrcHN3PVKI zAyM=e)6hvn5LdE=A#q|<)Sf=dWS?^Dqw1-mcZquleiIkXNb z9X}D|zUJ=)RmU2G6 zO$D4|fc%S}Qy#!Z=YDdRHgbsISXCs7D(6ylzi$s zg&9N3J-%?jAS77{(b_)hVeMp2geY;wVOHaZM2NBog#cyURnK`YwCsS)poS#sACn+^ zuTYxjA5BZcMbbq&KuGs)NGu6WfbBPlQB3j;|jOF^)#(xDWDz?Ed$S6 zxGIhU^4lqxs}a0GA;LtB8;KAl4iWlD5up&E5TDH~BO+@#QK!}N`AvE{YY}9}#=+$m z7&ee*Z8CXoFP2;ZBVe2i!#Ekocbub~<5y|hzOxcC_)M2ttIV0%GiEVS@GS4-O34xU z+)$*9T9-xc6pzDL)+eP(h~CJ+2@$FiS5>%CfEvZ8Dm)=NV_Q#7%Vn zkUe{VXF>@JvAvQoVK{WhdJdBm(_z@7!BTsO?{G2a8I)rfAHdx`ybK=Lts^ zfe5X6`nBYn)kyvYZIr_25+aObs7hQ^xKV*C>U&Uls-m-swNciY>!izTB28SzU>>px zL6{P9C4%QP@yX3e7zrx4oQGHSisAnGZA?lrlM*T3gs{UoAnH7Jl#pK@dsR_8<($4Y z#3L+-5aIb`i0D)WXDMr~-1E06ptzN^$t8nNNvuH-Hb-BBoKbN4hK2DXMK>qVq}~e@ zu)B>(XdzurBQ1^EF{BJHjY=f%%sL4T-@LsP({`LnSwxBg2N5R9+fsDiquu@YsL-gh zpsiL_ab-*^CwrZ-r?Un@h+jwlC|a4@%t`pHh2g_ta3J=blwf5B^sQ8zl*6A00p8j5 zG(UKw+yEqI5QS=I=N&?tE!pIiT}R$I_3Q<0wByJ{A)#25_fR54a2myC&V{>_%{eja z&x*#1YVIR#MkUP)-T-^7M37c_oa3EV!PGnpGZPf=K15BU@{2tkY1CMuq4ErM{t+V}zrD%nR3Hy&FM zA*I)Z2({v<)dmR`)IcNWot5S9e)d`@R3&m)i) zuiHUxX%*z2Sw*^nM%vJEl2T7!r_?hyDDk7qw7K^@MYkTKpvn%?6*kg}oLWA%l3aI{ zk|rUKmd2)%W6*jfQCo{3RG1}^37i+2D4(mt@I?G|pwfVh22QX8$(0jp)|qRh%Wou) zjA~jC8YiSX_=a_-T9|By@Hm-rSWHqXhagXU%7@f@j3FDdsa2+OIt}_ z)+Qd}!LQx;eV3#nJ|E2~btK%M?UdkiJw zDb?ImOYLA{E#|zjitjK8SiUJ;qys{0+2#xhfv~DhZVw*PruJjxm0K(C!Ckz@{Un8U4u9ap7U{CcLwXFw)MDK{s?9M&WV7vQ`-kNgUoL}Qo|v74(4h!|2; zjEOYmnJc0Nh^eHQTad-OwY)0*lv=^O3YQ3Bp}-G^~>gg}xXh;--u(QkaY+t}Y43uTWgi z#}wIelnk7Av>YmKEY|FN!-QnVz_t9kj8ju%w0B?hGBFLU=-KLSOA?fe9G_Vo)v7 zk8SHdBgD?-YWl)t{6nxdK`?enOp076?xrHdQp7{-bfH4XEHt1drB*ct|6dfnUA~)j zV(=FmOo@T=#*JVZmwdkQh zo^s*C-(4hEqtu0q+=|kl_Fg1T7TP+Bbw4sBwr-j5jdSz$gR6pj$^{ifV}}>Cd6*yrYn8 zO~ioa1Ic|QUcvM8qw4ddPj-S3mS{0xWa_bgg_vt9G5496FDS=I4D=OrRU#9!?V~HSx$iPXcbz3q z?$*(vk10`4c7jApv0#L7T-VY^rQEyW(KA{U%y=?biG!GABQfztVqy>X)B1xKDeAy^ zTEX6j>WQ|)V*eLm(h~%5*X^8`PXR0{5lSEiEy^xd<;kaS(2kP|Fb21js{bKAcWbnO=;wnS0{}u+7*SEYZQNckeKNG=V*1yaSE*M zAzg6`dFIxU2e&7p(9_FBGK@M8O$CCWm!PO2`eF~96JoaZU80TM=P9D~I2mdVkglki zRxp7wKvP9&qB0$v&k5Qd!!&p7_Yw%2sK3?tAwV!X_(v$+eEB{yRCP+rg5<08Z{;!+ zLra5kD;i`~l+IG;KTPtdy$3K7^wR!TV`R|F(94AdqdMyWA&9HS$1pQ%w zOT^%2!<=LSAkV;zk9W|zjJ9PN3&a-uNhaTb-`UQOgRPE^jz2T!;d$#d#{V$>|4++S ze4%a#qFxOI$PNF%$@Kx-_^#1b*gT3A6ofCm`Q}^iYCc?~eE`>&-?}i~fvF3#kN)0KbA*5P4Z&Qq|C_%a z=`q&W6F-$}J_;LP3v3eG-hKBMLyaEYdTqu#@BA8x1ro Qq5uE@07*qoM6N<$f^%>Et^fc4 literal 0 HcmV?d00001 diff --git a/android/res/mipmap-hdpi/ic_launcher_foreground.png b/android/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd4b7ebadac9b9ead5b7d2e04a62d4eab414b49 GIT binary patch literal 8456 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 literal 0 HcmV?d00001 diff --git a/android/res/mipmap-hdpi/ic_launcher_round.png b/android/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..33e11da64714aa7c3819e1765c971596fe7eec06 GIT binary patch literal 6731 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 literal 0 HcmV?d00001 diff --git a/android/res/mipmap-mdpi/ic_launcher.png b/android/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..78762e0a13e85c5d57c84394aaa8fb809f2742d3 GIT binary patch literal 2581 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 roNWhHA?>Eln2kx00000NkvXXu0mjfng0L{ literal 0 HcmV?d00001 diff --git a/android/res/mipmap-mdpi/ic_launcher_foreground.png b/android/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..00db9645c02693aee68c1c4c07db7a0f213a5555 GIT binary patch literal 5094 zcma)A=QkV<)78taw#w>D^s+ju3#&x$(V`OsAtHLO(W69*y1GQ~qOVTW$S$H2B#4q= z^(Vi-;C=5oGxOzsm@_kH=G=+b*L^|?U;toYVUcR6s~G-^?*ADP!9V}b$#e<}i>g*b zMakIT`sf*MQLdUkIVPyLmi5xLc;GV^dwn2csPPml!N2@O3O+H%q?zaO^kPj zPq##F)hO`hXKfgSkzBC#WhKk3!+c}!MOBL%zwBfmm9&ekbXWgclf6fD_w}qrYj1pf zZu0OebO(~Wv_C?l4m$ynx5ZG3d4cA%8a@}qgRkbtZelcfdh%eg(^#dXyjb=lm_cW3 zs(jk?cxIPk@l0lnQfN+7uKy2-j1z&k8WsluhCZ7LFM2QWtEIy9{HNZD(F)9ECQlWs zbvuDm&)SUO0a;qF&$%h~kKYd~32J=eKA7~)=*XhtXg}gxdVNRyqVu5~(7t{b37vE) zM&}S}7D%_HR5OR{%3!pO!DAs`O(>0n!qqPe9+f+LWK_r&eTw@^bH{f%FXHY16txZ3 z`621(FR@`PwV+{mP8Y@0dd^ebbE~cz{ae@$&C8dgY=7lN_w3mfDZMpm$H@L-e1acQ zBrkB*3Y`C02|w!m_+XBTn=GS;Fp6ProsEN?A?nboLs>Qachl)^9|*lLWi0N%lJ;oS zS>zw#7*{gPh(y>VjPBKQ(C0y?VsfH)+vcjfYW*073D2<;*Zop?_eCD(7gLgSJCPaa zrz-m3vMb0Lu*qtqqN~WrqiayC>6D0ugkj%_uCMQmQM8-kRWiLji%lR?6m%`1shQR9 zDgIL6o>zZj*}BG{K;g60R0J>Kw9!C*`lq7)MVY2cTYdJHTDGQU;j!KyLDoF# zJxO_&!Lrg9wuvk_B3P zC`{eoXns`q#ZlWS7WmiY@F=jyg)SVC5>F#3GUhdV+pA9Dcp4K_hPjnDR0+tYF5EVM zbwOrs)tv~~<*Du_av(PjRi>9|az^3_ct8AoLFQGi@JgrL`}H|#$36Gb0$=68*#>=+ zTcCvStEhi7@k^K|^eIae>t(0`g`>jR7v^+AH?ZiX_!&4p0;x*WkWqF^i8k;bOWwV# zLG{>CVt8Yni(06Dm!+0fje1Cinn~1N}i@OdX1arZl{4UGiz?xe*R4?1j8X; zVb)24c;iZ;uM-`O);RK(1DsmdS*xE2Wt5BJ(up~)iH6Ch2p!!ltI^WQa+>TYUgSsY z^_qeSZxm;`ohaLWP-sMHHd8jyeKL0EMrHoqtz5)x@!i&F7WJ5x6crX!_P)w?*f2

C4LZGLVwx;g3V81gT@{KCBvF{zZ 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 N002ovPDHLkV1hUL3B&*Z literal 0 HcmV?d00001 diff --git a/android/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..1711f0f7b98982df5a9f6c5c27704cc120148ec8 GIT binary patch literal 12254 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 literal 0 HcmV?d00001 diff --git a/android/res/mipmap-xxhdpi/ic_launcher_round.png b/android/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..792f90ca0ae900b471705c2e3f72fbc63a97e7a6 GIT binary patch literal 16345 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 z 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 bEm{6Q?2jv-#RW^F00000NkvXXu0mjfz&))9 literal 0 HcmV?d00001 diff --git a/android/res/mipmap-xxxhdpi/ic_launcher.png b/android/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..dc6461c734a50d643a47be730905f2f61178c65e GIT binary patch literal 14281 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 A^#A|> literal 0 HcmV?d00001 diff --git a/android/res/values/ic_launcher_background.xml b/android/res/values/ic_launcher_background.xml new file mode 100644 index 0000000000..eceed3d93c --- /dev/null +++ b/android/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #2D4553 + \ No newline at end of file From ab5172cd6ebe9836624649cab21b9a81171e76d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 2 Mar 2023 12:51:55 +0100 Subject: [PATCH 076/141] Gradle updates --- android/build.gradle | 10 ++++++---- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index c7f80f319c..bd11462d06 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,7 +22,7 @@ dependencies { android { flavorDimensions "variant" - + namespace 'org.ppsspp.ppsspp' signingConfigs { debug { storeFile file("debug.keystore") @@ -51,11 +51,11 @@ android { applicationId 'org.ppsspp.ppsspp' if (androidGitVersion.name() != "unknown" && androidGitVersion.code() >= 14000000) { // Start using automatic Android version numbers from version 1.4. - println "Overriding Android Version Name, Code: " + androidGitVersion.name() + " " + androidGitVersion.code(); + println "Overriding Android Version Name, Code: " + androidGitVersion.name() + " " + androidGitVersion.code() versionName androidGitVersion.name() versionCode androidGitVersion.code() } else { - println "(not using these:) Android Version Name, Code: " + androidGitVersion.name() + " " + androidGitVersion.code(); + println "(not using these:) Android Version Name, Code: " + androidGitVersion.name() + " " + androidGitVersion.code() } new File("versionname.txt").write(androidGitVersion.name()) @@ -92,7 +92,9 @@ android { } } packagingOptions { - exclude 'lib/arm64-v8a/libopenxr_loader.so' + jniLibs { + excludes += ['lib/arm64-v8a/libopenxr_loader.so'] + } } sourceSets { main { diff --git a/build.gradle b/build.gradle index d9fc3ebd95..4bf0701175 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'com.android.tools.build:gradle:7.4.2' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 86463708b7..b9eead5b1f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,6 +3,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip android.useAndroidX=true android.enableJetifier=true From cf6b00e3aa165c6ea5d109ab17d44b32eb6ce954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 2 Mar 2023 13:13:50 +0100 Subject: [PATCH 077/141] Also add new gold icon --- .../gold/res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++++ .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++++ android/gold/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4088 bytes .../res/mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 8215 bytes .../gold/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6568 bytes android/gold/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2467 bytes .../res/mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 4781 bytes .../gold/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3855 bytes android/gold/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6021 bytes .../res/mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 12189 bytes .../gold/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 9704 bytes android/gold/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 9989 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 21422 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 16019 bytes android/gold/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 14644 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 32536 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 23603 bytes .../gold/res/values/ic_launcher_background.xml | 4 ++++ 18 files changed, 14 insertions(+) create mode 100644 android/gold/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/gold/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/gold/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/gold/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 android/gold/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 android/gold/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/gold/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 android/gold/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/gold/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/gold/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 android/gold/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 android/gold/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/gold/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 android/gold/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 android/gold/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/gold/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/gold/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/gold/res/values/ic_launcher_background.xml diff --git a/android/gold/res/mipmap-anydpi-v26/ic_launcher.xml b/android/gold/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..036d09bc5f --- /dev/null +++ b/android/gold/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/gold/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/gold/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..036d09bc5f --- /dev/null +++ b/android/gold/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/gold/res/mipmap-hdpi/ic_launcher.png b/android/gold/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..3b6eb448759c6c28cec4350d03336708e5be5bf8 GIT binary patch literal 4088 zcmV~FqM)FtU_nt9OY9{YV`5elqgZ1@v7y-L zh#dTdU12_>pzH@$OaQNnZ-~GSa-ow$+c{|>Yx8v=2 zJ8Jx>Wo~X>_f3LrQZ=$tW62Vo+N$rpDWl4^m7HLdjghUhjnQ&?UGF@s>pFV zuL`A2hOjd2w4H6Oj6ikm2J3Gwo=p8FmB$d{?DP=e-VvWqX^n%QHN}+;4RC*JT|C-d z3#of`1bgQ-g`>4$8K18@>_I}H0iAZRt(6t%dofuR1sXlf024>*B4}I(d>habXI8zB zn;Yxnx9xTCcn^uOuQr~B*2lB3_mLU?33AVL#?u4uAYolY`mDa%e6NBcflNE^WLqmG z&1t31o<1&kp!uek;UMxZK^+7*Ebw3NfDSuZy(M21q~B6xk8&AdNIceoSx3 z69z(Yevsh#;ScfC_w_K*sZ$wqSDiwEh79<07u))m3q%zVCzk&W8FBV_ex?sH&bvTz zD+oE{9eL5+A&WC95~D1#BHH5754G@*ac#AduwT1Cj#h^F(yt|+9&3lZN9Ul({x=j4 zHbQaP4vIuGH9=I7L>k};dE0jZ&4t*i^H-JEE|8P85oWlyLrPd1W6%M(l2kIkobz^Y9qB?(7dF=u*-p)PoTjW$^ogW3oqmxkF^d;lV_Z~rl zDV`l_jO0!4P{q?#o4cx13xP%rH9*vwCX|%xL3Z66isb3)LcD~`@K*SJM{UeM2M|Azb3fa8jvTezh5l`V#YgsiU-^3P|x#`ngHb?i|dE9gAI}N z&AVh|`3)m)SH?SZ!A!S~Sm*x9wh^Y`F9R1a*Bk5gx?@NJF#3qGQ)$b0x#NL`VqSPapj zA8I6$JLj*g6Uy-3FujFZFI9a6q6b?!sg-!p+%uijgm@7-bS-a7ehsOE>7(0<927XF zz3>R-X112dPrN1JuA3W6SzO;zb4a7R(KU5&a{2peIjq_U#MQ^gu_IDwQ_3rxeX2vL zJy%)e$10md_KdBwKZ5;W0}3ZSvEyfP(vLQy@atLbJiMJr3w;;Y|5+bnhIgr+0x=vO zr&ly8I>z7zP#V>hj3_H7nF;~;x=UZZi!rv8d#nwxC9t`8k8_f$r@RdIu-~XiOn2>2 z>Y1vIK)lfu?rp7$wNskomo0ULq3rsZ;U7a5+ozO4xo5kGeDYsPwDUY$!+~;GfLjMq z2|28CPHP^f40~+qfd*JPx%8&@wFxvzBvM@jd$z--fR+^hABYOWE^a@Vs*Xc@gfDGe zluObMHW24OoRsAh5>F{4`29nleL$k>iO`Dvyb+!p`Vi?6y5)wID$?klcy_cULYKBL zm52T!febNbgdY4{I*H@a-NDU7Wx2cg9rA$MV#S%1J+*N0tA;`}CoAe_taP#5rzLLu z^Br+u#vb$hNK<4SQ-;yq&Gq2z&=FI;hT_mSqmgp3IkImCL3YVeO@Q){#q~$VDSaeu z(-T`CtqbI7ucvIp{954%HDZ^)ZlEl+A_>bI?@sFw0_J#cB6b&EPRCv_0 z!yh3njKye%bL;*Ne^)&?3^9PqP+e@F*Pg<)GbLt2toAp+vrGPv-1`c$U%!RoQFtZ1 zM@fF76>=|)#*XFYMVnr&2*i_f9(fY)iQLN`Q2ZFEJS8mSpb4?9EryuUXxqXr-=dH|#s>??eqQun+t1bIn5xVUe0QKHt0 zK+Jgi&bYs)y~qo~?H}$^o)Btqj9QX>N}kz~eUKU1mxM4u^rk*Vf8ycN9_-x>xA&VN zJKh#K=j|Z54yNrfWiEEK

IvypFUYQN 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 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-hdpi/ic_launcher_round.png b/android/gold/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..0bab70e86de578859f196dfab1cae88bd3efdb53 GIT binary patch literal 6568 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 FRJ6`l~phyw1q~L(uY<6KR3IqPVJg@ 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 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/gold/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..1b164184ef69a0a0d4786fb0af049bbe53241eeb GIT binary patch literal 12189 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 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xhdpi/ic_launcher_round.png b/android/gold/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..545f37de120c3cf60ef16b49ef00861009f23e41 GIT binary patch literal 9704 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 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/gold/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9aad451c46c24f8266c6b1ef3edb786c5854112c GIT binary patch literal 21422 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 u6lP)|W-E248&33chZ2#a^ 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 _$~&>N^MeF>a7+jBe-n|kYz^M1ceF1(GCq_} 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 N002ovPDHLkV1jEdB8UJ0 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xxxhdpi/ic_launcher.png b/android/gold/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..61c0d3398f83a28e608daf87cd084840c7a8af9a GIT binary patch literal 14644 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 literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/gold/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c11c07b0952b3ac646c5fc607e9f0f01875b142f GIT binary patch literal 32536 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 WEX!4_00D!6Aup{WRViWe<^KS&FFnBk literal 0 HcmV?d00001 diff --git a/android/gold/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/gold/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..45b824286890d8df7d41552657f0fc512add8cc5 GIT binary patch literal 23603 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 + + #53482D + \ No newline at end of file From f55782558cb7a273c4e0dc9a8cc6959e6bc1856b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 2 Mar 2023 13:59:07 +0100 Subject: [PATCH 078/141] Debugger: Copy Address will now copy addresses for the whole selection --- Windows/Debugger/CtrlDisAsmView.cpp | 26 ++++++++++---------------- Windows/Debugger/CtrlDisAsmView.h | 8 +++++++- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 3f54114fc5..8b28551ac7 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -906,10 +906,8 @@ void CtrlDisAsmView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) redraw(); } -void CtrlDisAsmView::copyInstructions(u32 startAddr, u32 endAddr, bool withDisasm) -{ - if (withDisasm == false) - { +void CtrlDisAsmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode) { + if (mode != CopyInstructionsMode::DISASM) { int instructionSize = debugger->getInstructionSize(0); int count = (endAddr - startAddr) / instructionSize; int space = count * 32; @@ -918,7 +916,8 @@ void CtrlDisAsmView::copyInstructions(u32 startAddr, u32 endAddr, bool withDisas char *p = temp, *end = temp + space; for (u32 pos = startAddr; pos < endAddr && p < end; pos += instructionSize) { - p += snprintf(p, end - p, "%08X", debugger->readMemory(pos)); + u32 data = mode == CopyInstructionsMode::OPCODES ? debugger->readMemory(pos) : pos; + p += snprintf(p, end - p, "%08X", data); // Don't leave a trailing newline. if (pos + instructionSize < endAddr && p < end) @@ -926,8 +925,7 @@ void CtrlDisAsmView::copyInstructions(u32 startAddr, u32 endAddr, bool withDisas } W32Util::CopyTextToClipboard(wnd, temp); delete [] temp; - } else - { + } else { std::string disassembly = disassembleRange(startAddr,endAddr-startAddr); W32Util::CopyTextToClipboard(wnd, disassembly.c_str()); } @@ -956,14 +954,13 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) assembleOpcode(curAddress,""); break; case ID_DISASM_COPYINSTRUCTIONDISASM: - copyInstructions(selectRangeStart, selectRangeEnd, true); + CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM); break; case ID_DISASM_COPYADDRESS: - { - char temp[16]; - sprintf(temp,"%08X",curAddress); - W32Util::CopyTextToClipboard(wnd, temp); - } + CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::ADDRESSES); + break; + case ID_DISASM_COPYINSTRUCTIONHEX: + CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::OPCODES); break; case ID_DISASM_SETPCTOHERE: debugger->setPC(curAddress); @@ -972,9 +969,6 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_DISASM_FOLLOWBRANCH: followBranch(); break; - case ID_DISASM_COPYINSTRUCTIONHEX: - copyInstructions(selectRangeStart, selectRangeEnd, false); - break; case ID_DISASM_RUNTOHERE: { SendMessage(GetParent(wnd), WM_COMMAND, ID_DEBUG_RUNTOLINE, 0); diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index 2930e41544..ebddca20de 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -64,6 +64,12 @@ class CtrlDisAsmView bool dontRedraw; bool keyTaken; + enum class CopyInstructionsMode { + OPCODES, + DISASM, + ADDRESSES, + }; + void assembleOpcode(u32 address, std::string defaultText); std::string disassembleRange(u32 start, u32 size); void disassembleToFile(); @@ -73,7 +79,7 @@ class CtrlDisAsmView bool getDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData); void updateStatusBarText(); void drawBranchLine(HDC hdc, std::map &addressPositions, const BranchLine &line); - void copyInstructions(u32 startAddr, u32 endAddr, bool withDisasm); + void CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode); std::set getSelectedLineArguments(); void drawArguments(HDC hdc, const DisassemblyLineInfo &line, int x, int y, int textColor, const std::set ¤tArguments); From 82097b4a3b8423638f3e42d309a3f4e69e03343e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 2 Mar 2023 14:28:51 +0100 Subject: [PATCH 079/141] Implement NopInstruction feature in debugger See #16975 --- Windows/Debugger/CtrlDisAsmView.cpp | 21 +++++++++++++++++---- Windows/Debugger/CtrlDisAsmView.h | 1 + Windows/ppsspp.rc | 1 + Windows/resource.h | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 8b28551ac7..7b46ae2402 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -206,7 +206,7 @@ CtrlDisAsmView::~CtrlDisAsmView() manager.clear(); } -COLORREF scaleColor(COLORREF color, float factor) +static COLORREF scaleColor(COLORREF color, float factor) { unsigned char r = color & 0xFF; unsigned char g = (color >> 8) & 0xFF; @@ -315,7 +315,7 @@ void CtrlDisAsmView::assembleOpcode(u32 address, std::string defaultText) // try to assemble the input if it failed } - result = MIPSAsm::MipsAssembleOpcode(op.c_str(),debugger,address); + result = MIPSAsm::MipsAssembleOpcode(op.c_str(), debugger, address); Reporting::NotifyDebugger(); if (result == true) { @@ -331,7 +331,6 @@ void CtrlDisAsmView::assembleOpcode(u32 address, std::string defaultText) } } - void CtrlDisAsmView::drawBranchLine(HDC hdc, std::map &addressPositions, const BranchLine &line) { HPEN pen; u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); @@ -725,7 +724,7 @@ void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) break; case 'c': case VK_INSERT: - copyInstructions(selectRangeStart, selectRangeEnd, true); + CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM); break; case 'x': disassembleToFile(); @@ -931,6 +930,16 @@ void CtrlDisAsmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructio } } +void CtrlDisAsmView::NopInstructions(u32 selectRangeStart, u32 selectRangeEnd) { + for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) { + Memory::Write_U32(0, addr); + } + + if (currentMIPS) { + currentMIPS->InvalidateICache(selectRangeStart, selectRangeEnd - selectRangeStart); + } +} + void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (button == 1) @@ -962,6 +971,10 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_DISASM_COPYINSTRUCTIONHEX: CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::OPCODES); break; + case ID_DISASM_NOPINSTRUCTION: + NopInstructions(selectRangeStart, selectRangeEnd); + redraw(); + break; case ID_DISASM_SETPCTOHERE: debugger->setPC(curAddress); redraw(); diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index ebddca20de..c67306a76c 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -80,6 +80,7 @@ class CtrlDisAsmView void updateStatusBarText(); void drawBranchLine(HDC hdc, std::map &addressPositions, const BranchLine &line); void CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode); + void NopInstructions(u32 startAddr, u32 endAddr); std::set getSelectedLineArguments(); void drawArguments(HDC hdc, const DisassemblyLineInfo &line, int x, int y, int textColor, const std::set ¤tArguments); diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index d5109b9cd0..d2a2c921d9 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -721,6 +721,7 @@ BEGIN MENUITEM "Rename Function...", ID_DISASM_RENAMEFUNCTION MENUITEM "Remove Function", ID_DISASM_REMOVEFUNCTION MENUITEM "Add Function Here", ID_DISASM_ADDFUNCTION + MENUITEM "NOP instruction(s)", ID_DISASM_NOPINSTRUCTION END POPUP "reglist" BEGIN diff --git a/Windows/resource.h b/Windows/resource.h index fc421b7e60..ffe653d58a 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -334,6 +334,7 @@ #define ID_GEDBG_COPY_IMAGE_ALPHA 40225 #define ID_GEDBG_TRACK_PIXEL 40226 #define ID_GEDBG_TRACK_PIXEL_STOP 40227 +#define ID_DISASM_NOPINSTRUCTION 40228 // Dummy option to let the buffered rendering hotkey cycle through all the options. From eab06caba9cf10e249cac06be1f833560aab6c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Moraz=C3=A1n?= <35014183+GABO1423@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:44:07 -0400 Subject: [PATCH 080/141] (UWP) More Cleanups --- UWP/UWP.vcxproj | 128 ++++++------------------------------------------ 1 file changed, 15 insertions(+), 113 deletions(-) diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 561080d0eb..0d06192ecb 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -64,121 +64,23 @@ 10.0 - + Application true - - Application - true - - - Application - true - - - Application - true - - - Application - false - true - true - - - Application - false - true - true - - + Application false false true - - Application - false - false - true - - - Application - false - false - true - - - Application - false - false - true - - - Application - false - true - true - - - Application - false - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -215,7 +117,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -240,7 +142,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -265,7 +167,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -290,7 +192,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -315,7 +217,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -340,7 +242,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -365,7 +267,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -390,7 +292,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -415,7 +317,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -440,7 +342,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -465,7 +367,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + @@ -490,7 +392,7 @@ robocopy "..\Assets\." "Content\." /xd "mime" /E /R:0 /W:0 ../Windows/git-version-gen.cmd EXIT 0 - + From 63f370916b883708abe83a6cdf7e45bc1319b4b7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 3 Mar 2023 03:37:58 +0000 Subject: [PATCH 081/141] riscv: Enable Zba/Zbb by soc compatible. Instead of u74, because apparently some older u74 don't support it. --- Common/RiscVCPUDetect.cpp | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/Common/RiscVCPUDetect.cpp b/Common/RiscVCPUDetect.cpp index f05203488a..b4f8b608d8 100644 --- a/Common/RiscVCPUDetect.cpp +++ b/Common/RiscVCPUDetect.cpp @@ -40,6 +40,7 @@ const char procfile[] = "/proc/cpuinfo"; // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu const char syscpupresentfile[] = "/sys/devices/system/cpu/present"; +const char firmwarefile[] = "/sys/firmware/devicetree/base/compatible"; class RiscVCPUInfoParser { public: @@ -49,9 +50,12 @@ public: int TotalLogicalCount(); std::string ISAString(); + bool FirmwareMatchesCompatible(const std::string &str); private: std::vector> cores_; + std::vector firmware_; + bool firmwareLoaded_ = false; }; RiscVCPUInfoParser::RiscVCPUInfoParser() { @@ -126,6 +130,25 @@ std::string RiscVCPUInfoParser::ISAString() { return "Unknown"; } + +bool RiscVCPUInfoParser::FirmwareMatchesCompatible(const std::string &str) { + if (!firmwareLoaded_) { + firmwareLoaded_ = true; + + std::string data; + if (!File::ReadFileToString(true, Path(firmwarefile), data)) + return false; + + SplitString(data, '\0', firmware_); + } + + for (auto compatible : firmware_) { + if (compatible == str) + return true; + } + + return false; +} #endif static bool ExtensionSupported(unsigned long v, char c) { @@ -170,6 +193,12 @@ void CPUInfo::Detect() logical_cpu_count = 1; truncate_cpy(cpu_string, parser.ISAString().c_str()); + + // A number of CPUs support a limited set of B. It's not all U74, so we use SOC for now... + if (parser.FirmwareMatchesCompatible("starfive,jh7110")) { + RiscV_Zba = true; + RiscV_Zbb = true; + } #endif unsigned long hwcap = getauxval(AT_HWCAP); @@ -183,12 +212,6 @@ void CPUInfo::Detect() // Let's assume for now... RiscV_Zicsr = RiscV_M && RiscV_A && RiscV_F && RiscV_D; - // A number of CPUs support a limited set of B. - if (!strcmp(brand_string, "sifive,u74-mc")) { - RiscV_Zba = true; - RiscV_Zbb = true; - } - #ifdef USE_CPU_FEATURES cpu_features::RiscvInfo info = cpu_features::GetRiscvInfo(); CPU64bit = info.features.RV64I; From 2d5086112bf28ecc88b06f6ecb34cc2b1d065e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Moraz=C3=A1n?= <35014183+GABO1423@users.noreply.github.com> Date: Sat, 4 Mar 2023 13:59:10 -0400 Subject: [PATCH 082/141] (UWP) Remove All Redundant Conditionals --- UWP/Armips_UWP/Armips_UWP.vcxproj | 98 +- UWP/CommonUWP/CommonUWP.vcxproj | 122 +- UWP/CoreUWP/CoreUWP.vcxproj | 4626 +---------------- UWP/GPU_UWP/GPU_UWP.vcxproj | 135 +- UWP/PPSSPP_UWP.sln | 10 +- UWP/PPSSPP_UWP_TemporaryKey.pfx | Bin 2520 -> 2520 bytes UWP/Package.appxmanifest | 1 + UWP/PackageGold.appxmanifest | 1 + UWP/PackageNormal.appxmanifest | 1 + UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj | 135 +- UWP/UI_UWP/UI_UWP.vcxproj | 135 +- UWP/UWP.vcxproj | 3 +- UWP/cpu_features_UWP/cpu_features_UWP.vcxproj | 135 +- UWP/glslang_UWP/glslang_UWP.vcxproj | 134 +- UWP/libkirk_UWP/libkirk_UWP.vcxproj | 122 +- UWP/libzstd_UWP/libzstd_UWP.vcxproj | 96 +- UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj | 135 +- UWP/zlib_UWP/zlib_UWP.vcxproj | 122 +- 18 files changed, 319 insertions(+), 5692 deletions(-) diff --git a/UWP/Armips_UWP/Armips_UWP.vcxproj b/UWP/Armips_UWP/Armips_UWP.vcxproj index f10d16ddfa..330b730cd1 100644 --- a/UWP/Armips_UWP/Armips_UWP.vcxproj +++ b/UWP/Armips_UWP/Armips_UWP.vcxproj @@ -47,38 +47,11 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false true @@ -88,61 +61,11 @@ - - - - - - - - - - - - - - - - - - - - - - + - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; - - + false $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; @@ -373,17 +296,8 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - + Create + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 4a10010455..a0747fc3e9 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 886dc9f29e..65b384657b 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -204,7 +90,7 @@ false false pch.h - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -220,7 +106,7 @@ false false pch.h - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -236,7 +122,7 @@ false false pch.h - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -252,7 +138,7 @@ false false pch.h - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -268,7 +154,7 @@ false false pch.h - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -284,7 +170,8 @@ false false pch.h - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -300,7 +187,7 @@ false false pch.h - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -316,7 +203,7 @@ false false pch.h - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -332,7 +219,7 @@ false false pch.h - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;ARMIPS_USE_STD_FILESYSTEM;%(ClCompile.PreprocessorDefinitions) stdcpp17 @@ -348,7 +235,7 @@ false false pch.h - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -364,7 +251,7 @@ false false pch.h - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -380,7 +267,7 @@ false false pch.h - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -642,20 +529,7 @@ - - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - + @@ -836,20 +710,7 @@ - - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/ARM64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - ../../ffmpeg/Windows10/x64/include;../../ffmpeg/WindowsInclude;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;../../ext/armips/;../../ext/armips/ext/filesystem/include/;../../ext/armips/ext/tinyformat/;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - + @@ -896,4467 +757,492 @@ - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - - - + NotUsing + - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing + NotUsing - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 03ce6c90bb..0448b3e1bd 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -503,18 +389,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/PPSSPP_UWP.sln b/UWP/PPSSPP_UWP.sln index 8fe54683af..e54559c70a 100644 --- a/UWP/PPSSPP_UWP.sln +++ b/UWP/PPSSPP_UWP.sln @@ -2,6 +2,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PPSSPP_UWP", "UWP.vcxproj", "{01D7D581-09A3-4A26-94BD-A7C529B29329}" + ProjectSection(ProjectDependencies) = postProject + {D31FD4F0-53EB-477C-9DC7-149796F628E2} = {D31FD4F0-53EB-477C-9DC7-149796F628E2} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonUWP", "CommonUWP\CommonUWP.vcxproj", "{ACB316CA-3ECB-48E5-BE0A-91E72D5B0F12}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CoreUWP", "CoreUWP\CoreUWP.vcxproj", "{40B76674-02DE-40EF-889B-FAD1489685E7}" @@ -17,11 +22,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libkirk_UWP", "libkirk_UWP\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib_UWP", "zlib_UWP\zlib_UWP.vcxproj", "{DDF90203-0AAE-4F38-B589-2E9637658CE6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PPSSPP_UWP", "UWP.vcxproj", "{01D7D581-09A3-4A26-94BD-A7C529B29329}" - ProjectSection(ProjectDependencies) = postProject - {D31FD4F0-53EB-477C-9DC7-149796F628E2} = {D31FD4F0-53EB-477C-9DC7-149796F628E2} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glslang_UWP", "glslang_UWP\glslang_UWP.vcxproj", "{D326891E-ECE4-4B94-B5E7-8AA0A8E8ECBC}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SPIRVCross_UWP", "SPIRVCross_UWP\SPIRVCross_UWP.vcxproj", "{2B2D16BD-1D37-46AF-A3F8-552900951B26}" diff --git a/UWP/PPSSPP_UWP_TemporaryKey.pfx b/UWP/PPSSPP_UWP_TemporaryKey.pfx index 0d737acfe6d5fa2f9da43235e39ff9b2310ff61e..bbae735015bd5cf27a20835222f98c94826e4452 100644 GIT binary patch delta 2265 zcmV;~2qyQ~6W9}wXn!*2eVl2hdRYPj2haq91lTJ zY*yIzSoa|k{C{W0oF?L2!Ea5VaVG4*)?wAgt0eKXX$Y6dr8*=8NcR)aE}TU645JfHjSJUtjB?Mt&NF5q@YOJlcMt z-C!HTtd{9-V;;7&A)oTZtvx0T;yq0D!-Wkr>vv3-)_>yMMMj7FOAF(Ik-}lQ%7rXD zKAY;!e*q2da?0#ghoqtV7`}~ezNnAxl9Z+$tbMx?5ZkD+NRz4rn+%rOo#Cyz{%p9O zwOiogK{umq$Fv7GYtZo#1kywy(wND02#NB*d2b65Bm{xA8YYWhrL2}+-E)qw)zjV1 zKYopKe1DLN=!F5-D9H?y+R4Pmfwzy}l(F!g{!l>X{mYV|kaFWY=uZmNO@7urRiJX$oid8e39mgQ9-NniToK=6z^E(+7wrednV=nEFfN4DMySB7e$j{z&ly$`a}xK!a%TOHEiX_6YEX zzOLOO!ujbzP-*Bpqs3O(M3y}0Qad2pYqon?_?1Y6uXvbxaiJtl#js`5UT{B$6^c;6Fl>{g^GFF8>GNQD#`PP0XV@U=tK(P-+05`RDVf_;VkF{qaP#*YHFAFT;me8#BgE<+6y4aE9cfQwbnTaUNkf_(?Jir?&-n8Bx09$A5hN zgKIak&8v>3PZ@+Axym%~>9nVCe%F_LUu<^Q(v6!f-;SCo^3Y!~{H$a^HUi z6I*ii3$gy_j3$LvqZKknes{{~n@y>I`6~BB8v&{OWPeLVH9U!QReJH*u+K^0WJNKk z=Go-1B{vdsYc=h#QV_L*+PO{*4xL%>?#ixgThfz2=z1_>&LNQUf|x*;;n9W1Oh#3f9m7X-SsrQJ43^S^@?Rb7TlNfXs(Og%u0ah z^=t!PUdwA;-Xepf7NG~R_;vW5j(%B9c%a1cdk+*j)T^z?B;qRu(xWba1P2F_P?2^P zEcmOnymci8Yr0@sKOasBUn4N}J=mZ<1ycOCbDX zhY@eh+WowsTj>0$DTnNrol~JKlL(F2Pi#oiQgs$zVj&B3rQCMUmOpP~qX;ow?Q1C3l z(|ZEEhvPOww7AW9ypsp*k-MR0IX5Ar>YS}8F^gWgWq=v+K72h|8aLK1LMgd&=uZaf zH`RkT6ARz$Q3fV|1JHAA>^-V(b~y`^g3L&IqKghT3w1A%{L^jR5c>_ zwwGQ}Yxqo43V>a`vt9qJE%3&3Y87k4bpc#onq6+^qdiy^&(xg%CDY@}KiQ`X{I=Ss@H?M@F%P?>=l=!wBges_u4L zxNqG9=v|Jnx^(g{8(c{zuTdK;0#BULDqt`>Fdr}n1_dh)0|FWZ6eNDWf|Y^ufaduZ nvPfalRcgNd`verVW@vUWxNqod>?Y#kfH)<^4eSO40s;rnZOBUS delta 2265 zcmV;~2qyQ~6W9}wXn%KkgX5rn(VYSU2haq91lW}gE9wXuU>LW7!&{GSU3?$`N^5GU zsn57nuN83N#A)-ei1N}lN_CUQgmCB~B1P+#(&NZ^} zZ}T+FB*1BlpHSDa)c_C;Eor-0R3JbC>txlw$RNB1G%5+VPO#gzYNbx3l zZ`JCobaz4F3ptlPk>@z7TeL36D2{LBhx>AfZ14bHtvU(ov;rTlY%7;#B|oc)|phwLEQlTZ48dFAyh)svi)@tyx`!iWJHmx#{P!70D;fFUq-R)~bRR^==1gjudIyLIcnVPh)MF0@03s4aq zy)uTm;d{wP1jwnyIwS_#)v6%pt2`zE)VIBPe{xS5STSu*dCf;ixtLKEfSGer4f1Wj z<-{3lnSY%9UH3xr@GIbbuHJV|3BL&@^on-u`cyAyjhVWr(@OVhq{W`i^qqk$A`nJN z)JV(7dNpcqwkC)b?mv+xBvQOKxFvbHVY+)yKTWB-0hfBgJ~W< z@ylZ)=ocRoiQ)aR4riO+=eUsa#mxJn*nhnQSy?eEvP5$Lv9t%ZI`{Nt^CDNDr-!hp z+JB0{ON$#f^G=(yhn3;1kPFnw1h1smF1h0A!`rB`xJHs9`7BUuuf5O;ACa?Zc5vnO zSj=dU@SVo<1{-pPp6tv28pcur*Dxq7WmmbAGKQ5wEF!$o7DJMj$AxSzTZ~m9n!ZwH z1B4EEw#;upUy8rxjLXmnofvUv%HuUUihtEG{;TfE%~=|VF7wO!iFH)$lgOW2Lr}5O ze$sAfl^WjU9;hh2H%H;Qb&V*l@~G(UJ|rlvQ2(qEdqG&LU?7jzQVdY3moIv9d<>bL zDwQi8cN{QUMJ4jR}8ZHdhF49*Ah*=f&M>cdqmp3H3gv=MBOyq=BltrK=%19-W7q8+uxr4CiH!&BqPAm z1_gi+xzNT5ZXleTwNHoM>M9stD?+~nV6{|e^}r8Cy8_LBB9ci0I%UsFH=dn`9tTYE z|575BBw~ZhrR3&`8E>f7njc>VI)Ct&*dg3Lv>I{eU0EGZ!wRp-W|3l3?9Tcj)%_#( z|Aex4n1PoSo*Ct>MtCq|(RA1({stj(iC-deV=&LNQUQF#u)&VgM}wWdJe&HUKaHEdVqCV*oM$HUKREVgO?RH~=>QEdXNxGXOFG zVE|+RV*oV(GXP@%H~?jnzXhCs1|7&tflqq~0s;rnfPw=s?V*b{{{1qTulk2o^ufyF@xZ|K_W0|Bx^6g~Sv zp#QORV+^5fNztV!;?|rl!(R2t4kD~&<+Ym1SXg(<*57L*8cwvOqkb7!or$NPv~93G3SG%Xu+jS#U4V<^Dg6a`&!&;%O%T4kPZ z#}svfCrnM~_shO1P8-F4ihd(K#3|M{q}v9f0lwUY=~LiAKu$Xfr^)8+EHjmGp-cNy*F+MrFFJFdU@k95YQT_dhFzw*&Bh4ZUJ~*JOcFi7}#1 zUH_YuuH~_zhqzC2>|b+%WN}K&!Ml*34R;&hK)QD;MgYSywzyQLZ~xJL2~0m&$ue|> zO1c=3oR#)MGrqgHcjUT0q|=4Xe~sS55(4T7MDy}@mG3$h8Mp-o4GdjPng{`*nc2K* zvhWs8k|;(vWDi__QP%K2WZr2P`|Ho%T2oM> z@P{BXR;^@Syph{QATPxkmTWRx@OmNvBZCGc)4hPLRibox!I@E{)zTLee}{2l(t`SS z#l!jI`Rnqyx#8gj}b%hkQ8~=Ow(d~xzrv!5=#Jo|8wy#OeeN4gVQ!*bCSkI zYZlY$hL_h34*L_ci2xJpEl_%W4CHKvSAD58M#`3lLgfi8p@bF^ELIWAABEuhtlErtGwlQwUE=3XZ%1 z$U-`uiF#+^BfsJ4pkKN~$A6;4`}FP^v>`A%Fdr}n1_dh)0|FWZ6sT>S2@~xPlb~0s;rnk(Vp! diff --git a/UWP/Package.appxmanifest b/UWP/Package.appxmanifest index b1e9b4db73..967c48e26c 100644 --- a/UWP/Package.appxmanifest +++ b/UWP/Package.appxmanifest @@ -31,5 +31,6 @@ + diff --git a/UWP/PackageGold.appxmanifest b/UWP/PackageGold.appxmanifest index 216c2dfb5e..384fa2d9d7 100644 --- a/UWP/PackageGold.appxmanifest +++ b/UWP/PackageGold.appxmanifest @@ -35,5 +35,6 @@ + diff --git a/UWP/PackageNormal.appxmanifest b/UWP/PackageNormal.appxmanifest index c564357242..cc6f628a24 100644 --- a/UWP/PackageNormal.appxmanifest +++ b/UWP/PackageNormal.appxmanifest @@ -31,5 +31,6 @@ + diff --git a/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj b/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj index f57655dc95..4b0eeb4236 100644 --- a/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj +++ b/UWP/SPIRVCross_UWP/SPIRVCross_UWP.vcxproj @@ -63,58 +63,11 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - + StaticLibrary false true @@ -124,78 +77,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -392,18 +278,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index ad66ad74dc..9e892566ca 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -446,18 +332,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 0d06192ecb..7a89e3c9f4 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -89,11 +89,12 @@ True False Never - 169879A9D7A20F22B0DD435921035E3E6D5996A4 + C8DEB388B9BC89D1DC61324E4E9D9FE6A796B7AA False 1 OnApplicationRun True + PPSSPP_UWP_TemporaryKey.pfx diff --git a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj index 722e0e8b16..6dcea065c8 100644 --- a/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj +++ b/UWP/cpu_features_UWP/cpu_features_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -422,18 +308,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/glslang_UWP/glslang_UWP.vcxproj b/UWP/glslang_UWP/glslang_UWP.vcxproj index 656b796b7d..1be05c7670 100644 --- a/UWP/glslang_UWP/glslang_UWP.vcxproj +++ b/UWP/glslang_UWP/glslang_UWP.vcxproj @@ -63,139 +63,26 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -491,18 +378,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/libkirk_UWP/libkirk_UWP.vcxproj b/UWP/libkirk_UWP/libkirk_UWP.vcxproj index 5664ad0d4a..045f0b48f2 100644 --- a/UWP/libkirk_UWP/libkirk_UWP.vcxproj +++ b/UWP/libkirk_UWP/libkirk_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false diff --git a/UWP/libzstd_UWP/libzstd_UWP.vcxproj b/UWP/libzstd_UWP/libzstd_UWP.vcxproj index 3f063dfb71..09cbd21e9b 100644 --- a/UWP/libzstd_UWP/libzstd_UWP.vcxproj +++ b/UWP/libzstd_UWP/libzstd_UWP.vcxproj @@ -47,38 +47,11 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false true @@ -88,61 +61,11 @@ - - - - - - - - - - - - - - - - - - - - - - + - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; - - + false $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; @@ -299,16 +222,7 @@ - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj b/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj index e89bebd5f5..cc9ce92352 100644 --- a/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj +++ b/UWP/miniupnpc_UWP/miniupnpc_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false @@ -469,18 +355,7 @@ cd ..\..\..\..\UWP\miniupnpc_UWP - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create + Create diff --git a/UWP/zlib_UWP/zlib_UWP.vcxproj b/UWP/zlib_UWP/zlib_UWP.vcxproj index ea314b2c44..f60d2aa01a 100644 --- a/UWP/zlib_UWP/zlib_UWP.vcxproj +++ b/UWP/zlib_UWP/zlib_UWP.vcxproj @@ -63,139 +63,25 @@ 10.0 - + StaticLibrary true - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - true - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - + StaticLibrary false false - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - false - - - StaticLibrary - false - true - - - StaticLibrary - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - + false From d47e46dbb4cfd97748d312c9bb47ed8f5b137fcc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 4 Mar 2023 21:57:04 -0800 Subject: [PATCH 083/141] iOS: Allow jit to be re-enabled. --- Core/Config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 8b4cbe05ab..99575c3e33 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1558,8 +1558,8 @@ bool Config::Save(const char *saveReason) { void Config::PostLoadCleanup(bool gameSpecific) { // Override ppsspp.ini JIT value to prevent crashing - if (DefaultCpuCore() != (int)CPUCore::JIT && g_Config.iCpuCore == (int)CPUCore::JIT) { - jitForcedOff = true; + jitForcedOff = DefaultCpuCore() != (int)CPUCore::JIT && g_Config.iCpuCore == (int)CPUCore::JIT; + if (jitForcedOff) { g_Config.iCpuCore = (int)CPUCore::IR_JIT; } From cc9c01b1d06f6de86ef90ba2cdf4325e0499fe0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 4 Mar 2023 09:54:27 +0100 Subject: [PATCH 084/141] Vulkan texture uploads: Take optimalBufferCopyRowPitchAlignment into account Might marginally increase texture upload performance on some GPUs, but mainly just the right thing to do. For example, on Intel, this is 64. --- Common/GPU/Vulkan/VulkanContext.h | 3 ++- Common/Math/math_util.h | 4 ++++ GPU/Vulkan/TextureCacheVulkan.cpp | 20 +++++++++++--------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h index 4cc097b758..21dbaad08e 100644 --- a/Common/GPU/Vulkan/VulkanContext.h +++ b/Common/GPU/Vulkan/VulkanContext.h @@ -222,7 +222,8 @@ public: // Simple workaround for the casting warning. template void SetDebugName(T handle, VkObjectType type, const char *name) { - if (extensionsLookup_.EXT_debug_utils) { + if (extensionsLookup_.EXT_debug_utils && handle != VK_NULL_HANDLE) { + _dbg_assert_(handle != VK_NULL_HANDLE); SetDebugNameImpl((uint64_t)handle, type, name); } } diff --git a/Common/Math/math_util.h b/Common/Math/math_util.h index fd47662b54..b76e1f3937 100644 --- a/Common/Math/math_util.h +++ b/Common/Math/math_util.h @@ -40,6 +40,10 @@ inline uint32_t RoundUpToPowerOf2(uint32_t v) { return v; } +inline uint32_t RoundUpToPowerOf2(uint32_t v, uint32_t power) { + return (v + power - 1) & ~(power - 1); +} + inline uint32_t log2i(uint32_t val) { unsigned int ret = -1; while (val != 0) { diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 1444c6abca..108356cadc 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -541,8 +541,10 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { plan.GetMipSize(i, &mipWidth, &mipHeight); int bpp = VkFormatBytesPerPixel(actualFmt); - int stride = (mipWidth * bpp + 15) & ~15; // output stride - int uploadSize = stride * mipHeight; + int optimalStrideAlignment = std::max(4, (int)vulkan->GetPhysicalDeviceProperties().properties.limits.optimalBufferCopyRowPitchAlignment); + int byteStride = RoundUpToPowerOf2(mipWidth * bpp, optimalStrideAlignment); // output stride + int pixelStride = byteStride / bpp; + int uploadSize = byteStride * mipHeight; uint32_t bufferOffset; VkBuffer texBuf; @@ -568,17 +570,17 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(uploadSize, &bufferOffset, &texBuf, pushAlignment); double replaceStart = time_now_d(); - plan.replaced->Load(plan.baseLevelSrc + i, data, stride); // if it fails, it'll just be garbage data... OK for now. + plan.replaced->Load(plan.baseLevelSrc + i, data, byteStride); // if it fails, it'll just be garbage data... OK for now. replacementTimeThisFrame_ += time_now_d() - replaceStart; VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, "Copy Upload (replaced): %dx%d", mipWidth, mipHeight); - entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, stride / bpp); + entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, pixelStride); VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); } else { if (plan.depth != 1) { // 3D texturing. - loadLevel(uploadSize, i, stride, plan.scaleFactor); - entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, stride / bpp); + loadLevel(uploadSize, i, byteStride, plan.scaleFactor); + entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, pixelStride); } else if (computeUpload) { int srcBpp = VkFormatBytesPerPixel(dstFmt); int srcStride = mipUnscaledWidth * srcBpp; @@ -599,10 +601,10 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); vulkan->Delete().QueueDeleteImageView(view); } else { - loadLevel(uploadSize, i == 0 ? plan.baseLevelSrc : i, stride, plan.scaleFactor); + loadLevel(uploadSize, i == 0 ? plan.baseLevelSrc : i, byteStride, plan.scaleFactor); VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, "Copy Upload: %dx%d", mipWidth, mipHeight); - entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, stride / bpp); + entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, 0, texBuf, bufferOffset, pixelStride); VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); } // Format might be wrong in lowMemoryMode_, so don't save. @@ -620,7 +622,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { replacedInfo.scaleFactor = plan.scaleFactor; replacedInfo.fmt = FromVulkanFormat(actualFmt); - replacer_.NotifyTextureDecoded(replacedInfo, data, stride, plan.baseLevelSrc + i, w, h); + replacer_.NotifyTextureDecoded(replacedInfo, data, byteStride, plan.baseLevelSrc + i, w, h); } } } From 9fcc1509e45ac72e684327a3041376f6127cff1b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 5 Mar 2023 08:41:44 -0800 Subject: [PATCH 085/141] GPU: Correct depth clip/cull for zero scale. --- GPU/Common/ShaderUniforms.cpp | 3 ++- GPU/Common/VertexShaderGenerator.cpp | 3 ++- GPU/Directx9/ShaderManagerDX9.cpp | 2 +- GPU/GLES/ShaderManagerGLES.cpp | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 207778cf56..173574fadd 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -252,6 +252,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView // These are just the reverse of the formulas in GPUStateUtils. float halfActualZRange = gstate_c.vpDepthScale != 0.0f ? vpZScale / gstate_c.vpDepthScale : 0.0f; + float inverseDepthScale = gstate_c.vpDepthScale != 0.0f ? 1.0f / gstate_c.vpDepthScale : 0.0f; float minz = -((gstate_c.vpZOffset * halfActualZRange) - vpZCenter) - halfActualZRange; float viewZScale = halfActualZRange * 2.0f; float viewZCenter = minz; @@ -259,7 +260,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView ub->depthRange[0] = viewZScale; ub->depthRange[1] = viewZCenter; ub->depthRange[2] = gstate_c.vpZOffset * 0.5f + 0.5f; - ub->depthRange[3] = 2.0f * (1.0f / gstate_c.vpDepthScale); + ub->depthRange[3] = 2.0f * inverseDepthScale; } if (dirtyUniforms & DIRTY_CULLRANGE) { diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 7da56ed7a0..4404ec254b 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1350,7 +1350,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } if (gstate_c.Use(GPU_USE_CULL_DISTANCE)) { // Cull any triangle fully outside in the same direction when depth clamp enabled. - WRITE(p, " if (u_cullRangeMin.w > 0.0) {\n"); + // We check u_depthRange in case depthScale was zero - in that case we can't work out the cull distance. + WRITE(p, " if (u_cullRangeMin.w > 0.0 && u_depthRange.w != 0.0f) {\n"); WRITE(p, " %sgl_CullDistance%s = projPos.z - u_cullRangeMin.z;\n", compat.vsOutPrefix, cull0); WRITE(p, " %sgl_CullDistance%s = u_cullRangeMax.z - projPos.z;\n", compat.vsOutPrefix, cull1); WRITE(p, " } else {\n"); diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index dee68027b1..374d66fb24 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -466,7 +466,7 @@ void ShaderManagerDX9::VSUpdateUniforms(u64 dirtyUniforms) { float minz = -((gstate_c.vpZOffset * halfActualZRange) - vpZCenter) - halfActualZRange; float viewZScale = halfActualZRange * 2.0f; float viewZCenter = minz; - float reverseScale = 2.0f * (1.0f / gstate_c.vpDepthScale); + float reverseScale = gstate_c.vpDepthScale != 0.0f ? 2.0f * (1.0f / gstate_c.vpDepthScale) : 0.0f; float reverseTranslate = gstate_c.vpZOffset * 0.5f + 0.5f; float data[4] = { viewZScale, viewZCenter, reverseTranslate, reverseScale }; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index e050b9381a..8007b31f46 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -589,6 +589,7 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin // These are just the reverse of the formulas in GPUStateUtils. float halfActualZRange = gstate_c.vpDepthScale != 0.0f ? vpZScale / gstate_c.vpDepthScale : 0.0f; + float inverseDepthScale = gstate_c.vpDepthScale != 0.0f ? 1.0f / gstate_c.vpDepthScale : 0.0f; float minz = -((gstate_c.vpZOffset * halfActualZRange) - vpZCenter) - halfActualZRange; float viewZScale = halfActualZRange; float viewZCenter = minz + halfActualZRange; @@ -598,7 +599,7 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin viewZCenter = vpZCenter; } - float data[4] = { viewZScale, viewZCenter, gstate_c.vpZOffset, 1.0f / gstate_c.vpDepthScale }; + float data[4] = { viewZScale, viewZCenter, gstate_c.vpZOffset, inverseDepthScale }; SetFloatUniform4(render_, &u_depthRange, data); } if (dirty & DIRTY_CULLRANGE) { From 3b39e9e068e344d24f403a178183297cf614d67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 14:23:56 +0100 Subject: [PATCH 086/141] Turn the VFS into a class, to be able to reuse it for other purposes. --- Common/Data/Format/IniFile.cpp | 2 +- Common/Data/Format/JSONReader.cpp | 2 +- Common/Data/Format/ZIMLoad.cpp | 2 +- Common/Data/Text/I18n.cpp | 2 +- Common/File/AndroidStorage.cpp | 2 + Common/File/VFS/VFS.cpp | 56 +++++++++++------------- Common/File/VFS/VFS.h | 30 +++++++++---- Common/GPU/OpenGL/GLSLProgram.cpp | 4 +- Common/Render/ManagedTexture.cpp | 2 +- Core/FileSystems/DirectoryFileSystem.cpp | 4 +- Core/Reporting.cpp | 2 +- Core/Util/PPGeDraw.cpp | 2 +- Core/WebServer.cpp | 2 +- GPU/Common/PostShader.cpp | 2 +- GPU/Common/PresentationCommon.cpp | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- SDL/SDLJoystick.cpp | 4 +- UI/BackgroundAudio.cpp | 2 +- UI/GameInfoCache.cpp | 2 +- UI/MiscScreens.cpp | 2 +- UI/NativeApp.cpp | 24 +++++----- UI/Theme.cpp | 6 +-- UWP/PPSSPP_UWPMain.cpp | 5 ++- Windows/main.cpp | 6 +-- android/jni/app-android.cpp | 4 +- headless/Headless.cpp | 8 ++-- headless/SDLHeadlessHost.cpp | 6 +-- headless/WindowsHeadlessHost.cpp | 10 ++--- libretro/libretro.cpp | 4 +- 29 files changed, 106 insertions(+), 95 deletions(-) diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp index 72aeee6f89..27f38e3c16 100644 --- a/Common/Data/Format/IniFile.cpp +++ b/Common/Data/Format/IniFile.cpp @@ -569,7 +569,7 @@ bool IniFile::Load(const Path &path) bool IniFile::LoadFromVFS(const std::string &filename) { size_t size; - uint8_t *data = VFSReadFile(filename.c_str(), &size); + uint8_t *data = g_VFS.ReadFile(filename.c_str(), &size); if (!data) return false; std::string str((const char*)data, size); diff --git a/Common/Data/Format/JSONReader.cpp b/Common/Data/Format/JSONReader.cpp index 9c2128e6ef..b65381d986 100644 --- a/Common/Data/Format/JSONReader.cpp +++ b/Common/Data/Format/JSONReader.cpp @@ -8,7 +8,7 @@ namespace json { JsonReader::JsonReader(const std::string &filename) { size_t buf_size; - buffer_ = (char *)VFSReadFile(filename.c_str(), &buf_size); + buffer_ = (char *)g_VFS.ReadFile(filename.c_str(), &buf_size); if (buffer_) { parse(); } else { diff --git a/Common/Data/Format/ZIMLoad.cpp b/Common/Data/Format/ZIMLoad.cpp index 3fd759e92c..9a145afbe1 100644 --- a/Common/Data/Format/ZIMLoad.cpp +++ b/Common/Data/Format/ZIMLoad.cpp @@ -126,7 +126,7 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int int LoadZIM(const char *filename, int *width, int *height, int *format, uint8_t **image) { size_t size; - uint8_t *buffer = VFSReadFile(filename, &size); + uint8_t *buffer = g_VFS.ReadFile(filename, &size); if (!buffer) { ERROR_LOG(IO, "Couldn't read data for '%s'", filename); return 0; diff --git a/Common/Data/Text/I18n.cpp b/Common/Data/Text/I18n.cpp index 9c222da8b8..ebcb95a90e 100644 --- a/Common/Data/Text/I18n.cpp +++ b/Common/Data/Text/I18n.cpp @@ -73,7 +73,7 @@ Path I18NRepo::GetIniPath(const std::string &languageID) const { bool I18NRepo::IniExists(const std::string &languageID) const { File::FileInfo info; - if (!VFSGetFileInfo(GetIniPath(languageID).ToString().c_str(), &info)) + if (!g_VFS.GetFileInfo(GetIniPath(languageID).ToString().c_str(), &info)) return false; if (!info.exists) return false; diff --git a/Common/File/AndroidStorage.cpp b/Common/File/AndroidStorage.cpp index ec8248947b..c4ddcc73c1 100644 --- a/Common/File/AndroidStorage.cpp +++ b/Common/File/AndroidStorage.cpp @@ -1,3 +1,5 @@ +#include + #include "Common/File/AndroidStorage.h" #include "Common/StringUtils.h" #include "Common/Log.h" diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index cf347dcf44..3dc9c66a1f 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -3,26 +3,20 @@ #include "Common/File/VFS/AssetReader.h" #include "Common/File/AndroidStorage.h" -struct VFSEntry { - const char *prefix; - AssetReader *reader; -}; +VFS g_VFS; -static VFSEntry entries[16]; -static int num_entries = 0; - -void VFSRegister(const char *prefix, AssetReader *reader) { - entries[num_entries].prefix = prefix; - entries[num_entries].reader = reader; +void VFS::Register(const char *prefix, AssetReader *reader) { + entries_[numEntries_].prefix = prefix; + entries_[numEntries_].reader = reader; DEBUG_LOG(IO, "Registered VFS for prefix %s: %s", prefix, reader->toString().c_str()); - num_entries++; + numEntries_++; } -void VFSShutdown() { - for (int i = 0; i < num_entries; i++) { - delete entries[i].reader; +void VFS::Clear() { + for (int i = 0; i < numEntries_; i++) { + delete entries_[i].reader; } - num_entries = 0; + numEntries_ = 0; } // TODO: Use Path more. @@ -38,7 +32,7 @@ static bool IsLocalAbsolutePath(const char *path) { } // The returned data should be free'd with delete[]. -uint8_t *VFSReadFile(const char *filename, size_t *size) { +uint8_t *VFS::ReadFile(const char *filename, size_t *size) { if (IsLocalAbsolutePath(filename)) { // Local path, not VFS. // INFO_LOG(IO, "Not a VFS path: %s . Reading local file.", filename); @@ -47,13 +41,13 @@ uint8_t *VFSReadFile(const char *filename, size_t *size) { int fn_len = (int)strlen(filename); bool fileSystemFound = false; - for (int i = 0; i < num_entries; i++) { - int prefix_len = (int)strlen(entries[i].prefix); + for (int i = 0; i < numEntries_; i++) { + int prefix_len = (int)strlen(entries_[i].prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(filename, entries[i].prefix, prefix_len)) { + if (0 == memcmp(filename, entries_[i].prefix, prefix_len)) { fileSystemFound = true; // INFO_LOG(IO, "Prefix match: %s (%s) -> %s", entries[i].prefix, filename, filename + prefix_len); - uint8_t *data = entries[i].reader->ReadAsset(filename + prefix_len, size); + uint8_t *data = entries_[i].reader->ReadAsset(filename + prefix_len, size); if (data) return data; else @@ -67,7 +61,7 @@ uint8_t *VFSReadFile(const char *filename, size_t *size) { return 0; } -bool VFSGetFileListing(const char *path, std::vector *listing, const char *filter) { +bool VFS::GetFileListing(const char *path, std::vector *listing, const char *filter) { if (IsLocalAbsolutePath(path)) { // Local path, not VFS. // INFO_LOG(IO, "Not a VFS path: %s . Reading local directory.", path); @@ -77,12 +71,12 @@ bool VFSGetFileListing(const char *path, std::vector *listing, c int fn_len = (int)strlen(path); bool fileSystemFound = false; - for (int i = 0; i < num_entries; i++) { - int prefix_len = (int)strlen(entries[i].prefix); + for (int i = 0; i < numEntries_; i++) { + int prefix_len = (int)strlen(entries_[i].prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(path, entries[i].prefix, prefix_len)) { + if (0 == memcmp(path, entries_[i].prefix, prefix_len)) { fileSystemFound = true; - if (entries[i].reader->GetFileListing(path + prefix_len, listing, filter)) { + if (entries_[i].reader->GetFileListing(path + prefix_len, listing, filter)) { return true; } } @@ -94,7 +88,7 @@ bool VFSGetFileListing(const char *path, std::vector *listing, c return false; } -bool VFSGetFileInfo(const char *path, File::FileInfo *info) { +bool VFS::GetFileInfo(const char *path, File::FileInfo *info) { if (IsLocalAbsolutePath(path)) { // Local path, not VFS. // INFO_LOG(IO, "Not a VFS path: %s . Getting local file info.", path); @@ -103,19 +97,19 @@ bool VFSGetFileInfo(const char *path, File::FileInfo *info) { bool fileSystemFound = false; int fn_len = (int)strlen(path); - for (int i = 0; i < num_entries; i++) { - int prefix_len = (int)strlen(entries[i].prefix); + for (int i = 0; i < numEntries_; i++) { + int prefix_len = (int)strlen(entries_[i].prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(path, entries[i].prefix, prefix_len)) { + if (0 == memcmp(path, entries_[i].prefix, prefix_len)) { fileSystemFound = true; - if (entries[i].reader->GetFileInfo(path + prefix_len, info)) + if (entries_[i].reader->GetFileInfo(path + prefix_len, info)) return true; else continue; } } if (!fileSystemFound) { - ERROR_LOG(IO, "Missing filesystem for %s", path); + ERROR_LOG(IO, "Missing filesystem for '%s'", path); } // Otherwise, the file was just missing. No need to log. return false; } diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 0f7e03c3fa..baaeebf068 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -10,12 +10,26 @@ class AssetReader; -void VFSRegister(const char *prefix, AssetReader *reader); -void VFSShutdown(); +class VFS { +public: + void Register(const char *prefix, AssetReader *reader); + void Clear(); -// Use delete [] to release the returned memory. -// Always allocates an extra zero byte at the end, so that it -// can be used for text like shader sources. -uint8_t *VFSReadFile(const char *filename, size_t *size); -bool VFSGetFileListing(const char *path, std::vector *listing, const char *filter = 0); -bool VFSGetFileInfo(const char *filename, File::FileInfo *fileInfo); + // Use delete [] to release the returned memory. + // Always allocates an extra zero byte at the end, so that it + // can be used for text like shader sources. + uint8_t *ReadFile(const char *filename, size_t *size); + bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0); + bool GetFileInfo(const char *filename, File::FileInfo *fileInfo); + +private: + struct VFSEntry { + const char *prefix; + AssetReader *reader; + }; + + VFSEntry entries_[16]; + int numEntries_ = 0; +}; + +extern VFS g_VFS; diff --git a/Common/GPU/OpenGL/GLSLProgram.cpp b/Common/GPU/OpenGL/GLSLProgram.cpp index 45b098d714..fc5698085e 100644 --- a/Common/GPU/OpenGL/GLSLProgram.cpp +++ b/Common/GPU/OpenGL/GLSLProgram.cpp @@ -102,7 +102,7 @@ bool glsl_recompile(GLSLProgram *program, std::string *error_message) { if (!program->vshader_source && !vsh_src) { size_t sz; - vsh_src.reset((char *)VFSReadFile(program->vshader_filename, &sz)); + vsh_src.reset((char *)g_VFS.ReadFile(program->vshader_filename, &sz)); } if (!program->vshader_source && !vsh_src) { ERROR_LOG(G3D, "File missing: %s", program->vshader_filename); @@ -113,7 +113,7 @@ bool glsl_recompile(GLSLProgram *program, std::string *error_message) { } if (!program->fshader_source && !fsh_src) { size_t sz; - fsh_src.reset((char *)VFSReadFile(program->fshader_filename, &sz)); + fsh_src.reset((char *)g_VFS.ReadFile(program->fshader_filename, &sz)); } if (!program->fshader_source && !fsh_src) { ERROR_LOG(G3D, "File missing: %s", program->fshader_filename); diff --git a/Common/Render/ManagedTexture.cpp b/Common/Render/ManagedTexture.cpp index 771d4f8a92..dce43253c2 100644 --- a/Common/Render/ManagedTexture.cpp +++ b/Common/Render/ManagedTexture.cpp @@ -148,7 +148,7 @@ bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, Imag bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { generateMips_ = generateMips; size_t fileSize; - uint8_t *buffer = VFSReadFile(filename.c_str(), &fileSize); + uint8_t *buffer = g_VFS.ReadFile(filename.c_str(), &fileSize); if (!buffer) { filename_.clear(); ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str()); diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 27abc90564..b4fc833452 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -980,7 +980,7 @@ int VFSFileSystem::OpenFile(std::string filename, FileAccess access, const char VERBOSE_LOG(FILESYS, "VFSFileSystem actually opening %s (%s)", fullNameC, filename.c_str()); size_t size; - u8 *data = VFSReadFile(fullNameC, &size); + u8 *data = g_VFS.ReadFile(fullNameC, &size); if (!data) { ERROR_LOG(FILESYS, "VFSFileSystem failed to open %s", filename.c_str()); return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND; @@ -1001,7 +1001,7 @@ PSPFileInfo VFSFileSystem::GetFileInfo(std::string filename) { std::string fullName = GetLocalPath(filename); File::FileInfo fo; - if (VFSGetFileInfo(fullName.c_str(), &fo)) { + if (g_VFS.GetFileInfo(fullName.c_str(), &fo)) { x.exists = fo.exists; if (x.exists) { x.size = fo.size; diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index d85533d670..946285dfea 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -569,7 +569,7 @@ namespace Reporting return false; #else File::FileInfo fo; - if (!VFSGetFileInfo("flash0/font/jpn0.pgf", &fo)) + if (!g_VFS.GetFileInfo("flash0/font/jpn0.pgf", &fo)) return false; #endif diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 525937a512..12dc1e86a9 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -252,7 +252,7 @@ void __PPGeInit() { if (loadedZIM) { size_t atlas_data_size; if (!g_ppge_atlas.IsMetadataLoaded()) { - uint8_t *atlas_data = VFSReadFile("ppge_atlas.meta", &atlas_data_size); + uint8_t *atlas_data = g_VFS.ReadFile("ppge_atlas.meta", &atlas_data_size); if (atlas_data) g_ppge_atlas.Load(atlas_data, atlas_data_size); delete[] atlas_data; diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 3de0fabc7b..5784827021 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -237,7 +237,7 @@ static bool ServeDebuggerFile(const http::Request &request) { return false; size_t size; - uint8_t *data = VFSReadFile(filename, &size); + uint8_t *data = g_VFS.ReadFile(filename, &size); if (!data) return false; diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index 831bc1bf85..ae03877d98 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -70,7 +70,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct for (size_t d = 0; d < directories.size(); d++) { std::vector fileInfo; - VFSGetFileListing(directories[d].c_str(), &fileInfo, "ini:"); + g_VFS.GetFileListing(directories[d].c_str(), &fileInfo, "ini:"); if (fileInfo.empty()) { File::GetFilesInDir(directories[d], &fileInfo, "ini:"); diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 3305e03b73..3da9c795f9 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -212,7 +212,7 @@ void PresentationCommon::CalculatePostShaderUniforms(int bufferWidth, int buffer static std::string ReadShaderSrc(const Path &filename) { size_t sz = 0; - char *data = (char *)VFSReadFile(filename.c_str(), &sz); + char *data = (char *)g_VFS.ReadFile(filename.c_str(), &sz); if (!data) { return ""; } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 108356cadc..21cf131393 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -260,7 +260,7 @@ void TextureCacheVulkan::NotifyConfigChanged() { static std::string ReadShaderSrc(const Path &filename) { size_t sz = 0; - char *data = (char *)VFSReadFile(filename.c_str(), &sz); + char *data = (char *)g_VFS.ReadFile(filename.c_str(), &sz); if (!data) return std::string(); diff --git a/SDL/SDLJoystick.cpp b/SDL/SDLJoystick.cpp index 0a53ef80b7..59906f4a3e 100644 --- a/SDL/SDLJoystick.cpp +++ b/SDL/SDLJoystick.cpp @@ -29,7 +29,7 @@ SDLJoystick::SDLJoystick(bool init_SDL ) : registeredAsEventHandler(false) { cout << "loading control pad mappings from " << dbPath << ": "; size_t size; - u8 *mappingData = VFSReadFile(dbPath, &size); + u8 *mappingData = g_VFS.ReadFile(dbPath, &size); if (mappingData) { SDL_RWops *rw = SDL_RWFromConstMem(mappingData, size); // 1 to free the rw after use @@ -191,7 +191,7 @@ void SDLJoystick::ProcessInput(SDL_Event &event){ NativeAxis(axis); break; case SDL_CONTROLLERDEVICEREMOVED: - // for removal events, "which" is the instance ID for SDL_CONTROLLERDEVICEREMOVED + // for removal events, "which" is the instance ID for SDL_CONTROLLERDEVICEREMOVED for (auto it = controllers.begin(); it != controllers.end(); ++it) { if (SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(*it)) == event.cdevice.which) { SDL_GameControllerClose(*it); diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index ff83abcfc5..e9f13a4837 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -257,7 +257,7 @@ BackgroundAudio::~BackgroundAudio() { BackgroundAudio::Sample *BackgroundAudio::LoadSample(const std::string &path) { size_t bytes; - uint8_t *data = VFSReadFile(path.c_str(), &bytes); + uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); if (!data) { return nullptr; } diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index c71e5e6fc4..ceef80cedd 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -314,7 +314,7 @@ static bool ReadFileToString(IFileSystem *fs, const char *filename, std::string static bool ReadVFSToString(const char *filename, std::string *contents, std::mutex *mtx) { size_t sz; - uint8_t *data = VFSReadFile(filename, &sz); + uint8_t *data = g_VFS.ReadFile(filename, &sz); if (data) { if (mtx) { std::lock_guard lock(*mtx); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 6887d5ecd2..b9e93d7ab0 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -590,7 +590,7 @@ NewLanguageScreen::NewLanguageScreen(const std::string &title) : ListPopupScreen auto &langValuesMapping = g_Config.GetLangValuesMapping(); std::vector tempLangs; - VFSGetFileListing("lang", &tempLangs, "ini"); + g_VFS.GetFileListing("lang", &tempLangs, "ini"); std::vector listing; int selected = -1; int counter = 0; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index c89c7ec122..833f8fdd75 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -467,27 +467,27 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // We want this to be FIRST. #if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC) // Packed assets are included in app - VFSRegister("", new DirectoryAssetReader(Path(external_dir))); + g_VFS.Register("", new DirectoryAssetReader(Path(external_dir))); #endif #if defined(ASSETS_DIR) - VFSRegister("", new DirectoryAssetReader(Path(ASSETS_DIR))); + g_VFS.Register("", new DirectoryAssetReader(Path(ASSETS_DIR))); #endif #if !defined(MOBILE_DEVICE) && !defined(_WIN32) && !PPSSPP_PLATFORM(SWITCH) - VFSRegister("", new DirectoryAssetReader(File::GetExeDirectory() / "assets")); - VFSRegister("", new DirectoryAssetReader(File::GetExeDirectory())); - VFSRegister("", new DirectoryAssetReader(Path("/usr/local/share/ppsspp/assets"))); - VFSRegister("", new DirectoryAssetReader(Path("/usr/local/share/games/ppsspp/assets"))); - VFSRegister("", new DirectoryAssetReader(Path("/usr/share/ppsspp/assets"))); - VFSRegister("", new DirectoryAssetReader(Path("/usr/share/games/ppsspp/assets"))); + g_VFS.Register("", new DirectoryAssetReader(File::GetExeDirectory() / "assets")); + g_VFS.Register("", new DirectoryAssetReader(File::GetExeDirectory())); + g_VFS.Register("", new DirectoryAssetReader(Path("/usr/local/share/ppsspp/assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path("/usr/local/share/games/ppsspp/assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path("/usr/share/ppsspp/assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path("/usr/share/games/ppsspp/assets"))); #endif #if PPSSPP_PLATFORM(SWITCH) Path assetPath = Path(user_data_path) / "assets"; - VFSRegister("", new DirectoryAssetReader(assetPath)); + g_VFS.Register("", new DirectoryAssetReader(assetPath)); #else - VFSRegister("", new DirectoryAssetReader(Path("assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path("assets"))); #endif - VFSRegister("", new DirectoryAssetReader(Path(savegame_dir))); + g_VFS.Register("", new DirectoryAssetReader(Path(savegame_dir))); #if (defined(MOBILE_DEVICE) || !defined(USING_QT_UI)) && !PPSSPP_PLATFORM(UWP) if (host == nullptr) { @@ -777,7 +777,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } #elif defined(USING_QT_UI) size_t fontSize = 0; - uint8_t *fontData = VFSReadFile("Roboto-Condensed.ttf", &fontSize); + uint8_t *fontData = g_VFS.ReadFile("Roboto-Condensed.ttf", &fontSize); if (fontData) { int fontID = QFontDatabase::addApplicationFontFromData(QByteArray((const char *)fontData, fontSize)); delete [] fontData; diff --git a/UI/Theme.cpp b/UI/Theme.cpp index 59be83522a..f733c37c75 100644 --- a/UI/Theme.cpp +++ b/UI/Theme.cpp @@ -84,7 +84,7 @@ static void LoadThemeInfo(const std::vector &directories) { for (size_t d = 0; d < directories.size(); d++) { std::vector fileInfo; - VFSGetFileListing(directories[d].c_str(), &fileInfo, "ini:"); + g_VFS.GetFileListing(directories[d].c_str(), &fileInfo, "ini:"); if (fileInfo.empty()) { File::GetFilesInDir(directories[d], &fileInfo, "ini:"); @@ -138,7 +138,7 @@ static void LoadThemeInfo(const std::vector &directories) { tmpPath = (path / tmpPath).ToString(); File::FileInfo tmpInfo; - if (VFSGetFileInfo((tmpPath+".meta").c_str(), &tmpInfo) && VFSGetFileInfo((tmpPath+".zim").c_str(), &tmpInfo)) { + if (g_VFS.GetFileInfo((tmpPath + ".meta").c_str(), &tmpInfo) && g_VFS.GetFileInfo((tmpPath + ".zim").c_str(), &tmpInfo)) { info.sUIAtlas = tmpPath; } } @@ -158,7 +158,7 @@ static UI::Style MakeStyle(uint32_t fg, uint32_t bg) { static void LoadAtlasMetadata(Atlas &metadata, const char *filename, bool required) { size_t atlas_data_size = 0; - const uint8_t *atlas_data = VFSReadFile(filename, &atlas_data_size); + const uint8_t *atlas_data = g_VFS.ReadFile(filename, &atlas_data_size); bool load_success = atlas_data != nullptr && metadata.Load(atlas_data, atlas_data_size); if (!load_success) { if (required) diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 23d1801cf2..c2995c8711 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -76,8 +76,8 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptrGetDrawContext()->HandleEvent(Draw::Event::LOST_BACKBUFFER, 0, 0, nullptr); NativeShutdownGraphics(); NativeShutdown(); + g_VFS.Clear(); // Deregister device notification m_deviceResources->RegisterDeviceNotify(nullptr); diff --git a/Windows/main.cpp b/Windows/main.cpp index d14b654f9e..0c313e72ad 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -583,8 +583,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin #endif const Path &exePath = File::GetExeDirectory(); - VFSRegister("", new DirectoryAssetReader(exePath / "assets")); - VFSRegister("", new DirectoryAssetReader(exePath)); + g_VFS.Register("", new DirectoryAssetReader(exePath / "assets")); + g_VFS.Register("", new DirectoryAssetReader(exePath)); langRegion = GetDefaultLangRegion(); osName = GetWindowsVersion() + " " + GetWindowsSystemArchitecture(); @@ -796,7 +796,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin } } - VFSShutdown(); + g_VFS.Clear(); MainWindow::DestroyDebugWindows(); DialogManager::DestroyAll(); diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 42d46cae43..e09288ee8d 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -695,7 +695,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init deviceType = jdeviceType; std::string apkPath = GetJavaString(env, japkpath); - VFSRegister("", new ZipAssetReader(apkPath.c_str(), "assets/")); + g_VFS.Register("", new ZipAssetReader(apkPath.c_str(), "assets/")); systemName = GetJavaString(env, jmodel); langRegion = GetJavaString(env, jlangRegion); @@ -907,7 +907,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { std::lock_guard guard(renderLock); inputBoxCallbacks.clear(); NativeShutdown(); - VFSShutdown(); + g_VFS.Clear(); } { diff --git a/headless/Headless.cpp b/headless/Headless.cpp index fa63a489e1..1f3c54ed90 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -482,13 +482,13 @@ int main(int argc, const char* argv[]) #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? if (File::Exists(Path("/data/app/org.ppsspp.ppsspp-2.apk"))) { - VFSRegister("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp-2.apk", "assets/")); + g_VFS.Register("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp-2.apk", "assets/")); } if (File::Exists(Path("/data/app/org.ppsspp.ppsspp.apk"))) { - VFSRegister("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp.apk", "assets/")); + g_VFS.Register("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp.apk", "assets/")); } #elif !PPSSPP_PLATFORM(WINDOWS) - VFSRegister("", new DirectoryAssetReader(g_Config.flash0Directory / "..")); + g_VFS.Register("", new DirectoryAssetReader(g_Config.flash0Directory / "..")); #endif UpdateUIState(UISTATE_INGAME); @@ -557,7 +557,7 @@ int main(int argc, const char* argv[]) host = nullptr; headlessHost = nullptr; - VFSShutdown(); + g_VFS.Clear(); LogManager::Shutdown(); delete printfLogger; diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessHost.cpp index 9960b01364..0a25cbdb8f 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessHost.cpp @@ -100,9 +100,9 @@ private: }; void SDLHeadlessHost::LoadNativeAssets() { - VFSRegister("", new DirectoryAssetReader(Path("assets"))); - VFSRegister("", new DirectoryAssetReader(Path(""))); - VFSRegister("", new DirectoryAssetReader(Path(".."))); + g_VFS.Register("", new DirectoryAssetReader(Path("assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path(""))); + g_VFS.Register("", new DirectoryAssetReader(Path(".."))); } bool GLDummyGraphicsContext::InitFromRenderThread(std::string *errorMessage) { diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index 32624f000d..7a9e744bb6 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -68,11 +68,11 @@ HWND CreateHiddenWindow() { void WindowsHeadlessHost::LoadNativeAssets() { - VFSRegister("", new DirectoryAssetReader(Path("assets"))); - VFSRegister("", new DirectoryAssetReader(Path(""))); - VFSRegister("", new DirectoryAssetReader(Path(".."))); - VFSRegister("", new DirectoryAssetReader(Path("../Windows/assets"))); - VFSRegister("", new DirectoryAssetReader(Path("../Windows"))); + g_VFS.Register("", new DirectoryAssetReader(Path("assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path(""))); + g_VFS.Register("", new DirectoryAssetReader(Path(".."))); + g_VFS.Register("", new DirectoryAssetReader(Path("../Windows/assets"))); + g_VFS.Register("", new DirectoryAssetReader(Path("../Windows"))); } void WindowsHeadlessHost::SendDebugOutput(const std::string &output) diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index a5909da4ae..26a33e59f6 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1284,7 +1284,7 @@ void retro_init(void) g_Config.bEnableNetworkChat = false; g_Config.bDiscordPresence = false; - VFSRegister("", new DirectoryAssetReader(retro_base_dir)); + g_VFS.Register("", new DirectoryAssetReader(retro_base_dir)); host = new LibretroHost(); } @@ -1486,7 +1486,7 @@ void retro_unload_game(void) Libretro::EmuThreadStop(); PSP_Shutdown(); - VFSShutdown(); + g_VFS.Clear(); delete ctx; ctx = nullptr; From 76497941644ba4c04947de9f674dea603f6af6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 14:29:47 +0100 Subject: [PATCH 087/141] Remove some ifdefs --- Common/File/VFS/AssetReader.cpp | 16 +++++----------- Common/File/VFS/AssetReader.h | 21 ++++----------------- Common/File/VFS/VFS.h | 11 ++++++++++- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/Common/File/VFS/AssetReader.cpp b/Common/File/VFS/AssetReader.cpp index 306cd0c5e0..64116703b9 100644 --- a/Common/File/VFS/AssetReader.cpp +++ b/Common/File/VFS/AssetReader.cpp @@ -3,15 +3,16 @@ #include #include -#ifdef __ANDROID__ +#ifdef SHARED_LIBZIP #include +#else +#include "ext/libzip/zip.h" #endif #include "Common/Common.h" #include "Common/Log.h" #include "Common/File/VFS/AssetReader.h" -#ifdef __ANDROID__ uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { // Figure out the file size first. struct zip_stat zstat; @@ -31,10 +32,6 @@ uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { return contents; } -#endif - -#ifdef __ANDROID__ - ZipAssetReader::ZipAssetReader(const char *zip_file, const char *in_zip_path) { zip_file_ = zip_open(zip_file, 0, NULL); strcpy(in_zip_path_, in_zip_path); @@ -155,9 +152,8 @@ void ZipAssetReader::GetZipListings(const char *path, std::set &fil bool ZipAssetReader::GetFileInfo(const char *path, File::FileInfo *info) { struct zip_stat zstat; char temp_path[1024]; - strcpy(temp_path, in_zip_path_); - strcat(temp_path, path); - if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE|ZIP_FL_UNCHANGED, &zstat)) { + snprintf(temp_path, sizeof(temp_path), "%s%s", in_zip_path_, path); + if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { // ZIP files do not have real directories, so we'll end up here if we // try to stat one. For now that's fine. info->exists = false; @@ -173,8 +169,6 @@ bool ZipAssetReader::GetFileInfo(const char *path, File::FileInfo *info) { return true; } -#endif - DirectoryAssetReader::DirectoryAssetReader(const Path &path) { path_ = path; } diff --git a/Common/File/VFS/AssetReader.h b/Common/File/VFS/AssetReader.h index 8352f8eea8..b1ec629725 100644 --- a/Common/File/VFS/AssetReader.h +++ b/Common/File/VFS/AssetReader.h @@ -1,8 +1,9 @@ -// TODO: Move much of this code to vfs.cpp #pragma once -#ifdef __ANDROID__ +#ifdef SHARED_LIBZIP #include +#else +#include "ext/libzip/zip.h" #endif #include @@ -14,24 +15,11 @@ #include "Common/File/FileUtil.h" #include "Common/File/Path.h" -class AssetReader { -public: - virtual ~AssetReader() {} - // use delete[] - virtual uint8_t *ReadAsset(const char *path, size_t *size) = 0; - // Filter support is optional but nice to have - virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; - virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0; - virtual std::string toString() const = 0; -}; - -#ifdef __ANDROID__ -uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size); class ZipAssetReader : public AssetReader { public: ZipAssetReader(const char *zip_file, const char *in_zip_path); ~ZipAssetReader(); - // use delete[] + // use delete[] on the returned value. uint8_t *ReadAsset(const char *path, size_t *size) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; @@ -46,7 +34,6 @@ private: std::mutex lock_; char in_zip_path_[256]; }; -#endif class DirectoryAssetReader : public AssetReader { public: diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index baaeebf068..c6a2479e80 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -8,7 +8,16 @@ // read them manually out of the APK zipfile, while being able to run on other // platforms as well with the appropriate directory set-up. -class AssetReader; +class AssetReader { +public: + virtual ~AssetReader() {} + // use delete[] + virtual uint8_t *ReadAsset(const char *path, size_t *size) = 0; + // Filter support is optional but nice to have + virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; + virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0; + virtual std::string toString() const = 0; +}; class VFS { public: From 54af2400135e8fef2639ecbc09a2cf1d2ea401b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 14:37:11 +0100 Subject: [PATCH 088/141] Use a vector internally in VFS. --- Common/File/DirListing.h | 4 +--- Common/File/VFS/VFS.cpp | 34 ++++++++++++++++------------------ Common/File/VFS/VFS.h | 16 +++++++++++----- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Common/File/DirListing.h b/Common/File/DirListing.h index c53d420588..4ad9ce0c3e 100644 --- a/Common/File/DirListing.h +++ b/Common/File/DirListing.h @@ -2,10 +2,8 @@ #include #include - #include - -#include +#include #include "Common/File/Path.h" diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index 3dc9c66a1f..e765c9ca78 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -6,17 +6,15 @@ VFS g_VFS; void VFS::Register(const char *prefix, AssetReader *reader) { - entries_[numEntries_].prefix = prefix; - entries_[numEntries_].reader = reader; + entries_.push_back(VFSEntry{ prefix, reader }); DEBUG_LOG(IO, "Registered VFS for prefix %s: %s", prefix, reader->toString().c_str()); - numEntries_++; } void VFS::Clear() { - for (int i = 0; i < numEntries_; i++) { - delete entries_[i].reader; + for (auto &entry : entries_) { + delete entry.reader; } - numEntries_ = 0; + entries_.clear(); } // TODO: Use Path more. @@ -41,13 +39,13 @@ uint8_t *VFS::ReadFile(const char *filename, size_t *size) { int fn_len = (int)strlen(filename); bool fileSystemFound = false; - for (int i = 0; i < numEntries_; i++) { - int prefix_len = (int)strlen(entries_[i].prefix); + for (const auto &entry : entries_) { + int prefix_len = (int)strlen(entry.prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(filename, entries_[i].prefix, prefix_len)) { + if (0 == memcmp(filename, entry.prefix, prefix_len)) { fileSystemFound = true; // INFO_LOG(IO, "Prefix match: %s (%s) -> %s", entries[i].prefix, filename, filename + prefix_len); - uint8_t *data = entries_[i].reader->ReadAsset(filename + prefix_len, size); + uint8_t *data = entry.reader->ReadAsset(filename + prefix_len, size); if (data) return data; else @@ -71,12 +69,12 @@ bool VFS::GetFileListing(const char *path, std::vector *listing, int fn_len = (int)strlen(path); bool fileSystemFound = false; - for (int i = 0; i < numEntries_; i++) { - int prefix_len = (int)strlen(entries_[i].prefix); + for (const auto &entry : entries_) { + int prefix_len = (int)strlen(entry.prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(path, entries_[i].prefix, prefix_len)) { + if (0 == memcmp(path, entry.prefix, prefix_len)) { fileSystemFound = true; - if (entries_[i].reader->GetFileListing(path + prefix_len, listing, filter)) { + if (entry.reader->GetFileListing(path + prefix_len, listing, filter)) { return true; } } @@ -97,12 +95,12 @@ bool VFS::GetFileInfo(const char *path, File::FileInfo *info) { bool fileSystemFound = false; int fn_len = (int)strlen(path); - for (int i = 0; i < numEntries_; i++) { - int prefix_len = (int)strlen(entries_[i].prefix); + for (const auto &entry : entries_) { + int prefix_len = (int)strlen(entry.prefix); if (prefix_len >= fn_len) continue; - if (0 == memcmp(path, entries_[i].prefix, prefix_len)) { + if (0 == memcmp(path, entry.prefix, prefix_len)) { fileSystemFound = true; - if (entries_[i].reader->GetFileInfo(path + prefix_len, info)) + if (entry.reader->GetFileInfo(path + prefix_len, info)) return true; else continue; diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index c6a2479e80..41ac6ae3f9 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -1,18 +1,25 @@ #pragma once #include +#include #include "Common/File/DirListing.h" -// Basic virtual file system. Used to manage assets on Android, where we have to +// Basic read-only virtual file system. Used to manage assets on Android, where we have to // read them manually out of the APK zipfile, while being able to run on other // platforms as well with the appropriate directory set-up. +// Note that this is kinda similar in concept to Core/MetaFileSystem.h, but that one +// is specifically for operations done by the emulated PSP, while this is for operations +// on the system level, like loading assets, and maybe texture packs. Also, as mentioned, +// this one is read-only, so a bit smaller and simpler. + class AssetReader { public: virtual ~AssetReader() {} - // use delete[] + // use delete[] to release the returned memory. virtual uint8_t *ReadAsset(const char *path, size_t *size) = 0; + // Filter support is optional but nice to have virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0; @@ -21,6 +28,7 @@ public: class VFS { public: + ~VFS() { Clear(); } void Register(const char *prefix, AssetReader *reader); void Clear(); @@ -36,9 +44,7 @@ private: const char *prefix; AssetReader *reader; }; - - VFSEntry entries_[16]; - int numEntries_ = 0; + std::vector entries_; }; extern VFS g_VFS; From 53172eff6376481ab03f58b42b6dda57c294bdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 15:30:39 +0100 Subject: [PATCH 089/141] Renaming and file splitting --- CMakeLists.txt | 8 ++- Common/Common.vcxproj | 6 +- Common/Common.vcxproj.filters | 18 ++++-- Common/File/VFS/DirectoryReader.cpp | 31 ++++++++++ Common/File/VFS/DirectoryReader.h | 21 +++++++ Common/File/VFS/VFS.cpp | 8 ++- Common/File/VFS/VFS.h | 10 ++-- .../{AssetReader.cpp => ZipFileReader.cpp} | 57 +++++-------------- .../VFS/{AssetReader.h => ZipFileReader.h} | 25 ++------ Qt/QtMain.h | 3 +- UI/NativeApp.cpp | 25 ++++---- UWP/CommonUWP/CommonUWP.vcxproj | 6 +- UWP/CommonUWP/CommonUWP.vcxproj.filters | 10 +++- UWP/PPSSPP_UWPMain.cpp | 6 +- Windows/main.cpp | 6 +- android/jni/Android.mk | 3 +- android/jni/app-android.cpp | 5 +- headless/Headless.cpp | 11 ++-- headless/SDLHeadlessHost.cpp | 8 +-- headless/WindowsHeadlessHost.cpp | 12 ++-- ios/PPSSPPUIApplication.mm | 12 ++-- libretro/Makefile.common | 5 +- libretro/libretro.cpp | 12 ++-- 23 files changed, 169 insertions(+), 139 deletions(-) create mode 100644 Common/File/VFS/DirectoryReader.cpp create mode 100644 Common/File/VFS/DirectoryReader.h rename Common/File/VFS/{AssetReader.cpp => ZipFileReader.cpp} (70%) rename Common/File/VFS/{AssetReader.h => ZipFileReader.h} (51%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebd1b151c0..91f08e233b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -588,8 +588,10 @@ add_library(Common STATIC Common/Data/Random/Rng.h Common/File/VFS/VFS.h Common/File/VFS/VFS.cpp - Common/File/VFS/AssetReader.cpp - Common/File/VFS/AssetReader.h + Common/File/VFS/ZipFileReader.cpp + Common/File/VFS/ZipFileReader.h + Common/File/VFS/DirectoryReader.cpp + Common/File/VFS/DirectoryReader.h Common/File/AndroidStorage.h Common/File/AndroidStorage.cpp Common/File/DiskFree.h @@ -1150,7 +1152,7 @@ elseif(IOS AND NOT LIBRETRO) UI/DarwinFileSystemServices.h Common/Battery/AppleBatteryClient.m ) - + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia -framework CoreServices" ) if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 451e8dd426..8557f133d3 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -425,8 +425,9 @@ + - + @@ -861,8 +862,9 @@ + - + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 3c0a310aff..0f40093153 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -176,9 +176,6 @@ File\VFS - - File\VFS - Data\Format @@ -467,6 +464,12 @@ GPU\OpenGL + + File\VFS + + + File\VFS + @@ -628,9 +631,6 @@ File\VFS - - File\VFS - Data\Format @@ -884,6 +884,12 @@ GPU\OpenGL + + File\VFS + + + File\VFS + diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp new file mode 100644 index 0000000000..d979dd4d7c --- /dev/null +++ b/Common/File/VFS/DirectoryReader.cpp @@ -0,0 +1,31 @@ +#include "Common/Common.h" +#include "Common/Log.h" +#include "Common/File/VFS/DirectoryReader.h" + +DirectoryReader::DirectoryReader(const Path &path) { + path_ = path; +} + +uint8_t *DirectoryReader::ReadFile(const char *path, size_t *size) { + Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; + return File::ReadLocalFile(new_path, size); +} + +bool DirectoryReader::GetFileListing(const char *path, std::vector *listing, const char *filter = nullptr) { + Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; + + File::FileInfo info; + if (!File::GetFileInfo(new_path, &info)) + return false; + + if (info.isDirectory) { + File::GetFilesInDir(new_path, listing, filter); + return true; + } + return false; +} + +bool DirectoryReader::GetFileInfo(const char *path, File::FileInfo *info) { + Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; + return File::GetFileInfo(new_path, info); +} diff --git a/Common/File/VFS/DirectoryReader.h b/Common/File/VFS/DirectoryReader.h new file mode 100644 index 0000000000..facd3efe72 --- /dev/null +++ b/Common/File/VFS/DirectoryReader.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Common/File/VFS/VFS.h" +#include "Common/File/FileUtil.h" +#include "Common/File/Path.h" + +class DirectoryReader : public VFSBackend { +public: + explicit DirectoryReader(const Path &path); + // use delete[] on the returned value. + uint8_t *ReadFile(const char *path, size_t *size) override; + bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; + bool GetFileInfo(const char *path, File::FileInfo *info) override; + std::string toString() const override { + return path_.ToString(); + } + +private: + Path path_; +}; + diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index e765c9ca78..634f53baa1 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -1,11 +1,13 @@ +#include + #include "Common/Log.h" #include "Common/File/VFS/VFS.h" -#include "Common/File/VFS/AssetReader.h" +#include "Common/File/FileUtil.h" #include "Common/File/AndroidStorage.h" VFS g_VFS; -void VFS::Register(const char *prefix, AssetReader *reader) { +void VFS::Register(const char *prefix, VFSBackend *reader) { entries_.push_back(VFSEntry{ prefix, reader }); DEBUG_LOG(IO, "Registered VFS for prefix %s: %s", prefix, reader->toString().c_str()); } @@ -45,7 +47,7 @@ uint8_t *VFS::ReadFile(const char *filename, size_t *size) { if (0 == memcmp(filename, entry.prefix, prefix_len)) { fileSystemFound = true; // INFO_LOG(IO, "Prefix match: %s (%s) -> %s", entries[i].prefix, filename, filename + prefix_len); - uint8_t *data = entry.reader->ReadAsset(filename + prefix_len, size); + uint8_t *data = entry.reader->ReadFile(filename + prefix_len, size); if (data) return data; else diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 41ac6ae3f9..bd6cc6f3fb 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -14,11 +14,11 @@ // on the system level, like loading assets, and maybe texture packs. Also, as mentioned, // this one is read-only, so a bit smaller and simpler. -class AssetReader { +class VFSBackend { public: - virtual ~AssetReader() {} + virtual ~VFSBackend() {} // use delete[] to release the returned memory. - virtual uint8_t *ReadAsset(const char *path, size_t *size) = 0; + virtual uint8_t *ReadFile(const char *path, size_t *size) = 0; // Filter support is optional but nice to have virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; @@ -29,7 +29,7 @@ public: class VFS { public: ~VFS() { Clear(); } - void Register(const char *prefix, AssetReader *reader); + void Register(const char *prefix, VFSBackend *reader); void Clear(); // Use delete [] to release the returned memory. @@ -42,7 +42,7 @@ public: private: struct VFSEntry { const char *prefix; - AssetReader *reader; + VFSBackend *reader; }; std::vector entries_; }; diff --git a/Common/File/VFS/AssetReader.cpp b/Common/File/VFS/ZipFileReader.cpp similarity index 70% rename from Common/File/VFS/AssetReader.cpp rename to Common/File/VFS/ZipFileReader.cpp index 64116703b9..f058deb486 100644 --- a/Common/File/VFS/AssetReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -1,7 +1,8 @@ #include #include #include -#include +#include +#include #ifdef SHARED_LIBZIP #include @@ -11,9 +12,9 @@ #include "Common/Common.h" #include "Common/Log.h" -#include "Common/File/VFS/AssetReader.h" +#include "Common/File/VFS/ZipFileReader.h" -uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { +static uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { // Figure out the file size first. struct zip_stat zstat; zip_file *file = zip_fopen(archive, filename, ZIP_FL_NOCASE|ZIP_FL_UNCHANGED); @@ -32,7 +33,7 @@ uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { return contents; } -ZipAssetReader::ZipAssetReader(const char *zip_file, const char *in_zip_path) { +ZipFileReader::ZipFileReader(const char *zip_file, const char *in_zip_path) { zip_file_ = zip_open(zip_file, 0, NULL); strcpy(in_zip_path_, in_zip_path); if (!zip_file_) { @@ -50,24 +51,22 @@ ZipAssetReader::ZipAssetReader(const char *zip_file, const char *in_zip_path) { } } -ZipAssetReader::~ZipAssetReader() { +ZipFileReader::~ZipFileReader() { std::lock_guard guard(lock_); zip_close(zip_file_); } -uint8_t *ZipAssetReader::ReadAsset(const char *path, size_t *size) { - char temp_path[1024]; - strcpy(temp_path, in_zip_path_); - strcat(temp_path, path); +uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { + char temp_path[2048]; + snprintf(temp_path, sizeof(temp_path), "%s%s", in_zip_path_, path); std::lock_guard guard(lock_); return ReadFromZip(zip_file_, temp_path, size); } -bool ZipAssetReader::GetFileListing(const char *orig_path, std::vector *listing, const char *filter = 0) { - char path[1024]; - strcpy(path, in_zip_path_); - strcat(path, orig_path); +bool ZipFileReader::GetFileListing(const char *orig_path, std::vector *listing, const char *filter = 0) { + char path[2048]; + snprintf(path, sizeof(path), "%s%s", in_zip_path_, orig_path); std::set filters; std::string tmp; @@ -123,7 +122,7 @@ bool ZipAssetReader::GetFileListing(const char *orig_path, std::vector &files, std::set &directories) { +void ZipFileReader::GetZipListings(const char *path, std::set &files, std::set &directories) { size_t pathlen = strlen(path); if (path[pathlen - 1] == '/') pathlen--; @@ -149,7 +148,7 @@ void ZipAssetReader::GetZipListings(const char *path, std::set &fil } } -bool ZipAssetReader::GetFileInfo(const char *path, File::FileInfo *info) { +bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { struct zip_stat zstat; char temp_path[1024]; snprintf(temp_path, sizeof(temp_path), "%s%s", in_zip_path_, path); @@ -168,31 +167,3 @@ bool ZipAssetReader::GetFileInfo(const char *path, File::FileInfo *info) { info->size = zstat.size; return true; } - -DirectoryAssetReader::DirectoryAssetReader(const Path &path) { - path_ = path; -} - -uint8_t *DirectoryAssetReader::ReadAsset(const char *path, size_t *size) { - Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; - return File::ReadLocalFile(new_path, size); -} - -bool DirectoryAssetReader::GetFileListing(const char *path, std::vector *listing, const char *filter = nullptr) { - Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; - - File::FileInfo info; - if (!File::GetFileInfo(new_path, &info)) - return false; - - if (info.isDirectory) { - File::GetFilesInDir(new_path, listing, filter); - return true; - } - return false; -} - -bool DirectoryAssetReader::GetFileInfo(const char *path, File::FileInfo *info) { - Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; - return File::GetFileInfo(new_path, info); -} diff --git a/Common/File/VFS/AssetReader.h b/Common/File/VFS/ZipFileReader.h similarity index 51% rename from Common/File/VFS/AssetReader.h rename to Common/File/VFS/ZipFileReader.h index b1ec629725..0480d9673d 100644 --- a/Common/File/VFS/AssetReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -8,19 +8,18 @@ #include #include -#include #include #include "Common/File/VFS/VFS.h" #include "Common/File/FileUtil.h" #include "Common/File/Path.h" -class ZipAssetReader : public AssetReader { +class ZipFileReader : public VFSBackend { public: - ZipAssetReader(const char *zip_file, const char *in_zip_path); - ~ZipAssetReader(); + ZipFileReader(const char *zip_file, const char *in_zip_path); + ~ZipFileReader(); // use delete[] on the returned value. - uint8_t *ReadAsset(const char *path, size_t *size) override; + uint8_t *ReadFile(const char *path, size_t *size) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; std::string toString() const override { @@ -34,19 +33,3 @@ private: std::mutex lock_; char in_zip_path_[256]; }; - -class DirectoryAssetReader : public AssetReader { -public: - explicit DirectoryAssetReader(const Path &path); - // use delete[] - uint8_t *ReadAsset(const char *path, size_t *size) override; - bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; - bool GetFileInfo(const char *path, File::FileInfo *info) override; - std::string toString() const override { - return path_.ToString(); - } - -private: - Path path_; -}; - diff --git a/Qt/QtMain.h b/Qt/QtMain.h index 0177ca9292..639806bcf5 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -25,7 +25,7 @@ QTM_USE_NAMESPACE #include "Common/System/Display.h" #include "Common/TimeUtil.h" #include "Common/File/VFS/VFS.h" -#include "Common/File/VFS/AssetReader.h" +#include "Common/File/VFS/DirectoryReader.h" #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/Input/InputState.h" @@ -191,4 +191,3 @@ private: #endif //SDL #endif - diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 833f8fdd75..e482eca117 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -67,7 +67,8 @@ #include "Common/Profiler/Profiler.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/File/VFS/VFS.h" -#include "Common/File/VFS/AssetReader.h" +#include "Common/File/VFS/ZipFileReader.h" +#include "Common/File/VFS/DirectoryReader.h" #include "Common/CPUDetect.h" #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" @@ -467,27 +468,27 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // We want this to be FIRST. #if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC) // Packed assets are included in app - g_VFS.Register("", new DirectoryAssetReader(Path(external_dir))); + g_VFS.Register("", new DirectoryReader(Path(external_dir))); #endif #if defined(ASSETS_DIR) - g_VFS.Register("", new DirectoryAssetReader(Path(ASSETS_DIR))); + g_VFS.Register("", new DirectoryReader(Path(ASSETS_DIR))); #endif #if !defined(MOBILE_DEVICE) && !defined(_WIN32) && !PPSSPP_PLATFORM(SWITCH) - g_VFS.Register("", new DirectoryAssetReader(File::GetExeDirectory() / "assets")); - g_VFS.Register("", new DirectoryAssetReader(File::GetExeDirectory())); - g_VFS.Register("", new DirectoryAssetReader(Path("/usr/local/share/ppsspp/assets"))); - g_VFS.Register("", new DirectoryAssetReader(Path("/usr/local/share/games/ppsspp/assets"))); - g_VFS.Register("", new DirectoryAssetReader(Path("/usr/share/ppsspp/assets"))); - g_VFS.Register("", new DirectoryAssetReader(Path("/usr/share/games/ppsspp/assets"))); + g_VFS.Register("", new DirectoryReader(File::GetExeDirectory() / "assets")); + g_VFS.Register("", new DirectoryReader(File::GetExeDirectory())); + g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/ppsspp/assets"))); + g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/games/ppsspp/assets"))); + g_VFS.Register("", new DirectoryReader(Path("/usr/share/ppsspp/assets"))); + g_VFS.Register("", new DirectoryReader(Path("/usr/share/games/ppsspp/assets"))); #endif #if PPSSPP_PLATFORM(SWITCH) Path assetPath = Path(user_data_path) / "assets"; - g_VFS.Register("", new DirectoryAssetReader(assetPath)); + g_VFS.Register("", new DirectoryReader(assetPath)); #else - g_VFS.Register("", new DirectoryAssetReader(Path("assets"))); + g_VFS.Register("", new DirectoryReader(Path("assets"))); #endif - g_VFS.Register("", new DirectoryAssetReader(Path(savegame_dir))); + g_VFS.Register("", new DirectoryReader(Path(savegame_dir))); #if (defined(MOBILE_DEVICE) || !defined(USING_QT_UI)) && !PPSSPP_PLATFORM(UWP) if (host == nullptr) { diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index a0747fc3e9..6160d923d6 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -306,7 +306,8 @@ - + + @@ -443,7 +444,8 @@ - + + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index f8e8679432..40b8f80da4 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -252,7 +252,10 @@ Data\Format - + + File\VFS + + File\VFS @@ -589,7 +592,10 @@ Data\Format - + + File\VFS + + File\VFS diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index c2995c8711..5fabe3a4e1 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -11,7 +11,7 @@ #include "Common/Common.h" #include "Common/Input/InputState.h" #include "Common/File/VFS/VFS.h" -#include "Common/File/VFS/AssetReader.h" +#include "Common/File/VFS/DirectoryReader.h" #include "Common/Thread/ThreadUtil.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/DirectXHelper.h" @@ -76,8 +76,8 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptr Date: Mon, 6 Mar 2023 16:34:52 +0100 Subject: [PATCH 090/141] VFS: Add support for opening zip files through a Content URI --- Common/File/VFS/ZipFileReader.cpp | 35 ++++++++++++++----------------- Common/File/VFS/ZipFileReader.h | 6 +++--- android/jni/app-android.cpp | 4 ++-- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index f058deb486..aea50985bb 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -13,6 +13,7 @@ #include "Common/Common.h" #include "Common/Log.h" #include "Common/File/VFS/ZipFileReader.h" +#include "Common/StringUtils.h" static uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { // Figure out the file size first. @@ -33,21 +34,17 @@ static uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { return contents; } -ZipFileReader::ZipFileReader(const char *zip_file, const char *in_zip_path) { - zip_file_ = zip_open(zip_file, 0, NULL); - strcpy(in_zip_path_, in_zip_path); - if (!zip_file_) { - ERROR_LOG(IO, "Failed to open %s as a zip file", zip_file); +ZipFileReader::ZipFileReader(const Path &zipFile, const char *inZipPath) { + int error = 0; + if (zipFile.Type() == PathType::CONTENT_URI) { + int fd = File::OpenFD(zipFile, File::OPEN_READ); + zip_file_ = zip_fdopen(fd, 0, &error); + } else { + zip_file_ = zip_open(zipFile.c_str(), 0, &error); } - - std::vector info; - GetFileListing("assets", &info, 0); - for (size_t i = 0; i < info.size(); i++) { - if (info[i].isDirectory) { - DEBUG_LOG(IO, "Directory: %s", info[i].name.c_str()); - } else { - DEBUG_LOG(IO, "File: %s", info[i].name.c_str()); - } + truncate_cpy(inZipPath_, inZipPath); + if (!zip_file_) { + ERROR_LOG(IO, "Failed to open %s as a zip file", zipFile.c_str()); } } @@ -58,7 +55,7 @@ ZipFileReader::~ZipFileReader() { uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { char temp_path[2048]; - snprintf(temp_path, sizeof(temp_path), "%s%s", in_zip_path_, path); + snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); std::lock_guard guard(lock_); return ReadFromZip(zip_file_, temp_path, size); @@ -66,7 +63,7 @@ uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { bool ZipFileReader::GetFileListing(const char *orig_path, std::vector *listing, const char *filter = 0) { char path[2048]; - snprintf(path, sizeof(path), "%s%s", in_zip_path_, orig_path); + snprintf(path, sizeof(path), "%s%s", inZipPath_, orig_path); std::set filters; std::string tmp; @@ -94,7 +91,7 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vector &file bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { struct zip_stat zstat; char temp_path[1024]; - snprintf(temp_path, sizeof(temp_path), "%s%s", in_zip_path_, path); + snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { // ZIP files do not have real directories, so we'll end up here if we // try to stat one. For now that's fine. diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 0480d9673d..9930ea1bad 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -16,14 +16,14 @@ class ZipFileReader : public VFSBackend { public: - ZipFileReader(const char *zip_file, const char *in_zip_path); + ZipFileReader(const Path &zipFile, const char *inZipPath); ~ZipFileReader(); // use delete[] on the returned value. uint8_t *ReadFile(const char *path, size_t *size) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; std::string toString() const override { - return in_zip_path_; + return inZipPath_; } private: @@ -31,5 +31,5 @@ private: zip *zip_file_; std::mutex lock_; - char in_zip_path_[256]; + char inZipPath_[256]; }; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 5fc9b2cba0..a85fba04f2 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -695,8 +695,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init androidVersion = jAndroidVersion; deviceType = jdeviceType; - std::string apkPath = GetJavaString(env, japkpath); - g_VFS.Register("", new ZipFileReader(apkPath.c_str(), "assets/")); + Path apkPath(GetJavaString(env, japkpath)); + g_VFS.Register("", new ZipFileReader(apkPath, "assets/")); systemName = GetJavaString(env, jmodel); langRegion = GetJavaString(env, jlangRegion); From a8bdf11fdcfdf469e203d81aeb740f71ec7d0077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 16:56:16 +0100 Subject: [PATCH 091/141] Headless buildfix --- headless/Headless.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 5e034ede20..c61e248d0a 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -483,10 +483,10 @@ int main(int argc, const char* argv[]) #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? if (File::Exists(Path("/data/app/org.ppsspp.ppsspp-2.apk"))) { - g_VFS.Register("", new ZipFileReader("/data/app/org.ppsspp.ppsspp-2.apk", "assets/")); + g_VFS.Register("", new ZipFileReader(Path("/data/app/org.ppsspp.ppsspp-2.apk"), "assets/")); } if (File::Exists(Path("/data/app/org.ppsspp.ppsspp.apk"))) { - g_VFS.Register("", new ZipFileReader("/data/app/org.ppsspp.ppsspp.apk", "assets/")); + g_VFS.Register("", new ZipFileReader(Path("/data/app/org.ppsspp.ppsspp.apk"), "assets/")); } #elif !PPSSPP_PLATFORM(WINDOWS) g_VFS.Register("", new DirectoryReader(g_Config.flash0Directory / "..")); From 3d5526a1757e7ee929c4c17821f51f75991304cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 10:02:59 +0100 Subject: [PATCH 092/141] Allow specifying the VFS when loading INI files. Will be useful in the tex replacer. --- Common/Data/Format/IniFile.cpp | 4 ++-- Common/Data/Format/IniFile.h | 4 +++- Common/Data/Text/I18n.cpp | 4 ++-- Common/File/VFS/VFS.h | 17 ++++++++++++----- Core/Compatibility.cpp | 5 +++-- Core/Config.cpp | 9 +++++---- Core/TextureReplacer.cpp | 9 +++++---- GPU/Common/PostShader.cpp | 2 +- UI/Theme.cpp | 2 +- 9 files changed, 34 insertions(+), 22 deletions(-) diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp index 27f38e3c16..02bd00d2ef 100644 --- a/Common/Data/Format/IniFile.cpp +++ b/Common/Data/Format/IniFile.cpp @@ -567,9 +567,9 @@ bool IniFile::Load(const Path &path) return success; } -bool IniFile::LoadFromVFS(const std::string &filename) { +bool IniFile::LoadFromVFS(VFSInterface &vfs, const std::string &filename) { size_t size; - uint8_t *data = g_VFS.ReadFile(filename.c_str(), &size); + uint8_t *data = vfs.ReadFile(filename.c_str(), &size); if (!data) return false; std::string str((const char*)data, size); diff --git a/Common/Data/Format/IniFile.h b/Common/Data/Format/IniFile.h index 707f0ab3eb..1008fff5df 100644 --- a/Common/Data/Format/IniFile.h +++ b/Common/Data/Format/IniFile.h @@ -11,6 +11,8 @@ #include "Common/File/Path.h" +class VFSInterface; + class Section { friend class IniFile; @@ -86,7 +88,7 @@ public: bool Load(const Path &path); bool Load(const std::string &filename) { return Load(Path(filename)); } bool Load(std::istream &istream); - bool LoadFromVFS(const std::string &filename); + bool LoadFromVFS(VFSInterface &vfs, const std::string &filename); bool Save(const Path &path); bool Save(const std::string &filename) { return Save(Path(filename)); } diff --git a/Common/Data/Text/I18n.cpp b/Common/Data/Text/I18n.cpp index ebcb95a90e..ab5509dcbd 100644 --- a/Common/Data/Text/I18n.cpp +++ b/Common/Data/Text/I18n.cpp @@ -91,7 +91,7 @@ bool I18NRepo::LoadIni(const std::string &languageID, const Path &overridePath) iniPath = GetIniPath(languageID); } - if (!ini.LoadFromVFS(iniPath.ToString())) + if (!ini.LoadFromVFS(g_VFS, iniPath.ToString())) return false; Clear(); @@ -127,7 +127,7 @@ I18NCategory *I18NRepo::LoadSection(const Section *section, const char *name) { return cat; } -// This is a very light touched save variant - it won't overwrite +// This is a very light touched save variant - it won't overwrite // anything, only create new entries. void I18NRepo::SaveIni(const std::string &languageID) { IniFile ini; diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index bd6cc6f3fb..544ad969b7 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -14,11 +14,18 @@ // on the system level, like loading assets, and maybe texture packs. Also, as mentioned, // this one is read-only, so a bit smaller and simpler. -class VFSBackend { +// Common inteface parts between VFSBackend and VFS. +// Sometimes you don't need the VFS multiplexing and only have a VFSBackend *, sometimes you do need it, +// and it would be cool to be able to use the same interface, like when loading INI files. +class VFSInterface { public: - virtual ~VFSBackend() {} - // use delete[] to release the returned memory. + virtual ~VFSInterface() {} virtual uint8_t *ReadFile(const char *path, size_t *size) = 0; +}; + +class VFSBackend : public VFSInterface { +public: + // use delete[] to release the returned memory. // Filter support is optional but nice to have virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; @@ -26,7 +33,7 @@ public: virtual std::string toString() const = 0; }; -class VFS { +class VFS : public VFSInterface { public: ~VFS() { Clear(); } void Register(const char *prefix, VFSBackend *reader); @@ -35,7 +42,7 @@ public: // Use delete [] to release the returned memory. // Always allocates an extra zero byte at the end, so that it // can be used for text like shader sources. - uint8_t *ReadFile(const char *filename, size_t *size); + uint8_t *ReadFile(const char *filename, size_t *size) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0); bool GetFileInfo(const char *filename, File::FileInfo *fileInfo); diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index a40a62bf4d..bf4d1a564d 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -19,6 +19,7 @@ #include "Common/Log.h" #include "Common/Data/Format/IniFile.h" +#include "Common/File/VFS/VFS.h" #include "Common/StringUtils.h" #include "Core/Compatibility.h" #include "Core/Config.h" @@ -38,7 +39,7 @@ void Compatibility::Load(const std::string &gameID) { { IniFile compat; // This loads from assets. - if (compat.LoadFromVFS("compat.ini")) { + if (compat.LoadFromVFS(g_VFS, "compat.ini")) { CheckSettings(compat, gameID); } } @@ -55,7 +56,7 @@ void Compatibility::Load(const std::string &gameID) { { IniFile compat; // This loads from assets. - if (compat.LoadFromVFS("compatvr.ini")) { + if (compat.LoadFromVFS(g_VFS, "compatvr.ini")) { CheckVRSettings(compat, gameID); } } diff --git a/Core/Config.cpp b/Core/Config.cpp index 99575c3e33..ca8e44e023 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -39,6 +39,7 @@ #include "Common/Data/Text/Parsers.h" #include "Common/CPUDetect.h" #include "Common/File/FileUtil.h" +#include "Common/File/VFS/VFS.h" #include "Common/LogManager.h" #include "Common/OSVersion.h" #include "Common/System/Display.h" @@ -436,7 +437,7 @@ const char *DefaultLangRegion() { } else if (langRegion.length() >= 3) { // Don't give up. Let's try a fuzzy match - so nl_BE can match nl_NL. IniFile mapping; - mapping.LoadFromVFS("langregion.ini"); + mapping.LoadFromVFS(g_VFS, "langregion.ini"); std::vector keys; mapping.GetKeys("LangRegionNames", keys); @@ -1222,7 +1223,7 @@ Config::~Config() { void Config::LoadLangValuesMapping() { IniFile mapping; - mapping.LoadFromVFS("langregion.ini"); + mapping.LoadFromVFS(g_VFS, "langregion.ini"); std::vector keys; mapping.GetKeys("LangRegionNames", keys); @@ -1932,8 +1933,8 @@ bool Config::loadGameConfig(const std::string &pGameId, const std::string &title }); KeyMap::LoadFromIni(iniFile); - - if (!appendedConfigFileName_.ToString().empty() && + + if (!appendedConfigFileName_.ToString().empty() && std::find(appendedConfigUpdatedGames_.begin(), appendedConfigUpdatedGames_.end(), pGameId) == appendedConfigUpdatedGames_.end()) { LoadAppendedConfig(); diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index ef6f20e27c..0bfee36647 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -35,6 +35,7 @@ #include "Common/Data/Text/I18n.h" #include "Common/Data/Text/Parsers.h" #include "Common/File/FileUtil.h" +#include "Common/File/VFS/VFS.h" #include "Common/LogReporting.h" #include "Common/StringUtils.h" #include "Common/Thread/ParallelLoop.h" @@ -179,7 +180,7 @@ bool TextureReplacer::LoadIni() { } if (!iniLoaded) { - iniLoaded = ini.LoadFromVFS((basePath_ / INI_FILENAME).ToString()); + iniLoaded = ini.LoadFromVFS(g_VFS, (basePath_ / INI_FILENAME).ToString()); } if (iniLoaded) { @@ -196,7 +197,7 @@ bool TextureReplacer::LoadIni() { std::lock_guard guard(zip_.lock); iniLoaded = LoadIniZip(overrideIni, zip_.z, overrideFilename); } else { - iniLoaded = overrideIni.LoadFromVFS((basePath_ / overrideFilename).ToString()); + iniLoaded = overrideIni.LoadFromVFS(g_VFS, (basePath_ / overrideFilename).ToString()); } if (!iniLoaded) { ERROR_LOG(G3D, "Failed to load extra texture ini: %s", overrideFilename.c_str()); @@ -298,7 +299,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { if (ini.HasSection("reducehashranges")) { auto reducehashranges = ini.GetOrCreateSection("reducehashranges")->ToMap(); - // Format: w,h = reducehashvalues + // Format: w,h = reducehashvalues for (const auto& item : reducehashranges) { ParseReduceHashRange(item.first, item.second); } @@ -390,7 +391,7 @@ void TextureReplacer::ParseReduceHashRange(const std::string& key, const std::st const u64 reducerangeKey = ((u64)forW << 16) | forH; reducehashranges_[reducerangeKey] = rhashvalue; -} +} u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV) { _dbg_assert_msg_(enabled_, "Replacement not enabled"); diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index ae03877d98..51864c903c 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -90,7 +90,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct if (path.ToString().substr(0, 7) == "assets/") path = Path(path.ToString().substr(7)); - if (ini.LoadFromVFS(name.ToString()) || ini.Load(fileInfo[f].fullName)) { + if (ini.LoadFromVFS(g_VFS, name.ToString()) || ini.Load(fileInfo[f].fullName)) { success = true; // vsh load. meh. } diff --git a/UI/Theme.cpp b/UI/Theme.cpp index f733c37c75..9119259e7c 100644 --- a/UI/Theme.cpp +++ b/UI/Theme.cpp @@ -104,7 +104,7 @@ static void LoadThemeInfo(const std::vector &directories) { if (path.ToString().substr(0, 7) == "assets/") path = Path(path.ToString().substr(7)); - if (ini.LoadFromVFS(name.ToString()) || ini.Load(fileInfo[f].fullName)) { + if (ini.LoadFromVFS(g_VFS, name.ToString()) || ini.Load(fileInfo[f].fullName)) { success = true; } From 9fe7eabadee3264622384f29ead5d9746348dc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 23:25:49 +0100 Subject: [PATCH 093/141] IniFile: Allow specifying which VFS to load from --- Common/Data/Format/IniFile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp index 02bd00d2ef..91c042d5b4 100644 --- a/Common/Data/Format/IniFile.cpp +++ b/Common/Data/Format/IniFile.cpp @@ -582,10 +582,10 @@ bool IniFile::LoadFromVFS(VFSInterface &vfs, const std::string &filename) { bool IniFile::Load(std::istream &in) { // Maximum number of letters in a line static const int MAX_BYTES = 1024*32; + char *templine = new char[MAX_BYTES]; // avoid using up massive stack space while (!(in.eof() || in.fail())) { - char templine[MAX_BYTES]; in.getline(templine, MAX_BYTES); std::string line = templine; @@ -624,6 +624,7 @@ bool IniFile::Load(std::istream &in) { } } + delete[] templine; return true; } From 23b310f59a054739e8ac1245ecbf0027fa304111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 17:30:13 +0100 Subject: [PATCH 094/141] VFS: Add boilerplate --- Common/File/VFS/DirectoryReader.cpp | 36 +++++++++++++++++++++++++++++ Common/File/VFS/DirectoryReader.h | 10 +++++++- Common/File/VFS/VFS.h | 24 +++++++++++++++++++ Common/File/VFS/ZipFileReader.cpp | 34 +++++++++++++++++++++++++++ Common/File/VFS/ZipFileReader.h | 9 ++++++++ 5 files changed, 112 insertions(+), 1 deletion(-) diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index d979dd4d7c..332bb0bb48 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -29,3 +29,39 @@ bool DirectoryReader::GetFileInfo(const char *path, File::FileInfo *info) { Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path; return File::GetFileInfo(new_path, info); } + +class DirectoryReaderFileReference : public VFSFileReference { +public: + Path path; +}; + +class DirectoryReaderOpenFile : public VFSOpenFile { +public: + FILE *file; +}; + +VFSFileReference *DirectoryReader::GetFile(const char *path) { + return nullptr; +} + +void DirectoryReader::ReleaseFile(VFSFileReference *reference) { + DirectoryReaderFileReference *file = (DirectoryReaderFileReference *)reference; +} + +VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *reference) { + DirectoryReaderFileReference *file = (DirectoryReaderFileReference *)reference; + return nullptr; +} + +void DirectoryReader::Rewind(VFSOpenFile *openFile) { + DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; +} + +size_t DirectoryReader::Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) { + DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; + return 0; +} + +void DirectoryReader::CloseFile(VFSOpenFile *openFile) { + DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; +} diff --git a/Common/File/VFS/DirectoryReader.h b/Common/File/VFS/DirectoryReader.h index facd3efe72..00cb8f7878 100644 --- a/Common/File/VFS/DirectoryReader.h +++ b/Common/File/VFS/DirectoryReader.h @@ -9,6 +9,15 @@ public: explicit DirectoryReader(const Path &path); // use delete[] on the returned value. uint8_t *ReadFile(const char *path, size_t *size) override; + + virtual VFSFileReference *GetFile(const char *path) override; + virtual void ReleaseFile(VFSFileReference *reference) override; + + virtual VFSOpenFile *OpenFileForRead(VFSFileReference *reference) override; + virtual void Rewind(VFSOpenFile *openFile) override; + virtual size_t Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) override; + virtual void CloseFile(VFSOpenFile *openFile) override; + bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; std::string toString() const override { @@ -18,4 +27,3 @@ public: private: Path path_; }; - diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 544ad969b7..6c94bfbecb 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -14,6 +14,22 @@ // on the system level, like loading assets, and maybe texture packs. Also, as mentioned, // this one is read-only, so a bit smaller and simpler. +// VFSBackend instances can be used on their own, without the VFS, to serve as an abstraction of +// a single directory or ZIP file. + +// The VFSFileReference level of abstraction is there to hold things like zip file indices, +// for fast re-open etc. + +class VFSFileReference { +public: + virtual ~VFSFileReference() {} +}; + +class VFSOpenFile { +public: + virtual ~VFSOpenFile() {} +}; + // Common inteface parts between VFSBackend and VFS. // Sometimes you don't need the VFS multiplexing and only have a VFSBackend *, sometimes you do need it, // and it would be cool to be able to use the same interface, like when loading INI files. @@ -27,6 +43,14 @@ class VFSBackend : public VFSInterface { public: // use delete[] to release the returned memory. + virtual VFSFileReference *GetFile(const char *path) = 0; + virtual void ReleaseFile(VFSFileReference *file) = 0; + + virtual VFSOpenFile *OpenFileForRead(VFSFileReference *reference) = 0; + virtual void Rewind(VFSOpenFile *file) = 0; + virtual size_t Read(VFSOpenFile *file, uint8_t *buffer, size_t length) = 0; + virtual void CloseFile(VFSOpenFile *file) = 0; + // Filter support is optional but nice to have virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0; diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index aea50985bb..2c63a9c4db 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -164,3 +164,37 @@ bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { info->size = zstat.size; return true; } + +class ZipFileReaderFileReference : public VFSFileReference { +public: +}; + +class ZipFileReaderOpenFile : public VFSOpenFile { +public: +}; + +VFSFileReference *ZipFileReader::GetFile(const char *path) { + return nullptr; +} + +void ZipFileReader::ReleaseFile(VFSFileReference *reference) { + ZipFileReaderFileReference *file = (ZipFileReaderFileReference *)reference; +} + +VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *reference) { + ZipFileReaderFileReference *file = (ZipFileReaderFileReference *)reference; + return nullptr; +} + +void ZipFileReader::Rewind(VFSOpenFile *openFile) { + ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; +} + +size_t ZipFileReader::Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) { + ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; + return 0; +} + +void ZipFileReader::CloseFile(VFSOpenFile *openFile) { + ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; +} diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 9930ea1bad..841491fb44 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -20,6 +20,15 @@ public: ~ZipFileReader(); // use delete[] on the returned value. uint8_t *ReadFile(const char *path, size_t *size) override; + + VFSFileReference *GetFile(const char *path) override; + void ReleaseFile(VFSFileReference *reference) override; + + VFSOpenFile *OpenFileForRead(VFSFileReference *reference) override; + void Rewind(VFSOpenFile *openFile) override; + size_t Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) override; + void CloseFile(VFSOpenFile *openFile) override; + bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; std::string toString() const override { From 433be0a098d7789cdd59ed61a13a1dd9ce798e94 Mon Sep 17 00:00:00 2001 From: Felipe Date: Tue, 7 Mar 2023 13:26:23 -0300 Subject: [PATCH 095/141] Updated brazilian portuguese Updated all strings based on the several changes that were made. --- assets/lang/pt_BR.ini | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 61a97a6d42..f7963e1dff 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -114,11 +114,11 @@ Swipe = Deslizar Swipe sensitivity = Sensibilidade do deslizar Swipe smoothing = Suavidade do deslizar Thin borders = Bordas finas -Tilt control setup = Personalizar inclinação... +Tilt control setup = Configuração do controle da inclinação Tilt Input Type = Controle da entrada da inclinação Tilt Sensitivity along X axis = Sensibilidade da inclinação junto ao eixo X Tilt Sensitivity along Y axis = Sensibilidade da inclinação junto ao eixo Y -To Calibrate = Segure o dispositivo em seu ângulo preferido e pressione "Calibrar". +To Calibrate = Mantenha o dispositivo no seu ângulo preferido e pressione "Calibrar". Toggle mode = Alternar modo Repeat mode = Modo de repetição Touch Control Visibility = Visibilidade do controle de toque @@ -243,7 +243,7 @@ Backspace = Backspace Block address = Bloquear endereço By Address = Pelo endereço Copy savestates to memstick root = Copiar os save states pra raiz do cartão de memória -Create/Open textures.ini file for current game = Criar/abrir o arquivo textures.ini pro jogo atual +Create/Open textures.ini file for current game = Criar/Abrir o arquivo textures.ini pro jogo atual Current = Atual Dev Tools = Ferramentas de desenvolvimento DevMenu = Menu do DEV @@ -262,7 +262,7 @@ GPU Profile = Perfil da GPU GPU log profiler = Criador do perfil do registro da GPU Jit Compare = Comparar JIT JIT debug tools = Ferramentas de debug do JIT -Log Dropped Frame Statistics = Pôr no registro as estátisticas da queda dos frames +Log Dropped Frame Statistics = Registrar as estátisticas da queda dos frames Log Level = Nível do registro Log View = Visualização do registro Logging Channels = Canais do registro @@ -273,7 +273,7 @@ Random = Aleatório Replace textures = Substituir texturas Reset = Resetar Reset limited logging = Resetar o registro limitado -RestoreDefaultSettings = Você tem certeza que você quer restaurar todas as configurações de volta aos padrões delas?\n\nVocê não pode desfazer isto.\nPor favor reinicie o PPSSPP para as mudanças terem efeito. +RestoreDefaultSettings = Restaurar estas configurações aos padrões delas?\nVocê não pode desfazer isto.\nPor favor reinicie o PPSSPP após restaurar as configurações. RestoreGameDefaultSettings = Você tem certeza que você quer restaurar as configurações específicas do jogo\nde volta para os padrões do PPSSPP? Resume = Resumo Run CPU Tests = Executar testes da CPU @@ -372,11 +372,11 @@ Zoom = Zoom A PSP game couldn't be found on the disc. = Um jogo de PSP não pôde ser achado no disco. Cannot boot ELF located outside mountRoot. = Não pôde dar boot no ELF localizado fora do mountRoot. Could not save screenshot file = Não pôde salvar o arquivo da screenshot. -D3D9or11 = Direct3D 9? (ou "não" pro Direct3D 11) +D3D9or11 = Direct3D 9? (Ou "não" pro Direct3D 11) D3D11CompilerMissing = O D3DCompiler_47.dll não foi achado. Por favor instale-o. Ou pressione Sim pra tentar de novo usando o Direct3D 9 ao invés disto. D3D11InitializationError = Erro de inicialização do Direct3D 11 -D3D11Missing = Sua versão do sistema operacional não inclui o D3D11. Por favor execute o Windows Update.\n\nPressione Sim pra tentar de novo usando o Direct3D 9 ao invés disto. -D3D11NotSupported = Sua GPU não aparenta suportar o Direct3D 11.\n\nVocê gostaria de tentar de novo usando o Direct3D 9 ao invés disto? +D3D11Missing = Sua versão do sistema operacional não inclui o D3D11. Por favor execute o Windows Update.\n\nPressione Sim pra tentar de novo usando o Direct3D 9 ao invés deste. +D3D11NotSupported = Sua GPU não aparenta suportar o Direct3D 11.\n\nVocê gostaria de tentar de novo usando o Direct3D 9 ao invés deste? Disk full while writing data = O disco ficou cheio enquanto gravava os dados. ELF file truncated - can't load = Arquivo ELF truncado - não pôde carregar Error loading file = Não pôde carregar o arquivo. @@ -405,7 +405,7 @@ OpenGLDriverError = Erro do driver do OpenGL PPSSPP doesn't support UMD Music. = O PPSSPP não suporta a música do UMD. PPSSPP doesn't support UMD Video. = O PPSSPP não suporta o vídeo do UMD. PPSSPP plays PSP games, not PlayStation 1 or 2 games. = O PPSSPP roda jogos de PSP, não jogos de PlayStation 1 ou 2. -PPSSPPDoesNotSupportInternet = O PPSSPP atualmente não suporta conexão com a Internet pra DLC, PSN ou atualizações de jogos. +PPSSPPDoesNotSupportInternet = O PPSSPP atualmente não suporta conexão com a Internet pro DLC, PSN ou atualizações de jogos. PS1 EBOOTs are not supported by PPSSPP. = OS EBOOTS do PS1 não são suportados pelo PPSSPP. PSX game image detected. = O arquivo é uma imagem do MODE2. O PPSSPP não suporta jogos de PS1. RAR file detected (Require UnRAR) = O arquivo está comprimido (RAR).\nPor favor descomprima-o primeiro (tente o UnRAR). @@ -482,7 +482,7 @@ Aspect Ratio = Proporção do Aspecto Auto = Auto Auto (1:1) = Auto (1:1) Auto (same as Rendering) = Auto (a mesma que a da resolução de renderização) -Auto FrameSkip = Auto-pulo dos frames +Auto FrameSkip = Auto frameskip Auto Max Quality = Auto-maximizar a qualidade Auto Scaling = Auto-redimensionamento Backend = Backend @@ -517,7 +517,7 @@ Frame Skipping = Pulo dos frames Frame Skipping Type = Tipo de pulo dos frames FullScreen = Tela cheia Geometry shader culling = Abate do shader da geometria -Hack Settings = Configurações dos hacks (pode causar erros gráficos) +Hack Settings = Configurações dos hacks (pode causar erros gráficos) Hardware Tessellation = Tesselação por hardware Hardware Transform = Transformação por hardware hardware transform error - falling back to software = Erro de transformação pelo hardware, retrocedendo pro software. @@ -541,8 +541,8 @@ Must Restart = Você deve reiniciar o PPSSPP pra esta mudança ter efeito. Native device resolution = Resolução nativa do dispositivo Nearest = Mais próximo No buffer = Sem buffer -Show Battery % = Show Battery % -Show Speed = Show Speed +Show Battery % = Mostrar Bateria % +Show Speed = Mostrar Velocidade Skip Buffer Effects = Ignorar efeitos do buffer None = Nenhum Number of Frames = Número de frames @@ -564,7 +564,7 @@ Safe = Seguro Screen Scaling Filter = Filtro do dimensionamento da tela Show Debug Statistics = Mostrar estatísticas do debug Show FPS Counter = Mostrar contador de FPS -Skip GPU Readbacks = Ignorar Leituras da GPU +Skip GPU Readbacks = Ignorar leituras da GPU Software Rendering = Renderização por software (lento) Software Skinning = Skinning via software SoftwareSkinning Tip = Combina os desenhos dos modelos de skinning na CPU, mais rápido na maioria dos jogos @@ -599,7 +599,7 @@ Install game from ZIP file? = Instalar o jogo do arquivo ZIP? Install textures from ZIP file? = Instalar as texturas do arquivo ZIP? Installed! = Instalado! Texture pack doesn't support install = O pacote das texturas não suporta a instalação -Zip archive corrupt = Arquivo ZIP compactado corrompido +Zip archive corrupt = Arquivo ZIP corrompido Zip file does not contain PSP software = O arquivo ZIP não contém software do PSP [KeyMapping] @@ -696,15 +696,15 @@ R = R RapidFire = Fogo rápido Record = Gravar Remote hold = Segurar remoto -Rewind = Rebobinar +Rewind = Retroceder Right = Direcional Direito Right Analog Stick = Direcional Análogico Direito RightAn.Down = Analógico Direito pra Baixo RightAn.Left = Analógico Direito a Esquerda RightAn.Right = Analógico Direito a Direita RightAn.Up = Analógico Direito pra Cima -Rotate Analog (CCW) = Rotação do analógico (Anti-horário) -Rotate Analog (CW) = Rotação do analógico (sentido horário) +Rotate Analog (CCW) = Rotação do analógico (Sentido anti-horário) +Rotate Analog (CW) = Rotação do analógico (Sentido horário) Save State = Salvar state Screen = Tela Screenshot = Screenshot @@ -807,7 +807,7 @@ Delete Game Config = Apagar configuração do jogo Exit to menu = Sair pro menu Game Settings = Configurações do jogo Load State = Carregar state -Rewind = Rebobinar +Rewind = Retroceder Save State = Salvar state Settings = Configurações Switch UMD = Trocar UMD @@ -886,10 +886,10 @@ tools = Ferramentas grátis usadas: # Leave extra lines blank. 4 contributors per line seems to look best. translators1 = Papel, gabrielmop, Efraim Lopes, AkiraJkr translators2 = Felipe -translators3 = -translators4 = -translators5 = -translators6 = +translators3 = +translators4 = +translators5 = +translators6 = Twitter @PPSSPP_emu = Twitter @PPSSPP_emu website = Verifique o site da web: written = Escrito em C++ pela velocidade e portabilidade @@ -932,7 +932,7 @@ Remote Port = Porta remota Remote Server = Servidor remoto Remote Subdirectory = Sub-diretório remoto RemoteISODesc = Os jogos na sua lista de "Recentes" serão compartilhados -RemoteISOLoading = Conectado, carregando a lista de jogos... +RemoteISOLoading = Conectado, carregando a lista dos jogos... RemoteISOScanning = Escaneando... Cliqme em "compartilhar jogos" no dispositivo do seu servidor RemoteISOScanningTimeout = Escaneando... Verifique suas configurações do firewall na área de trabalho RemoteISOWifi = Nota: Conecte os dois dispositivos na mesma rede Wi-Fi @@ -1062,7 +1062,7 @@ Debugger Present = Debugger presente Depth buffer format = Formato do buffer de profundidade Device Info = Informação do dispositivo Directories = Diretórios -Display Information = Informação da tela +Display Information = Exibir informação DPI = DPI Driver bugs = Bugs do driver Driver Version = Versão do driver @@ -1143,13 +1143,13 @@ Fast (lag on slow storage) = Rápido (atraso na armazenagem lenta) Fast Memory = Memória rápida (instável) Floating symbols = Símbolos flutuantes Force real clock sync (slower, less lag) = Forçar sincronização com o relógio real (mais lento, menos atrasos) -seconds, 0:off = seconds, 0 = Desligado +seconds, 0:off = segundos, 0 = Desligado Game crashed = O jogo teve um crash Games list settings = Configurações da lista de jogos General = Geral Grid icon size = Tamanho do ícone da grade Help the PPSSPP team = Ajude o time do PPSSPP -Host (bugs, less lag) = Hospedeiro (bugs, menos atrasos) +Host (bugs, less lag) = Hospedeiro (tem bugs, menos atrasos) Ignore bad memory accesses = Ignorar acessos ruins da memória Increase size = Aumentar tamanho Interpreter = Interpretador @@ -1177,13 +1177,13 @@ Record Audio = Gravar áudio Record Display = Gravar tela Reset Recording on Save/Load State = Resetar a gravação ao salvar/carregar o state Restore Default Settings = Restaurar as configurações do PPSSPP para os padrões -Rewind Snapshot Frequency = Retroceder a frequência dos snapshots (consome memória) +Rewind Snapshot Frequency = Retroceder a frequência dos snapshots (consome muita memória) Save path in installed.txt = Caminho do save em installed.txt Save path in My Documents = Caminho do save em Meus Documentos Savestate Slot = Slot do state salvo Savestate slot backups = Backups dos slots dos states salvos Screenshots as PNG = Salvar as screenshots no formato PNG -Set Memory Stick folder = Set Memory Stick folder +Set Memory Stick folder = Definir a pasta do cartão de memória Set UI background... = Definir o cenário de fundo da interface do usuário... Show ID = Mostrar ID Show region flag = Mostrar a bandeira da região @@ -1255,9 +1255,9 @@ Screen representation = Representação da tela % of native FoV = % do campo de visão nativo 6DoF movement = Movimentação do 6DoF Distance to 2D menus and scenes = Distância até os menus e cenas 2D -Field of view scale = Campo da escala de visualização +Field of view scale = Escala do campo de visualização Force 72Hz update = Forçar atualização em 72 Hz -Heads-up display scale = Aviso da escala de exibição +Heads-up display scale = Escala do aviso antecipado da exibição Map controller movements to keys = Mapear os movimentos do controle nas teclas Motion needed to generate action = Movimento necessário pra gerar ação Stereoscopic vision (Experimental) = Visão estereoscópica (Experimental) From 97cf5f8d81ec8221e2c074d2dee553efe4e48e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2023 22:46:05 +0100 Subject: [PATCH 096/141] More renaming, initial implementation of manual file access for both VFS backends --- Common/File/VFS/DirectoryReader.cpp | 44 +++++++---- Common/File/VFS/DirectoryReader.h | 13 ++-- Common/File/VFS/VFS.h | 17 +++-- Common/File/VFS/ZipFileReader.cpp | 114 ++++++++++++++++++++++------ Common/File/VFS/ZipFileReader.h | 18 +++-- 5 files changed, 148 insertions(+), 58 deletions(-) diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index 332bb0bb48..e4da6a712d 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -41,27 +41,45 @@ public: }; VFSFileReference *DirectoryReader::GetFile(const char *path) { - return nullptr; + DirectoryReaderFileReference *reference = new DirectoryReaderFileReference(); + reference->path = path_ / path; + return reference; } -void DirectoryReader::ReleaseFile(VFSFileReference *reference) { - DirectoryReaderFileReference *file = (DirectoryReaderFileReference *)reference; +bool DirectoryReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) { + DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference; + return File::GetFileInfo(reference->path, fileInfo); } -VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *reference) { - DirectoryReaderFileReference *file = (DirectoryReaderFileReference *)reference; - return nullptr; +void DirectoryReader::ReleaseFile(VFSFileReference *vfsReference) { + DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference; + delete reference; } -void DirectoryReader::Rewind(VFSOpenFile *openFile) { - DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; +VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference) { + DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference; + FILE *file = File::OpenCFile(reference->path, "rb"); + if (!file) { + return nullptr; + } + + DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile(); + openFile->file = file; + return openFile; } -size_t DirectoryReader::Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) { - DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; - return 0; +void DirectoryReader::Rewind(VFSOpenFile *vfsOpenFile) { + DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile; + fseek(openFile->file, 0, SEEK_SET); } -void DirectoryReader::CloseFile(VFSOpenFile *openFile) { - DirectoryReaderOpenFile *file = (DirectoryReaderOpenFile *)openFile; +size_t DirectoryReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) { + DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile; + return fread(buffer, 1, length, openFile->file); +} + +void DirectoryReader::CloseFile(VFSOpenFile *vfsOpenFile) { + DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile; + fclose(openFile->file); + delete openFile; } diff --git a/Common/File/VFS/DirectoryReader.h b/Common/File/VFS/DirectoryReader.h index 00cb8f7878..cc6d971371 100644 --- a/Common/File/VFS/DirectoryReader.h +++ b/Common/File/VFS/DirectoryReader.h @@ -10,13 +10,14 @@ public: // use delete[] on the returned value. uint8_t *ReadFile(const char *path, size_t *size) override; - virtual VFSFileReference *GetFile(const char *path) override; - virtual void ReleaseFile(VFSFileReference *reference) override; + VFSFileReference *GetFile(const char *path) override; + bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) override; + void ReleaseFile(VFSFileReference *vfsReference) override; - virtual VFSOpenFile *OpenFileForRead(VFSFileReference *reference) override; - virtual void Rewind(VFSOpenFile *openFile) override; - virtual size_t Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) override; - virtual void CloseFile(VFSOpenFile *openFile) override; + VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) override; + void Rewind(VFSOpenFile *vfsOpenFile) override; + size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) override; + void CloseFile(VFSOpenFile *vfsOpenFile) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 6c94bfbecb..22a242c903 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -30,13 +30,14 @@ public: virtual ~VFSOpenFile() {} }; -// Common inteface parts between VFSBackend and VFS. +// Common interface parts between VFSBackend and VFS. // Sometimes you don't need the VFS multiplexing and only have a VFSBackend *, sometimes you do need it, // and it would be cool to be able to use the same interface, like when loading INI files. class VFSInterface { public: virtual ~VFSInterface() {} virtual uint8_t *ReadFile(const char *path, size_t *size) = 0; + virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = nullptr) = 0; }; class VFSBackend : public VFSInterface { @@ -44,15 +45,15 @@ public: // use delete[] to release the returned memory. virtual VFSFileReference *GetFile(const char *path) = 0; - virtual void ReleaseFile(VFSFileReference *file) = 0; + virtual bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) = 0; + virtual void ReleaseFile(VFSFileReference *vfsReference) = 0; - virtual VFSOpenFile *OpenFileForRead(VFSFileReference *reference) = 0; - virtual void Rewind(VFSOpenFile *file) = 0; - virtual size_t Read(VFSOpenFile *file, uint8_t *buffer, size_t length) = 0; - virtual void CloseFile(VFSOpenFile *file) = 0; + virtual VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) = 0; + virtual void Rewind(VFSOpenFile *vfsOpenFile) = 0; + virtual size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) = 0; + virtual void CloseFile(VFSOpenFile *vfsOpenFile) = 0; // Filter support is optional but nice to have - virtual bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0) = 0; virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0; virtual std::string toString() const = 0; }; @@ -67,8 +68,8 @@ public: // Always allocates an extra zero byte at the end, so that it // can be used for text like shader sources. uint8_t *ReadFile(const char *filename, size_t *size) override; - bool GetFileListing(const char *path, std::vector *listing, const char *filter = 0); bool GetFileInfo(const char *filename, File::FileInfo *fileInfo); + bool GetFileListing(const char *path, std::vector *listing, const char *filter = nullptr) override; private: struct VFSEntry { diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 2c63a9c4db..1ba7e5bf9f 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -34,18 +34,29 @@ static uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { return contents; } -ZipFileReader::ZipFileReader(const Path &zipFile, const char *inZipPath) { +ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath) { int error = 0; + zip *zip_file; if (zipFile.Type() == PathType::CONTENT_URI) { int fd = File::OpenFD(zipFile, File::OPEN_READ); - zip_file_ = zip_fdopen(fd, 0, &error); + if (!fd) { + ERROR_LOG(IO, "Failed to open FD for %s as zip file", zipFile.c_str()); + return nullptr; + } + zip_file = zip_fdopen(fd, 0, &error); } else { - zip_file_ = zip_open(zipFile.c_str(), 0, &error); + zip_file = zip_open(zipFile.c_str(), 0, &error); } - truncate_cpy(inZipPath_, inZipPath); - if (!zip_file_) { + + if (!zip_file) { ERROR_LOG(IO, "Failed to open %s as a zip file", zipFile.c_str()); + return nullptr; } + + ZipFileReader *reader = new ZipFileReader(); + reader->zip_file_ = zip_file; + truncate_cpy(reader->inZipPath_, inZipPath); + return reader; } ZipFileReader::~ZipFileReader() { @@ -78,6 +89,7 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vectorexists = false; - info->size = 0; - return false; + + { + std::lock_guard guard(lock_); + if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { + // ZIP files do not have real directories, so we'll end up here if we + // try to stat one. For now that's fine. + info->exists = false; + info->size = 0; + return false; + } } info->fullName = Path(path); @@ -167,34 +183,84 @@ bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { class ZipFileReaderFileReference : public VFSFileReference { public: + int zi; }; class ZipFileReaderOpenFile : public VFSOpenFile { public: + ZipFileReaderFileReference *reference; + zip_file_t *zf; }; +static constexpr zip_uint64_t INVALID_ZIP_SIZE = 0xFFFFFFFFFFFFFFFFULL; + VFSFileReference *ZipFileReader::GetFile(const char *path) { - return nullptr; + std::lock_guard guard(lock_); + int zi = zip_name_locate(zip_file_, path, ZIP_FL_NOCASE); + if (zi < 0) { + // Not found. + return nullptr; + } + ZipFileReaderFileReference *ref = new ZipFileReaderFileReference(); + ref->zi = zi; + return ref; } -void ZipFileReader::ReleaseFile(VFSFileReference *reference) { - ZipFileReaderFileReference *file = (ZipFileReaderFileReference *)reference; +bool ZipFileReader::GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) { + ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference; + // If you crash here, you called this while having the lock held by having the file open. + // Don't do that, check the info before you open the file. + std::lock_guard guard(lock_); + zip_stat_t zstat; + if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0) + return false; + *fileInfo = File::FileInfo{}; + fileInfo->size = 0; + if (zstat.valid & ZIP_STAT_SIZE) + fileInfo->size = zstat.size; + return zstat.size; } -VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *reference) { - ZipFileReaderFileReference *file = (ZipFileReaderFileReference *)reference; - return nullptr; +void ZipFileReader::ReleaseFile(VFSFileReference *vfsReference) { + ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference; + // Don't do anything other than deleting it. + delete reference; } -void ZipFileReader::Rewind(VFSOpenFile *openFile) { - ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; +VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference) { + ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference; + ZipFileReaderOpenFile *openFile = new ZipFileReaderOpenFile(); + openFile->reference = reference; + // We only allow one file to be open for read concurrently. It's possible that this can be improved, + // especially if we only access by index like this. + lock_.lock(); + openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0); + + if (!openFile->zf) { + WARN_LOG(G3D, "File with index %d not found in zip", reference->zi); + lock_.unlock(); + return nullptr; + } + + return openFile; } -size_t ZipFileReader::Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) { - ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; - return 0; +void ZipFileReader::Rewind(VFSOpenFile *vfsOpenFile) { + ZipFileReaderOpenFile *openFile = (ZipFileReaderOpenFile *)vfsOpenFile; + // Close and re-open. + zip_fclose(openFile->zf); + openFile->zf = zip_fopen_index(zip_file_, openFile->reference->zi, 0); } -void ZipFileReader::CloseFile(VFSOpenFile *openFile) { - ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)openFile; +size_t ZipFileReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) { + ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile; + return zip_fread(file->zf, buffer, length); +} + +void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) { + ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile; + _dbg_assert_(file->zf != nullptr); + zip_fclose(file->zf); + lock_.unlock(); + delete file; } diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 841491fb44..e3c50b38bf 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -16,18 +16,22 @@ class ZipFileReader : public VFSBackend { public: - ZipFileReader(const Path &zipFile, const char *inZipPath); + static ZipFileReader *Create(const Path &zipFile, const char *inZipPath); ~ZipFileReader(); + + bool IsValid() const { return zip_file_ != nullptr; } + // use delete[] on the returned value. uint8_t *ReadFile(const char *path, size_t *size) override; VFSFileReference *GetFile(const char *path) override; - void ReleaseFile(VFSFileReference *reference) override; + bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) override; + void ReleaseFile(VFSFileReference *vfsReference) override; - VFSOpenFile *OpenFileForRead(VFSFileReference *reference) override; - void Rewind(VFSOpenFile *openFile) override; - size_t Read(VFSOpenFile *openFile, uint8_t *buffer, size_t length) override; - void CloseFile(VFSOpenFile *openFile) override; + VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) override; + void Rewind(VFSOpenFile *vfsOpenFile) override; + size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) override; + void CloseFile(VFSOpenFile *vfsOpenFile) override; bool GetFileListing(const char *path, std::vector *listing, const char *filter) override; bool GetFileInfo(const char *path, File::FileInfo *info) override; @@ -38,7 +42,7 @@ public: private: void GetZipListings(const char *path, std::set &files, std::set &directories); - zip *zip_file_; + zip *zip_file_ = nullptr; std::mutex lock_; char inZipPath_[256]; }; From a43b62537a0f606a61d7d4f5884291ae643c458d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 20:22:28 +0100 Subject: [PATCH 097/141] Buildfixes --- Common/File/VFS/VFS.cpp | 8 ++++++-- android/jni/app-android.cpp | 2 +- headless/Headless.cpp | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index 634f53baa1..260a6b007a 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -8,8 +8,12 @@ VFS g_VFS; void VFS::Register(const char *prefix, VFSBackend *reader) { - entries_.push_back(VFSEntry{ prefix, reader }); - DEBUG_LOG(IO, "Registered VFS for prefix %s: %s", prefix, reader->toString().c_str()); + if (reader) { + entries_.push_back(VFSEntry{ prefix, reader }); + DEBUG_LOG(IO, "Registered VFS for prefix %s: %s", prefix, reader->toString().c_str()); + } else { + ERROR_LOG(IO, "Trying to register null VFS backend for prefix %s", prefix); + } } void VFS::Clear() { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index a85fba04f2..d7ac20ec4b 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -696,7 +696,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init deviceType = jdeviceType; Path apkPath(GetJavaString(env, japkpath)); - g_VFS.Register("", new ZipFileReader(apkPath, "assets/")); + g_VFS.Register("", ZipFileReader::Create(apkPath, "assets/")); systemName = GetJavaString(env, jmodel); langRegion = GetJavaString(env, jlangRegion); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index c61e248d0a..9f9da25825 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -483,10 +483,10 @@ int main(int argc, const char* argv[]) #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? if (File::Exists(Path("/data/app/org.ppsspp.ppsspp-2.apk"))) { - g_VFS.Register("", new ZipFileReader(Path("/data/app/org.ppsspp.ppsspp-2.apk"), "assets/")); + g_VFS.Register("", ZipFileReader::Create(Path("/data/app/org.ppsspp.ppsspp-2.apk"), "assets/")); } if (File::Exists(Path("/data/app/org.ppsspp.ppsspp.apk"))) { - g_VFS.Register("", new ZipFileReader(Path("/data/app/org.ppsspp.ppsspp.apk"), "assets/")); + g_VFS.Register("", ZipFileReader::Create(Path("/data/app/org.ppsspp.ppsspp.apk"), "assets/")); } #elif !PPSSPP_PLATFORM(WINDOWS) g_VFS.Register("", new DirectoryReader(g_Config.flash0Directory / "..")); From 76c6058abe052120e47d59997c2a8c705f94ac6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 22:20:56 +0100 Subject: [PATCH 098/141] Texture replacer: Faster way to get width/height from a png --- Common/Data/Format/PNGLoad.cpp | 11 +++++++++++ Common/Data/Format/PNGLoad.h | 25 ++++++++++++++++++++++--- Core/TextureReplacer.cpp | 25 +++++++------------------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Common/Data/Format/PNGLoad.cpp b/Common/Data/Format/PNGLoad.cpp index 0174b156fe..b2ed18f99b 100644 --- a/Common/Data/Format/PNGLoad.cpp +++ b/Common/Data/Format/PNGLoad.cpp @@ -59,3 +59,14 @@ int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, in png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL); return 1; } + +bool PNGHeaderPeek::IsValidPNGHeader() const { + if (magic != 0x474e5089 || ihdrTag != 0x52444849) { + return false; + } + // Reject crazy sized images, too. + if (Width() > 32768 && Height() > 32768) { + return false; + } + return true; +} diff --git a/Common/Data/Format/PNGLoad.h b/Common/Data/Format/PNGLoad.h index 48eaf6a1db..282fed9d44 100644 --- a/Common/Data/Format/PNGLoad.h +++ b/Common/Data/Format/PNGLoad.h @@ -1,5 +1,8 @@ -#ifndef _PNG_LOAD_H -#define _PNG_LOAD_H +#pragma once + +#include + +#include "Common/BitSet.h" // *image_data_ptr should be deleted with free() // return value of 1 == success. @@ -9,4 +12,20 @@ int pngLoad(const char *file, int *pwidth, int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, int *pheight, unsigned char **image_data_ptr); -#endif // _PNG_LOAD_H +// PNG peeker - just read the start of a PNG straight into this struct, in order to +// look at basic parameters like width and height. Note that while PNG is a chunk-based +// format, the IHDR chunk is REQUIRED to be the first one, so this will work. +struct PNGHeaderPeek { + uint32_t magic; + uint32_t ignore0; + uint32_t ignore1; + uint32_t ihdrTag; + uint32_t be_width; // big endian + uint32_t be_height; + uint8_t bitDepth; // bits per channel, can be 1, 2, 4, 8, 16 + uint8_t colorType; // really, pixel format. 0 = grayscale, 2 = rgb, 3 = palette index, 4 = gray+alpha, 6 = rgba + + bool IsValidPNGHeader() const; + int Width() const { return swap32(be_width); } + int Height() const { return swap32(be_height); } +}; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 0bfee36647..d30da0beed 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -32,6 +32,7 @@ #include "Common/Data/Convert/ColorConv.h" #include "Common/Data/Format/IniFile.h" #include "Common/Data/Format/ZIMLoad.h" +#include "Common/Data/Format/PNGLoad.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Text/Parsers.h" #include "Common/File/FileUtil.h" @@ -645,27 +646,15 @@ bool TextureReplacer::PopulateLevelFromZip(ReplacedTextureLevel &level, bool ign good = (flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; } } else if (imageType == ReplacedImageType::PNG) { - png_image png = {}; - png.version = PNG_IMAGE_VERSION; - - // TODO: Use some way to stream data into libpng. Better than the IO lookups on Android... - zip_uint64_t zsize = ZipFileSize(level.zinfo->z, level.zi); - std::string pngdata; - if (zsize != INVALID_ZIP_SIZE) - pngdata.resize(zsize); - if (!pngdata.empty()) { - pngdata.resize(zip_fread(zf, &pngdata[0], pngdata.size())); - } - - if (png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { - // We pad files that have been hashrange'd so they are the same texture size. - level.w = png.width; - level.h = png.height; + PNGHeaderPeek headerPeek; + good = zip_fread(zf, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); + if (headerPeek.IsValidPNGHeader()) { + level.w = headerPeek.Width(); + level.h = headerPeek.Height(); good = true; } else { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", level.file.ToVisualString().c_str(), png.message); + ERROR_LOG(G3D, "Could not get PNG dimensions: %s (zip)", level.file.ToVisualString().c_str()); } - png_image_free(&png); } else { ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format (zip)", level.file.ToVisualString().c_str()); } From 84b7df62ab575cac29872b9be583bda94aaf7d24 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 7 Mar 2023 21:36:33 -0800 Subject: [PATCH 099/141] Common: Cleanup zip file stat flag checks. --- Common/File/VFS/ZipFileReader.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 1ba7e5bf9f..2e7e87863c 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -162,22 +162,31 @@ bool ZipFileReader::GetFileInfo(const char *path, File::FileInfo *info) { char temp_path[1024]; snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); + // Clear some things to start. + info->isDirectory = false; + info->isWritable = false; + info->size = 0; + { std::lock_guard guard(lock_); if (0 != zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat)) { // ZIP files do not have real directories, so we'll end up here if we // try to stat one. For now that's fine. info->exists = false; - info->size = 0; return false; } } + // Zips usually don't contain directory entries, but they may. + if ((zstat.valid & ZIP_STAT_NAME) != 0 && zstat.name) { + info->isDirectory = zstat.name[strlen(zstat.name) - 1] == '/'; + } + if ((zstat.valid & ZIP_STAT_SIZE) != 0) { + info->size = zstat.size; + } + info->fullName = Path(path); - info->exists = true; // TODO - info->isWritable = false; - info->isDirectory = false; // TODO - info->size = zstat.size; + info->exists = true; return true; } From 2fce2df7f6d278bbcd62b975e0c2b2ed348370aa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 7 Mar 2023 21:37:04 -0800 Subject: [PATCH 100/141] Common: Ensure VFS references not directly deleted. --- Common/File/VFS/DirectoryReader.cpp | 7 ++++++- Common/File/VFS/VFS.h | 2 -- Common/File/VFS/ZipFileReader.cpp | 7 ++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index e4da6a712d..999807ed0a 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -37,7 +37,10 @@ public: class DirectoryReaderOpenFile : public VFSOpenFile { public: - FILE *file; + ~DirectoryReaderOpenFile() { + _dbg_assert_(file == nullptr); + } + FILE *file = nullptr; }; VFSFileReference *DirectoryReader::GetFile(const char *path) { @@ -80,6 +83,8 @@ size_t DirectoryReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t leng void DirectoryReader::CloseFile(VFSOpenFile *vfsOpenFile) { DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile; + _dbg_assert_(openFile->file != nullptr); fclose(openFile->file); + openFile->file = nullptr; delete openFile; } diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 22a242c903..67d737d5f7 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -42,8 +42,6 @@ public: class VFSBackend : public VFSInterface { public: - // use delete[] to release the returned memory. - virtual VFSFileReference *GetFile(const char *path) = 0; virtual bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) = 0; virtual void ReleaseFile(VFSFileReference *vfsReference) = 0; diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 2e7e87863c..92b78f5355 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -197,8 +197,12 @@ public: class ZipFileReaderOpenFile : public VFSOpenFile { public: + ~ZipFileReaderOpenFile() { + // Needs to be closed properly and unlocked. + _dbg_assert_(zf == nullptr); + } ZipFileReaderFileReference *reference; - zip_file_t *zf; + zip_file_t *zf = nullptr; }; static constexpr zip_uint64_t INVALID_ZIP_SIZE = 0xFFFFFFFFFFFFFFFFULL; @@ -270,6 +274,7 @@ void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) { ZipFileReaderOpenFile *file = (ZipFileReaderOpenFile *)vfsOpenFile; _dbg_assert_(file->zf != nullptr); zip_fclose(file->zf); + file->zf = nullptr; lock_.unlock(); delete file; } From bfb7a5b093d5143ee75bc3979ef75c228c749a47 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 7 Mar 2023 22:00:30 -0800 Subject: [PATCH 101/141] Common: Cleanup an unused constant. --- Common/File/VFS/ZipFileReader.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 92b78f5355..694ef95864 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -205,8 +205,6 @@ public: zip_file_t *zf = nullptr; }; -static constexpr zip_uint64_t INVALID_ZIP_SIZE = 0xFFFFFFFFFFFFFFFFULL; - VFSFileReference *ZipFileReader::GetFile(const char *path) { std::lock_guard guard(lock_); int zi = zip_name_locate(zip_file_, path, ZIP_FL_NOCASE); From c4652d30e282918efcdbf4234e5cc808801a38e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 08:29:23 +0100 Subject: [PATCH 102/141] Address feedback --- Common/Data/Format/PNGLoad.h | 2 ++ Core/TextureReplacer.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Common/Data/Format/PNGLoad.h b/Common/Data/Format/PNGLoad.h index 282fed9d44..76ab04ee9e 100644 --- a/Common/Data/Format/PNGLoad.h +++ b/Common/Data/Format/PNGLoad.h @@ -15,6 +15,8 @@ int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, // PNG peeker - just read the start of a PNG straight into this struct, in order to // look at basic parameters like width and height. Note that while PNG is a chunk-based // format, the IHDR chunk is REQUIRED to be the first one, so this will work. +// Does not handle Apple's weirdo extension CgBI. http://iphonedevwiki.net/index.php/CgBI_file_format +// That should not be an issue. struct PNGHeaderPeek { uint32_t magic; uint32_t ignore0; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index d30da0beed..875ded80fe 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -648,12 +648,13 @@ bool TextureReplacer::PopulateLevelFromZip(ReplacedTextureLevel &level, bool ign } else if (imageType == ReplacedImageType::PNG) { PNGHeaderPeek headerPeek; good = zip_fread(zf, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); - if (headerPeek.IsValidPNGHeader()) { + if (good && headerPeek.IsValidPNGHeader()) { level.w = headerPeek.Width(); level.h = headerPeek.Height(); good = true; } else { ERROR_LOG(G3D, "Could not get PNG dimensions: %s (zip)", level.file.ToVisualString().c_str()); + good = false; } } else { ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format (zip)", level.file.ToVisualString().c_str()); From 10ecc4afbd345696e966a94088ccc59aee0720c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 20:53:50 +0100 Subject: [PATCH 103/141] Use the VFS in the replacer to remove duplicated code --- Core/TextureReplacer.cpp | 330 +++++++++++---------------------------- Core/TextureReplacer.h | 30 ++-- 2 files changed, 104 insertions(+), 256 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 875ded80fe..43e6fbf83b 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -21,11 +21,6 @@ #include #include #include -#ifdef SHARED_LIBZIP -#include -#else -#include "ext/libzip/zip.h" -#endif #include "ext/xxhash.h" @@ -35,6 +30,8 @@ #include "Common/Data/Format/PNGLoad.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Text/Parsers.h" +#include "Common/File/VFS/DirectoryReader.h" +#include "Common/File/VFS/ZipFileReader.h" #include "Common/File/FileUtil.h" #include "Common/File/VFS/VFS.h" #include "Common/LogReporting.h" @@ -63,17 +60,8 @@ TextureReplacer::TextureReplacer() { none_.prepareDone_ = true; } -void ReplacerZipInfo::Close() { - if (!z) - return; - - std::lock_guard guard(lock); - zip_close(z); - z = nullptr; -} - TextureReplacer::~TextureReplacer() { - zip_.Close(); + delete vfs_; } void TextureReplacer::Init() { @@ -98,7 +86,8 @@ void TextureReplacer::NotifyConfigChanged() { enabled_ = File::IsDirectory(basePath_); } else if (wasEnabled) { - zip_.Close(); + delete vfs_; + vfs_ = nullptr; Decimate(ReplacerDecimateMode::ALL); } @@ -107,46 +96,7 @@ void TextureReplacer::NotifyConfigChanged() { } } -static struct zip *ZipOpenPath(Path fileName) { - int error = 0; - if (fileName.Type() == PathType::CONTENT_URI) { - int fd = File::OpenFD(fileName, File::OPEN_READ); - return zip_fdopen(fd, 0, &error); - } - return zip_open(fileName.c_str(), 0, &error); -} - -static constexpr zip_uint64_t INVALID_ZIP_SIZE = 0xFFFFFFFFFFFFFFFFULL; -static zip_uint64_t ZipFileSize(zip *z, zip_int64_t i) { - zip_stat_t zstat; - if (zip_stat_index(z, i, 0, &zstat) != 0) - return INVALID_ZIP_SIZE; - if ((zstat.valid & ZIP_STAT_SIZE) == 0) - return INVALID_ZIP_SIZE; - return zstat.size; -} - -static bool LoadIniZip(IniFile &ini, zip *z, const std::string &filename) { - zip_int64_t i = zip_name_locate(z, filename.c_str(), ZIP_FL_NOCASE); - if (i < 0) - return false; - - std::string inistr; - zip_uint64_t sz = ZipFileSize(z, i); - if (sz == INVALID_ZIP_SIZE) - return false; - inistr.resize(sz); - - zip_file_t *zf = zip_fopen_index(z, i, 0); - inistr.resize(zip_fread(zf, &inistr[0], inistr.size())); - zip_fclose(zf); - - std::stringstream sstream(inistr); - return ini.Load(sstream); -} - bool TextureReplacer::LoadIni() { - // TODO: Use crc32c? hash_ = ReplacedTextureHash::QUICK; aliases_.clear(); hashranges_.clear(); @@ -160,32 +110,21 @@ bool TextureReplacer::LoadIni() { // Prevents dumping the mipmaps. ignoreMipmap_ = false; - zip_.Close(); - - IniFile ini; - bool iniLoaded = false; + delete vfs_; + vfs_ = nullptr; // First, check for textures.zip, which is used to reduce IO. - zip *z = ZipOpenPath(basePath_ / ZIP_FILENAME); - if (z) { - iniLoaded = LoadIniZip(ini, z, INI_FILENAME); - // Require the zip have textures.ini to use it. - if (iniLoaded) { - iniLoaded = true; - std::lock_guard guard(zip_.lock); - zip_.z = z; - } else { - zip_close(z); - z = nullptr; - } + VFSBackend *dir = ZipFileReader::Create(basePath_ / ZIP_FILENAME, ""); + if (!dir) { + dir = new DirectoryReader(basePath_); } - if (!iniLoaded) { - iniLoaded = ini.LoadFromVFS(g_VFS, (basePath_ / INI_FILENAME).ToString()); - } + IniFile ini; + bool iniLoaded = ini.LoadFromVFS(g_VFS, (basePath_ / INI_FILENAME).ToString()); if (iniLoaded) { if (!LoadIniValues(ini)) { + delete dir; return false; } @@ -194,26 +133,26 @@ bool TextureReplacer::LoadIni() { if (ini.GetOrCreateSection("games")->Get(gameID_.c_str(), &overrideFilename, "")) { if (!overrideFilename.empty() && overrideFilename != INI_FILENAME) { IniFile overrideIni; - if (zip_.z) { - std::lock_guard guard(zip_.lock); - iniLoaded = LoadIniZip(overrideIni, zip_.z, overrideFilename); - } else { - iniLoaded = overrideIni.LoadFromVFS(g_VFS, (basePath_ / overrideFilename).ToString()); - } + iniLoaded = overrideIni.LoadFromVFS(*dir, overrideFilename); if (!iniLoaded) { ERROR_LOG(G3D, "Failed to load extra texture ini: %s", overrideFilename.c_str()); + // TODO: Should we just move on instead? + delete dir; return false; } INFO_LOG(G3D, "Loading extra texture ini: %s", overrideFilename.c_str()); if (!LoadIniValues(overrideIni, true)) { + delete dir; return false; } } } } - // The ini doesn't have to exist for it to be valid. + vfs_ = dir; + + // The ini doesn't have to exist for the texture directory or zip to be valid. return true; } @@ -481,6 +420,7 @@ ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, // Okay, let's construct the result. ReplacedTexture &result = cache_[replacementKey]; + result.vfs_ = this->vfs_; if (!g_Config.bReplaceTexturesAllowLate || budget > 0.0) { PopulateReplacement(&result, cachekey, hash, w, h); } @@ -498,27 +438,24 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, for (int i = 0; i < MAX_MIP_LEVELS; ++i) { const std::string hashfile = LookupHashFile(cachekey, hash, i); - const Path filename = basePath_ / hashfile; if (hashfile.empty()) { // Out of valid mip levels. Bail out. break; } + const Path filename = basePath_ / hashfile; + ReplacedTextureLevel level; level.fmt = Draw::DataFormat::R8G8B8A8_UNORM; level.file = filename; bool good; bool logError = hashfile != HashName(cachekey, hash, i) + ".png"; - if (zip_.z) { - level.zinfo = &zip_; - std::lock_guard guard(zip_.lock); - level.zi = zip_name_locate(zip_.z, hashfile.c_str(), ZIP_FL_NOCASE); - good = PopulateLevelFromZip(level, !logError); - } else { - good = PopulateLevelFromPath(level, !logError); - } + VFSFileReference *fileRef = vfs_->GetFile(hashfile.c_str()); + + level.fileRef = fileRef; + good = PopulateLevel(level, !logError); // We pad files that have been hashrange'd so they are the same texture size. level.w = (level.w * w) / newW; @@ -554,7 +491,7 @@ enum class ReplacedImageType { INVALID, }; -static ReplacedImageType Identify(const uint8_t magic[4]) { +static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { if (strncmp((const char *)magic, "ZIMG", 4) == 0) return ReplacedImageType::ZIM; if (magic[0] == 0x89 && strncmp((const char *)&magic[1], "PNG", 3) == 0) @@ -562,92 +499,57 @@ static ReplacedImageType Identify(const uint8_t magic[4]) { return ReplacedImageType::INVALID; } -static ReplacedImageType Identify(FILE *fp) { +static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic) { uint8_t magic[4]; - if (fread(magic, 1, 4, fp) != 4) + if (vfs->Read(openFile, magic, 4) != 4) { + *outMagic = "FAIL"; return ReplacedImageType::INVALID; - rewind(fp); - - return Identify(magic); -} - -static ReplacedImageType Identify(zip_file_t *zf) { - uint8_t magic[4]; - if (zip_fread(zf, magic, 4) != 4) - return ReplacedImageType::INVALID; - - return Identify(magic); -} - -bool TextureReplacer::PopulateLevelFromPath(ReplacedTextureLevel &level, bool ignoreError) { - bool good = false; - - FILE *fp = File::OpenCFile(level.file, "rb"); - if (!fp) { - if (!ignoreError) - ERROR_LOG(G3D, "Error opening replacement texture file '%s'", level.file.c_str()); - return false; } - - auto imageType = Identify(fp); - if (imageType == ReplacedImageType::ZIM) { - fseek(fp, 4, SEEK_SET); - good = fread(&level.w, 4, 1, fp) == 1; - good = good && fread(&level.h, 4, 1, fp) == 1; - int flags; - if (good && fread(&flags, 4, 1, fp) == 1) { - good = (flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; + // Turn the signature into a readable string that we can display in an error message. + *outMagic = std::string((const char *)magic, 4); + for (int i = 0; i < outMagic->size(); i++) { + if ((s8)(*outMagic)[i] < 32) { + (*outMagic)[i] = '_'; } - } else if (imageType == ReplacedImageType::PNG) { - png_image png = {}; - png.version = PNG_IMAGE_VERSION; - if (png_image_begin_read_from_stdio(&png, fp)) { - // We pad files that have been hashrange'd so they are the same texture size. - level.w = png.width; - level.h = png.height; - good = true; - } else { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", level.file.ToVisualString().c_str(), png.message); - } - png_image_free(&png); - } else { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format", level.file.ToVisualString().c_str()); } - fclose(fp); - - return good; + vfs->Rewind(openFile); + return IdentifyMagic(magic); } -bool TextureReplacer::PopulateLevelFromZip(ReplacedTextureLevel &level, bool ignoreError) { +bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { bool good = false; // Everything in here is within a lock, so we don't need to relock. - if (!level.zinfo || !level.zinfo->z || level.zi < 0) { + if (!level.fileRef) { if (!ignoreError) ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str()); return false; } + File::FileInfo info; + vfs_->GetFileInfo(level.fileRef, &info); + size_t zsize = info.size; - zip_file_t *zf = zip_fopen_index(level.zinfo->z, level.zi, 0); - if (!zf) + VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef); + if (!file) { return false; + } - auto imageType = Identify(zf); - zip_fclose(zf); + std::string magic; + auto imageType = Identify(vfs_, file, &magic); - zf = zip_fopen_index(level.zinfo->z, level.zi, 0); if (imageType == ReplacedImageType::ZIM) { uint32_t ignore = 0; - good = zip_fread(zf, &ignore, 4) == 4; - good = good && zip_fread(zf, &level.w, 4) == 4; - good = good && zip_fread(zf, &level.h, 4) == 4; - int flags; - if (good && zip_fread(zf, &flags, 4) == 4) { - good = (flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; - } + struct ZimHeader { + uint32_t magic; + uint32_t w; + uint32_t h; + uint32_t flags; + } header; + good = vfs_->Read(file, &header, sizeof(header)) == sizeof(header); + good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; } else if (imageType == ReplacedImageType::PNG) { PNGHeaderPeek headerPeek; - good = zip_fread(zf, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); + good = vfs_->Read(file, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); if (good && headerPeek.IsValidPNGHeader()) { level.w = headerPeek.Width(); level.h = headerPeek.Height(); @@ -657,9 +559,9 @@ bool TextureReplacer::PopulateLevelFromZip(ReplacedTextureLevel &level, bool ign good = false; } } else { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format (zip)", level.file.ToVisualString().c_str()); + ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format %s", level.file.ToVisualString().c_str(), magic.c_str()); } - zip_fclose(zf); + vfs_->CloseFile(file); return good; } @@ -986,7 +888,7 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { class ReplacedTextureTask : public Task { public: - ReplacedTextureTask(ReplacedTexture &tex, LimitedWaitable *w) : tex_(tex), waitable_(w) {} + ReplacedTextureTask(VFSBackend *vfs, ReplacedTexture &tex, LimitedWaitable *w) : vfs_(vfs), tex_(tex), waitable_(w) {} TaskType Type() const override { return TaskType::IO_BLOCKING; @@ -997,16 +899,18 @@ public: } void Run() override { - tex_.Prepare(); + tex_.Prepare(vfs_); waitable_->Notify(); } private: ReplacedTexture &tex_; LimitedWaitable *waitable_; + VFSBackend *vfs_; }; bool ReplacedTexture::IsReady(double budget) { + _assert_(vfs_ != nullptr); lastUsed_ = time_now_d(); if (threadWaitable_ && !threadWaitable_->WaitFor(budget)) { return false; @@ -1028,14 +932,14 @@ bool ReplacedTexture::IsReady(double budget) { if (threadWaitable_) delete threadWaitable_; threadWaitable_ = new LimitedWaitable(); - g_threadManager.EnqueueTask(new ReplacedTextureTask(*this, threadWaitable_)); + g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); if (threadWaitable_->WaitFor(budget)) { // If we finished all the levels, we're done. return initDone_ && !levelData_.empty(); } } else { - Prepare(); + Prepare(vfs_); _assert_(initDone_); return true; } @@ -1044,8 +948,9 @@ bool ReplacedTexture::IsReady(double budget) { return false; } -void ReplacedTexture::Prepare() { +void ReplacedTexture::Prepare(VFSBackend *vfs) { std::unique_lock lock(mutex_); + this->vfs_ = vfs; if (cancelPrepare_) { initDone_ = true; return; @@ -1069,83 +974,47 @@ void ReplacedTexture::PrepareData(int level) { // We must lock around access to levelData_ in case two textures try to load it at once. std::lock_guard guard(levelData_[level]->lock); - const ReplacedTextureLevel &info = levels_[level]; + ReplacedTextureLevel &info = levels_[level]; std::vector &out = levelData_[level]->data; // Already populated from cache. if (!out.empty()) return; - FILE *fp = nullptr; - zip_file_t *zf = nullptr; ReplacedImageType imageType; - std::unique_lock zguard; - if (info.zinfo && info.zinfo->z) { - zguard = std::unique_lock(info.zinfo->lock); - zf = zip_fopen_index(info.zinfo->z, info.zi, 0); - if (!zf) - return; - imageType = Identify(zf); - // Can't assume we can seek. Reopen. - zip_fclose(zf); - zf = zip_fopen_index(info.zinfo->z, info.zi, 0); - } else { - fp = File::OpenCFile(info.file, "rb"); - if (!fp) { - // Leaving the data sized at zero means failure. - return; - } - - imageType = Identify(fp); + File::FileInfo fileInfo; + if (!vfs_->GetFileInfo(info.fileRef, &fileInfo)) { + ERROR_LOG(G3D, "Failed to get file info for level"); + return; } + size_t fileSize = fileInfo.size; + + VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef); + std::string magic; + imageType = Identify(vfs_, openFile, &magic); auto cleanup = [&] { - if (zf) - zip_fclose(zf); - if (fp) - fclose(fp); + vfs_->CloseFile(openFile); }; if (imageType == ReplacedImageType::ZIM) { - size_t zimSize = 0; - if (fp) { - zimSize = File::GetFileSize(fp); - } else if (zf) { - zip_uint64_t zsize = ZipFileSize(info.zinfo->z, info.zi); - zimSize = zsize == INVALID_ZIP_SIZE ? 0 : (size_t)zsize; - } else { - _assert_(false); - } - std::unique_ptr zim(new uint8_t[zimSize]); + std::unique_ptr zim(new uint8_t[fileSize]); if (!zim) { ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); cleanup(); return; } - if (fp) { - if (fread(&zim[0], 1, zimSize, fp) != zimSize) { - ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); - cleanup(); - return; - } - } else if (zf) { - if (zip_fread(zf, &zim[0], zimSize) != zimSize) { - ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM (zip)", info.file.c_str()); - cleanup(); - return; - } - // Try to unlock early to prevent blocking threads. - zguard.unlock(); - } else { - _assert_(false); + if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) { + ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); + cleanup(); + return; } int w, h, f; uint8_t *image; - - if (LoadZIMPtr(&zim[0], zimSize, &w, &h, &f, &image)) { + if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) { if (w > info.w || h > info.h) { ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); cleanup(); @@ -1171,31 +1040,13 @@ void ReplacedTexture::PrepareData(int level) { png_image png = {}; png.version = PNG_IMAGE_VERSION; - // Needs to survive for a little while, used for zip only. std::string pngdata; - if (fp) { - if (!png_image_begin_read_from_stdio(&png, fp)) { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s", info.file.c_str(), png.message); - cleanup(); - return; - } - } else if (zf) { - zip_uint64_t zsize = ZipFileSize(info.zinfo->z, info.zi); - if (zsize != INVALID_ZIP_SIZE) - pngdata.resize(zsize); - if (!pngdata.empty()) { - pngdata.resize(zip_fread(zf, &pngdata[0], pngdata.size())); - } - // Try to unlock early to prevent blocking threads. - zguard.unlock(); - - if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); - cleanup(); - return; - } - } else { - _assert_(false); + pngdata.resize(fileSize); + vfs_->Read(openFile, &pngdata[0], fileSize); + if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { + ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); + cleanup(); + return; } if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) { ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); @@ -1280,6 +1131,7 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { if (data.empty()) return false; + if (rowPitch < info.w * 4) { ERROR_LOG_REPORT(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); return false; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index cc9e4abed1..849186bc5f 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -27,6 +27,7 @@ #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" #include "Common/File/Path.h" +#include "Common/File/VFS/VFS.h" #include "Common/GPU/DataFormat.h" #include "GPU/Common/TextureDecoder.h" @@ -37,7 +38,7 @@ class TextureCacheCommon; class TextureReplacer; class ReplacedTextureTask; class LimitedWaitable; -struct zip; +class VFSBackend; // These must match the constants in TextureCacheCommon. enum class ReplacedTextureAlpha { @@ -52,13 +53,6 @@ enum class ReplacedTextureHash { XXH64, }; -struct ReplacerZipInfo { - zip *z = nullptr; - std::mutex lock; - - void Close(); -}; - struct ReplacedTextureLevel { int w; int h; @@ -66,9 +60,7 @@ struct ReplacedTextureLevel { Path file; // Can be ignored for hashing/equal, since file has all uniqueness. // To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t. - ReplacerZipInfo *zinfo = nullptr; - int64_t zi = -1; - + VFSFileReference *fileRef; bool operator ==(const ReplacedTextureLevel &other) const { if (w != other.w || h != other.h || fmt != other.fmt) return false; @@ -206,7 +198,7 @@ struct ReplacedTexture { bool Load(int level, void *out, int rowPitch); protected: - void Prepare(); + void Prepare(VFSBackend *vfs); void PrepareData(int level); void PurgeIfOlder(double t); @@ -216,10 +208,13 @@ protected: double lastUsed_ = 0.0; LimitedWaitable *threadWaitable_ = nullptr; std::mutex mutex_; + bool cancelPrepare_ = false; bool initDone_ = false; bool prepareDone_ = false; + VFSBackend *vfs_ = nullptr; + friend TextureReplacer; friend ReplacedTextureTask; }; @@ -282,21 +277,22 @@ protected: std::string LookupHashFile(u64 cachekey, u32 hash, int level); std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); - bool PopulateLevelFromPath(ReplacedTextureLevel &level, bool ignoreError); - bool PopulateLevelFromZip(ReplacedTextureLevel &level, bool ignoreError); + bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); bool enabled_ = false; bool allowVideo_ = false; bool ignoreAddress_ = false; bool reduceHash_ = false; - float reduceHashSize = 1.0; // default value with reduceHash to false - float reduceHashGlobalValue = 0.5; // Global value for textures dump pngs of all sizes, 0.5 by default but can be set in textures.ini + float reduceHashSize = 1.0f; // default value with reduceHash to false + float reduceHashGlobalValue = 0.5f; // Global value for textures dump pngs of all sizes, 0.5 by default but can be set in textures.ini + double lastTextureCacheSizeGB_ = 0.0; bool ignoreMipmap_ = false; std::string gameID_; Path basePath_; ReplacedTextureHash hash_ = ReplacedTextureHash::QUICK; - ReplacerZipInfo zip_; + + VFSBackend *vfs_ = nullptr; typedef std::pair WidthHeightPair; std::unordered_map hashranges_; From 16b0e1103a00991fda4a2ccd2816a451ab3ab983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 20:59:34 +0100 Subject: [PATCH 104/141] Check file existence early. Although, not sure if this is the right thing on Android... --- Common/File/VFS/DirectoryReader.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index 999807ed0a..af05120450 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -44,8 +44,13 @@ public: }; VFSFileReference *DirectoryReader::GetFile(const char *path) { + Path filePath = path_ / path; + if (!File::Exists(filePath)) { + return nullptr; + } + DirectoryReaderFileReference *reference = new DirectoryReaderFileReference(); - reference->path = path_ / path; + reference->path = filePath; return reference; } From 41fcb7a79f958dc053dd29099ef92cb71f18b9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 7 Mar 2023 22:02:44 +0100 Subject: [PATCH 105/141] Add necessary check --- Core/TextureReplacer.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 43e6fbf83b..98e20a480e 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -453,6 +453,10 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, bool logError = hashfile != HashName(cachekey, hash, i) + ".png"; VFSFileReference *fileRef = vfs_->GetFile(hashfile.c_str()); + if (!fileRef) { + // If the file doesn't exist, let's just bail immediately here. + break; + } level.fileRef = fileRef; good = PopulateLevel(level, !logError); @@ -525,8 +529,11 @@ bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreErro ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str()); return false; } + File::FileInfo info; - vfs_->GetFileInfo(level.fileRef, &info); + if (!vfs_->GetFileInfo(level.fileRef, &info)) { + return false; + } size_t zsize = info.size; VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef); From ce9000e7e04e7d258bda00fb30cda9058f0e3d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 08:23:13 +0100 Subject: [PATCH 106/141] Address most of the feedback --- Core/TextureReplacer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 98e20a480e..6d2cab9f60 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -136,7 +136,8 @@ bool TextureReplacer::LoadIni() { iniLoaded = overrideIni.LoadFromVFS(*dir, overrideFilename); if (!iniLoaded) { ERROR_LOG(G3D, "Failed to load extra texture ini: %s", overrideFilename.c_str()); - // TODO: Should we just move on instead? + // Since this error is most likely to occure for texture pack creators, let's just bail here + // so that the creator is more likely to look in the logs for what happened. delete dir; return false; } @@ -523,7 +524,6 @@ static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::s bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { bool good = false; - // Everything in here is within a lock, so we don't need to relock. if (!level.fileRef) { if (!ignoreError) ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str()); @@ -553,6 +553,8 @@ bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreErro uint32_t flags; } header; good = vfs_->Read(file, &header, sizeof(header)) == sizeof(header); + level.w = header.w; + level.h = header.h; good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; } else if (imageType == ReplacedImageType::PNG) { PNGHeaderPeek headerPeek; @@ -911,9 +913,9 @@ public: } private: + VFSBackend *vfs_; ReplacedTexture &tex_; LimitedWaitable *waitable_; - VFSBackend *vfs_; }; bool ReplacedTexture::IsReady(double budget) { @@ -981,7 +983,7 @@ void ReplacedTexture::PrepareData(int level) { // We must lock around access to levelData_ in case two textures try to load it at once. std::lock_guard guard(levelData_[level]->lock); - ReplacedTextureLevel &info = levels_[level]; + const ReplacedTextureLevel &info = levels_[level]; std::vector &out = levelData_[level]->data; // Already populated from cache. From e277db63c0586904db20bfa612585098ca22998b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 09:11:25 +0100 Subject: [PATCH 107/141] Optimize away those extra GetFileInfo, since both backends can get the size during Open efficiently. --- Common/File/VFS/DirectoryReader.cpp | 8 ++++++-- Common/File/VFS/DirectoryReader.h | 2 +- Common/File/VFS/VFS.h | 4 +++- Common/File/VFS/ZipFileReader.cpp | 12 ++++++++++-- Common/File/VFS/ZipFileReader.h | 2 +- Core/TextureReplacer.cpp | 20 +++++--------------- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index af05120450..981dff905b 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -1,3 +1,5 @@ +#include + #include "Common/Common.h" #include "Common/Log.h" #include "Common/File/VFS/DirectoryReader.h" @@ -64,13 +66,15 @@ void DirectoryReader::ReleaseFile(VFSFileReference *vfsReference) { delete reference; } -VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference) { +VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) { DirectoryReaderFileReference *reference = (DirectoryReaderFileReference *)vfsReference; FILE *file = File::OpenCFile(reference->path, "rb"); if (!file) { return nullptr; } - + fseek(file, 0, SEEK_END); + *size = ftell(file); + fseek(file, 0, SEEK_SET); DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile(); openFile->file = file; return openFile; diff --git a/Common/File/VFS/DirectoryReader.h b/Common/File/VFS/DirectoryReader.h index cc6d971371..f742c6b3da 100644 --- a/Common/File/VFS/DirectoryReader.h +++ b/Common/File/VFS/DirectoryReader.h @@ -14,7 +14,7 @@ public: bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) override; void ReleaseFile(VFSFileReference *vfsReference) override; - VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) override; + VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference, size_t *size) override; void Rewind(VFSOpenFile *vfsOpenFile) override; size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) override; void CloseFile(VFSOpenFile *vfsOpenFile) override; diff --git a/Common/File/VFS/VFS.h b/Common/File/VFS/VFS.h index 67d737d5f7..5d585e309e 100644 --- a/Common/File/VFS/VFS.h +++ b/Common/File/VFS/VFS.h @@ -46,7 +46,9 @@ public: virtual bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) = 0; virtual void ReleaseFile(VFSFileReference *vfsReference) = 0; - virtual VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) = 0; + // Must write the size of the file to *size. Both backends can do this efficiently here, + // avoiding a call to GetFileInfo. + virtual VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference, size_t *size) = 0; virtual void Rewind(VFSOpenFile *vfsOpenFile) = 0; virtual size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) = 0; virtual void CloseFile(VFSOpenFile *vfsOpenFile) = 0; diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 694ef95864..a03307ed72 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -238,21 +238,29 @@ void ZipFileReader::ReleaseFile(VFSFileReference *vfsReference) { delete reference; } -VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference) { +VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size_t *size) { ZipFileReaderFileReference *reference = (ZipFileReaderFileReference *)vfsReference; ZipFileReaderOpenFile *openFile = new ZipFileReaderOpenFile(); openFile->reference = reference; + *size = 0; // We only allow one file to be open for read concurrently. It's possible that this can be improved, // especially if we only access by index like this. lock_.lock(); - openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0); + zip_stat_t zstat; + if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0) { + lock_.unlock(); + return false; + } + openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0); if (!openFile->zf) { WARN_LOG(G3D, "File with index %d not found in zip", reference->zi); lock_.unlock(); return nullptr; } + *size = zstat.size; + // Intentionally leaving the mutex locked, will be closed in CloseFile. return openFile; } diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index e3c50b38bf..f39058beed 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -28,7 +28,7 @@ public: bool GetFileInfo(VFSFileReference *vfsReference, File::FileInfo *fileInfo) override; void ReleaseFile(VFSFileReference *vfsReference) override; - VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference) override; + VFSOpenFile *OpenFileForRead(VFSFileReference *vfsReference, size_t *size) override; void Rewind(VFSOpenFile *vfsOpenFile) override; size_t Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) override; void CloseFile(VFSOpenFile *vfsOpenFile) override; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 6d2cab9f60..bc81d0e032 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -530,13 +530,8 @@ bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreErro return false; } - File::FileInfo info; - if (!vfs_->GetFileInfo(level.fileRef, &info)) { - return false; - } - size_t zsize = info.size; - - VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef); + size_t fileSize; + VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef, &fileSize); if (!file) { return false; } @@ -992,14 +987,9 @@ void ReplacedTexture::PrepareData(int level) { ReplacedImageType imageType; - File::FileInfo fileInfo; - if (!vfs_->GetFileInfo(info.fileRef, &fileInfo)) { - ERROR_LOG(G3D, "Failed to get file info for level"); - return; - } - size_t fileSize = fileInfo.size; + size_t fileSize; + VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef, &fileSize); - VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef); std::string magic; imageType = Identify(vfs_, openFile, &magic); @@ -1051,7 +1041,7 @@ void ReplacedTexture::PrepareData(int level) { std::string pngdata; pngdata.resize(fileSize); - vfs_->Read(openFile, &pngdata[0], fileSize); + pngdata.resize(vfs_->Read(openFile, &pngdata[0], fileSize)); if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); cleanup(); From 74cad6b5211a2f821e4ceae379e67cbaf6395cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 09:17:26 +0100 Subject: [PATCH 108/141] Quick buildfix --- Common/File/VFS/ZipFileReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index a03307ed72..35a8aa4afa 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -249,7 +249,7 @@ VFSOpenFile *ZipFileReader::OpenFileForRead(VFSFileReference *vfsReference, size zip_stat_t zstat; if (zip_stat_index(zip_file_, reference->zi, 0, &zstat) != 0) { lock_.unlock(); - return false; + return nullptr; } openFile->zf = zip_fopen_index(zip_file_, reference->zi, 0); From 306fdde9d0dd606fad5732de917709573b52e393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 09:29:19 +0100 Subject: [PATCH 109/141] Correct error handling for missing ini (only accept it in directories, not zip) --- Common/File/VFS/ZipFileReader.cpp | 35 +++++++++++++------------------ Core/TextureReplacer.cpp | 13 ++++++++++++ Core/TextureReplacer.h | 1 + 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 35a8aa4afa..d4e4f69e66 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -15,25 +15,6 @@ #include "Common/File/VFS/ZipFileReader.h" #include "Common/StringUtils.h" -static uint8_t *ReadFromZip(zip *archive, const char* filename, size_t *size) { - // Figure out the file size first. - struct zip_stat zstat; - zip_file *file = zip_fopen(archive, filename, ZIP_FL_NOCASE|ZIP_FL_UNCHANGED); - if (!file) { - ERROR_LOG(IO, "Error opening %s from ZIP", filename); - return 0; - } - zip_stat(archive, filename, ZIP_FL_NOCASE|ZIP_FL_UNCHANGED, &zstat); - - uint8_t *contents = new uint8_t[zstat.size + 1]; - zip_fread(file, contents, zstat.size); - zip_fclose(file); - contents[zstat.size] = 0; - - *size = zstat.size; - return contents; -} - ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath) { int error = 0; zip *zip_file; @@ -69,7 +50,21 @@ uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { snprintf(temp_path, sizeof(temp_path), "%s%s", inZipPath_, path); std::lock_guard guard(lock_); - return ReadFromZip(zip_file_, temp_path, size); + // Figure out the file size first. + struct zip_stat zstat; + zip_stat(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat); + zip_file *file = zip_fopen(zip_file_, temp_path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED); + if (!file) { + ERROR_LOG(IO, "Error opening %s from ZIP", temp_path); + return 0; + } + uint8_t *contents = new uint8_t[zstat.size + 1]; + zip_fread(file, contents, zstat.size); + zip_fclose(file); + contents[zstat.size] = 0; + + *size = zstat.size; + return contents; } bool ZipFileReader::GetFileListing(const char *orig_path, std::vector *listing, const char *filter = 0) { diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index bc81d0e032..89b71963d2 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -116,7 +116,10 @@ bool TextureReplacer::LoadIni() { // First, check for textures.zip, which is used to reduce IO. VFSBackend *dir = ZipFileReader::Create(basePath_ / ZIP_FILENAME, ""); if (!dir) { + vfsIsZip_ = false; dir = new DirectoryReader(basePath_); + } else { + vfsIsZip_ = true; } IniFile ini; @@ -149,9 +152,19 @@ bool TextureReplacer::LoadIni() { } } } + } else { + if (vfsIsZip_) { + // We don't accept zip files without inis. + ERROR_LOG(G3D, "Texture pack lacking ini file: %s", basePath_.c_str()); + delete dir; + return false; + } else { + WARN_LOG(G3D, "Texture pack lacking ini file: %s", basePath_.c_str()); + } } vfs_ = dir; + INFO_LOG(G3D, "Texture pack activated from '%s'", basePath_.c_str()); // The ini doesn't have to exist for the texture directory or zip to be valid. return true; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 849186bc5f..9281edf3ef 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -293,6 +293,7 @@ protected: ReplacedTextureHash hash_ = ReplacedTextureHash::QUICK; VFSBackend *vfs_ = nullptr; + bool vfsIsZip_ = false; typedef std::pair WidthHeightPair; std::unordered_map hashranges_; From bfddd04c118455ec8d084f035719af21d414c88c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 10:57:26 +0100 Subject: [PATCH 110/141] Simplify GenerateIni using raw strings --- Core/TextureReplacer.cpp | 46 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 89b71963d2..e82bcc744f 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -1188,31 +1188,33 @@ bool TextureReplacer::GenerateIni(const std::string &gameID, Path &generatedFile FILE *f = File::OpenCFile(generatedFilename, "wb"); if (f) { + // Unicode byte order mark fwrite("\xEF\xBB\xBF", 1, 3, f); // Let's also write some defaults. - fprintf(f, "# This file is optional and describes your textures.\n"); - fprintf(f, "# Some information on syntax available here:\n"); - fprintf(f, "# https://github.com/hrydgard/ppsspp/wiki/Texture-replacement-ini-syntax \n"); - fprintf(f, "[options]\n"); - fprintf(f, "version = 1\n"); - fprintf(f, "hash = quick\n"); - fprintf(f, "ignoreMipmap = false\n"); - fprintf(f, "\n"); - fprintf(f, "[games]\n"); - fprintf(f, "# Used to make it easier to install, and override settings for other regions.\n"); - fprintf(f, "# Files still have to be copied to each TEXTURES folder."); - fprintf(f, "%s = %s\n", gameID.c_str(), INI_FILENAME.c_str()); - fprintf(f, "\n"); - fprintf(f, "[hashes]\n"); - fprintf(f, "# Use / for folders not \\, avoid special characters, and stick to lowercase.\n"); - fprintf(f, "# See wiki for more info.\n"); - fprintf(f, "\n"); - fprintf(f, "[hashranges]\n"); - fprintf(f, "\n"); - fprintf(f, "[filtering]\n"); - fprintf(f, "\n"); - fprintf(f, "[reducehashranges]\n"); + fprintf(f, R"(# This file is optional and describes your textures. +# Some information on syntax available here: +# https://github.com/hrydgard/ppsspp/wiki/Texture-replacement-ini-syntax +[options] +version = 1 +hash = quick +ignoreMipmap = false + +[games] +# Used to make it easier to install, and override settings for other regions. +# Files still have to be copied to each TEXTURES folder. +%s = %s + +[hashes] +# Use / for folders not \\, avoid special characters, and stick to lowercase. +# See wiki for more info. + +[hashranges] + +[filtering] + +[reducehashranges] +)", gameID.c_str(), INI_FILENAME.c_str()); fclose(f); } return File::Exists(generatedFilename); From 81351056f4306d10736fcf197929ce9a9a4feca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 11:06:29 +0100 Subject: [PATCH 111/141] Move file identification to the top of the file --- Core/TextureReplacer.cpp | 63 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index e82bcc744f..e2cc217feb 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "ppsspp_config.h" + #include #include #include @@ -55,6 +56,37 @@ static const int VERSION = 1; static const int MAX_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP. static const double MAX_CACHE_SIZE = 4.0; +enum class ReplacedImageType { + PNG, + ZIM, + INVALID, +}; + +static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { + if (strncmp((const char *)magic, "ZIMG", 4) == 0) + return ReplacedImageType::ZIM; + if (magic[0] == 0x89 && strncmp((const char *)&magic[1], "PNG", 3) == 0) + return ReplacedImageType::PNG; + return ReplacedImageType::INVALID; +} + +static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic) { + uint8_t magic[4]; + if (vfs->Read(openFile, magic, 4) != 4) { + *outMagic = "FAIL"; + return ReplacedImageType::INVALID; + } + // Turn the signature into a readable string that we can display in an error message. + *outMagic = std::string((const char *)magic, 4); + for (int i = 0; i < outMagic->size(); i++) { + if ((s8)(*outMagic)[i] < 32) { + (*outMagic)[i] = '_'; + } + } + vfs->Rewind(openFile); + return IdentifyMagic(magic); +} + TextureReplacer::TextureReplacer() { none_.initDone_ = true; none_.prepareDone_ = true; @@ -503,37 +535,6 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, result->prepareDone_ = true; } -enum class ReplacedImageType { - PNG, - ZIM, - INVALID, -}; - -static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { - if (strncmp((const char *)magic, "ZIMG", 4) == 0) - return ReplacedImageType::ZIM; - if (magic[0] == 0x89 && strncmp((const char *)&magic[1], "PNG", 3) == 0) - return ReplacedImageType::PNG; - return ReplacedImageType::INVALID; -} - -static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic) { - uint8_t magic[4]; - if (vfs->Read(openFile, magic, 4) != 4) { - *outMagic = "FAIL"; - return ReplacedImageType::INVALID; - } - // Turn the signature into a readable string that we can display in an error message. - *outMagic = std::string((const char *)magic, 4); - for (int i = 0; i < outMagic->size(); i++) { - if ((s8)(*outMagic)[i] < 32) { - (*outMagic)[i] = '_'; - } - } - vfs->Rewind(openFile); - return IdentifyMagic(magic); -} - bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { bool good = false; From 226197f30f6a919f4b1952ba955ea65809e3a2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 8 Mar 2023 11:31:32 +0100 Subject: [PATCH 112/141] Replace "none_" with nullptr. --- Core/TextureReplacer.cpp | 31 +++++++++++++++++-------------- Core/TextureReplacer.h | 18 ++++++++---------- GPU/Common/TextureCacheCommon.cpp | 27 ++++++++++++++++----------- GPU/Common/TextureCacheCommon.h | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index e2cc217feb..42c58460d5 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -87,12 +87,13 @@ static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::s return IdentifyMagic(magic); } -TextureReplacer::TextureReplacer() { - none_.initDone_ = true; - none_.prepareDone_ = true; -} +TextureReplacer::TextureReplacer() {} TextureReplacer::~TextureReplacer() { + for (auto &iter : cache_) { + delete iter.second; + } + delete vfs_; } @@ -206,7 +207,6 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { auto options = ini.GetOrCreateSection("options"); std::string hash; options->Get("hash", &hash, ""); - // TODO: crc32c. if (strcasecmp(hash.c_str(), "quick") == 0) { hash_ = ReplacedTextureHash::QUICK; } else if (strcasecmp(hash.c_str(), "xxh32") == 0) { @@ -448,27 +448,29 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, GETextureForm } } -ReplacedTexture &TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget) { +ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget) { // Only actually replace if we're replacing. We might just be saving. if (!Enabled() || !g_Config.bReplaceTextures) { - return none_; + return nullptr; } ReplacementCacheKey replacementKey(cachekey, hash); auto it = cache_.find(replacementKey); if (it != cache_.end()) { - if (!it->second.prepareDone_ && budget > 0.0) { + if (!it->second->prepareDone_ && budget > 0.0) { // We don't do this on a thread, but we only do it while within budget. - PopulateReplacement(&it->second, cachekey, hash, w, h); + PopulateReplacement(it->second, cachekey, hash, w, h); } return it->second; } // Okay, let's construct the result. - ReplacedTexture &result = cache_[replacementKey]; - result.vfs_ = this->vfs_; + + ReplacedTexture *result = new ReplacedTexture(); + cache_[replacementKey] = result; + result->vfs_ = this->vfs_; if (!g_Config.bReplaceTexturesAllowLate || budget > 0.0) { - PopulateReplacement(&result, cachekey, hash, w, h); + PopulateReplacement(result, cachekey, hash, w, h); } return result; } @@ -531,7 +533,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, for (size_t i = 0; i < result->levels_.size(); ++i) { result->levelData_[i] = &levelCache_[result->levels_[i]]; } - + result->prepareDone_ = true; } @@ -776,7 +778,8 @@ void TextureReplacer::Decimate(ReplacerDecimateMode mode) { const double threshold = time_now_d() - age; for (auto &item : cache_) { - item.second.PurgeIfOlder(threshold); + item.second->PurgeIfOlder(threshold); + // don't actually delete the items here, just clean out the data. } size_t totalSize = 0; diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 9281edf3ef..0b4633e05f 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -54,13 +54,14 @@ enum class ReplacedTextureHash { }; struct ReplacedTextureLevel { - int w; - int h; - Draw::DataFormat fmt; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. + ReplacedTextureLevel() {} + int w = 0; + int h = 0; + Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. Path file; // Can be ignored for hashing/equal, since file has all uniqueness. // To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t. - VFSFileReference *fileRef; + VFSFileReference *fileRef = nullptr; bool operator ==(const ReplacedTextureLevel &other) const { if (w != other.w || h != other.h || fmt != other.fmt) return false; @@ -249,11 +250,9 @@ public: u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV); - ReplacedTexture &FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget); + // Returns nullptr if not found. + ReplacedTexture *FindReplacement(u64 cachekey, u32 hash, int w, int h, double budget); bool FindFiltering(u64 cachekey, u32 hash, TextureFiltering *forceFiltering); - ReplacedTexture &FindNone() { - return none_; - } // Check if a NotifyTextureDecoded for this texture is desired (used to avoid reads from write-combined memory.) bool WillSave(const ReplacedTextureDecodeInfo &replacedInfo); @@ -301,8 +300,7 @@ protected: std::unordered_map aliases_; std::unordered_map filtering_; - ReplacedTexture none_; - std::unordered_map cache_; + std::unordered_map cache_; std::unordered_map> savedCache_; std::unordered_map levelCache_; }; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index c7985e82fa..7a35252f36 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -528,8 +528,8 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { int w0 = gstate.getTextureWidth(0); int h0 = gstate.getTextureHeight(0); int d0 = 1; - ReplacedTexture &replaced = FindReplacement(entry, w0, h0, d0); - if (replaced.IsInvalid()) { + ReplacedTexture *replaced = FindReplacement(entry, w0, h0, d0); + if (replaced && replaced->IsInvalid()) { entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; if (g_Config.bSaveNewTextures) { // Load once more to actually save. @@ -1482,17 +1482,17 @@ u32 TextureCacheCommon::EstimateTexMemoryUsage(const TexCacheEntry *entry) { return pixelSize << (dimW + dimH); } -ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int &w, int &h, int &d) { +ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int &w, int &h, int &d) { if (d != 1) { // We don't yet support replacing 3D textures. - return replacer_.FindNone(); + return nullptr; } // Short circuit the non-enabled case. // Otherwise, due to bReplaceTexturesAllowLate, we'll still spawn tasks looking for replacements // that then won't be used. if (!replacer_.Enabled()) { - return replacer_.FindNone(); + return nullptr; } // Allow some delay to reduce pop-in. @@ -1501,16 +1501,21 @@ ReplacedTexture &TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & double replaceStart = time_now_d(); double budget = std::min(MAX_BUDGET_PER_TEX, replacementFrameBudget_ - replacementTimeThisFrame_); u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; - ReplacedTexture &replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h, budget); - if (replaced.IsReady(budget)) { - if (replaced.GetSize(0, w, h)) { + ReplacedTexture *replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h, budget); + if (!replaced) { + replacementTimeThisFrame_ += time_now_d() - replaceStart; + return nullptr; + } + + if (replaced->IsReady(budget)) { + if (replaced->GetSize(0, w, h)) { // Consider it already "scaled." entry->status |= TexCacheEntry::STATUS_IS_SCALED; } // Remove the flag, even if it was invalid. entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - } else if (!replaced.IsInvalid()) { + } else if (!replaced->IsInvalid()) { entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } replacementTimeThisFrame_ += time_now_d() - replaceStart; @@ -2778,10 +2783,10 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt } if (canReplace) { - plan.replaced = &FindReplacement(entry, plan.w, plan.h, plan.depth); + plan.replaced = FindReplacement(entry, plan.w, plan.h, plan.depth); plan.replaceValid = plan.replaced->Valid(); } else { - plan.replaced = &replacer_.FindNone(); + plan.replaced = nullptr; plan.replaceValid = false; } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index ec45251fa8..da42b07de4 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -378,7 +378,7 @@ protected: CheckAlphaResult DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, TexDecodeFlags flags); void UnswizzleFromMem(u32 *dest, u32 destPitch, const u8 *texptr, u32 bufw, u32 height, u32 bytesPerPixel); CheckAlphaResult ReadIndexedTex(u8 *out, int outPitch, int level, const u8 *texptr, int bytesPerIndex, int bufw, bool reverseColors, bool expandTo32Bit); - ReplacedTexture &FindReplacement(TexCacheEntry *entry, int &w, int &h, int &d); + ReplacedTexture *FindReplacement(TexCacheEntry *entry, int &w, int &h, int &d); // Return value is mapData normally, but could be another buffer allocated with AllocateAlignedMemory. void LoadTextureLevel(TexCacheEntry &entry, uint8_t *mapData, int mapRowPitch, BuildTexturePlan &plan, int srcLevel, Draw::DataFormat dstFmt, TexDecodeFlags texDecFlags); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 21cf131393..6c898b5579 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -501,7 +501,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { } // Turn off texture replacement for this texture. - plan.replaced = &replacer_.FindNone(); + plan.replaced = nullptr; plan.createW /= plan.scaleFactor; plan.createH /= plan.scaleFactor; From 0fe5bd79c471e7a7151546ab4f5adcba242a0e92 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 8 Mar 2023 22:13:55 +0100 Subject: [PATCH 113/141] OpenXR - Manifest merge fail fixed --- android/VRManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/VRManifest.xml b/android/VRManifest.xml index e607741e3b..4dfda7647a 100644 --- a/android/VRManifest.xml +++ b/android/VRManifest.xml @@ -6,7 +6,7 @@ Date: Wed, 8 Mar 2023 22:23:54 +0100 Subject: [PATCH 114/141] OpenXR - Only horizontal HMD rotations to keys --- Core/Config.cpp | 2 +- Core/Config.h | 2 +- UI/GameSettingsScreen.cpp | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index ca8e44e023..3b23a7791f 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1154,7 +1154,7 @@ static const ConfigSetting vrSettings[] = { ConfigSetting("VRMotionLength", &g_Config.fMotionLength, 0.5f), ConfigSetting("VRHeadRotationScale", &g_Config.fHeadRotationScale, 5.0f), ConfigSetting("VRHeadRotationSmoothing", &g_Config.bHeadRotationSmoothing, false), - ConfigSetting("VRHeadRotation", &g_Config.iHeadRotation, 0), + ConfigSetting("VRHeadRotation", &g_Config.iHeadRotation, false), }; struct ConfigSectionSettings { diff --git a/Core/Config.h b/Core/Config.h index 17f198f920..aa57a2d3c7 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -472,7 +472,7 @@ public: float fMotionLength; float fHeadRotationScale; bool bHeadRotationSmoothing; - int iHeadRotation; + bool iHeadRotation; // Debugger int iDisasmWindowX; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 6c8f8469f3..0e902ad9a2 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1143,8 +1143,7 @@ void GameSettingsScreen::CreateVRSettings(UI::ViewGroup *vrSettings) { vrSettings->Add(new ItemHeader(vr->T("Experts only"))); vrSettings->Add(new CheckBox(&g_Config.bManualForceVR, vr->T("Manual switching between flat screen and VR using SCREEN key"))); - static const char *vrHeadRotations[] = { vr->T("Disabled"), vr->T("Horizontal only"), vr->T("Horizontal and vertical") }; - vrSettings->Add(new PopupMultiChoice(&g_Config.iHeadRotation, vr->T("Map HMD rotations on keys instead of VR camera"), vrHeadRotations, 0, ARRAY_SIZE(vrHeadRotations), vr->GetName(), screenManager())); + vrSettings->Add(new CheckBox(&g_Config.iHeadRotation, vr->T("Map HMD rotations on keys instead of VR camera"))); PopupSliderChoiceFloat *vrHeadRotationScale = vrSettings->Add(new PopupSliderChoiceFloat(&g_Config.fHeadRotationScale, 0.1f, 10.0f, vr->T("Game camera rotation step per frame"), 0.1f, screenManager(), "°")); vrHeadRotationScale->SetEnabledFunc([&] { return g_Config.iHeadRotation > 0; }); CheckBox *vrHeadRotationSmoothing = vrSettings->Add(new CheckBox(&g_Config.bHeadRotationSmoothing, vr->T("Game camera uses rotation smoothing"))); From 4ba200147962f37d379de283671881e6d663d2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 00:01:38 +0100 Subject: [PATCH 115/141] Replacer: Make the cache data model texture-centric instead of level-centric --- Core/TextureReplacer.cpp | 155 +++++++++++++++++++----------- Core/TextureReplacer.h | 67 +++---------- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 14 ++- 4 files changed, 121 insertions(+), 117 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 42c58460d5..2c2022a0ce 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -243,10 +243,14 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { auto hashes = ini.GetOrCreateSection("hashes")->ToMap(); // Format: hashname = filename.png bool checkFilenames = g_Config.bSaveNewTextures && !g_Config.bIgnoreTextureFilenames; + + std::map> filenameMap; + for (const auto &item : hashes) { - ReplacementAliasKey key(0, 0, 0); - if (sscanf(item.first.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &key.level) >= 1) { - aliases_[key] = item.second; + ReplacementCacheKey key(0, 0); + int level = 0; // sscanf might fail to pluck the level, but that's ok, we default to 0. + if (sscanf(item.first.c_str(), "%16llx%8x_%d", &key.cachekey, &key.hash, &level) >= 1) { + filenameMap[key][level] = item.second; if (checkFilenames) { #if PPSSPP_PLATFORM(WINDOWS) // Uppercase probably means the filenames don't match. @@ -260,11 +264,30 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { ERROR_LOG(G3D, "Unsupported syntax under [hashes]: %s", item.first.c_str()); } } + + // Now, translate the filenameMap to the final aliasMap. + for (auto &pair : filenameMap) { + std::string alias; + int mipIndex = 0; + for (auto &level : pair.second) { + if (level.first == mipIndex) { + alias += level.second + "|"; + mipIndex++; + } else { + WARN_LOG(G3D, "Non-sequential mip index %d, breaking", level.first); + break; + } + } + if (alias == "|") { + alias = ""; // marker for no replacement + } + aliases_[pair.first] = alias; + } } if (filenameWarning) { auto err = GetI18NCategory("Error"); - host->NotifyUserMessage(err->T("textures.ini filenames may not be cross-platform"), 6.0f); + host->NotifyUserMessage(err->T("textures.ini filenames may not be cross-platform (banned characters)"), 6.0f); } if (ini.HasSection("hashranges")) { @@ -475,7 +498,7 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, return result; } -void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h) { +void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey, u32 hash, int w, int h) { int newW = w; int newH = h; LookupHashRange(cachekey >> 32, newW, newH); @@ -484,30 +507,43 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, cachekey = cachekey & 0xFFFFFFFFULL; } - for (int i = 0; i < MAX_MIP_LEVELS; ++i) { - const std::string hashfile = LookupHashFile(cachekey, hash, i); - if (hashfile.empty()) { + bool foundReplacement = false; + const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement); + + if (!foundReplacement) { + // nothing to do? + return; + } + + INFO_LOG(G3D, "Found: %s", hashfiles.c_str()); + + std::vector filenames; + SplitString(hashfiles, '|', filenames); + + for (int i = 0; i < std::min(MAX_MIP_LEVELS, (int)filenames.size()); ++i) { + if (filenames[i].empty()) { // Out of valid mip levels. Bail out. break; } - const Path filename = basePath_ / hashfile; + const Path filename = basePath_ / filenames[i]; + // TODO: Here, if we find a file with multiple built-in mipmap levels, + // we'll have to change a bit how things work... ReplacedTextureLevel level; level.fmt = Draw::DataFormat::R8G8B8A8_UNORM; level.file = filename; bool good; - bool logError = hashfile != HashName(cachekey, hash, i) + ".png"; - VFSFileReference *fileRef = vfs_->GetFile(hashfile.c_str()); + VFSFileReference *fileRef = vfs_->GetFile(filenames[i].c_str()); if (!fileRef) { // If the file doesn't exist, let's just bail immediately here. break; } level.fileRef = fileRef; - good = PopulateLevel(level, !logError); + good = PopulateLevel(level, false); // We pad files that have been hashrange'd so they are the same texture size. level.w = (level.w * w) / newW; @@ -515,26 +551,22 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *result, u64 cachekey, if (good && i != 0) { // Check that the mipmap size is correct. Can't load mips of the wrong size. - if (level.w != (result->levels_[0].w >> i) || level.h != (result->levels_[0].h >> i)) { - WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, result->levels_[0].w >> i, result->levels_[0].h >> i, i, filename.c_str()); + if (level.w != (texture->levels_[0].w >> i) || level.h != (texture->levels_[0].h >> i)) { + WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, texture->levels_[0].w >> i, texture->levels_[0].h >> i, i, filename.c_str()); good = false; } } if (good) - result->levels_.push_back(level); + texture->levels_.push_back(level); // Otherwise, we're done loading mips (bad PNG or bad size, either way.) else break; } - // Populate the level data pointers for each level. - result->levelData_.resize(result->levels_.size()); - for (size_t i = 0; i < result->levels_.size(); ++i) { - result->levelData_[i] = &levelCache_[result->levels_[i]]; - } - - result->prepareDone_ = true; + // Populate the data pointer. + texture->levelData_ = &levelCache_[hashfiles]; + texture->prepareDone_ = true; } bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { @@ -607,7 +639,7 @@ static bool WriteTextureToPNG(png_imagep image, const Path &filename, int conver class TextureSaveTask : public Task { public: // Could probably just use a vector. - SimpleBuf data; + std::vector rgbaData; int w = 0; int h = 0; @@ -619,7 +651,7 @@ public: bool skipIfExists = false; - TextureSaveTask(SimpleBuf _data) : data(std::move(_data)) {} + TextureSaveTask(std::vector _rgbaData) : rgbaData(std::move(_rgbaData)) {} // This must be IO blocking because of Android storage, despite being CPU heavy. TaskType Type() const override { return TaskType::IO_BLOCKING; } @@ -661,7 +693,7 @@ public: png.format = PNG_FORMAT_RGBA; png.width = w; png.height = h; - bool success = WriteTextureToPNG(&png, saveFilename, 0, data.data(), pitch, nullptr); + bool success = WriteTextureToPNG(&png, saveFilename, 0, rgbaData.data(), pitch, nullptr); png_image_free(&png); if (png.warning_or_error >= 2) { ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors."); @@ -699,7 +731,9 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl cachekey = cachekey & 0xFFFFFFFFULL; } - std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, level); + /* + bool found = false; + std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &found); const Path filename = basePath_ / hashfile; // If it's empty, it's an ignored hash, we intentionally don't save. @@ -732,11 +766,11 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl h = lookupH * replacedInfo.scaleFactor; } - SimpleBuf saveBuf; + std::vector saveBuf; // Copy data to a buffer so we can send it to the thread. Might as well compact-away the pitch // while we're at it. - saveBuf.resize(w * h); + saveBuf.resize(w * h * 4); for (int y = 0; y < h; y++) { memcpy((u8 *)saveBuf.data() + y * w * 4, (const u8 *)data + y * pitch, w * sizeof(u32)); } @@ -761,6 +795,7 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl saved.w = w; saved.h = h; savedCache_[replacementKey] = std::make_pair(saved, now); + */ } void TextureReplacer::Decimate(ReplacerDecimateMode mode) { @@ -860,15 +895,16 @@ bool TextureReplacer::FindFiltering(u64 cachekey, u32 hash, TextureFiltering *fo return false; } -std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, int level) { - ReplacementAliasKey key(cachekey, hash, level); +std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement) { + ReplacementCacheKey key(cachekey, hash); auto alias = LookupWildcard(aliases_, key, cachekey, hash, ignoreAddress_); if (alias != aliases_.end()) { // Note: this will be blank if explicitly ignored. + *foundReplacement = alias->second.size() > 0; return alias->second; } - - return HashName(cachekey, hash, level) + ".png"; + *foundReplacement = false; + return ""; } std::string TextureReplacer::HashName(u64 cachekey, u32 hash, int level) { @@ -938,11 +974,12 @@ bool ReplacedTexture::IsReady(double budget) { } // Loaded already, or not yet on a thread? - if (initDone_ && !levelData_.empty()) { - for (auto &l : levelData_) - l->lastUsed = lastUsed_; + if (initDone_ && levelData_ && !levelData_->data.empty()) { + // TODO: lock? + levelData_->lastUsed = lastUsed_; return true; } + // Let's not even start a new texture if we're already behind. if (budget < 0.0) return false; @@ -957,7 +994,7 @@ bool ReplacedTexture::IsReady(double budget) { if (threadWaitable_->WaitFor(budget)) { // If we finished all the levels, we're done. - return initDone_ && !levelData_.empty(); + return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); } } else { Prepare(vfs_); @@ -990,13 +1027,18 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) { void ReplacedTexture::PrepareData(int level) { _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); - _assert_msg_(levelData_[level] != nullptr, "Level cache not set for miplevel"); + _assert_msg_(levelData_ != nullptr, "Level cache not set"); // We must lock around access to levelData_ in case two textures try to load it at once. - std::lock_guard guard(levelData_[level]->lock); + std::lock_guard guard(levelData_->lock); const ReplacedTextureLevel &info = levels_[level]; - std::vector &out = levelData_[level]->data; + + if (levelData_->data.size() <= level) { + levelData_->data.resize(level + 1); + } + + std::vector &out = levelData_->data[level]; // Already populated from cache. if (!out.empty()) @@ -1107,14 +1149,12 @@ void ReplacedTexture::PurgeIfOlder(double t) { if (lastUsed_ >= t) return; - for (auto &l : levelData_) { - if (l->lastUsed < t) { - // We have to lock since multiple textures might reference this same data. - std::lock_guard guard(l->lock); - l->data.clear(); - // This means we have to reload. If we never purge any, there's no need. - initDone_ = false; - } + if (levelData_->lastUsed < t) { + // We have to lock since multiple textures might reference this same data. + std::lock_guard guard(levelData_->lock); + levelData_->data.clear(); + // This means we have to reload. If we never purge any, there's no need. + initDone_ = false; } } @@ -1128,28 +1168,28 @@ ReplacedTexture::~ReplacedTexture() { } } -bool ReplacedTexture::Load(int level, void *out, int rowPitch) { +bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); - if (!initDone_) + if (!initDone_) { + WARN_LOG(G3D, "Init not done yet"); return false; - if (levelData_.empty()) - return false; - - _assert_msg_(levelData_[level] != nullptr, "Level cache not set for miplevel"); + } // We probably could avoid this lock, but better to play it safe. - std::lock_guard guard(levelData_[level]->lock); + std::lock_guard guard(levelData_->lock); const ReplacedTextureLevel &info = levels_[level]; - const std::vector &data = levelData_[level]->data; + const std::vector &data = levelData_->data[level]; - if (data.empty()) + if (data.empty()) { + WARN_LOG(G3D, "Level %d is empty", level); return false; + } if (rowPitch < info.w * 4) { - ERROR_LOG_REPORT(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); + ERROR_LOG(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); return false; } _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); @@ -1165,6 +1205,7 @@ bool ReplacedTexture::Load(int level, void *out, int rowPitch) { }, 0, info.h, MIN_LINES_PER_THREAD); } + INFO_LOG(G3D, "Successfully copied texture level"); return true; } diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 0b4633e05f..880b3d1d7e 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -46,13 +46,14 @@ enum class ReplacedTextureAlpha { FULL = 0x00, }; -// For forward comatibility, we specify the hash. +// For forward compatibility, we specify the hash. enum class ReplacedTextureHash { QUICK, XXH32, XXH64, }; +// Metadata about a given texture level. struct ReplacedTextureLevel { ReplacedTextureLevel() {} int w = 0; @@ -69,25 +70,9 @@ struct ReplacedTextureLevel { } }; -namespace std { - template <> - struct hash { - std::size_t operator()(const ReplacedTextureLevel &k) const { -#if PPSSPP_ARCH(64BIT) - uint64_t v = (uint64_t)k.w | ((uint64_t)k.h << 32); - v = __rotl64(v ^ (uint64_t)k.fmt, 13); -#else - uint32_t v = k.w ^ (uint32_t)k.fmt; - v = __rotl(__rotl(v, 13) ^ k.h, 13); -#endif - return v ^ hash()(k.file.ToString()); - } - }; -} - -struct ReplacedLevelCache { +struct ReplacedLevelsCache { std::mutex lock; - std::vector data; + std::vector> data; double lastUsed = 0.0; }; @@ -109,30 +94,6 @@ struct ReplacementCacheKey { } }; -struct ReplacementAliasKey { - u64 cachekey; - union { - u64 hashAndLevel; - struct { - u32 level; - u32 hash; - }; - }; - - ReplacementAliasKey(u64 c, u32 h, u32 l) : cachekey(c), level(l), hash(h) { } - - bool operator ==(const ReplacementAliasKey &k) const { - return k.cachekey == cachekey && k.hashAndLevel == hashAndLevel; - } - - bool operator <(const ReplacementAliasKey &k) const { - if (k.cachekey == cachekey) { - return k.hashAndLevel < hashAndLevel; - } - return k.cachekey < cachekey; - } -}; - namespace std { template <> struct hash { @@ -140,13 +101,6 @@ namespace std { return std::hash()(k.cachekey ^ ((u64)k.hash << 32)); } }; - - template <> - struct hash { - size_t operator()(const ReplacementAliasKey &k) const { - return std::hash()(k.cachekey ^ k.hashAndLevel); - } - }; } struct ReplacedTexture { @@ -196,7 +150,7 @@ struct ReplacedTexture { bool IsReady(double budget); - bool Load(int level, void *out, int rowPitch); + bool CopyLevelTo(int level, void *out, int rowPitch); protected: void Prepare(VFSBackend *vfs); @@ -204,7 +158,8 @@ protected: void PurgeIfOlder(double t); std::vector levels_; - std::vector levelData_; + ReplacedLevelsCache *levelData_; + ReplacedTextureAlpha alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; double lastUsed_ = 0.0; LimitedWaitable *threadWaitable_ = nullptr; @@ -273,7 +228,7 @@ protected: void ParseReduceHashRange(const std::string& key, const std::string& value); bool LookupHashRange(u32 addr, int &w, int &h); float LookupReduceHashRange(int& w, int& h); - std::string LookupHashFile(u64 cachekey, u32 hash, int level); + std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement); std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); @@ -297,10 +252,12 @@ protected: typedef std::pair WidthHeightPair; std::unordered_map hashranges_; std::unordered_map reducehashranges_; - std::unordered_map aliases_; + std::unordered_map aliases_; std::unordered_map filtering_; std::unordered_map cache_; std::unordered_map> savedCache_; - std::unordered_map levelCache_; + + // the key is from aliases_. It's a |-separated sequence of texture filenames of the levels of a texture. + std::unordered_map levelCache_; }; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 7a35252f36..9fe735487c 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2854,7 +2854,7 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i if (plan.replaceValid && plan.replaced->GetSize(srcLevel, w, h)) { double replaceStart = time_now_d(); - plan.replaced->Load(srcLevel, data, stride); + plan.replaced->CopyLevelTo(srcLevel, data, stride); replacementTimeThisFrame_ += time_now_d() - replaceStart; } else { GETextureFormat tfmt = (GETextureFormat)entry.format; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 6c898b5579..83bd9bc520 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -532,6 +532,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { levels = plan.levelsToLoad; } + VulkanPushBuffer *pushBuffer = drawEngine_->GetPushBufferForTextureData(); + for (int i = 0; i < levels; i++) { int mipUnscaledWidth = gstate.getTextureWidth(i); int mipUnscaledHeight = gstate.getTextureHeight(i); @@ -553,24 +555,28 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { void *data; std::vector saveData; + // Simple wrapper to avoid reading back from VRAM (very, very expensive). auto loadLevel = [&](int sz, int srcLevel, int lstride, int lfactor) { if (plan.saveTexture) { saveData.resize(sz); data = &saveData[0]; } else { - data = drawEngine_->GetPushBufferForTextureData()->PushAligned(sz, &bufferOffset, &texBuf, pushAlignment); + data = pushBuffer->PushAligned(sz, &bufferOffset, &texBuf, pushAlignment); } LoadVulkanTextureLevel(*entry, (uint8_t *)data, lstride, srcLevel, lfactor, actualFmt); if (plan.saveTexture) - bufferOffset = drawEngine_->GetPushBufferForTextureData()->PushAligned(&saveData[0], sz, pushAlignment, &texBuf); + bufferOffset = pushBuffer->PushAligned(&saveData[0], sz, pushAlignment, &texBuf); }; bool dataScaled = true; if (plan.replaceValid) { // Directly load the replaced image. - data = drawEngine_->GetPushBufferForTextureData()->PushAligned(uploadSize, &bufferOffset, &texBuf, pushAlignment); + data = pushBuffer->PushAligned(uploadSize, &bufferOffset, &texBuf, pushAlignment); double replaceStart = time_now_d(); - plan.replaced->Load(plan.baseLevelSrc + i, data, byteStride); // if it fails, it'll just be garbage data... OK for now. + if (!plan.replaced->CopyLevelTo(plan.baseLevelSrc + i, data, byteStride)) { // If plan.replaceValid, this shouldn't fail. + WARN_LOG(G3D, "Failed to copy replaced texture level"); + // TODO: Fill with some pattern? + } replacementTimeThisFrame_ += time_now_d() - replaceStart; VK_PROFILE_BEGIN(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT, "Copy Upload (replaced): %dx%d", mipWidth, mipHeight); From bda09be1094b8e66abae956f9e6a50e0c5fc1849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 00:05:24 +0100 Subject: [PATCH 116/141] Texture color format doesn't belong as a per-level property, that's not supported. All levels of a texture must have the same format. --- Core/TextureReplacer.cpp | 11 ++++++++--- Core/TextureReplacer.h | 28 ++++++++++++---------------- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/D3D11/TextureCacheD3D11.cpp | 4 ++-- GPU/Directx9/TextureCacheDX9.cpp | 2 +- GPU/GLES/TextureCacheGLES.cpp | 4 ++-- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 2c2022a0ce..e15df31edd 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -87,7 +87,9 @@ static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::s return IdentifyMagic(magic); } -TextureReplacer::TextureReplacer() {} +TextureReplacer::TextureReplacer(Draw::DrawContext *draw) { + // TODO: Check draw for supported texture formats. +} TextureReplacer::~TextureReplacer() { for (auto &iter : cache_) { @@ -274,7 +276,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { alias += level.second + "|"; mipIndex++; } else { - WARN_LOG(G3D, "Non-sequential mip index %d, breaking", level.first); + WARN_LOG(G3D, "Non-sequential mip index %d, breaking. filenames=%s", level.first, level.second.c_str()); break; } } @@ -531,9 +533,12 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey // TODO: Here, if we find a file with multiple built-in mipmap levels, // we'll have to change a bit how things work... ReplacedTextureLevel level; - level.fmt = Draw::DataFormat::R8G8B8A8_UNORM; level.file = filename; + if (i == 0) { + texture->fmt = Draw::DataFormat::R8G8B8A8_UNORM; + } + bool good; VFSFileReference *fileRef = vfs_->GetFile(filenames[i].c_str()); diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 880b3d1d7e..efcd4269ae 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -55,19 +55,13 @@ enum class ReplacedTextureHash { // Metadata about a given texture level. struct ReplacedTextureLevel { - ReplacedTextureLevel() {} int w = 0; int h = 0; - Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. Path file; - // Can be ignored for hashing/equal, since file has all uniqueness. + // To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t. + // TODO: This really belongs on the level in the cache, not in the individual ReplacedTextureLevel objects. VFSFileReference *fileRef = nullptr; - bool operator ==(const ReplacedTextureLevel &other) const { - if (w != other.w || h != other.h || fmt != other.fmt) - return false; - return file == other.file; - } }; struct ReplacedLevelsCache { @@ -135,13 +129,13 @@ struct ReplacedTexture { return (int)levels_.size(); } - Draw::DataFormat Format(int level) const { + Draw::DataFormat Format() const { if (initDone_) { - if ((size_t)level < levels_.size()) { - return levels_[level].fmt; - } + return fmt; + } else { + // Shouldn't get here. + return Draw::DataFormat::UNDEFINED; } - return Draw::DataFormat::R8G8B8A8_UNORM; } u8 AlphaStatus() const { @@ -164,6 +158,7 @@ protected: double lastUsed_ = 0.0; LimitedWaitable *threadWaitable_ = nullptr; std::mutex mutex_; + Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. bool cancelPrepare_ = false; bool initDone_ = false; @@ -171,8 +166,8 @@ protected: VFSBackend *vfs_ = nullptr; - friend TextureReplacer; - friend ReplacedTextureTask; + friend class TextureReplacer; + friend class ReplacedTextureTask; }; struct ReplacedTextureDecodeInfo { @@ -193,7 +188,8 @@ enum class ReplacerDecimateMode { class TextureReplacer { public: - TextureReplacer(); + // The draw context will be checked for supported texture formats. + TextureReplacer(Draw::DrawContext *draw); ~TextureReplacer(); void Init(); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 9fe735487c..745fd29a3e 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -107,7 +107,7 @@ inline int dimHeight(u16 dim) { } TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw, Draw2D *draw2D) - : draw_(draw), draw2D_(draw2D) { + : draw_(draw), draw2D_(draw2D), replacer_(draw) { decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL; // It's only possible to have 1KB of palette entries, although we allow 2KB in a hack. diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 047a3504f9..b551e573df 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -263,7 +263,7 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { DXGI_FORMAT dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); if (plan.replaceValid) { - dstFmt = ToDXGIFormat(plan.replaced->Format(plan.baseLevelSrc)); + dstFmt = ToDXGIFormat(plan.replaced->Format()); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = DXGI_FORMAT_B8G8R8A8_UNORM; } else if (plan.decodeToClut8) { @@ -339,7 +339,7 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { // For UpdateSubresource, we can't decode directly into the texture so we allocate a buffer :( // NOTE: Could reuse it between levels or textures! if (plan.replaceValid) { - bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format(srcLevel)); + bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format()); } else { if (plan.scaleFactor > 1) { bpp = 4; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 4e349fbe73..dc1ed423d6 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -235,7 +235,7 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { D3DFORMAT dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); if (plan.replaceValid) { - dstFmt = ToD3D9Format(plan.replaced->Format(plan.baseLevelSrc)); + dstFmt = ToD3D9Format(plan.replaced->Format()); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = D3DFMT_A8R8G8B8; } else if (plan.decodeToClut8) { diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index aacba8e3d6..127b6d7699 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -247,7 +247,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); if (plan.replaced->GetSize(plan.baseLevelSrc, tw, th)) { - dstFmt = plan.replaced->Format(plan.baseLevelSrc); + dstFmt = plan.replaced->Format(); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = Draw::DataFormat::R8G8B8A8_UNORM; } else if (plan.decodeToClut8) { @@ -294,7 +294,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { int bpp; if (plan.replaceValid) { - bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format(srcLevel)); + bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format()); } else { if (plan.scaleFactor > 1) { bpp = 4; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 83bd9bc520..16eeb41a98 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -443,7 +443,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Any texture scaling is gonna move away from the original 16-bit format, if any. VkFormat actualFmt = plan.scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt; if (plan.replaceValid) { - actualFmt = ToVulkanFormat(plan.replaced->Format(plan.baseLevelSrc)); + actualFmt = ToVulkanFormat(plan.replaced->Format()); } bool computeUpload = false; From 4752e20ad42dff8afd2015a7a74fca42da680943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 00:24:30 +0100 Subject: [PATCH 117/141] Don't reuse the ReplacedTexture struct in "saved cache". --- Core/TextureReplacer.cpp | 18 ++++++++---------- Core/TextureReplacer.h | 9 ++++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index e15df31edd..22ce25ebb1 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -736,7 +736,6 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl cachekey = cachekey & 0xFFFFFFFFULL; } - /* bool found = false; std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &found); const Path filename = basePath_ / hashfile; @@ -753,9 +752,10 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl double now = time_now_d(); if (it != savedCache_.end()) { // We've already saved this texture. Let's only save if it's bigger (e.g. scaled now.) - if (it->second.first.w >= w && it->second.first.h >= h) { + // TODO: Isn't this check backwards? + if (it->second.levelW[level] >= w && it->second.levelH[level] >= h) { // If it's been more than 5 seconds, we'll check again. Maybe they deleted. - double age = now - it->second.second; + double age = now - it->second.lastTimeSaved; if (age < 5.0) return; @@ -794,13 +794,11 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl // Remember that we've saved this for next time. // Should be OK that the actual disk write may not be finished yet. - ReplacedTextureLevel saved; - saved.fmt = Draw::DataFormat::R8G8B8A8_UNORM; - saved.file = filename; - saved.w = w; - saved.h = h; - savedCache_[replacementKey] = std::make_pair(saved, now); - */ + SavedTextureCacheData &saveData = savedCache_[replacementKey]; + saveData.levelW[level] = w; + saveData.levelH[level] = h; + saveData.levelSaved[level] = true; + saveData.lastTimeSaved = now; } void TextureReplacer::Decimate(ReplacerDecimateMode mode) { diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index efcd4269ae..7d6eafefcb 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -64,6 +64,13 @@ struct ReplacedTextureLevel { VFSFileReference *fileRef = nullptr; }; +struct SavedTextureCacheData { + int levelW[8]{}; + int levelH[8]{}; + bool levelSaved[8]{}; + double lastTimeSaved; +}; + struct ReplacedLevelsCache { std::mutex lock; std::vector> data; @@ -252,7 +259,7 @@ protected: std::unordered_map filtering_; std::unordered_map cache_; - std::unordered_map> savedCache_; + std::unordered_map savedCache_; // the key is from aliases_. It's a |-separated sequence of texture filenames of the levels of a texture. std::unordered_map levelCache_; From ef14f28099756bf4bf34ca9d9f1d3cbd76e6ea64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 00:26:45 +0100 Subject: [PATCH 118/141] Fix logspam --- Core/TextureReplacer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 22ce25ebb1..31fd7edef0 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -517,7 +517,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey return; } - INFO_LOG(G3D, "Found: %s", hashfiles.c_str()); + // INFO_LOG(G3D, "Found: %s", hashfiles.c_str()); std::vector filenames; SplitString(hashfiles, '|', filenames); @@ -1208,7 +1208,6 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { }, 0, info.h, MIN_LINES_PER_THREAD); } - INFO_LOG(G3D, "Successfully copied texture level"); return true; } From 03df5b78311ddd0744d779dbd8a64968bbadbefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 00:33:37 +0100 Subject: [PATCH 119/141] Fix fileref memory leak --- Core/TextureReplacer.cpp | 5 +++++ Core/TextureReplacer.h | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 31fd7edef0..c1d02a2792 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -1169,6 +1169,11 @@ ReplacedTexture::~ReplacedTexture() { threadWaitable_->WaitAndRelease(); threadWaitable_ = nullptr; } + + for (auto &level : levels_) { + vfs_->ReleaseFile(level.fileRef); + level.fileRef = nullptr; + } } bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index 7d6eafefcb..fec6a69a4c 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -150,7 +150,6 @@ struct ReplacedTexture { } bool IsReady(double budget); - bool CopyLevelTo(int level, void *out, int rowPitch); protected: From 092bbf5eaaa2ded689a7df490c1f18978d466bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 10:51:15 +0100 Subject: [PATCH 120/141] Fix saving of textures --- Core/TextureReplacer.cpp | 52 ++++++++++++++++++------------- Core/TextureReplacer.h | 4 +-- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index c1d02a2792..6fd0205f71 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -244,7 +244,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { if (ini.HasSection("hashes")) { auto hashes = ini.GetOrCreateSection("hashes")->ToMap(); // Format: hashname = filename.png - bool checkFilenames = g_Config.bSaveNewTextures && !g_Config.bIgnoreTextureFilenames; + bool checkFilenames = g_Config.bSaveNewTextures && !g_Config.bIgnoreTextureFilenames && !vfsIsZip_; std::map> filenameMap; @@ -257,7 +257,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { #if PPSSPP_PLATFORM(WINDOWS) // Uppercase probably means the filenames don't match. // Avoiding an actual check of the filenames to avoid performance impact. - filenameWarning = filenameWarning || item.second.find_first_of("\\ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos; + filenameWarning = filenameWarning || item.second.find_first_of("\\ABCDEFGHIJKLMNOPQRSTUVWXYZ:<>|?*") != std::string::npos; #else filenameWarning = filenameWarning || item.second.find_first_of("\\:<>|?*") != std::string::npos; #endif @@ -510,9 +510,10 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey } bool foundReplacement = false; - const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement); + bool ignored = false; + const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement, &ignored); - if (!foundReplacement) { + if (!foundReplacement || ignored) { // nothing to do? return; } @@ -641,9 +642,10 @@ static bool WriteTextureToPNG(png_imagep image, const Path &filename, int conver } } -class TextureSaveTask : public Task { +// We save textures on threadpool tasks since it's a fire-and-forget task, and both I/O and png compression +// can be pretty slow. +class SaveTextureTask : public Task { public: - // Could probably just use a vector. std::vector rgbaData; int w = 0; @@ -652,13 +654,13 @@ public: Path basePath; std::string hashfile; - u32 replacedInfoHash; + u32 replacedInfoHash = 0; bool skipIfExists = false; - TextureSaveTask(std::vector _rgbaData) : rgbaData(std::move(_rgbaData)) {} + SaveTextureTask(std::vector &&_rgbaData) : rgbaData(std::move(_rgbaData)) {} - // This must be IO blocking because of Android storage, despite being CPU heavy. + // This must be set to I/O blocking because of Android storage (so we attach the thread to JNI), while being CPU heavy too. TaskType Type() const override { return TaskType::IO_BLOCKING; } TaskPriority Priority() const override { @@ -670,9 +672,9 @@ public: const Path saveFilename = basePath / NEW_TEXTURE_DIR / hashfile; // Should we skip writing if the newly saved data already exists? - // We do this on the thread due to slow IO. - if (skipIfExists && File::Exists(saveFilename)) + if (skipIfExists && File::Exists(saveFilename)) { return; + } // And we always skip if the replace file already exists. if (File::Exists(filename)) @@ -701,9 +703,11 @@ public: bool success = WriteTextureToPNG(&png, saveFilename, 0, rgbaData.data(), pitch, nullptr); png_image_free(&png); if (png.warning_or_error >= 2) { - ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors."); + ERROR_LOG(G3D, "Saving screenshot to PNG produced errors."); } else if (success) { NOTICE_LOG(G3D, "Saving texture for replacement: %08x / %dx%d in '%s'", replacedInfoHash, w, h, saveFilename.ToVisualString().c_str()); + } else { + ERROR_LOG(G3D, "Failed to write '%s'", saveFilename.c_str()); } } }; @@ -736,29 +740,32 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl cachekey = cachekey & 0xFFFFFFFFULL; } - bool found = false; - std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &found); - const Path filename = basePath_ / hashfile; + bool found = false, ignored = false; + std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &found, &ignored); // If it's empty, it's an ignored hash, we intentionally don't save. - if (hashfile.empty()) { + if (found) { // If it exists, must've been decoded and saved as a new texture already. return; } + // Generate a new filename. + hashfile = HashName(cachekey, replacedInfo.hash, level) + ".png"; + + const Path filename = basePath_ / hashfile; + ReplacementCacheKey replacementKey(cachekey, replacedInfo.hash); auto it = savedCache_.find(replacementKey); bool skipIfExists = false; double now = time_now_d(); if (it != savedCache_.end()) { // We've already saved this texture. Let's only save if it's bigger (e.g. scaled now.) - // TODO: Isn't this check backwards? + // This check isn't backwards, it's just to check if we should *skip* saving, a bit confusing. if (it->second.levelW[level] >= w && it->second.levelH[level] >= h) { // If it's been more than 5 seconds, we'll check again. Maybe they deleted. double age = now - it->second.lastTimeSaved; if (age < 5.0) return; - skipIfExists = true; } } @@ -781,8 +788,7 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl } pitch = w * 4; - TextureSaveTask *task = new TextureSaveTask(std::move(saveBuf)); - // Should probably do a proper move constructor but this'll work. + SaveTextureTask *task = new SaveTextureTask(std::move(saveBuf)); task->w = w; task->h = h; task->pitch = pitch; @@ -898,15 +904,17 @@ bool TextureReplacer::FindFiltering(u64 cachekey, u32 hash, TextureFiltering *fo return false; } -std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement) { +std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement, bool *ignored) { ReplacementCacheKey key(cachekey, hash); auto alias = LookupWildcard(aliases_, key, cachekey, hash, ignoreAddress_); if (alias != aliases_.end()) { // Note: this will be blank if explicitly ignored. - *foundReplacement = alias->second.size() > 0; + *foundReplacement = true; + *ignored = alias->second.empty(); return alias->second; } *foundReplacement = false; + *ignored = false; return ""; } diff --git a/Core/TextureReplacer.h b/Core/TextureReplacer.h index fec6a69a4c..a2faa7a763 100644 --- a/Core/TextureReplacer.h +++ b/Core/TextureReplacer.h @@ -68,7 +68,7 @@ struct SavedTextureCacheData { int levelW[8]{}; int levelH[8]{}; bool levelSaved[8]{}; - double lastTimeSaved; + double lastTimeSaved = 0.0; }; struct ReplacedLevelsCache { @@ -230,7 +230,7 @@ protected: void ParseReduceHashRange(const std::string& key, const std::string& value); bool LookupHashRange(u32 addr, int &w, int &h); float LookupReduceHashRange(int& w, int& h); - std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement); + std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement, bool *ignored); std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 745fd29a3e..48a4a4e14c 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2901,7 +2901,7 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i } } - if (replacer_.Enabled() && plan.replaced->IsInvalid()) { + if (plan.saveTexture && !lowMemoryMode_) { ReplacedTextureDecodeInfo replacedInfo; replacedInfo.cachekey = entry.CacheKey(); replacedInfo.hash = entry.fullhash; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 16eeb41a98..2ec829f511 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -614,7 +614,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { VK_PROFILE_END(vulkan, cmdInit, VK_PIPELINE_STAGE_TRANSFER_BIT); } // Format might be wrong in lowMemoryMode_, so don't save. - if (replacer_.Enabled() && plan.replaced->IsInvalid() && !lowMemoryMode_) { + if (plan.saveTexture && !lowMemoryMode_) { // When hardware texture scaling is enabled, this saves the original. int w = dataScaled ? mipWidth : mipUnscaledWidth; int h = dataScaled ? mipHeight : mipUnscaledHeight; From b1f41c44fedfdfe6dfe01c39b663520d9a8521d0 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 9 Mar 2023 11:22:54 +0100 Subject: [PATCH 121/141] OpenXR - Vertical HMD rotation to keys cleanup --- Common/VR/PPSSPPVR.cpp | 24 ++---------------------- Core/Config.cpp | 2 +- Core/Config.h | 2 +- UI/GameSettingsScreen.cpp | 6 +++--- 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 20a54ab678..e9d40b3cbc 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -315,7 +315,7 @@ void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale) { } // Head control - if (g_Config.iHeadRotation) { + if (g_Config.bHeadRotationEnabled) { float pitch = -VR_GetHMDAngles().x; float yaw = -VR_GetHMDAngles().y; bool disable = vrFlatForced || appMode == VR_MENU_MODE; @@ -342,25 +342,6 @@ void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale) { float limit = isVR ? g_Config.fHeadRotationScale : 20; keyInput.deviceId = DEVICE_ID_XR_HMD; - // vertical rotations - if (g_Config.iHeadRotation == 2) { - //up - activate = !disable && pitch > limit; - keyInput.flags = activate ? KEY_DOWN : KEY_UP; - keyInput.keyCode = NKCODE_EXT_ROTATION_UP; - if (hmdMotion[0] != activate) NativeKey(keyInput); - if (isVR && activate) hmdMotionDiff[0] -= limit; - hmdMotion[0] = activate; - - //down - activate = !disable && pitch < -limit; - keyInput.flags = activate ? KEY_DOWN : KEY_UP; - keyInput.keyCode = NKCODE_EXT_ROTATION_DOWN; - if (hmdMotion[1] != activate) NativeKey(keyInput); - if (isVR && activate) hmdMotionDiff[0] += limit; - hmdMotion[1] = activate; - } - //left activate = !disable && yaw < -limit; keyInput.flags = activate ? KEY_DOWN : KEY_UP; @@ -690,8 +671,7 @@ bool StartVRRender() { float mRoll = mz * ToRadians(rotation.z); // use in-game camera interpolated rotation - if (g_Config.iHeadRotation >= 2) mPitch = -mx * ToRadians(hmdMotionDiffLast[0]); // vertical - if (g_Config.iHeadRotation >= 1) mYaw = -my * ToRadians(hmdMotionDiffLast[1]); // horizontal + if (g_Config.bHeadRotationEnabled) mYaw = -my * ToRadians(hmdMotionDiffLast[1]); // horizontal // create updated quaternion XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mPitch); diff --git a/Core/Config.cpp b/Core/Config.cpp index 3b23a7791f..da0b1aa0d1 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1153,8 +1153,8 @@ static const ConfigSetting vrSettings[] = { ConfigSetting("VRHeadUpDisplayScale", &g_Config.fHeadUpDisplayScale, 0.3f), ConfigSetting("VRMotionLength", &g_Config.fMotionLength, 0.5f), ConfigSetting("VRHeadRotationScale", &g_Config.fHeadRotationScale, 5.0f), + ConfigSetting("VRHeadRotationEnabled", &g_Config.bHeadRotationEnabled, false), ConfigSetting("VRHeadRotationSmoothing", &g_Config.bHeadRotationSmoothing, false), - ConfigSetting("VRHeadRotation", &g_Config.iHeadRotation, false), }; struct ConfigSectionSettings { diff --git a/Core/Config.h b/Core/Config.h index aa57a2d3c7..f5f902c5f1 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -471,8 +471,8 @@ public: float fHeadUpDisplayScale; float fMotionLength; float fHeadRotationScale; + bool bHeadRotationEnabled; bool bHeadRotationSmoothing; - bool iHeadRotation; // Debugger int iDisasmWindowX; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 0e902ad9a2..abf2e2034f 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1143,11 +1143,11 @@ void GameSettingsScreen::CreateVRSettings(UI::ViewGroup *vrSettings) { vrSettings->Add(new ItemHeader(vr->T("Experts only"))); vrSettings->Add(new CheckBox(&g_Config.bManualForceVR, vr->T("Manual switching between flat screen and VR using SCREEN key"))); - vrSettings->Add(new CheckBox(&g_Config.iHeadRotation, vr->T("Map HMD rotations on keys instead of VR camera"))); + vrSettings->Add(new CheckBox(&g_Config.bHeadRotationEnabled, vr->T("Map HMD rotations on keys instead of VR camera"))); PopupSliderChoiceFloat *vrHeadRotationScale = vrSettings->Add(new PopupSliderChoiceFloat(&g_Config.fHeadRotationScale, 0.1f, 10.0f, vr->T("Game camera rotation step per frame"), 0.1f, screenManager(), "°")); - vrHeadRotationScale->SetEnabledFunc([&] { return g_Config.iHeadRotation > 0; }); + vrHeadRotationScale->SetEnabledPtr(&g_Config.bHeadRotationEnabled); CheckBox *vrHeadRotationSmoothing = vrSettings->Add(new CheckBox(&g_Config.bHeadRotationSmoothing, vr->T("Game camera uses rotation smoothing"))); - vrHeadRotationSmoothing->SetEnabledFunc([&] { return g_Config.iHeadRotation > 0; }); + vrHeadRotationSmoothing->SetEnabledPtr(&g_Config.bHeadRotationEnabled); vrSettings->Add(new CheckBox(&g_Config.bEnableMotions, vr->T("Map controller movements to keys"))); PopupSliderChoiceFloat *vrMotions = vrSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMotionLength, 0.3f, 1.0f, vr->T("Motion needed to generate action"), 0.1f, screenManager(), vr->T("m"))); vrMotions->SetEnabledPtr(&g_Config.bEnableMotions); From 44c4e637538376f1012409d2a5ce0f6cfabe3e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 14:43:59 +0100 Subject: [PATCH 122/141] Simplifies and speeds up D3D11 texture uploads. Also, fixes a bug with replacement off --- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/D3D11/TextureCacheD3D11.cpp | 104 ++++++++++++++++++------------ 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 48a4a4e14c..f89bf64898 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2784,7 +2784,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt if (canReplace) { plan.replaced = FindReplacement(entry, plan.w, plan.h, plan.depth); - plan.replaceValid = plan.replaced->Valid(); + plan.replaceValid = plan.replaced ? plan.replaced->Valid() : plan.replaced; } else { plan.replaced = nullptr; plan.replaceValid = false; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index b551e573df..90d1927898 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -284,45 +284,16 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { if (th > 16384) th = 16384; + // The PSP only supports 8 mip levels, but we support 12 in the texture replacer (4k textures down to 1). + D3D11_SUBRESOURCE_DATA subresData[12]{}; + if (plan.depth == 1) { // We don't yet have mip generation, so clamp the number of levels to the ones we can load directly. levels = std::min(plan.levelsToCreate, plan.levelsToLoad); - - ID3D11Texture2D *tex = nullptr; - D3D11_TEXTURE2D_DESC desc{}; - desc.CPUAccessFlags = 0; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - desc.Width = tw; - desc.Height = th; - desc.Format = dstFmt; - desc.MipLevels = levels; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - ASSERT_SUCCESS(device_->CreateTexture2D(&desc, nullptr, &tex)); - texture = tex; } else { - ID3D11Texture3D *tex = nullptr; - D3D11_TEXTURE3D_DESC desc{}; - desc.CPUAccessFlags = 0; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.Width = tw; - desc.Height = th; - desc.Depth = plan.depth; - desc.Format = dstFmt; - desc.MipLevels = 1; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - ASSERT_SUCCESS(device_->CreateTexture3D(&desc, nullptr, &tex)); - texture = tex; - levels = plan.depth; } - ASSERT_SUCCESS(device_->CreateShaderResourceView(texture, nullptr, &view)); - entry->texturePtr = texture; - entry->textureView = view; - Draw::DataFormat texFmt = FromD3D11Format(dstFmt); for (int i = 0; i < levels; i++) { @@ -349,7 +320,20 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { } stride = std::max(mipWidth * bpp, 16); - data = (u8 *)AllocateAlignedMemory(stride * mipHeight, 16); + + if (plan.depth == 1) { + data = (u8 *)AllocateAlignedMemory(stride * mipHeight, 16); + subresData[i].pSysMem = data; + subresData[i].SysMemPitch = stride; + subresData[i].SysMemSlicePitch = 0; + } else { + if (i == 0) { + subresData[0].pSysMem = AllocateAlignedMemory(stride * mipHeight * plan.depth, 16); + subresData[0].SysMemPitch = stride; + subresData[0].SysMemSlicePitch = stride * mipHeight; + } + data = (uint8_t *)subresData[0].pSysMem + stride * mipHeight * i; + } if (!data) { ERROR_LOG(G3D, "Ran out of RAM trying to allocate a temporary texture upload buffer (%dx%d)", mipWidth, mipHeight); @@ -357,17 +341,51 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { } LoadTextureLevel(*entry, data, stride, plan, srcLevel, texFmt, TexDecodeFlags{}); - if (plan.depth == 1) { - context_->UpdateSubresource(texture, i, nullptr, data, stride, 0); - } else { - D3D11_BOX box{}; - box.front = i; - box.back = i + 1; - box.right = mipWidth; - box.bottom = mipHeight; - context_->UpdateSubresource(texture, 0, &box, data, stride, 0); + } + + if (plan.depth == 1) { + // We don't yet have mip generation, so clamp the number of levels to the ones we can load directly. + levels = std::min(plan.levelsToCreate, plan.levelsToLoad); + + ID3D11Texture2D *tex = nullptr; + D3D11_TEXTURE2D_DESC desc{}; + desc.CPUAccessFlags = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.Width = tw; + desc.Height = th; + desc.Format = dstFmt; + desc.MipLevels = levels; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + ASSERT_SUCCESS(device_->CreateTexture2D(&desc, subresData, &tex)); + texture = tex; + } else { + ID3D11Texture3D *tex = nullptr; + D3D11_TEXTURE3D_DESC desc{}; + desc.CPUAccessFlags = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.Width = tw; + desc.Height = th; + desc.Depth = plan.depth; + desc.Format = dstFmt; + desc.MipLevels = 1; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + ASSERT_SUCCESS(device_->CreateTexture3D(&desc, subresData, &tex)); + texture = tex; + + levels = plan.depth; + } + + ASSERT_SUCCESS(device_->CreateShaderResourceView(texture, nullptr, &view)); + entry->texturePtr = texture; + entry->textureView = view; + + for (int i = 0; i < 12; i++) { + if (subresData[i].pSysMem) { + FreeAlignedMemory((void *)subresData[i].pSysMem); } - FreeAlignedMemory(data); } // Signal that we support depth textures so use it as one. From b253225815b1aee417d919f41039810c55a60b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 14:57:38 +0100 Subject: [PATCH 123/141] Fix another replacer lifecycle issue, causing unnecessary texture invalidation. --- Core/TextureReplacer.cpp | 1 + GPU/Common/TextureCacheCommon.cpp | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 6fd0205f71..98de573b2f 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -515,6 +515,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey if (!foundReplacement || ignored) { // nothing to do? + texture->prepareDone_ = true; return; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index f89bf64898..8751efbb6e 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -529,16 +529,18 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { int h0 = gstate.getTextureHeight(0); int d0 = 1; ReplacedTexture *replaced = FindReplacement(entry, w0, h0, d0); - if (replaced && replaced->IsInvalid()) { - entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - if (g_Config.bSaveNewTextures) { - // Load once more to actually save. + if (replaced) { + if (replaced->IsInvalid()) { + entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; + if (g_Config.bSaveNewTextures) { + // Load once more to actually save. + match = false; + reason = "replacing"; + } + } else { match = false; reason = "replacing"; } - } else { - match = false; - reason = "replacing"; } } @@ -2784,7 +2786,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt if (canReplace) { plan.replaced = FindReplacement(entry, plan.w, plan.h, plan.depth); - plan.replaceValid = plan.replaced ? plan.replaced->Valid() : plan.replaced; + plan.replaceValid = plan.replaced ? plan.replaced->Valid() : false; } else { plan.replaced = nullptr; plan.replaceValid = false; From b75a51f05494cdf1dbd558978efb8fc423fe2fad Mon Sep 17 00:00:00 2001 From: xujibbs <79200793+xujibbs@users.noreply.github.com> Date: Thu, 9 Mar 2023 22:12:14 +0800 Subject: [PATCH 124/141] Update zh_CN.ini --- assets/lang/zh_CN.ini | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 9bb0c7a322..0c6796ad2d 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -1089,6 +1089,7 @@ AVI Dump started. = AVI转储开始 AVI Dump stopped. = AVI转储停止 Cache ISO in RAM = 在内存中缓存完整ISO Change CPU Clock = 修改PSP的CPU频率(不稳定) +Rewind Snapshot Interval = 倒带快照间隔 Color Tint = 颜色色调 Color Saturation = 饱和度 Error: load undo state is from a different game = 错误:撤回的即时存档是来自不同游戏的 @@ -1146,17 +1147,17 @@ Not a PSP game = 这不是一个PSP游戏 Off = 关闭 Oldest Save = 最旧的即时存档 Path does not exist! = 路径不存在! -PSP Memory Stick = PSP储存 -PSP Model = PSP机型 -PSP Settings = PSP设置 +PSP Memory Stick = PSP 储存 +PSP Model = PSP 机型 +PSP Settings = PSP 设置 PSP-1000 = PSP-1000 PSP-2000/3000 = PSP-2000/3000 Recent games = 最近的游戏 Record Audio = 录制音频 Record Display = 录制视频 Reset Recording on Save/Load State = 保存/载入存档时重置录制 -Restore Default Settings = 恢复默认PPSSPP设置 -Rewind Snapshot Frequency = 倒带存档频率 +Restore Default Settings = 恢复默认 PPSSPP 设置 +Rewind Snapshot Frequency = 倒带快照频率 Save path in installed.txt = 记忆棒目录使用"installed.txt" Save path in My Documents = 记忆棒目录使用"我的文档" Savestate Slot = 即时存档插槽 From 8e02f14276018bc09332be12f2d30f100089c06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 15:39:46 +0100 Subject: [PATCH 125/141] Remove hidden option ReplaceTextureAllowLate Don't want to have to test little-used paths like this. --- Core/Config.cpp | 1 - Core/Config.h | 1 - Core/TextureReplacer.cpp | 23 ++++++++--------------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index da0b1aa0d1..cce0b9d7cb 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -877,7 +877,6 @@ static const ConfigSetting graphicsSettings[] = { ReportedConfigSetting("ReplaceTextures", &g_Config.bReplaceTextures, true, true, true), ReportedConfigSetting("SaveNewTextures", &g_Config.bSaveNewTextures, false, true, true), ConfigSetting("IgnoreTextureFilenames", &g_Config.bIgnoreTextureFilenames, false, true, true), - ConfigSetting("ReplaceTexturesAllowLate", &g_Config.bReplaceTexturesAllowLate, true, true, true), ReportedConfigSetting("TexScalingLevel", &g_Config.iTexScalingLevel, 1, true, true), ReportedConfigSetting("TexScalingType", &g_Config.iTexScalingType, 0, true, true), diff --git a/Core/Config.h b/Core/Config.h index f5f902c5f1..38f90902a0 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -213,7 +213,6 @@ public: bool bReplaceTextures; bool bSaveNewTextures; bool bIgnoreTextureFilenames; - bool bReplaceTexturesAllowLate; int iTexScalingLevel; // 0 = auto, 1 = off, 2 = 2x, ..., 5 = 5x int iTexScalingType; // 0 = xBRZ, 1 = Hybrid bool bTexDeposterize; diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 98de573b2f..2ac2e23133 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -494,7 +494,7 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, ReplacedTexture *result = new ReplacedTexture(); cache_[replacementKey] = result; result->vfs_ = this->vfs_; - if (!g_Config.bReplaceTexturesAllowLate || budget > 0.0) { + if (budget > 0.0) { PopulateReplacement(result, cachekey, hash, w, h); } return result; @@ -998,20 +998,13 @@ bool ReplacedTexture::IsReady(double budget) { if (!prepareDone_) return false; - if (g_Config.bReplaceTexturesAllowLate) { - if (threadWaitable_) - delete threadWaitable_; - threadWaitable_ = new LimitedWaitable(); - g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); - - if (threadWaitable_->WaitFor(budget)) { - // If we finished all the levels, we're done. - return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); - } - } else { - Prepare(vfs_); - _assert_(initDone_); - return true; + if (threadWaitable_) + delete threadWaitable_; + threadWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); + if (threadWaitable_->WaitFor(budget)) { + // If we finished all the levels, we're done. + return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); } // Still pending on thread. From 61b05130468b240bf54637e6d7620038848fd58a Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 9 Mar 2023 17:54:34 +0100 Subject: [PATCH 126/141] OpenXR - Platform flags for extensions refactored --- Common/VR/PPSSPPVR.cpp | 4 ++-- Common/VR/VRBase.cpp | 6 +++--- Common/VR/VRBase.h | 4 ++-- Common/VR/VRFramebuffer.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index e9d40b3cbc..657f3e1420 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -150,10 +150,10 @@ void InitVROnAndroid(void* vm, void* activity, const char* system, int version, //Set platform flags if (strcmp(vendor, "PICO") == 0) { VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_PICO, true); - VR_SetPlatformFLag(VR_PLATFORM_INSTANCE_EXT, true); + VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_INSTANCE, true); } else if ((strcmp(vendor, "META") == 0) || (strcmp(vendor, "OCULUS") == 0)) { VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_QUEST, true); - VR_SetPlatformFLag(VR_PLATFORM_PERFORMANCE_EXT, true); + VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_PERFORMANCE, true); } VR_SetPlatformFLag(VR_PLATFORM_RENDERER_VULKAN, (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN); diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index 3ada93d754..6b795f4245 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -49,10 +49,10 @@ void VR_Init( void* system, const char* name, int version ) { } extensions.push_back(XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME); #ifdef ANDROID - if (VR_GetPlatformFlag(VR_PLATFORM_INSTANCE_EXT)) { + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_INSTANCE)) { extensions.push_back(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME); } - if (VR_GetPlatformFlag(VR_PLATFORM_PERFORMANCE_EXT)) { + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_PERFORMANCE)) { extensions.push_back(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME); extensions.push_back(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME); } @@ -80,7 +80,7 @@ void VR_Init( void* system, const char* name, int version ) { #ifdef ANDROID XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR}; - if (VR_GetPlatformFlag(VR_PLATFORM_INSTANCE_EXT)) { + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_INSTANCE)) { ovrJava* java = (ovrJava*)system; instanceCreateInfoAndroid.applicationVM = java->Vm; instanceCreateInfoAndroid.applicationActivity = java->ActivityObject; diff --git a/Common/VR/VRBase.h b/Common/VR/VRBase.h index 7ed105c846..157ae7c2b3 100644 --- a/Common/VR/VRBase.h +++ b/Common/VR/VRBase.h @@ -120,8 +120,8 @@ typedef struct { enum VRPlatformFlag { VR_PLATFORM_CONTROLLER_PICO, VR_PLATFORM_CONTROLLER_QUEST, - VR_PLATFORM_INSTANCE_EXT, - VR_PLATFORM_PERFORMANCE_EXT, + VR_PLATFORM_EXTENSION_INSTANCE, + VR_PLATFORM_EXTENSION_PERFORMANCE, VR_PLATFORM_RENDERER_VULKAN, VR_PLATFORM_TRACKING_FLOOR, VR_PLATFORM_MAX diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp index 6d8b94177a..0450712114 100644 --- a/Common/VR/VRFramebuffer.cpp +++ b/Common/VR/VRFramebuffer.cpp @@ -456,7 +456,7 @@ void ovrApp_HandleSessionStateChanges(ovrApp* app, XrSessionState state) { app->SessionActive = (result == XR_SUCCESS); #ifdef ANDROID - if (app->SessionActive && VR_GetPlatformFlag(VR_PLATFORM_PERFORMANCE_EXT)) { + if (app->SessionActive && VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_PERFORMANCE)) { XrPerfSettingsLevelEXT cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_PERFORMANCE_MAX_EXT; XrPerfSettingsLevelEXT gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_PERFORMANCE_MAX_EXT; From ecf113449418a0ec1d2a2a1980408a980ef6a1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 19:12:47 +0100 Subject: [PATCH 127/141] GL crashfix --- GPU/GLES/TextureCacheGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 127b6d7699..307a43c1e0 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -246,7 +246,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { int th = plan.createH; Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); - if (plan.replaced->GetSize(plan.baseLevelSrc, tw, th)) { + if (plan.replaced && plan.replaced->GetSize(plan.baseLevelSrc, tw, th)) { dstFmt = plan.replaced->Format(); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = Draw::DataFormat::R8G8B8A8_UNORM; From 4fcc69242ecabaa6bab4a070ff88b877a1bc7c6c Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 9 Mar 2023 20:42:49 +0100 Subject: [PATCH 128/141] OpenXR - Foveation implemented (Quest only) --- Common/VR/PPSSPPVR.cpp | 1 + Common/VR/VRBase.cpp | 9 ++++++ Common/VR/VRBase.h | 1 + Common/VR/VRFramebuffer.cpp | 59 +++++++++++++++++++++++++++++++++++++ Common/VR/VRFramebuffer.h | 3 ++ Common/VR/VRRenderer.cpp | 5 ++++ 6 files changed, 78 insertions(+) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 657f3e1420..f39f1bcc1d 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -153,6 +153,7 @@ void InitVROnAndroid(void* vm, void* activity, const char* system, int version, VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_INSTANCE, true); } else if ((strcmp(vendor, "META") == 0) || (strcmp(vendor, "OCULUS") == 0)) { VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_QUEST, true); + VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_FOVEATION, true); VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_PERFORMANCE, true); } VR_SetPlatformFLag(VR_PLATFORM_RENDERER_VULKAN, (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN); diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index 6b795f4245..715ea6644e 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -49,6 +49,15 @@ void VR_Init( void* system, const char* name, int version ) { } extensions.push_back(XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME); #ifdef ANDROID + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) { + extensions.push_back(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME); + extensions.push_back(XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME); + extensions.push_back(XR_FB_FOVEATION_EXTENSION_NAME); + extensions.push_back(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME); + if (VR_GetPlatformFlag(VR_PLATFORM_RENDERER_VULKAN)) { + extensions.push_back(XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME); + } + } if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_INSTANCE)) { extensions.push_back(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME); } diff --git a/Common/VR/VRBase.h b/Common/VR/VRBase.h index 157ae7c2b3..2d3405f8a7 100644 --- a/Common/VR/VRBase.h +++ b/Common/VR/VRBase.h @@ -120,6 +120,7 @@ typedef struct { enum VRPlatformFlag { VR_PLATFORM_CONTROLLER_PICO, VR_PLATFORM_CONTROLLER_QUEST, + VR_PLATFORM_EXTENSION_FOVEATION, VR_PLATFORM_EXTENSION_INSTANCE, VR_PLATFORM_EXTENSION_PERFORMANCE, VR_PLATFORM_RENDERER_VULKAN, diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp index 0450712114..4f53b9eb25 100644 --- a/Common/VR/VRFramebuffer.cpp +++ b/Common/VR/VRFramebuffer.cpp @@ -105,6 +105,15 @@ static bool ovrFramebuffer_CreateGLES(XrSession session, ovrFramebuffer* frameBu swapChainCreateInfo.mipCount = 1; swapChainCreateInfo.arraySize = multiview ? 2 : 1; +#ifdef ANDROID + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) { + XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo; + memset(&swapChainFoveationCreateInfo, 0, sizeof(swapChainFoveationCreateInfo)); + swapChainFoveationCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB; + swapChainCreateInfo.next = &swapChainFoveationCreateInfo; + } +#endif + frameBuffer->ColorSwapChain.Width = swapChainCreateInfo.width; frameBuffer->ColorSwapChain.Height = swapChainCreateInfo.height; @@ -198,6 +207,15 @@ static bool ovrFramebuffer_CreateVK(XrSession session, ovrFramebuffer* frameBuff swapChainCreateInfo.mipCount = 1; swapChainCreateInfo.arraySize = multiview ? 2 : 1; +#ifdef ANDROID + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) { + XrSwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo; + memset(&swapChainFoveationCreateInfo, 0, sizeof(swapChainFoveationCreateInfo)); + swapChainFoveationCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB; + swapChainCreateInfo.next = &swapChainFoveationCreateInfo; + } +#endif + frameBuffer->ColorSwapChain.Width = swapChainCreateInfo.width; frameBuffer->ColorSwapChain.Height = swapChainCreateInfo.height; @@ -407,6 +425,47 @@ void ovrRenderer_MouseCursor(ovrRenderer* renderer, int x, int y, int sx, int sy } } +#ifdef ANDROID +void ovrRenderer_SetFoveation(XrInstance* instance, XrSession* session, ovrRenderer* renderer, XrFoveationLevelFB level, float verticalOffset, XrFoveationDynamicFB dynamic) { + PFN_xrCreateFoveationProfileFB pfnCreateFoveationProfileFB; + OXR(xrGetInstanceProcAddr(*instance, "xrCreateFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnCreateFoveationProfileFB))); + + PFN_xrDestroyFoveationProfileFB pfnDestroyFoveationProfileFB; + OXR(xrGetInstanceProcAddr(*instance, "xrDestroyFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnDestroyFoveationProfileFB))); + + PFN_xrUpdateSwapchainFB pfnUpdateSwapchainFB; + OXR(xrGetInstanceProcAddr(*instance, "xrUpdateSwapchainFB", (PFN_xrVoidFunction*)(&pfnUpdateSwapchainFB))); + + int instances = renderer->Multiview ? 1 : ovrMaxNumEyes; + for (int eye = 0; eye < instances; eye++) { + XrFoveationLevelProfileCreateInfoFB levelProfileCreateInfo; + memset(&levelProfileCreateInfo, 0, sizeof(levelProfileCreateInfo)); + levelProfileCreateInfo.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB; + levelProfileCreateInfo.level = level; + levelProfileCreateInfo.verticalOffset = verticalOffset; + levelProfileCreateInfo.dynamic = dynamic; + + XrFoveationProfileCreateInfoFB profileCreateInfo; + memset(&profileCreateInfo, 0, sizeof(profileCreateInfo)); + profileCreateInfo.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB; + profileCreateInfo.next = &levelProfileCreateInfo; + + XrFoveationProfileFB foveationProfile; + + pfnCreateFoveationProfileFB(*session, &profileCreateInfo, &foveationProfile); + + XrSwapchainStateFoveationFB foveationUpdateState; + memset(&foveationUpdateState, 0, sizeof(foveationUpdateState)); + foveationUpdateState.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB; + foveationUpdateState.profile = foveationProfile; + + pfnUpdateSwapchainFB(renderer->FrameBuffer[eye].ColorSwapChain.Handle, (XrSwapchainStateBaseHeaderFB*)(&foveationUpdateState)); + + pfnDestroyFoveationProfileFB(foveationProfile); + } +} +#endif + /* ================================================================================ diff --git a/Common/VR/VRFramebuffer.h b/Common/VR/VRFramebuffer.h index 07a5c19834..49f7484c99 100644 --- a/Common/VR/VRFramebuffer.h +++ b/Common/VR/VRFramebuffer.h @@ -13,3 +13,6 @@ void* ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer); void ovrRenderer_Create(XrSession session, ovrRenderer* renderer, int width, int height, bool multiview, void* vulkanContext); void ovrRenderer_Destroy(ovrRenderer* renderer); void ovrRenderer_MouseCursor(ovrRenderer* renderer, int x, int y, int sx, int sy); +#ifdef ANDROID +void ovrRenderer_SetFoveation(XrInstance* instance, XrSession* session, ovrRenderer* renderer, XrFoveationLevelFB level, float verticalOffset, XrFoveationDynamicFB dynamic); +#endif diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index 8a3cc8ef90..8a4efd8657 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -216,6 +216,11 @@ void VR_InitRenderer( engine_t* engine, bool multiview ) { engine->appState.ViewConfigurationView[0].recommendedImageRectWidth, engine->appState.ViewConfigurationView[0].recommendedImageRectHeight, multiview, vulkanContext); +#ifdef ANDROID + if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) { + ovrRenderer_SetFoveation(&engine->appState.Instance, &engine->appState.Session, &engine->appState.Renderer, XR_FOVEATION_LEVEL_HIGH_FB, 0, XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB); + } +#endif initialized = true; } From f24026d1636760eb1902984da004880b8efb5b9b Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 9 Mar 2023 21:04:30 +0100 Subject: [PATCH 129/141] OpenXR - Increase foveation level --- Common/VR/VRRenderer.cpp | 2 +- ext/openxr/openxr.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index 8a4efd8657..5a9a208926 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -218,7 +218,7 @@ void VR_InitRenderer( engine_t* engine, bool multiview ) { multiview, vulkanContext); #ifdef ANDROID if (VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_FOVEATION)) { - ovrRenderer_SetFoveation(&engine->appState.Instance, &engine->appState.Session, &engine->appState.Renderer, XR_FOVEATION_LEVEL_HIGH_FB, 0, XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB); + ovrRenderer_SetFoveation(&engine->appState.Instance, &engine->appState.Session, &engine->appState.Renderer, XR_FOVEATION_LEVEL_HIGH_TOP_FB, 0, XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB); } #endif initialized = true; diff --git a/ext/openxr/openxr.h b/ext/openxr/openxr.h index 2e4dec9fe1..0e38fbc331 100644 --- a/ext/openxr/openxr.h +++ b/ext/openxr/openxr.h @@ -3476,6 +3476,7 @@ typedef enum XrFoveationLevelFB { XR_FOVEATION_LEVEL_LOW_FB = 1, XR_FOVEATION_LEVEL_MEDIUM_FB = 2, XR_FOVEATION_LEVEL_HIGH_FB = 3, + XR_FOVEATION_LEVEL_HIGH_TOP_FB = 4, XR_FOVEATION_LEVEL_MAX_ENUM_FB = 0x7FFFFFFF } XrFoveationLevelFB; From cb108625108732734af09742d53365e01ec5f129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 21:11:53 +0100 Subject: [PATCH 130/141] Move texture replacer to GPU/Common. Add new files ReplacedTexture.cpp/h too. --- CMakeLists.txt | 6 +- Core/Core.vcxproj | 2 - Core/Core.vcxproj.filters | 6 - GPU/Common/ReplacedTexture.cpp | 1 + GPU/Common/ReplacedTexture.h | 1 + GPU/Common/TextureCacheCommon.h | 2 +- {Core => GPU/Common}/TextureReplacer.cpp | 2 +- {Core => GPU/Common}/TextureReplacer.h | 0 GPU/GPU.vcxproj | 20 +- GPU/GPU.vcxproj.filters | 12 + UI/GameSettingsScreen.cpp | 2 +- UWP/CoreUWP/CoreUWP.vcxproj | 344 +++++++++++++++-------- UWP/CoreUWP/CoreUWP.vcxproj.filters | 2 - UWP/GPU_UWP/GPU_UWP.vcxproj | 28 +- UWP/GPU_UWP/GPU_UWP.vcxproj.filters | 4 + android/jni/Android.mk | 3 +- libretro/Makefile.common | 3 +- 17 files changed, 285 insertions(+), 153 deletions(-) create mode 100644 GPU/Common/ReplacedTexture.cpp create mode 100644 GPU/Common/ReplacedTexture.h rename {Core => GPU/Common}/TextureReplacer.cpp (99%) rename {Core => GPU/Common}/TextureReplacer.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91f08e233b..25505d597c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1639,6 +1639,10 @@ set(GPU_SOURCES GPU/Common/TextureScalerCommon.h GPU/Common/PostShader.cpp GPU/Common/PostShader.h + GPU/Common/TextureReplacer.cpp + GPU/Common/TextureReplacer.h + GPU/Common/ReplacedTexture.cpp + GPU/Common/ReplacedTexture.h GPU/Debugger/Breakpoints.cpp GPU/Debugger/Breakpoints.h GPU/Debugger/Debugger.cpp @@ -2047,8 +2051,6 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Screenshot.h Core/System.cpp Core/System.h - Core/TextureReplacer.cpp - Core/TextureReplacer.h Core/ThreadPools.cpp Core/ThreadPools.h Core/Util/AudioFormat.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 0ad2df793f..0fad6b30a7 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -591,7 +591,6 @@ - @@ -1157,7 +1156,6 @@ - diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 1fe06c2b6e..4b2b04de05 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -652,9 +652,6 @@ FileLoaders - - Core - MIPS\IR @@ -1737,9 +1734,6 @@ FileLoaders - - Core - MIPS\IR diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp new file mode 100644 index 0000000000..3bf7e731e0 --- /dev/null +++ b/GPU/Common/ReplacedTexture.cpp @@ -0,0 +1 @@ +#include "GPU/Common/ReplacedTexture.h" diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/GPU/Common/ReplacedTexture.h @@ -0,0 +1 @@ +#pragma once diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index da42b07de4..7db6507b13 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -23,13 +23,13 @@ #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" -#include "Core/TextureReplacer.h" #include "Core/System.h" #include "GPU/GPU.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Common/TextureScalerCommon.h" #include "GPU/Common/TextureShaderCommon.h" +#include "GPU/Common/TextureReplacer.h" class Draw2D; diff --git a/Core/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp similarity index 99% rename from Core/TextureReplacer.cpp rename to GPU/Common/TextureReplacer.cpp index 2ac2e23133..654a65d5e8 100644 --- a/Core/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -44,9 +44,9 @@ #include "Core/Config.h" #include "Core/Host.h" #include "Core/System.h" -#include "Core/TextureReplacer.h" #include "Core/ThreadPools.h" #include "Core/ELF/ParamSFO.h" +#include "GPU/Common/TextureReplacer.h" #include "GPU/Common/TextureDecoder.h" static const std::string INI_FILENAME = "textures.ini"; diff --git a/Core/TextureReplacer.h b/GPU/Common/TextureReplacer.h similarity index 100% rename from Core/TextureReplacer.h rename to GPU/Common/TextureReplacer.h diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 606e169859..3b6f07c66f 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -164,7 +164,7 @@ Level3 - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) StreamingSIMDExtensions2 Precise @@ -182,7 +182,7 @@ Level3 - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 NotSet Precise false @@ -202,7 +202,7 @@ Level3 - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 NotSet Precise false @@ -221,7 +221,7 @@ Level3 - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 NotSet Precise false @@ -244,7 +244,7 @@ MaxSpeed true true - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 false StreamingSIMDExtensions2 Precise @@ -267,7 +267,7 @@ MaxSpeed true true - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 false NotSet Precise @@ -292,7 +292,7 @@ MaxSpeed true true - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 false NotSet Precise @@ -317,7 +317,7 @@ MaxSpeed true true - ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib + ..\dx9sdk\Include\DX11;../common;..;../ext;../ext/glew;../ext/snappy;../ext/glslang;../ext/zstd/lib;../ext/libpng17 false NotSet Precise @@ -338,6 +338,8 @@ + + @@ -456,6 +458,8 @@ + + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index b34942e06c..6b35f8f33a 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -267,6 +267,12 @@ Common + + Common + + + Common + @@ -530,6 +536,12 @@ Common + + Common + + + Common + diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index abf2e2034f..0ef3060135 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -66,10 +66,10 @@ #include "Core/Instance.h" #include "Core/System.h" #include "Core/Reporting.h" -#include "Core/TextureReplacer.h" #include "Core/WebServer.h" #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbMic.h" +#include "GPU/Common/TextureReplacer.h" #include "GPU/Common/PostShader.h" #include "android/jni/TestRunner.h" #include "GPU/GPUInterface.h" diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 65b384657b..6fe4ea1320 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -476,7 +476,6 @@ - @@ -739,7 +738,6 @@ - @@ -758,459 +756,573 @@ NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing - + + NotUsing diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 119c2d0936..7fead01f06 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -108,7 +108,6 @@ - MIPS\ARM @@ -1150,7 +1149,6 @@ - diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 0448b3e1bd..0548c117a6 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -90,7 +90,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -105,7 +105,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -120,7 +120,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -135,7 +135,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -150,7 +150,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -165,7 +165,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -180,7 +180,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -195,7 +195,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -210,7 +210,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) @@ -225,7 +225,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -240,7 +240,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -255,7 +255,7 @@ false false pch.h - ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) + ../..;../../ext/snappy;../../ext/native;../../Common;../../ext;../../ext/glslang;../../ext/zstd/lib;../../ext/libpng17;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -265,6 +265,8 @@ + + @@ -329,6 +331,8 @@ + + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index dc746a6f6d..314574f863 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -62,6 +62,8 @@ + + @@ -125,5 +127,7 @@ + + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 3cc90b6dcf..57b695f3e5 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -389,6 +389,8 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Common/ShaderUniforms.cpp \ $(SRC)/GPU/Common/VertexShaderGenerator.cpp \ $(SRC)/GPU/Common/GeometryShaderGenerator.cpp \ + $(SRC)/GPU/Common/TextureReplacer.cpp \ + $(SRC)/GPU/Common/ReplacedTexture.cpp \ $(SRC)/GPU/Debugger/Breakpoints.cpp \ $(SRC)/GPU/Debugger/Debugger.cpp \ $(SRC)/GPU/Debugger/GECommandTable.cpp \ @@ -456,7 +458,6 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/SaveState.cpp \ $(SRC)/Core/Screenshot.cpp \ $(SRC)/Core/System.cpp \ - $(SRC)/Core/TextureReplacer.cpp \ $(SRC)/Core/TiltEventProcessor.cpp \ $(SRC)/Core/ThreadPools.cpp \ $(SRC)/Core/WebServer.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 2b3bcfed99..67149d94d4 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -386,6 +386,8 @@ SOURCES_CXX += \ $(GPUCOMMONDIR)/IndexGenerator.cpp \ $(GPUCOMMONDIR)/TextureDecoder.cpp \ $(GPUCOMMONDIR)/PostShader.cpp \ + $(GPUCOMMONDIR)/TextureReplacer.cpp \ + $(GPUCOMMONDIR)/ReplacedTexture.cpp \ $(COMMONDIR)/Data/Convert/ColorConv.cpp \ $(GPUDIR)/Debugger/Breakpoints.cpp \ $(GPUDIR)/Debugger/Debugger.cpp \ @@ -495,7 +497,6 @@ SOURCES_CXX += \ $(COREDIR)/AVIDump.cpp \ $(COREDIR)/Config.cpp \ $(COREDIR)/ControlMapper.cpp \ - $(COREDIR)/TextureReplacer.cpp \ $(COREDIR)/Core.cpp \ $(COREDIR)/WaveFile.cpp \ $(COREDIR)/KeyMap.cpp \ From 654f4bff40913897148abb88b73a073e42db7598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 9 Mar 2023 21:19:20 +0100 Subject: [PATCH 131/141] Split out ReplacedTexture into its own file. --- GPU/Common/ReplacedTexture.cpp | 294 +++++++++++++++++++++++++++++++++ GPU/Common/ReplacedTexture.h | 131 +++++++++++++++ GPU/Common/TextureReplacer.cpp | 272 +----------------------------- GPU/Common/TextureReplacer.h | 97 +---------- 4 files changed, 427 insertions(+), 367 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 3bf7e731e0..40b81c8a9b 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -1 +1,295 @@ +// Copyright (c) 2016- PPSSPP 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 git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "ppsspp_config.h" + +#include + #include "GPU/Common/ReplacedTexture.h" +#include "GPU/Common/TextureReplacer.h" + +#include "Common/Data/Format/IniFile.h" +#include "Common/Data/Format/ZIMLoad.h" +#include "Common/Data/Format/PNGLoad.h" +#include "Common/Thread/ParallelLoop.h" +#include "Common/Thread/Waitable.h" +#include "Common/Thread/ThreadManager.h" +#include "Common/Log.h" +#include "Common/TimeUtil.h" + +class ReplacedTextureTask : public Task { +public: + ReplacedTextureTask(VFSBackend *vfs, ReplacedTexture &tex, LimitedWaitable *w) : vfs_(vfs), tex_(tex), waitable_(w) {} + + TaskType Type() const override { + return TaskType::IO_BLOCKING; + } + + TaskPriority Priority() const override { + return TaskPriority::NORMAL; + } + + void Run() override { + tex_.Prepare(vfs_); + waitable_->Notify(); + } + +private: + VFSBackend *vfs_; + ReplacedTexture &tex_; + LimitedWaitable *waitable_; +}; + +bool ReplacedTexture::IsReady(double budget) { + _assert_(vfs_ != nullptr); + lastUsed_ = time_now_d(); + if (threadWaitable_ && !threadWaitable_->WaitFor(budget)) { + return false; + } + + // Loaded already, or not yet on a thread? + if (initDone_ && levelData_ && !levelData_->data.empty()) { + // TODO: lock? + levelData_->lastUsed = lastUsed_; + return true; + } + + // Let's not even start a new texture if we're already behind. + if (budget < 0.0) + return false; + if (!prepareDone_) + return false; + + if (threadWaitable_) + delete threadWaitable_; + threadWaitable_ = new LimitedWaitable(); + g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); + if (threadWaitable_->WaitFor(budget)) { + // If we finished all the levels, we're done. + return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); + } + + // Still pending on thread. + return false; +} + +void ReplacedTexture::Prepare(VFSBackend *vfs) { + std::unique_lock lock(mutex_); + this->vfs_ = vfs; + if (cancelPrepare_) { + initDone_ = true; + return; + } + + for (int i = 0; i < (int)levels_.size(); ++i) { + if (cancelPrepare_) + break; + PrepareData(i); + } + + initDone_ = true; + if (!cancelPrepare_ && threadWaitable_) + threadWaitable_->Notify(); +} + +void ReplacedTexture::PrepareData(int level) { + _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); + _assert_msg_(levelData_ != nullptr, "Level cache not set"); + + // We must lock around access to levelData_ in case two textures try to load it at once. + std::lock_guard guard(levelData_->lock); + + const ReplacedTextureLevel &info = levels_[level]; + + if (levelData_->data.size() <= level) { + levelData_->data.resize(level + 1); + } + + std::vector &out = levelData_->data[level]; + + // Already populated from cache. + if (!out.empty()) + return; + + ReplacedImageType imageType; + + size_t fileSize; + VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef, &fileSize); + + std::string magic; + imageType = Identify(vfs_, openFile, &magic); + + auto cleanup = [&] { + vfs_->CloseFile(openFile); + }; + + if (imageType == ReplacedImageType::ZIM) { + std::unique_ptr zim(new uint8_t[fileSize]); + if (!zim) { + ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); + cleanup(); + return; + } + + if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) { + ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); + cleanup(); + return; + } + + int w, h, f; + uint8_t *image; + if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) { + if (w > info.w || h > info.h) { + ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + cleanup(); + return; + } + + out.resize(info.w * info.h * 4); + if (w == info.w) { + memcpy(&out[0], image, info.w * 4 * info.h); + } else { + for (int y = 0; y < h; ++y) { + memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); + } + } + free(image); + } + + CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, w, h, 0xFF000000); + if (res == CHECKALPHA_ANY || level == 0) { + alphaStatus_ = ReplacedTextureAlpha(res); + } + } else if (imageType == ReplacedImageType::PNG) { + png_image png = {}; + png.version = PNG_IMAGE_VERSION; + + std::string pngdata; + pngdata.resize(fileSize); + pngdata.resize(vfs_->Read(openFile, &pngdata[0], fileSize)); + if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { + ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); + cleanup(); + return; + } + if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) { + ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + cleanup(); + return; + } + + bool checkedAlpha = false; + if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) { + // Well, we know for sure it doesn't have alpha. + if (level == 0) { + alphaStatus_ = ReplacedTextureAlpha::FULL; + } + checkedAlpha = true; + } + png.format = PNG_FORMAT_RGBA; + + out.resize(info.w * info.h * 4); + if (!png_image_finish_read(&png, nullptr, &out[0], info.w * 4, nullptr)) { + ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); + cleanup(); + out.resize(0); + return; + } + png_image_free(&png); + + if (!checkedAlpha) { + // This will only check the hashed bits. + CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, png.width, png.height, 0xFF000000); + if (res == CHECKALPHA_ANY || level == 0) { + alphaStatus_ = ReplacedTextureAlpha(res); + } + } + } + + cleanup(); +} + +void ReplacedTexture::PurgeIfOlder(double t) { + if (threadWaitable_ && !threadWaitable_->WaitFor(0.0)) + return; + if (lastUsed_ >= t) + return; + + if (levelData_->lastUsed < t) { + // We have to lock since multiple textures might reference this same data. + std::lock_guard guard(levelData_->lock); + levelData_->data.clear(); + // This means we have to reload. If we never purge any, there's no need. + initDone_ = false; + } +} + +ReplacedTexture::~ReplacedTexture() { + if (threadWaitable_) { + cancelPrepare_ = true; + + std::unique_lock lock(mutex_); + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + } + + for (auto &level : levels_) { + vfs_->ReleaseFile(level.fileRef); + level.fileRef = nullptr; + } +} + +bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { + _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); + _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); + + if (!initDone_) { + WARN_LOG(G3D, "Init not done yet"); + return false; + } + + // We probably could avoid this lock, but better to play it safe. + std::lock_guard guard(levelData_->lock); + + const ReplacedTextureLevel &info = levels_[level]; + const std::vector &data = levelData_->data[level]; + + if (data.empty()) { + WARN_LOG(G3D, "Level %d is empty", level); + return false; + } + + if (rowPitch < info.w * 4) { + ERROR_LOG(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); + return false; + } + _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); + + if (rowPitch == info.w * 4) { + ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); + } else { + const int MIN_LINES_PER_THREAD = 4; + ParallelRangeLoop(&g_threadManager, [&](int l, int h) { + for (int y = l; y < h; ++y) { + memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4); + } + }, 0, info.h, MIN_LINES_PER_THREAD); + } + + return true; +} diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index 6f70f09bee..4e00d8df28 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -1 +1,132 @@ +// Copyright (c) 2016- PPSSPP 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 git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + #pragma once + +#include +#include + +#include "Common/File/VFS/VFS.h" +#include "Common/GPU/thin3d.h" + +struct ReplacedLevelsCache; +class TextureReplacer; +class LimitedWaitable; + +// These must match the constants in TextureCacheCommon. +enum class ReplacedTextureAlpha { + UNKNOWN = 0x04, + FULL = 0x00, +}; + +// For forward compatibility, we specify the hash. +enum class ReplacedTextureHash { + QUICK, + XXH32, + XXH64, +}; + +enum class ReplacedImageType { + PNG, + ZIM, + INVALID, +}; + +// Metadata about a given texture level. +struct ReplacedTextureLevel { + int w = 0; + int h = 0; + Path file; + + // To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t. + // TODO: This really belongs on the level in the cache, not in the individual ReplacedTextureLevel objects. + VFSFileReference *fileRef = nullptr; +}; + +ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic); + +struct ReplacedTexture { + ~ReplacedTexture(); + + inline bool Valid() const { + if (!initDone_) + return false; + return !levels_.empty(); + } + + inline bool IsInvalid() const { + if (!initDone_) + return false; + return levels_.empty(); + } + + bool GetSize(int level, int &w, int &h) const { + if (!initDone_) + return false; + if ((size_t)level < levels_.size()) { + w = levels_[level].w; + h = levels_[level].h; + return true; + } + return false; + } + + int NumLevels() const { + if (!initDone_) + return 0; + return (int)levels_.size(); + } + + Draw::DataFormat Format() const { + if (initDone_) { + return fmt; + } else { + // Shouldn't get here. + return Draw::DataFormat::UNDEFINED; + } + } + + u8 AlphaStatus() const { + return (u8)alphaStatus_; + } + + bool IsReady(double budget); + bool CopyLevelTo(int level, void *out, int rowPitch); + +protected: + void Prepare(VFSBackend *vfs); + void PrepareData(int level); + void PurgeIfOlder(double t); + + std::vector levels_; + ReplacedLevelsCache *levelData_; + + ReplacedTextureAlpha alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; + double lastUsed_ = 0.0; + LimitedWaitable *threadWaitable_ = nullptr; + std::mutex mutex_; + Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. + + bool cancelPrepare_ = false; + bool initDone_ = false; + bool prepareDone_ = false; + + VFSBackend *vfs_ = nullptr; + + friend class TextureReplacer; + friend class ReplacedTextureTask; +}; diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 654a65d5e8..296acc0031 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -18,7 +18,6 @@ #include "ppsspp_config.h" #include -#include #include #include #include @@ -56,12 +55,6 @@ static const int VERSION = 1; static const int MAX_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP. static const double MAX_CACHE_SIZE = 4.0; -enum class ReplacedImageType { - PNG, - ZIM, - INVALID, -}; - static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { if (strncmp((const char *)magic, "ZIMG", 4) == 0) return ReplacedImageType::ZIM; @@ -70,7 +63,7 @@ static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { return ReplacedImageType::INVALID; } -static ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic) { +ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic) { uint8_t magic[4]; if (vfs->Read(openFile, magic, 4) != 4) { *outMagic = "FAIL"; @@ -955,269 +948,6 @@ float TextureReplacer::LookupReduceHashRange(int& w, int& h) { } } -class ReplacedTextureTask : public Task { -public: - ReplacedTextureTask(VFSBackend *vfs, ReplacedTexture &tex, LimitedWaitable *w) : vfs_(vfs), tex_(tex), waitable_(w) {} - - TaskType Type() const override { - return TaskType::IO_BLOCKING; - } - - TaskPriority Priority() const override { - return TaskPriority::NORMAL; - } - - void Run() override { - tex_.Prepare(vfs_); - waitable_->Notify(); - } - -private: - VFSBackend *vfs_; - ReplacedTexture &tex_; - LimitedWaitable *waitable_; -}; - -bool ReplacedTexture::IsReady(double budget) { - _assert_(vfs_ != nullptr); - lastUsed_ = time_now_d(); - if (threadWaitable_ && !threadWaitable_->WaitFor(budget)) { - return false; - } - - // Loaded already, or not yet on a thread? - if (initDone_ && levelData_ && !levelData_->data.empty()) { - // TODO: lock? - levelData_->lastUsed = lastUsed_; - return true; - } - - // Let's not even start a new texture if we're already behind. - if (budget < 0.0) - return false; - if (!prepareDone_) - return false; - - if (threadWaitable_) - delete threadWaitable_; - threadWaitable_ = new LimitedWaitable(); - g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); - if (threadWaitable_->WaitFor(budget)) { - // If we finished all the levels, we're done. - return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); - } - - // Still pending on thread. - return false; -} - -void ReplacedTexture::Prepare(VFSBackend *vfs) { - std::unique_lock lock(mutex_); - this->vfs_ = vfs; - if (cancelPrepare_) { - initDone_ = true; - return; - } - - for (int i = 0; i < (int)levels_.size(); ++i) { - if (cancelPrepare_) - break; - PrepareData(i); - } - - initDone_ = true; - if (!cancelPrepare_ && threadWaitable_) - threadWaitable_->Notify(); -} - -void ReplacedTexture::PrepareData(int level) { - _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); - _assert_msg_(levelData_ != nullptr, "Level cache not set"); - - // We must lock around access to levelData_ in case two textures try to load it at once. - std::lock_guard guard(levelData_->lock); - - const ReplacedTextureLevel &info = levels_[level]; - - if (levelData_->data.size() <= level) { - levelData_->data.resize(level + 1); - } - - std::vector &out = levelData_->data[level]; - - // Already populated from cache. - if (!out.empty()) - return; - - ReplacedImageType imageType; - - size_t fileSize; - VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef, &fileSize); - - std::string magic; - imageType = Identify(vfs_, openFile, &magic); - - auto cleanup = [&] { - vfs_->CloseFile(openFile); - }; - - if (imageType == ReplacedImageType::ZIM) { - std::unique_ptr zim(new uint8_t[fileSize]); - if (!zim) { - ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); - cleanup(); - return; - } - - if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) { - ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); - cleanup(); - return; - } - - int w, h, f; - uint8_t *image; - if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) { - if (w > info.w || h > info.h) { - ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); - cleanup(); - return; - } - - out.resize(info.w * info.h * 4); - if (w == info.w) { - memcpy(&out[0], image, info.w * 4 * info.h); - } else { - for (int y = 0; y < h; ++y) { - memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4); - } - } - free(image); - } - - CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, w, h, 0xFF000000); - if (res == CHECKALPHA_ANY || level == 0) { - alphaStatus_ = ReplacedTextureAlpha(res); - } - } else if (imageType == ReplacedImageType::PNG) { - png_image png = {}; - png.version = PNG_IMAGE_VERSION; - - std::string pngdata; - pngdata.resize(fileSize); - pngdata.resize(vfs_->Read(openFile, &pngdata[0], fileSize)); - if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); - cleanup(); - return; - } - if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) { - ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); - cleanup(); - return; - } - - bool checkedAlpha = false; - if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) { - // Well, we know for sure it doesn't have alpha. - if (level == 0) { - alphaStatus_ = ReplacedTextureAlpha::FULL; - } - checkedAlpha = true; - } - png.format = PNG_FORMAT_RGBA; - - out.resize(info.w * info.h * 4); - if (!png_image_finish_read(&png, nullptr, &out[0], info.w * 4, nullptr)) { - ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); - cleanup(); - out.resize(0); - return; - } - png_image_free(&png); - - if (!checkedAlpha) { - // This will only check the hashed bits. - CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, png.width, png.height, 0xFF000000); - if (res == CHECKALPHA_ANY || level == 0) { - alphaStatus_ = ReplacedTextureAlpha(res); - } - } - } - - cleanup(); -} - -void ReplacedTexture::PurgeIfOlder(double t) { - if (threadWaitable_ && !threadWaitable_->WaitFor(0.0)) - return; - if (lastUsed_ >= t) - return; - - if (levelData_->lastUsed < t) { - // We have to lock since multiple textures might reference this same data. - std::lock_guard guard(levelData_->lock); - levelData_->data.clear(); - // This means we have to reload. If we never purge any, there's no need. - initDone_ = false; - } -} - -ReplacedTexture::~ReplacedTexture() { - if (threadWaitable_) { - cancelPrepare_ = true; - - std::unique_lock lock(mutex_); - threadWaitable_->WaitAndRelease(); - threadWaitable_ = nullptr; - } - - for (auto &level : levels_) { - vfs_->ReleaseFile(level.fileRef); - level.fileRef = nullptr; - } -} - -bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { - _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); - _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); - - if (!initDone_) { - WARN_LOG(G3D, "Init not done yet"); - return false; - } - - // We probably could avoid this lock, but better to play it safe. - std::lock_guard guard(levelData_->lock); - - const ReplacedTextureLevel &info = levels_[level]; - const std::vector &data = levelData_->data[level]; - - if (data.empty()) { - WARN_LOG(G3D, "Level %d is empty", level); - return false; - } - - if (rowPitch < info.w * 4) { - ERROR_LOG(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); - return false; - } - _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); - - if (rowPitch == info.w * 4) { - ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); - } else { - const int MIN_LINES_PER_THREAD = 4; - ParallelRangeLoop(&g_threadManager, [&](int l, int h) { - for (int y = l; y < h; ++y) { - memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4); - } - }, 0, info.h, MIN_LINES_PER_THREAD); - } - - return true; -} - bool TextureReplacer::IniExists(const std::string &gameID) { if (gameID.empty()) return false; diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index a2faa7a763..1efc9d7a54 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -31,6 +31,7 @@ #include "Common/GPU/DataFormat.h" #include "GPU/Common/TextureDecoder.h" +#include "GPU/Common/ReplacedTexture.h" #include "GPU/ge_constants.h" class IniFile; @@ -40,30 +41,6 @@ class ReplacedTextureTask; class LimitedWaitable; class VFSBackend; -// These must match the constants in TextureCacheCommon. -enum class ReplacedTextureAlpha { - UNKNOWN = 0x04, - FULL = 0x00, -}; - -// For forward compatibility, we specify the hash. -enum class ReplacedTextureHash { - QUICK, - XXH32, - XXH64, -}; - -// Metadata about a given texture level. -struct ReplacedTextureLevel { - int w = 0; - int h = 0; - Path file; - - // To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t. - // TODO: This really belongs on the level in the cache, not in the individual ReplacedTextureLevel objects. - VFSFileReference *fileRef = nullptr; -}; - struct SavedTextureCacheData { int levelW[8]{}; int levelH[8]{}; @@ -104,78 +81,6 @@ namespace std { }; } -struct ReplacedTexture { - ~ReplacedTexture(); - - inline bool Valid() const { - if (!initDone_) - return false; - return !levels_.empty(); - } - - inline bool IsInvalid() const { - if (!initDone_) - return false; - return levels_.empty(); - } - - bool GetSize(int level, int &w, int &h) const { - if (!initDone_) - return false; - if ((size_t)level < levels_.size()) { - w = levels_[level].w; - h = levels_[level].h; - return true; - } - return false; - } - - int NumLevels() const { - if (!initDone_) - return 0; - return (int)levels_.size(); - } - - Draw::DataFormat Format() const { - if (initDone_) { - return fmt; - } else { - // Shouldn't get here. - return Draw::DataFormat::UNDEFINED; - } - } - - u8 AlphaStatus() const { - return (u8)alphaStatus_; - } - - bool IsReady(double budget); - bool CopyLevelTo(int level, void *out, int rowPitch); - -protected: - void Prepare(VFSBackend *vfs); - void PrepareData(int level); - void PurgeIfOlder(double t); - - std::vector levels_; - ReplacedLevelsCache *levelData_; - - ReplacedTextureAlpha alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; - double lastUsed_ = 0.0; - LimitedWaitable *threadWaitable_ = nullptr; - std::mutex mutex_; - Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. - - bool cancelPrepare_ = false; - bool initDone_ = false; - bool prepareDone_ = false; - - VFSBackend *vfs_ = nullptr; - - friend class TextureReplacer; - friend class ReplacedTextureTask; -}; - struct ReplacedTextureDecodeInfo { u64 cachekey; u32 hash; From 06b83115a5acf48ac22a58123ceb01ad1f466592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 12:20:55 +0100 Subject: [PATCH 132/141] Replacer: Avoid tracking video textures --- Common/GPU/Vulkan/VulkanDebug.cpp | 6 ++++++ GPU/Common/ReplacedTexture.h | 2 ++ GPU/Common/TextureCacheCommon.cpp | 11 ++++++++++- GPU/Common/TextureCacheCommon.h | 6 ++++-- GPU/Common/TextureReplacer.h | 3 +++ GPU/GPU.h | 4 ++++ GPU/GPUCommonHW.cpp | 3 +++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp index 4daf41bdf7..29a71cf61e 100644 --- a/Common/GPU/Vulkan/VulkanDebug.cpp +++ b/Common/GPU/Vulkan/VulkanDebug.cpp @@ -62,6 +62,12 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( // See https://github.com/hrydgard/ppsspp/pull/16354 return false; } + if (messageCode == -375211665) { + // VUID-vkAllocateMemory-pAllocateInfo-01713 + // Can happen when VMA aggressively tries to allocate aperture memory for upload. It gracefully + // falls back to regular video memory, so we just ignore this. I'd argue this is a VMA bug, actually. + return false; + } int count; { diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index 4e00d8df28..735be70590 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -59,6 +59,8 @@ struct ReplacedTextureLevel { ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic); +// These aren't actually all replaced, they can also represent a placeholder for a not-found +// replacement. struct ReplacedTexture { ~ReplacedTexture(); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 8751efbb6e..5d914c061b 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -144,6 +144,11 @@ void TextureCacheCommon::StartFrame() { timesInvalidatedAllThisFrame_ = 0; replacementTimeThisFrame_ = 0.0; + if (g_Config.bShowDebugStats) { + gpuStats.numReplacerTrackedTex = replacer_.GetNumTrackedTextures(); + gpuStats.numCachedReplacedTextures = replacer_.GetNumCachedReplacedTextures(); + } + if (texelsScaledThisFrame_) { VERBOSE_LOG(G3D, "Scaled %d texels", texelsScaledThisFrame_); } @@ -1497,6 +1502,10 @@ ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & return nullptr; } + if (entry->status & TexCacheEntry::STATUS_VIDEO) { + return nullptr; + } + // Allow some delay to reduce pop-in. constexpr double MAX_BUDGET_PER_TEX = 0.25 / 60.0; @@ -2039,7 +2048,7 @@ void TextureCacheCommon::ApplyTexture() { // Regardless of hash fails or otherwise, if this is a video, mark it frequently changing. // This prevents temporary scaling perf hits on the first second of video. if (IsVideo(entry->addr)) { - entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT; + entry->status |= TexCacheEntry::STATUS_CHANGE_FREQUENT | TexCacheEntry::STATUS_VIDEO; } if (nextNeedsRehash_) { diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 7db6507b13..a51660d364 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -149,10 +149,12 @@ struct TexCacheEntry { STATUS_3D = 0x4000, STATUS_CLUT_GPU = 0x8000, + + STATUS_VIDEO = 0x10000, }; - // Status, but int so we can zero initialize. - int status; + // TexStatus enum flag combination. + u32 status; u32 addr; u32 minihash; diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 1efc9d7a54..27c6b1b83f 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -127,6 +127,9 @@ public: static bool GenerateIni(const std::string &gameID, Path &generatedFilename); static bool IniExists(const std::string &gameID); + int GetNumTrackedTextures() const { return (int)cache_.size(); } + int GetNumCachedReplacedTextures() const { return (int)levelCache_.size(); } + protected: bool LoadIni(); bool LoadIniValues(IniFile &ini, bool isOverride = false); diff --git a/GPU/GPU.h b/GPU/GPU.h index 12274e51fb..c44749cb63 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -100,6 +100,8 @@ struct GPUStatistics { numColorCopies = 0; numCopiesForShaderBlend = 0; numCopiesForSelfTex = 0; + numReplacerTrackedTex = 0; + numCachedReplacedTextures = 0; msProcessingDisplayLists = 0; vertexGPUCycles = 0; otherGPUCycles = 0; @@ -133,6 +135,8 @@ struct GPUStatistics { int numColorCopies; int numCopiesForShaderBlend; int numCopiesForSelfTex; + int numReplacerTrackedTex; + int numCachedReplacedTextures; double msProcessingDisplayLists; int vertexGPUCycles; int otherGPUCycles; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 01d6dd02c2..e5fd62a0ae 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1636,6 +1636,7 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { "FBOs active: %d (evaluations: %d)\n" "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n" "readbacks %d (%d non-block), uploads %d, depal %d\n" + "replacer: tracks %d textures, %d unique loaded\n" "Copies: depth %d, color %d, reint %d, blend %d, selftex %d\n" "GPU cycles executed: %d (%f per vertex)\n", gpuStats.msProcessingDisplayLists * 1000.0f, @@ -1659,6 +1660,8 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { gpuStats.numReadbacks, gpuStats.numUploads, gpuStats.numDepal, + gpuStats.numReplacerTrackedTex, + gpuStats.numCachedReplacedTextures, gpuStats.numDepthCopies, gpuStats.numColorCopies, gpuStats.numReinterpretCopies, From 9f45271730938381be1daf2a8afa019d24bf0227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 13:43:48 +0100 Subject: [PATCH 133/141] Tex replacer state machine instead of flags --- GPU/Common/ReplacedTexture.cpp | 77 ++++++++++++++++++++++--------- GPU/Common/ReplacedTexture.h | 52 ++++++++++++--------- GPU/Common/TextureCacheCommon.cpp | 28 ++++++++--- GPU/Common/TextureReplacer.cpp | 20 ++++++-- 4 files changed, 120 insertions(+), 57 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 40b81c8a9b..f4b3c52b7c 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -56,33 +56,46 @@ private: bool ReplacedTexture::IsReady(double budget) { _assert_(vfs_ != nullptr); - lastUsed_ = time_now_d(); - if (threadWaitable_ && !threadWaitable_->WaitFor(budget)) { + + switch (State()) { + case ReplacementState::ACTIVE: + case ReplacementState::NOT_FOUND: + if (threadWaitable_) { + if (!threadWaitable_->WaitFor(budget)) { + return false; + } + // Successfully waited! Can get rid of it. + threadWaitable_->WaitAndRelease(); + threadWaitable_ = nullptr; + } + lastUsed_ = time_now_d(); + return true; + case ReplacementState::UNINITIALIZED: + // _dbg_assert_(false); return false; + case ReplacementState::CANCEL_INIT: + case ReplacementState::PENDING: + return false; + case ReplacementState::PREPARED: + // We're gonna need to spawn a task. + break; } - // Loaded already, or not yet on a thread? - if (initDone_ && levelData_ && !levelData_->data.empty()) { - // TODO: lock? - levelData_->lastUsed = lastUsed_; - return true; - } + lastUsed_ = time_now_d(); // Let's not even start a new texture if we're already behind. if (budget < 0.0) return false; - if (!prepareDone_) - return false; - if (threadWaitable_) - delete threadWaitable_; + _assert_(!threadWaitable_); threadWaitable_ = new LimitedWaitable(); g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); if (threadWaitable_->WaitFor(budget)) { - // If we finished all the levels, we're done. - return initDone_ && levelData_ != nullptr && !levelData_->data.empty(); + // If we successfully wait here, we're done. The thread will set state accordingly. + _assert_(State() == ReplacementState::ACTIVE || State() == ReplacementState::NOT_FOUND); + return true; } - + SetState(ReplacementState::PENDING); // Still pending on thread. return false; } @@ -90,19 +103,18 @@ bool ReplacedTexture::IsReady(double budget) { void ReplacedTexture::Prepare(VFSBackend *vfs) { std::unique_lock lock(mutex_); this->vfs_ = vfs; - if (cancelPrepare_) { - initDone_ = true; + + if (State() == ReplacementState::CANCEL_INIT) { return; } for (int i = 0; i < (int)levels_.size(); ++i) { - if (cancelPrepare_) + if (State() == ReplacementState::CANCEL_INIT) break; PrepareData(i); } - initDone_ = true; - if (!cancelPrepare_ && threadWaitable_) + if (threadWaitable_) threadWaitable_->Notify(); } @@ -141,12 +153,14 @@ void ReplacedTexture::PrepareData(int level) { std::unique_ptr zim(new uint8_t[fileSize]); if (!zim) { ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); + SetState(ReplacementState::NOT_FOUND); cleanup(); return; } if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) { ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str()); + SetState(ReplacementState::NOT_FOUND); cleanup(); return; } @@ -156,6 +170,7 @@ void ReplacedTexture::PrepareData(int level) { if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) { if (w > info.w || h > info.h) { ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + SetState(ReplacementState::NOT_FOUND); cleanup(); return; } @@ -184,11 +199,13 @@ void ReplacedTexture::PrepareData(int level) { pngdata.resize(vfs_->Read(openFile, &pngdata[0], fileSize)); if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) { ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message); + SetState(ReplacementState::NOT_FOUND); cleanup(); return; } if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) { ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str()); + SetState(ReplacementState::NOT_FOUND); cleanup(); return; } @@ -206,6 +223,7 @@ void ReplacedTexture::PrepareData(int level) { out.resize(info.w * info.h * 4); if (!png_image_finish_read(&png, nullptr, &out[0], info.w * 4, nullptr)) { ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message); + SetState(ReplacementState::NOT_FOUND); cleanup(); out.resize(0); return; @@ -221,6 +239,7 @@ void ReplacedTexture::PrepareData(int level) { } } + SetState(ReplacementState::ACTIVE); cleanup(); } @@ -235,13 +254,13 @@ void ReplacedTexture::PurgeIfOlder(double t) { std::lock_guard guard(levelData_->lock); levelData_->data.clear(); // This means we have to reload. If we never purge any, there's no need. - initDone_ = false; + SetState(ReplacementState::PREPARED); } } ReplacedTexture::~ReplacedTexture() { if (threadWaitable_) { - cancelPrepare_ = true; + SetState(ReplacementState::CANCEL_INIT); std::unique_lock lock(mutex_); threadWaitable_->WaitAndRelease(); @@ -258,7 +277,7 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { _assert_msg_((size_t)level < levels_.size(), "Invalid miplevel"); _assert_msg_(out != nullptr && rowPitch > 0, "Invalid out/pitch"); - if (!initDone_) { + if (State() != ReplacementState::ACTIVE) { WARN_LOG(G3D, "Init not done yet"); return false; } @@ -293,3 +312,15 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { return true; } + +const char *StateString(ReplacementState state) { + switch (state) { + case ReplacementState::UNINITIALIZED: return "UNINITIALIZED"; + case ReplacementState::PREPARED: return "PREPARED"; + case ReplacementState::PENDING: return "PENDING"; + case ReplacementState::NOT_FOUND: return "NOT_FOUND"; + case ReplacementState::ACTIVE: return "ACTIVE"; + case ReplacementState::CANCEL_INIT: return "CANCEL_INIT"; + default: return "N/A"; + } +} diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index 735be70590..d36e53cc50 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -22,6 +22,7 @@ #include "Common/File/VFS/VFS.h" #include "Common/GPU/thin3d.h" +#include "Common/Log.h" struct ReplacedLevelsCache; class TextureReplacer; @@ -59,26 +60,36 @@ struct ReplacedTextureLevel { ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string *outMagic); +enum class ReplacementState : uint32_t { + UNINITIALIZED, + PREPARED, // We located the texture files but have not started the thread. + PENDING, + NOT_FOUND, // Also used on error loading the images. + ACTIVE, + CANCEL_INIT, +}; + +const char *StateString(ReplacementState state); + // These aren't actually all replaced, they can also represent a placeholder for a not-found -// replacement. +// replacement (state_ == NOT_FOUND). struct ReplacedTexture { ~ReplacedTexture(); - inline bool Valid() const { - if (!initDone_) - return false; - return !levels_.empty(); + inline ReplacementState State() const { + return state_; } - inline bool IsInvalid() const { - if (!initDone_) - return false; - return levels_.empty(); + void SetState(ReplacementState state) { + _dbg_assert_(state != state_); +#ifdef _DEBUG + WARN_LOG(G3D, "Texture %s changed from %s to %s", logId_.c_str(), StateString(state_), StateString(state)); +#endif + state_ = state; } bool GetSize(int level, int &w, int &h) const { - if (!initDone_) - return false; + _dbg_assert_(State() == ReplacementState::ACTIVE); if ((size_t)level < levels_.size()) { w = levels_[level].w; h = levels_[level].h; @@ -88,18 +99,13 @@ struct ReplacedTexture { } int NumLevels() const { - if (!initDone_) - return 0; + _dbg_assert_(State() == ReplacementState::ACTIVE); return (int)levels_.size(); } Draw::DataFormat Format() const { - if (initDone_) { - return fmt; - } else { - // Shouldn't get here. - return Draw::DataFormat::UNDEFINED; - } + _dbg_assert_(State() == ReplacementState::ACTIVE); + return fmt; } u8 AlphaStatus() const { @@ -114,8 +120,10 @@ protected: void PrepareData(int level); void PurgeIfOlder(double t); + std::string logId_; + std::vector levels_; - ReplacedLevelsCache *levelData_; + ReplacedLevelsCache *levelData_ = nullptr; ReplacedTextureAlpha alphaStatus_ = ReplacedTextureAlpha::UNKNOWN; double lastUsed_ = 0.0; @@ -123,9 +131,7 @@ protected: std::mutex mutex_; Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM. - bool cancelPrepare_ = false; - bool initDone_ = false; - bool prepareDone_ = false; + ReplacementState state_ = ReplacementState::UNINITIALIZED; VFSBackend *vfs_ = nullptr; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 5d914c061b..c15876c538 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -535,16 +535,27 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { int d0 = 1; ReplacedTexture *replaced = FindReplacement(entry, w0, h0, d0); if (replaced) { - if (replaced->IsInvalid()) { + // This texture is pending a replacement load. + // So check the replacer if it's reached a conclusion. + switch (replaced->State()) { + case ReplacementState::NOT_FOUND: + // Didn't find a replacement, so stop looking. entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; if (g_Config.bSaveNewTextures) { - // Load once more to actually save. + // Load it once more to actually save it. Since we don't set STATUS_TO_REPLACE, we won't end up looping. match = false; reason = "replacing"; } - } else { + break; + case ReplacementState::ACTIVE: + // There is now replacement data available! + // Just reload the texture to process the replacement. match = false; reason = "replacing"; + break; + default: + // We'll just wait for a result. + break; } } } @@ -1514,19 +1525,22 @@ ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & u64 cachekey = replacer_.Enabled() ? entry->CacheKey() : 0; ReplacedTexture *replaced = replacer_.FindReplacement(cachekey, entry->fullhash, w, h, budget); if (!replaced) { + // TODO: Remove the flag here? + // entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; replacementTimeThisFrame_ += time_now_d() - replaceStart; return nullptr; } if (replaced->IsReady(budget)) { - if (replaced->GetSize(0, w, h)) { - // Consider it already "scaled." + if (replaced->State() == ReplacementState::ACTIVE && replaced->GetSize(0, w, h)) { + // Consider it already "scaled.". entry->status |= TexCacheEntry::STATUS_IS_SCALED; } // Remove the flag, even if it was invalid. entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - } else if (!replaced->IsInvalid()) { + } else if (replaced->State() == ReplacementState::PREPARED) { + // Make sure we keep polling. entry->status |= TexCacheEntry::STATUS_TO_REPLACE; } replacementTimeThisFrame_ += time_now_d() - replaceStart; @@ -2795,7 +2809,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt if (canReplace) { plan.replaced = FindReplacement(entry, plan.w, plan.h, plan.depth); - plan.replaceValid = plan.replaced ? plan.replaced->Valid() : false; + plan.replaceValid = plan.replaced ? plan.replaced->State() == ReplacementState::ACTIVE : false; } else { plan.replaced = nullptr; plan.replaceValid = false; diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 296acc0031..e2c4418897 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -235,6 +235,8 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { bool filenameWarning = false; if (ini.HasSection("hashes")) { + aliases_.clear(); + auto hashes = ini.GetOrCreateSection("hashes")->ToMap(); // Format: hashname = filename.png bool checkFilenames = g_Config.bSaveNewTextures && !g_Config.bIgnoreTextureFilenames && !vfsIsZip_; @@ -475,7 +477,7 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, ReplacementCacheKey replacementKey(cachekey, hash); auto it = cache_.find(replacementKey); if (it != cache_.end()) { - if (!it->second->prepareDone_ && budget > 0.0) { + if (it->second->State() == ReplacementState::UNINITIALIZED && budget > 0.0) { // We don't do this on a thread, but we only do it while within budget. PopulateReplacement(it->second, cachekey, hash, w, h); } @@ -485,11 +487,12 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, // Okay, let's construct the result. ReplacedTexture *result = new ReplacedTexture(); - cache_[replacementKey] = result; result->vfs_ = this->vfs_; if (budget > 0.0) { + _dbg_assert_(result->State() == ReplacementState::UNINITIALIZED); PopulateReplacement(result, cachekey, hash, w, h); } + cache_[replacementKey] = result; return result; } @@ -507,11 +510,13 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement, &ignored); if (!foundReplacement || ignored) { + WARN_LOG(G3D, "Not found/ignored: %s (%d, %d)", hashfiles.c_str(), (int)foundReplacement, (int)ignored); // nothing to do? - texture->prepareDone_ = true; + texture->SetState(ReplacementState::NOT_FOUND); return; } + texture->logId_ = hashfiles; // INFO_LOG(G3D, "Found: %s", hashfiles.c_str()); std::vector filenames; @@ -564,9 +569,16 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey break; } + if (texture->levels_.empty()) { + // Bad. + texture->SetState(ReplacementState::NOT_FOUND); + texture->levelData_ = nullptr; + return; + } + // Populate the data pointer. texture->levelData_ = &levelCache_[hashfiles]; - texture->prepareDone_ = true; + texture->SetState(ReplacementState::PREPARED); } bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { From 07fc551e4eca67cabc79f9d84807a4b002624a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 14:16:14 +0100 Subject: [PATCH 134/141] State fixes, disable logspam --- GPU/Common/ReplacedTexture.cpp | 1 + GPU/Common/ReplacedTexture.h | 13 +++++-------- GPU/Common/TextureCacheCommon.cpp | 18 +++++++++++++----- GPU/Common/TextureCacheCommon.h | 3 ++- GPU/Common/TextureReplacer.cpp | 4 +++- GPU/GLES/TextureCacheGLES.cpp | 3 ++- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index f4b3c52b7c..60073457fe 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -54,6 +54,7 @@ private: LimitedWaitable *waitable_; }; +// This can only return true if ACTIVE or NOT_FOUND. bool ReplacedTexture::IsReady(double budget) { _assert_(vfs_ != nullptr); diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index d36e53cc50..d7527e4bde 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -83,19 +83,16 @@ struct ReplacedTexture { void SetState(ReplacementState state) { _dbg_assert_(state != state_); #ifdef _DEBUG - WARN_LOG(G3D, "Texture %s changed from %s to %s", logId_.c_str(), StateString(state_), StateString(state)); + // WARN_LOG(G3D, "Texture %s changed state from %s to %s", logId_.c_str(), StateString(state_), StateString(state)); #endif state_ = state; } - bool GetSize(int level, int &w, int &h) const { + void GetSize(int level, int *w, int *h) const { _dbg_assert_(State() == ReplacementState::ACTIVE); - if ((size_t)level < levels_.size()) { - w = levels_[level].w; - h = levels_[level].h; - return true; - } - return false; + _dbg_assert_(level < levels_.size()); + *w = levels_[level].w; + *h = levels_[level].h; } int NumLevels() const { diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index c15876c538..fde5fff389 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -540,6 +540,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { switch (replaced->State()) { case ReplacementState::NOT_FOUND: // Didn't find a replacement, so stop looking. + WARN_LOG(G3D, "No replacement for texture %dx%d", w0, h0); entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; if (g_Config.bSaveNewTextures) { // Load it once more to actually save it. Since we don't set STATUS_TO_REPLACE, we won't end up looping. @@ -1532,16 +1533,23 @@ ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & } if (replaced->IsReady(budget)) { - if (replaced->State() == ReplacementState::ACTIVE && replaced->GetSize(0, w, h)) { + if (replaced->State() == ReplacementState::ACTIVE) { + replaced->GetSize(0, &w, &h); // Consider it already "scaled.". entry->status |= TexCacheEntry::STATUS_IS_SCALED; } // Remove the flag, even if it was invalid. entry->status &= ~TexCacheEntry::STATUS_TO_REPLACE; - } else if (replaced->State() == ReplacementState::PREPARED) { + } + + switch (replaced->State()) { + case ReplacementState::PREPARED: + case ReplacementState::PENDING: + case ReplacementState::UNINITIALIZED: // Make sure we keep polling. entry->status |= TexCacheEntry::STATUS_TO_REPLACE; + break; } replacementTimeThisFrame_ += time_now_d() - replaceStart; return replaced; @@ -2825,7 +2833,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt plan.levelsToCreate = std::min(plan.levelsToLoad, plan.levelsToCreate); plan.badMipSizes = false; // But, we still need to create the texture at a larger size. - plan.replaced->GetSize(0, plan.createW, plan.createH); + plan.replaced->GetSize(0, &plan.createW, &plan.createH); } else { if (replacer_.Enabled() && !plan.replaceValid && plan.depth == 1 && canReplace) { ReplacedTextureDecodeInfo replacedInfo; @@ -2833,7 +2841,6 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt replacedInfo.cachekey = entry->CacheKey(); replacedInfo.hash = entry->fullhash; replacedInfo.addr = entry->addr; - replacedInfo.isVideo = plan.isVideo; replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0; replacedInfo.scaleFactor = plan.scaleFactor; replacedInfo.fmt = Draw::DataFormat::R8G8B8A8_UNORM; @@ -2877,7 +2884,8 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i PROFILE_THIS_SCOPE("decodetex"); - if (plan.replaceValid && plan.replaced->GetSize(srcLevel, w, h)) { + if (plan.replaceValid) { + plan.replaced->GetSize(srcLevel, &w, &h); double replaceStart = time_now_d(); plan.replaced->CopyLevelTo(srcLevel, data, stride); replacementTimeThisFrame_ += time_now_d() - replaceStart; diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index a51660d364..ff44e35664 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -292,7 +292,8 @@ struct BuildTexturePlan { bool decodeToClut8; void GetMipSize(int level, int *w, int *h) const { - if (replaceValid && replaced->GetSize(level, *w, *h)) { + if (replaceValid) { + replaced->GetSize(level, w, h); return; } if (depth == 1) { diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index e2c4418897..095b83ae43 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -491,6 +491,8 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, if (budget > 0.0) { _dbg_assert_(result->State() == ReplacementState::UNINITIALIZED); PopulateReplacement(result, cachekey, hash, w, h); + } else { + // WARN_LOG(G3D, "Postponing preparing texture (%dx%d)", w, h); } cache_[replacementKey] = result; return result; @@ -510,7 +512,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement, &ignored); if (!foundReplacement || ignored) { - WARN_LOG(G3D, "Not found/ignored: %s (%d, %d)", hashfiles.c_str(), (int)foundReplacement, (int)ignored); + // WARN_LOG(G3D, "Not found/ignored: %s (%d, %d)", hashfiles.c_str(), (int)foundReplacement, (int)ignored); // nothing to do? texture->SetState(ReplacementState::NOT_FOUND); return; diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 307a43c1e0..d44a883bcc 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -246,7 +246,8 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { int th = plan.createH; Draw::DataFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); - if (plan.replaced && plan.replaced->GetSize(plan.baseLevelSrc, tw, th)) { + if (plan.replaceValid) { + plan.replaced->GetSize(plan.baseLevelSrc, &tw, &th); dstFmt = plan.replaced->Format(); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = Draw::DataFormat::R8G8B8A8_UNORM; From 53791b1816ae508e1448485ab54c16d390db22c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 14:18:42 +0100 Subject: [PATCH 135/141] Restore functionality of the allowVideo flag, while preserving the early-out. --- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/Common/TextureReplacer.h | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index fde5fff389..7387534084 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1514,7 +1514,7 @@ ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & return nullptr; } - if (entry->status & TexCacheEntry::STATUS_VIDEO) { + if ((entry->status & TexCacheEntry::STATUS_VIDEO) && replacer_.AllowVideo()) { return nullptr; } diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 27c6b1b83f..cae53a67f1 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -106,9 +106,8 @@ public: void Init(); void NotifyConfigChanged(); - inline bool Enabled() { - return enabled_; - } + bool Enabled() const { return enabled_; } + bool AllowVideo() const { return allowVideo_; } u32 ComputeHash(u32 addr, int bufw, int w, int h, GETextureFormat fmt, u16 maxSeenV); From f459a8f71a5a8a93fa519d7bf559d55a4d5b9425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 14:58:44 +0100 Subject: [PATCH 136/141] Fix ini-less texture replacement --- GPU/Common/ReplacedTexture.cpp | 6 ++-- GPU/Common/TextureCacheCommon.cpp | 1 + GPU/Common/TextureReplacer.cpp | 55 ++++++++++++++++++------------- GPU/Common/TextureReplacer.h | 1 + 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 60073457fe..76748f6b64 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -93,7 +93,7 @@ bool ReplacedTexture::IsReady(double budget) { g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_)); if (threadWaitable_->WaitFor(budget)) { // If we successfully wait here, we're done. The thread will set state accordingly. - _assert_(State() == ReplacementState::ACTIVE || State() == ReplacementState::NOT_FOUND); + _assert_(State() == ReplacementState::ACTIVE || State() == ReplacementState::NOT_FOUND || State() == ReplacementState::CANCEL_INIT); return true; } SetState(ReplacementState::PENDING); @@ -135,8 +135,10 @@ void ReplacedTexture::PrepareData(int level) { std::vector &out = levelData_->data[level]; // Already populated from cache. - if (!out.empty()) + if (!out.empty()) { + SetState(ReplacementState::ACTIVE); return; + } ReplacedImageType imageType; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 7387534084..fe016955b3 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2843,6 +2843,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt replacedInfo.addr = entry->addr; replacedInfo.isFinal = (entry->status & TexCacheEntry::STATUS_TO_SCALE) == 0; replacedInfo.scaleFactor = plan.scaleFactor; + replacedInfo.isVideo = plan.isVideo; replacedInfo.fmt = Draw::DataFormat::R8G8B8A8_UNORM; plan.saveTexture = replacer_.WillSave(replacedInfo); } diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 095b83ae43..3fd199ffc6 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -104,12 +104,12 @@ void TextureReplacer::NotifyConfigChanged() { if (enabled_) { basePath_ = GetSysDirectory(DIRECTORY_TEXTURES) / gameID_; - Path newTextureDir = basePath_ / NEW_TEXTURE_DIR; + newTextureDir_ = basePath_ / NEW_TEXTURE_DIR; // If we're saving, auto-create the directory. - if (g_Config.bSaveNewTextures && !File::Exists(newTextureDir)) { - File::CreateFullPath(newTextureDir); - File::CreateEmptyFile(newTextureDir / ".nomedia"); + if (g_Config.bSaveNewTextures && !File::Exists(newTextureDir_)) { + File::CreateFullPath(newTextureDir_); + File::CreateEmptyFile(newTextureDir_ / ".nomedia"); } enabled_ = File::IsDirectory(basePath_); @@ -507,22 +507,33 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey cachekey = cachekey & 0xFFFFFFFFULL; } - bool foundReplacement = false; + bool foundAlias = false; bool ignored = false; - const std::string hashfiles = LookupHashFile(cachekey, hash, &foundReplacement, &ignored); + std::string hashfiles = LookupHashFile(cachekey, hash, &foundAlias, &ignored); - if (!foundReplacement || ignored) { + if (ignored) { // WARN_LOG(G3D, "Not found/ignored: %s (%d, %d)", hashfiles.c_str(), (int)foundReplacement, (int)ignored); // nothing to do? texture->SetState(ReplacementState::NOT_FOUND); return; } - texture->logId_ = hashfiles; - // INFO_LOG(G3D, "Found: %s", hashfiles.c_str()); - std::vector filenames; - SplitString(hashfiles, '|', filenames); + + if (!foundAlias) { + // We'll just need to generate the names for each level. + // By default, we look for png since that's also what's dumped. + // For other file formats, use the ini to create aliases. + filenames.resize(MAX_MIP_LEVELS); + for (int level = 0; level < filenames.size(); level++) { + filenames[level] = HashName(cachekey, hash, level) + ".png"; + } + texture->logId_ = filenames[0]; + hashfiles = filenames[0]; // This is used as the key in the data cache. + } else { + texture->logId_ = hashfiles; + SplitString(hashfiles, '|', filenames); + } for (int i = 0; i < std::min(MAX_MIP_LEVELS, (int)filenames.size()); ++i) { if (filenames[i].empty()) { @@ -532,6 +543,12 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey const Path filename = basePath_ / filenames[i]; + VFSFileReference *fileRef = vfs_->GetFile(filenames[i].c_str()); + if (!fileRef) { + // If the file doesn't exist, let's just bail immediately here. + break; + } + // TODO: Here, if we find a file with multiple built-in mipmap levels, // we'll have to change a bit how things work... ReplacedTextureLevel level; @@ -543,12 +560,6 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey bool good; - VFSFileReference *fileRef = vfs_->GetFile(filenames[i].c_str()); - if (!fileRef) { - // If the file doesn't exist, let's just bail immediately here. - break; - } - level.fileRef = fileRef; good = PopulateLevel(level, false); @@ -748,19 +759,19 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl cachekey = cachekey & 0xFFFFFFFFULL; } - bool found = false, ignored = false; - std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &found, &ignored); + bool foundAlias = false, ignored = false; + std::string hashfile = LookupHashFile(cachekey, replacedInfo.hash, &foundAlias, &ignored); // If it's empty, it's an ignored hash, we intentionally don't save. - if (found) { + if (foundAlias || ignored) { // If it exists, must've been decoded and saved as a new texture already. return; } - // Generate a new filename. + // Generate a new PNG filename, complete with level. hashfile = HashName(cachekey, replacedInfo.hash, level) + ".png"; - const Path filename = basePath_ / hashfile; + const Path filename = newTextureDir_ / hashfile; ReplacementCacheKey replacementKey(cachekey, replacedInfo.hash); auto it = savedCache_.find(replacementKey); diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index cae53a67f1..08cac604e3 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -153,6 +153,7 @@ protected: bool ignoreMipmap_ = false; std::string gameID_; Path basePath_; + Path newTextureDir_; ReplacedTextureHash hash_ = ReplacedTextureHash::QUICK; VFSBackend *vfs_ = nullptr; From 207e2259e723a1efdbf830b7c341abe2df5f912c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 15:39:45 +0100 Subject: [PATCH 137/141] Cleanup texture saving a bit --- GPU/Common/TextureReplacer.cpp | 54 ++++++++++++++++++---------------- GPU/Common/TextureReplacer.h | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 3fd199ffc6..430b45f21c 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -671,8 +671,11 @@ public: int h = 0; int pitch = 0; // bytes - Path basePath; - std::string hashfile; + Path filename; + Path saveFilename; + bool createSaveDirectory; + Path saveDirectory; + u32 replacedInfoHash = 0; bool skipIfExists = false; @@ -687,9 +690,6 @@ public: } void Run() override { - const Path filename = basePath / hashfile; - const Path saveFilename = basePath / NEW_TEXTURE_DIR / hashfile; - // Should we skip writing if the newly saved data already exists? if (skipIfExists && File::Exists(saveFilename)) { return; @@ -699,19 +699,9 @@ public: if (File::Exists(filename)) return; - // Create subfolder as needed. -#ifdef _WIN32 - size_t slash = hashfile.find_last_of("/\\"); -#else - size_t slash = hashfile.find_last_of("/"); -#endif - if (slash != hashfile.npos) { - // Create any directory structure as needed. - const Path saveDirectory = basePath / NEW_TEXTURE_DIR / hashfile.substr(0, slash); - if (!File::Exists(saveDirectory)) { - File::CreateFullPath(saveDirectory); - File::CreateEmptyFile(saveDirectory / ".nomedia"); - } + if (createSaveDirectory && !File::Exists(saveDirectory)) { + File::CreateFullPath(saveDirectory); + File::CreateEmptyFile(saveDirectory / ".nomedia"); } png_image png{}; @@ -771,8 +761,6 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl // Generate a new PNG filename, complete with level. hashfile = HashName(cachekey, replacedInfo.hash, level) + ".png"; - const Path filename = newTextureDir_ / hashfile; - ReplacementCacheKey replacementKey(cachekey, replacedInfo.hash); auto it = savedCache_.find(replacementKey); bool skipIfExists = false; @@ -808,11 +796,27 @@ void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &repl pitch = w * 4; SaveTextureTask *task = new SaveTextureTask(std::move(saveBuf)); + + task->filename = basePath_ / hashfile; + task->saveFilename = newTextureDir_ / hashfile; + task->createSaveDirectory = false; + + // Create subfolder as needed. +#ifdef _WIN32 + size_t slash = hashfile.find_last_of("/\\"); +#else + size_t slash = hashfile.find_last_of("/"); +#endif + if (slash != hashfile.npos) { + // Does this ever happen? + // Create any directory structure as needed. + task->saveDirectory = newTextureDir_ / hashfile.substr(0, slash); + task->createSaveDirectory = true; + } + task->w = w; task->h = h; task->pitch = pitch; - task->basePath = basePath_; - task->hashfile = hashfile; task->replacedInfoHash = replacedInfo.hash; task->skipIfExists = skipIfExists; g_threadManager.EnqueueTask(task); // We don't care about waiting for the task. It'll be fine. @@ -923,16 +927,16 @@ bool TextureReplacer::FindFiltering(u64 cachekey, u32 hash, TextureFiltering *fo return false; } -std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement, bool *ignored) { +std::string TextureReplacer::LookupHashFile(u64 cachekey, u32 hash, bool *foundAlias, bool *ignored) { ReplacementCacheKey key(cachekey, hash); auto alias = LookupWildcard(aliases_, key, cachekey, hash, ignoreAddress_); if (alias != aliases_.end()) { // Note: this will be blank if explicitly ignored. - *foundReplacement = true; + *foundAlias = true; *ignored = alias->second.empty(); return alias->second; } - *foundReplacement = false; + *foundAlias = false; *ignored = false; return ""; } diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 08cac604e3..bb3c020a98 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -137,7 +137,7 @@ protected: void ParseReduceHashRange(const std::string& key, const std::string& value); bool LookupHashRange(u32 addr, int &w, int &h); float LookupReduceHashRange(int& w, int& h); - std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundReplacement, bool *ignored); + std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundAlias, bool *ignored); std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); From 36f78a46c1eb7f5ac59e59bce2d872ca5f59f322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 17:21:23 +0100 Subject: [PATCH 138/141] Rename PREPARED->POPULATED --- GPU/Common/ReplacedTexture.cpp | 6 +++--- GPU/Common/ReplacedTexture.h | 2 +- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/Common/TextureReplacer.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 76748f6b64..356cfc1858 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -77,7 +77,7 @@ bool ReplacedTexture::IsReady(double budget) { case ReplacementState::CANCEL_INIT: case ReplacementState::PENDING: return false; - case ReplacementState::PREPARED: + case ReplacementState::POPULATED: // We're gonna need to spawn a task. break; } @@ -257,7 +257,7 @@ void ReplacedTexture::PurgeIfOlder(double t) { std::lock_guard guard(levelData_->lock); levelData_->data.clear(); // This means we have to reload. If we never purge any, there's no need. - SetState(ReplacementState::PREPARED); + SetState(ReplacementState::POPULATED); } } @@ -319,7 +319,7 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { const char *StateString(ReplacementState state) { switch (state) { case ReplacementState::UNINITIALIZED: return "UNINITIALIZED"; - case ReplacementState::PREPARED: return "PREPARED"; + case ReplacementState::POPULATED: return "PREPARED"; case ReplacementState::PENDING: return "PENDING"; case ReplacementState::NOT_FOUND: return "NOT_FOUND"; case ReplacementState::ACTIVE: return "ACTIVE"; diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index d7527e4bde..b46261b46b 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -62,7 +62,7 @@ ReplacedImageType Identify(VFSBackend *vfs, VFSOpenFile *openFile, std::string * enum class ReplacementState : uint32_t { UNINITIALIZED, - PREPARED, // We located the texture files but have not started the thread. + POPULATED, // We located the texture files but have not started the thread. PENDING, NOT_FOUND, // Also used on error loading the images. ACTIVE, diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index fe016955b3..5c056eb87f 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1544,7 +1544,7 @@ ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int & } switch (replaced->State()) { - case ReplacementState::PREPARED: + case ReplacementState::POPULATED: case ReplacementState::PENDING: case ReplacementState::UNINITIALIZED: // Make sure we keep polling. diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 430b45f21c..a026d31c7b 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -591,7 +591,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey // Populate the data pointer. texture->levelData_ = &levelCache_[hashfiles]; - texture->SetState(ReplacementState::PREPARED); + texture->SetState(ReplacementState::POPULATED); } bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { From 96111537e2709cb0307c77ba5314f225091901da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 17:26:41 +0100 Subject: [PATCH 139/141] Move PopulateLevel to the texture --- GPU/Common/ReplacedTexture.cpp | 49 ++++++++++++++++++++++++++++++++ GPU/Common/ReplacedTexture.h | 2 ++ GPU/Common/TextureReplacer.cpp | 51 +--------------------------------- GPU/Common/TextureReplacer.h | 1 - 4 files changed, 52 insertions(+), 51 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 356cfc1858..9ec0fae2bb 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -101,6 +101,55 @@ bool ReplacedTexture::IsReady(double budget) { return false; } +bool ReplacedTexture::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { + bool good = false; + + if (!level.fileRef) { + if (!ignoreError) + ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str()); + return false; + } + + size_t fileSize; + VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef, &fileSize); + if (!file) { + return false; + } + + std::string magic; + auto imageType = Identify(vfs_, file, &magic); + + if (imageType == ReplacedImageType::ZIM) { + uint32_t ignore = 0; + struct ZimHeader { + uint32_t magic; + uint32_t w; + uint32_t h; + uint32_t flags; + } header; + good = vfs_->Read(file, &header, sizeof(header)) == sizeof(header); + level.w = header.w; + level.h = header.h; + good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; + } else if (imageType == ReplacedImageType::PNG) { + PNGHeaderPeek headerPeek; + good = vfs_->Read(file, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); + if (good && headerPeek.IsValidPNGHeader()) { + level.w = headerPeek.Width(); + level.h = headerPeek.Height(); + good = true; + } else { + ERROR_LOG(G3D, "Could not get PNG dimensions: %s (zip)", level.file.ToVisualString().c_str()); + good = false; + } + } else { + ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format %s", level.file.ToVisualString().c_str(), magic.c_str()); + } + vfs_->CloseFile(file); + + return good; +} + void ReplacedTexture::Prepare(VFSBackend *vfs) { std::unique_lock lock(mutex_); this->vfs_ = vfs; diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index b46261b46b..2e71b92210 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -112,6 +112,8 @@ struct ReplacedTexture { bool IsReady(double budget); bool CopyLevelTo(int level, void *out, int rowPitch); + bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); + protected: void Prepare(VFSBackend *vfs); void PrepareData(int level); diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index a026d31c7b..846146fffc 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -561,7 +561,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey bool good; level.fileRef = fileRef; - good = PopulateLevel(level, false); + good = texture->PopulateLevel(level, false); // We pad files that have been hashrange'd so they are the same texture size. level.w = (level.w * w) / newW; @@ -594,55 +594,6 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey texture->SetState(ReplacementState::POPULATED); } -bool TextureReplacer::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { - bool good = false; - - if (!level.fileRef) { - if (!ignoreError) - ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str()); - return false; - } - - size_t fileSize; - VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef, &fileSize); - if (!file) { - return false; - } - - std::string magic; - auto imageType = Identify(vfs_, file, &magic); - - if (imageType == ReplacedImageType::ZIM) { - uint32_t ignore = 0; - struct ZimHeader { - uint32_t magic; - uint32_t w; - uint32_t h; - uint32_t flags; - } header; - good = vfs_->Read(file, &header, sizeof(header)) == sizeof(header); - level.w = header.w; - level.h = header.h; - good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; - } else if (imageType == ReplacedImageType::PNG) { - PNGHeaderPeek headerPeek; - good = vfs_->Read(file, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); - if (good && headerPeek.IsValidPNGHeader()) { - level.w = headerPeek.Width(); - level.h = headerPeek.Height(); - good = true; - } else { - ERROR_LOG(G3D, "Could not get PNG dimensions: %s (zip)", level.file.ToVisualString().c_str()); - good = false; - } - } else { - ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format %s", level.file.ToVisualString().c_str(), magic.c_str()); - } - vfs_->CloseFile(file); - - return good; -} - static bool WriteTextureToPNG(png_imagep image, const Path &filename, int convert_to_8bit, const void *buffer, png_int_32 row_stride, const void *colormap) { FILE *fp = File::OpenCFile(filename, "wb"); if (!fp) { diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index bb3c020a98..8c6ee6e60d 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -140,7 +140,6 @@ protected: std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundAlias, bool *ignored); std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); - bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); bool enabled_ = false; bool allowVideo_ = false; From 9e8e2e41fa1e320b573713b17476349db4a6c201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 17:43:12 +0100 Subject: [PATCH 140/141] Move most of Populate to ReplacedTexture, although not the cpp --- GPU/Common/ReplacedTexture.h | 9 ++-- GPU/Common/TextureReplacer.cpp | 92 ++++++++++++++++++++++------------ GPU/Common/TextureReplacer.h | 3 +- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index 2e71b92210..8ed2250a8b 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -71,6 +71,8 @@ enum class ReplacementState : uint32_t { const char *StateString(ReplacementState state); +struct ReplacementDesc; + // These aren't actually all replaced, they can also represent a placeholder for a not-found // replacement (state_ == NOT_FOUND). struct ReplacedTexture { @@ -112,14 +114,15 @@ struct ReplacedTexture { bool IsReady(double budget); bool CopyLevelTo(int level, void *out, int rowPitch); - bool PopulateLevel(ReplacedTextureLevel &level, bool ignoreError); + void FinishPopulate(const ReplacementDesc &desc); + std::string logId_; -protected: +private: void Prepare(VFSBackend *vfs); void PrepareData(int level); void PurgeIfOlder(double t); - std::string logId_; + bool PopulateLevel(ReplacedTextureLevel & level, bool ignoreError); std::vector levels_; ReplacedLevelsCache *levelData_ = nullptr; diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 846146fffc..de5f579587 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -498,19 +498,41 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, return result; } +struct ReplacementDesc { + int newW; + int newH; + u64 cachekey; + u32 hash; + int w; + int h; + std::string hashfiles; + Path basePath; + bool foundAlias; + std::vector filenames; + std::string logId; + ReplacedLevelsCache *cache; +}; + void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey, u32 hash, int w, int h) { - int newW = w; - int newH = h; - LookupHashRange(cachekey >> 32, newW, newH); + ReplacementDesc desc; + desc.newW = w; + desc.newH = h; + desc.w = w; + desc.h = h; + desc.cachekey = cachekey; + desc.hash = hash; + desc.basePath = basePath_; + LookupHashRange(cachekey >> 32, desc.newW, desc.newH); if (ignoreAddress_) { cachekey = cachekey & 0xFFFFFFFFULL; } - bool foundAlias = false; + desc.foundAlias = false; bool ignored = false; - std::string hashfiles = LookupHashFile(cachekey, hash, &foundAlias, &ignored); + desc.hashfiles = LookupHashFile(cachekey, hash, &desc.foundAlias, &ignored); + // Early-out for ignored textures, let's not bother even starting a thread task. if (ignored) { // WARN_LOG(G3D, "Not found/ignored: %s (%d, %d)", hashfiles.c_str(), (int)foundReplacement, (int)ignored); // nothing to do? @@ -518,32 +540,41 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey return; } - std::vector filenames; - - if (!foundAlias) { + if (!desc.foundAlias) { // We'll just need to generate the names for each level. // By default, we look for png since that's also what's dumped. // For other file formats, use the ini to create aliases. - filenames.resize(MAX_MIP_LEVELS); - for (int level = 0; level < filenames.size(); level++) { - filenames[level] = HashName(cachekey, hash, level) + ".png"; + desc.filenames.resize(MAX_MIP_LEVELS); + for (int level = 0; level < desc.filenames.size(); level++) { + desc.filenames[level] = TextureReplacer::HashName(desc.cachekey, desc.hash, level) + ".png"; } - texture->logId_ = filenames[0]; - hashfiles = filenames[0]; // This is used as the key in the data cache. + desc.logId = desc.filenames[0]; + desc.hashfiles = desc.filenames[0]; // This is used as the key in the data cache. } else { - texture->logId_ = hashfiles; - SplitString(hashfiles, '|', filenames); + desc.logId = desc.hashfiles; + SplitString(desc.hashfiles, '|', desc.filenames); } - for (int i = 0; i < std::min(MAX_MIP_LEVELS, (int)filenames.size()); ++i) { - if (filenames[i].empty()) { + desc.cache = &levelCache_[desc.hashfiles]; + + texture->FinishPopulate(desc); +} + +void ReplacedTexture::FinishPopulate(const ReplacementDesc &desc) { + logId_ = desc.logId; + levelData_ = desc.cache; + + // The rest can be done on the thread. + + for (int i = 0; i < std::min(MAX_MIP_LEVELS, (int)desc.filenames.size()); ++i) { + if (desc.filenames[i].empty()) { // Out of valid mip levels. Bail out. break; } - const Path filename = basePath_ / filenames[i]; + const Path filename = desc.basePath / desc.filenames[i]; - VFSFileReference *fileRef = vfs_->GetFile(filenames[i].c_str()); + VFSFileReference *fileRef = vfs_->GetFile(desc.filenames[i].c_str()); if (!fileRef) { // If the file doesn't exist, let's just bail immediately here. break; @@ -555,43 +586,42 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey level.file = filename; if (i == 0) { - texture->fmt = Draw::DataFormat::R8G8B8A8_UNORM; + fmt = Draw::DataFormat::R8G8B8A8_UNORM; } bool good; level.fileRef = fileRef; - good = texture->PopulateLevel(level, false); + good = PopulateLevel(level, false); // We pad files that have been hashrange'd so they are the same texture size. - level.w = (level.w * w) / newW; - level.h = (level.h * h) / newH; + level.w = (level.w * desc.w) / desc.newW; + level.h = (level.h * desc.h) / desc.newH; if (good && i != 0) { // Check that the mipmap size is correct. Can't load mips of the wrong size. - if (level.w != (texture->levels_[0].w >> i) || level.h != (texture->levels_[0].h >> i)) { - WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, texture->levels_[0].w >> i, texture->levels_[0].h >> i, i, filename.c_str()); + if (level.w != (levels_[0].w >> i) || level.h != (levels_[0].h >> i)) { + WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, levels_[0].w >> i, levels_[0].h >> i, i, filename.c_str()); good = false; } } if (good) - texture->levels_.push_back(level); + levels_.push_back(level); // Otherwise, we're done loading mips (bad PNG or bad size, either way.) else break; } - if (texture->levels_.empty()) { + if (levels_.empty()) { // Bad. - texture->SetState(ReplacementState::NOT_FOUND); - texture->levelData_ = nullptr; + SetState(ReplacementState::NOT_FOUND); + levelData_ = nullptr; return; } // Populate the data pointer. - texture->levelData_ = &levelCache_[hashfiles]; - texture->SetState(ReplacementState::POPULATED); + SetState(ReplacementState::POPULATED); } static bool WriteTextureToPNG(png_imagep image, const Path &filename, int convert_to_8bit, const void *buffer, png_int_32 row_stride, const void *colormap) { diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 8c6ee6e60d..ed0df878c9 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -129,6 +129,8 @@ public: int GetNumTrackedTextures() const { return (int)cache_.size(); } int GetNumCachedReplacedTextures() const { return (int)levelCache_.size(); } + static std::string HashName(u64 cachekey, u32 hash, int level); + protected: bool LoadIni(); bool LoadIniValues(IniFile &ini, bool isOverride = false); @@ -138,7 +140,6 @@ protected: bool LookupHashRange(u32 addr, int &w, int &h); float LookupReduceHashRange(int& w, int& h); std::string LookupHashFile(u64 cachekey, u32 hash, bool *foundAlias, bool *ignored); - std::string HashName(u64 cachekey, u32 hash, int level); void PopulateReplacement(ReplacedTexture *result, u64 cachekey, u32 hash, int w, int h); bool enabled_ = false; From ed7947c94bb469d6158f569b0ac6ee6102ed570b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Mar 2023 17:49:30 +0100 Subject: [PATCH 141/141] Finish the move of Populate to ReplacedTexture --- GPU/Common/ReplacedTexture.cpp | 64 ++++++++++++++++++++++++++ GPU/Common/ReplacedTexture.h | 17 ++++++- GPU/Common/TextureReplacer.cpp | 84 +--------------------------------- 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 9ec0fae2bb..8265f19e82 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -101,6 +101,70 @@ bool ReplacedTexture::IsReady(double budget) { return false; } +void ReplacedTexture::FinishPopulate(const ReplacementDesc &desc) { + logId_ = desc.logId; + levelData_ = desc.cache; + + // TODO: The rest can be done on the thread. + + for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc.filenames.size()); ++i) { + if (desc.filenames[i].empty()) { + // Out of valid mip levels. Bail out. + break; + } + + const Path filename = desc.basePath / desc.filenames[i]; + + VFSFileReference *fileRef = vfs_->GetFile(desc.filenames[i].c_str()); + if (!fileRef) { + // If the file doesn't exist, let's just bail immediately here. + break; + } + + // TODO: Here, if we find a file with multiple built-in mipmap levels, + // we'll have to change a bit how things work... + ReplacedTextureLevel level; + level.file = filename; + + if (i == 0) { + fmt = Draw::DataFormat::R8G8B8A8_UNORM; + } + + bool good; + + level.fileRef = fileRef; + good = PopulateLevel(level, false); + + // We pad files that have been hashrange'd so they are the same texture size. + level.w = (level.w * desc.w) / desc.newW; + level.h = (level.h * desc.h) / desc.newH; + + if (good && i != 0) { + // Check that the mipmap size is correct. Can't load mips of the wrong size. + if (level.w != (levels_[0].w >> i) || level.h != (levels_[0].h >> i)) { + WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, levels_[0].w >> i, levels_[0].h >> i, i, filename.c_str()); + good = false; + } + } + + if (good) + levels_.push_back(level); + // Otherwise, we're done loading mips (bad PNG or bad size, either way.) + else + break; + } + + if (levels_.empty()) { + // Bad. + SetState(ReplacementState::NOT_FOUND); + levelData_ = nullptr; + return; + } + + // Populate the data pointer. + SetState(ReplacementState::POPULATED); +} + bool ReplacedTexture::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) { bool good = false; diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index 8ed2250a8b..2911c5fef1 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -47,6 +47,8 @@ enum class ReplacedImageType { INVALID, }; +static const int MAX_REPLACEMENT_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP. + // Metadata about a given texture level. struct ReplacedTextureLevel { int w = 0; @@ -71,7 +73,20 @@ enum class ReplacementState : uint32_t { const char *StateString(ReplacementState state); -struct ReplacementDesc; +struct ReplacementDesc { + int newW; + int newH; + uint64_t cachekey; + uint32_t hash; + int w; + int h; + std::string hashfiles; + Path basePath; + bool foundAlias; + std::vector filenames; + std::string logId; + ReplacedLevelsCache *cache; +}; // These aren't actually all replaced, they can also represent a placeholder for a not-found // replacement (state_ == NOT_FOUND). diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index de5f579587..ebe7bb4f25 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -52,7 +52,6 @@ static const std::string INI_FILENAME = "textures.ini"; static const std::string ZIP_FILENAME = "textures.zip"; static const std::string NEW_TEXTURE_DIR = "new/"; static const int VERSION = 1; -static const int MAX_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP. static const double MAX_CACHE_SIZE = 4.0; static inline ReplacedImageType IdentifyMagic(const uint8_t magic[4]) { @@ -498,21 +497,6 @@ ReplacedTexture *TextureReplacer::FindReplacement(u64 cachekey, u32 hash, int w, return result; } -struct ReplacementDesc { - int newW; - int newH; - u64 cachekey; - u32 hash; - int w; - int h; - std::string hashfiles; - Path basePath; - bool foundAlias; - std::vector filenames; - std::string logId; - ReplacedLevelsCache *cache; -}; - void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey, u32 hash, int w, int h) { ReplacementDesc desc; desc.newW = w; @@ -544,7 +528,7 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey // We'll just need to generate the names for each level. // By default, we look for png since that's also what's dumped. // For other file formats, use the ini to create aliases. - desc.filenames.resize(MAX_MIP_LEVELS); + desc.filenames.resize(MAX_REPLACEMENT_MIP_LEVELS); for (int level = 0; level < desc.filenames.size(); level++) { desc.filenames[level] = TextureReplacer::HashName(desc.cachekey, desc.hash, level) + ".png"; } @@ -560,70 +544,6 @@ void TextureReplacer::PopulateReplacement(ReplacedTexture *texture, u64 cachekey texture->FinishPopulate(desc); } -void ReplacedTexture::FinishPopulate(const ReplacementDesc &desc) { - logId_ = desc.logId; - levelData_ = desc.cache; - - // The rest can be done on the thread. - - for (int i = 0; i < std::min(MAX_MIP_LEVELS, (int)desc.filenames.size()); ++i) { - if (desc.filenames[i].empty()) { - // Out of valid mip levels. Bail out. - break; - } - - const Path filename = desc.basePath / desc.filenames[i]; - - VFSFileReference *fileRef = vfs_->GetFile(desc.filenames[i].c_str()); - if (!fileRef) { - // If the file doesn't exist, let's just bail immediately here. - break; - } - - // TODO: Here, if we find a file with multiple built-in mipmap levels, - // we'll have to change a bit how things work... - ReplacedTextureLevel level; - level.file = filename; - - if (i == 0) { - fmt = Draw::DataFormat::R8G8B8A8_UNORM; - } - - bool good; - - level.fileRef = fileRef; - good = PopulateLevel(level, false); - - // We pad files that have been hashrange'd so they are the same texture size. - level.w = (level.w * desc.w) / desc.newW; - level.h = (level.h * desc.h) / desc.newH; - - if (good && i != 0) { - // Check that the mipmap size is correct. Can't load mips of the wrong size. - if (level.w != (levels_[0].w >> i) || level.h != (levels_[0].h >> i)) { - WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, levels_[0].w >> i, levels_[0].h >> i, i, filename.c_str()); - good = false; - } - } - - if (good) - levels_.push_back(level); - // Otherwise, we're done loading mips (bad PNG or bad size, either way.) - else - break; - } - - if (levels_.empty()) { - // Bad. - SetState(ReplacementState::NOT_FOUND); - levelData_ = nullptr; - return; - } - - // Populate the data pointer. - SetState(ReplacementState::POPULATED); -} - static bool WriteTextureToPNG(png_imagep image, const Path &filename, int convert_to_8bit, const void *buffer, png_int_32 row_stride, const void *colormap) { FILE *fp = File::OpenCFile(filename, "wb"); if (!fp) { @@ -654,7 +574,7 @@ public: Path filename; Path saveFilename; - bool createSaveDirectory; + bool createSaveDirectory = false; Path saveDirectory; u32 replacedInfoHash = 0;