From 30a3cb622aed2e870fecc6300b2a17c24e76cf15 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 30 Aug 2022 21:09:12 +0200 Subject: [PATCH 001/326] Force clearing framebuffer for GTA games --- Common/GPU/OpenGL/GLQueueRunner.cpp | 8 ++++++++ Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + assets/compat.ini | 17 +++++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index f29ffbe2cd..986167903d 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -14,6 +14,7 @@ #include "Common/Data/Convert/SmallDataConvert.h" #include "Core/Reporting.h" +#include "Core/System.h" #include "GLQueueRunner.h" #include "GLRenderManager.h" #include "DataFormatGL.h" @@ -768,6 +769,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last PerformBindFramebufferAsRenderTarget(step); + if (IsVRBuild() && PSP_CoreParameter().compat.flags().VRClearFramebuffer) { + if ((strcmp(step.tag, "FramebufferSwitch") == 0) && (step.commands.size() > 50)) { + glClearColor(0.5f, 0.75f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + } + if (first) { glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 84d42df284..4487c3f203 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -97,6 +97,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "AllowLargeFBTextureOffsets", &flags_.AllowLargeFBTextureOffsets); CheckSetting(iniFile, gameID, "AtracLoopHack", &flags_.AtracLoopHack); CheckSetting(iniFile, gameID, "DeswizzleDepth", &flags_.DeswizzleDepth); + CheckSetting(iniFile, gameID, "VRClearFramebuffer", &flags_.VRClearFramebuffer); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index dc22c45e0f..941844bd85 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -87,6 +87,7 @@ struct CompatFlags { bool AllowLargeFBTextureOffsets; bool AtracLoopHack; bool DeswizzleDepth; + bool VRClearFramebuffer; }; class IniFile; diff --git a/assets/compat.ini b/assets/compat.ini index 27b8fd571b..692173597f 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1263,3 +1263,20 @@ UCKS45048 = true UCJS18030 = true UCJS18047 = true NPJG00015 = true + +[VRClearFramebuffer] +#Grand Theft Auto: Liberty City Stories +ULUS10041 = true +ULES00151 = true +ULJM05255 = true +ULJM05359 = true +ULJM05885 = true +NPJH50825 = true + +#Grand Theft Auto: Vice City Stories +ULES00502 = true +ULUS10160 = true +ULJM05297 = true +ULJM05395 = true +ULJM05884 = true +NPJH50827 = true From 4b129bb9c3afefe9d71d2850ea975631023544bd Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 7 Sep 2022 15:52:50 +0200 Subject: [PATCH 002/326] OpenXR - Refactor VR compat integration --- Common/GPU/OpenGL/GLQueueRunner.cpp | 8 ++------ Common/VR/PPSSPPVR.cpp | 30 +++++++++++++++++++++++++++++ Common/VR/PPSSPPVR.h | 19 ++++++++++++++++++ Core/Compatibility.cpp | 2 +- Core/Compatibility.h | 2 +- assets/compat.ini | 2 +- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index f6ca57725c..253df013fe 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -14,7 +14,6 @@ #include "Common/Data/Convert/SmallDataConvert.h" #include "Core/Reporting.h" -#include "Core/System.h" #include "GLQueueRunner.h" #include "GLRenderManager.h" #include "DataFormatGL.h" @@ -771,11 +770,8 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last PerformBindFramebufferAsRenderTarget(step); - if (IsVRBuild() && PSP_CoreParameter().compat.flags().VRClearFramebuffer) { - if ((strcmp(step.tag, "FramebufferSwitch") == 0) && (step.commands.size() > 50)) { - glClearColor(0.5f, 0.75f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - } + if (IsVRBuild()) { + PreGLRenderPass(step); } if (first) { diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 16b170e1fb..23f4c2d550 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -8,7 +8,9 @@ #include "Core/HLE/sceDisplay.h" #include "Core/Config.h" #include "Core/KeyMap.h" +#include "Core/System.h" +static long vrCompat[VR_COMPAT_MAX]; /* ================================================================================ @@ -210,6 +212,31 @@ void UpdateVRScreenKey(const KeyInput &key) { /* ================================================================================ +// VR games compatibility + +================================================================================ +*/ + +void PreGLRenderPass(const GLRStep& step) { + if (vrCompat[VR_COMPAT_SKYPLANE]) { + if (strcmp(step.tag, "FramebufferSwitch") == 0) { + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + } +} + +long GetVRCompat(VRCompat flag) { + return vrCompat[flag]; +} + +void SetVRCompat(VRCompat flag, long value) { + vrCompat[flag] = value; +} + +/* +================================================================================ + VR rendering integration ================================================================================ @@ -235,6 +262,9 @@ bool PreVRRender() { } VR_SetConfig(VR_CONFIG_3D_GEOMETRY_COUNT, VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) / 2); + // Set compatibility + vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.flags().VRSkyplane; + // Set customizations VR_SetConfig(VR_CONFIG_6DOF_ENABLED, g_Config.bEnable6DoF); VR_SetConfig(VR_CONFIG_CANVAS_DISTANCE, g_Config.iCanvasDistance); diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index b41b69f1be..8ce9ca26b0 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -2,6 +2,15 @@ #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" +#include "GPU/OpenGL/GLQueueRunner.h" + +enum VRCompat { + //compatibility tweaks + VR_COMPAT_SKYPLANE, + + //end + VR_COMPAT_MAX +}; #ifdef OPENXR @@ -13,6 +22,11 @@ void GetVRResolutionPerEye(int* width, int* height); void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale); void UpdateVRScreenKey(const KeyInput &key); +// VR games compatibility +void PreGLRenderPass(const GLRStep& step); +long GetVRCompat(VRCompat flag); +void SetVRCompat(VRCompat flag, long value); + // VR rendering integration void BindVRFramebuffer(); bool PreVRRender(); @@ -36,6 +50,11 @@ inline void GetVRResolutionPerEye(int* width, int* height) {} inline void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale) {} inline void UpdateVRScreenKey(const KeyInput &key) {} +// VR games compatibility +inline void PreGLRenderPass(const GLRStep& step) {} +inline long GetVRCompat(VRCompat flag) { return 0; } +inline void SetVRCompat(VRCompat flag, long value) {} + // VR rendering integration inline void BindVRFramebuffer() {} inline bool PreVRRender() { return false; } diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 00608ceb7c..d46a93e850 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -98,7 +98,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DeswizzleDepth", &flags_.DeswizzleDepth); CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin); CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn); - CheckSetting(iniFile, gameID, "VRClearFramebuffer", &flags_.VRClearFramebuffer); + CheckSetting(iniFile, gameID, "VRSkyplane", &flags_.VRSkyplane); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index f5e3dc0c31..07a2b99be0 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -88,7 +88,7 @@ struct CompatFlags { bool DeswizzleDepth; bool SplitFramebufferMargin; bool ForceLowerResolutionForEffectsOn; - bool VRClearFramebuffer; + bool VRSkyplane; }; class IniFile; diff --git a/assets/compat.ini b/assets/compat.ini index 2c2d430dc6..3c10463505 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1295,7 +1295,7 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true -[VRClearFramebuffer] +[VRSkyplane] # Grand Theft Auto: Liberty City Stories ULUS10041 = true ULES00151 = true From 52af230210ba33dd7925c1e3bead9e9689898462 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 7 Sep 2022 19:57:26 +0200 Subject: [PATCH 003/326] OpenXR - Use fog color to replace the sky --- Common/GPU/OpenGL/GLQueueRunner.cpp | 6 ++++ Common/VR/PPSSPPVR.cpp | 48 ++++++++++++++++++++++++++--- Common/VR/PPSSPPVR.h | 7 +++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 253df013fe..6603255077 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -821,6 +821,9 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last CHECK_GL_ERROR_IF_DEBUG(); auto &commands = step.commands; for (const auto &c : commands) { + if (IsVRBuild()) { + PreGLCommand(c); + } switch (c.cmd) { case GLRRenderCommand::DEPTH: if (c.depth.enabled) { @@ -1328,6 +1331,9 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last Crash(); break; } + if (IsVRBuild()) { + PostGLCommand(c); + } } for (int i = 0; i < 7; i++) { diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 23f4c2d550..28cf89a701 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -10,6 +10,9 @@ #include "Core/KeyMap.h" #include "Core/System.h" +#include "GPU/OpenGL/GLRenderManager.h" +#include "GPU/GPUState.h" + static long vrCompat[VR_COMPAT_MAX]; /* @@ -217,11 +220,48 @@ void UpdateVRScreenKey(const KeyInput &key) { ================================================================================ */ +bool IsSkyPlane(const GLRRenderData& data) { + if ((data.drawIndexed.count <= 60) && !vrCompat[VR_USE_CLIP]) { + //TODO:fix HUD items + return true; + } + return false; +} + void PreGLRenderPass(const GLRStep& step) { - if (vrCompat[VR_COMPAT_SKYPLANE]) { - if (strcmp(step.tag, "FramebufferSwitch") == 0) { - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + if (IsFlatVRScene()) { + return; + } + vrCompat[VR_COMPAT_GEOMETRY] = strcmp(step.tag, "FramebufferSwitch") == 0; + + // Clear the screen with fog color, only for passes containing geometry (and no lens flares) + if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY] && (step.commands.size() > 50)) { + float color[4]; + Uint8x3ToFloat4(color, gstate.fogcolor); + glClearColor(color[0], color[1], color[2], 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0, 0, 0, 1); + } +} + +void PreGLCommand(const GLRRenderData& data) { + if (IsFlatVRScene()) { + return; + } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { + if (data.cmd == GLRRenderCommand::BINDPROGRAM) { + vrCompat[VR_USE_CLIP] = data.program.program->use_clip_distance0; + } else if ((data.cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(data)) { + glColorMask(false, false, false, false); + } + } +} + +void PostGLCommand(const GLRRenderData& data) { + if (IsFlatVRScene()) { + return; + } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { + if ((data.cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(data)) { + glColorMask(true, true, true, true); } } } diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 8ce9ca26b0..4ec33406b9 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -8,6 +8,9 @@ enum VRCompat { //compatibility tweaks VR_COMPAT_SKYPLANE, + //render state + VR_COMPAT_GEOMETRY, VR_USE_CLIP, + //end VR_COMPAT_MAX }; @@ -24,6 +27,8 @@ void UpdateVRScreenKey(const KeyInput &key); // VR games compatibility void PreGLRenderPass(const GLRStep& step); +void PreGLCommand(const GLRRenderData& data); +void PostGLCommand(const GLRRenderData& data); long GetVRCompat(VRCompat flag); void SetVRCompat(VRCompat flag, long value); @@ -52,6 +57,8 @@ inline void UpdateVRScreenKey(const KeyInput &key) {} // VR games compatibility inline void PreGLRenderPass(const GLRStep& step) {} +inline void PreGLCommand(const GLRRenderData& data) {} +inline void PostGLCommand(const GLRRenderData& data) {} inline long GetVRCompat(VRCompat flag) { return 0; } inline void SetVRCompat(VRCompat flag, long value) {} From 6e4849b910e354c4590e38970821f06eb8a55ef7 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 8 Sep 2022 18:05:12 +0200 Subject: [PATCH 004/326] OpenXR - Refactor --- Common/GPU/OpenGL/GLQueueRunner.cpp | 13 ++++--------- Common/VR/PPSSPPVR.cpp | 20 +++++++++++--------- Common/VR/PPSSPPVR.h | 17 +---------------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 6603255077..5646fa16b4 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -770,9 +770,8 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last PerformBindFramebufferAsRenderTarget(step); - if (IsVRBuild()) { - PreGLRenderPass(step); - } + bool isVR = IsVRBuild(); + if (isVR) PreGLRenderPass(step); if (first) { glDisable(GL_DEPTH_TEST); @@ -821,9 +820,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last CHECK_GL_ERROR_IF_DEBUG(); auto &commands = step.commands; for (const auto &c : commands) { - if (IsVRBuild()) { - PreGLCommand(c); - } + if (isVR) PreGLCommand(c); switch (c.cmd) { case GLRRenderCommand::DEPTH: if (c.depth.enabled) { @@ -1331,9 +1328,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last Crash(); break; } - if (IsVRBuild()) { - PostGLCommand(c); - } + if (isVR) PostGLCommand(c); } for (int i = 0; i < 7; i++) { diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 28cf89a701..2a3f9e5fc2 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -10,9 +10,19 @@ #include "Core/KeyMap.h" #include "Core/System.h" -#include "GPU/OpenGL/GLRenderManager.h" #include "GPU/GPUState.h" +enum VRCompat { + //compatibility tweaks + VR_COMPAT_SKYPLANE, + + //render state + VR_COMPAT_GEOMETRY, VR_USE_CLIP, + + //end + VR_COMPAT_MAX +}; + static long vrCompat[VR_COMPAT_MAX]; /* @@ -266,14 +276,6 @@ void PostGLCommand(const GLRRenderData& data) { } } -long GetVRCompat(VRCompat flag) { - return vrCompat[flag]; -} - -void SetVRCompat(VRCompat flag, long value) { - vrCompat[flag] = value; -} - /* ================================================================================ diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 4ec33406b9..d177e3455a 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -1,19 +1,8 @@ #pragma once +#include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" -#include "GPU/OpenGL/GLQueueRunner.h" - -enum VRCompat { - //compatibility tweaks - VR_COMPAT_SKYPLANE, - - //render state - VR_COMPAT_GEOMETRY, VR_USE_CLIP, - - //end - VR_COMPAT_MAX -}; #ifdef OPENXR @@ -29,8 +18,6 @@ void UpdateVRScreenKey(const KeyInput &key); void PreGLRenderPass(const GLRStep& step); void PreGLCommand(const GLRRenderData& data); void PostGLCommand(const GLRRenderData& data); -long GetVRCompat(VRCompat flag); -void SetVRCompat(VRCompat flag, long value); // VR rendering integration void BindVRFramebuffer(); @@ -59,8 +46,6 @@ inline void UpdateVRScreenKey(const KeyInput &key) {} inline void PreGLRenderPass(const GLRStep& step) {} inline void PreGLCommand(const GLRRenderData& data) {} inline void PostGLCommand(const GLRRenderData& data) {} -inline long GetVRCompat(VRCompat flag) { return 0; } -inline void SetVRCompat(VRCompat flag, long value) {} // VR rendering integration inline void BindVRFramebuffer() {} From c9f08a85c66a2e91042e215763136e83deccecb0 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 8 Sep 2022 18:25:10 +0200 Subject: [PATCH 005/326] OpenXR - Do not mashup Common/GPU --- Common/VR/PPSSPPVR.cpp | 19 +++++-------------- Common/VR/PPSSPPVR.h | 16 ++++++++++++++++ GPU/GLES/ShaderManagerGLES.cpp | 1 + 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 2a3f9e5fc2..1ff55eaceb 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -10,19 +10,6 @@ #include "Core/KeyMap.h" #include "Core/System.h" -#include "GPU/GPUState.h" - -enum VRCompat { - //compatibility tweaks - VR_COMPAT_SKYPLANE, - - //render state - VR_COMPAT_GEOMETRY, VR_USE_CLIP, - - //end - VR_COMPAT_MAX -}; - static long vrCompat[VR_COMPAT_MAX]; /* @@ -247,7 +234,7 @@ void PreGLRenderPass(const GLRStep& step) { // Clear the screen with fog color, only for passes containing geometry (and no lens flares) if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY] && (step.commands.size() > 50)) { float color[4]; - Uint8x3ToFloat4(color, gstate.fogcolor); + Uint8x3ToFloat4(color, vrCompat[VR_COMPAT_FOG_COLOR]); glClearColor(color[0], color[1], color[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0, 0, 0, 1); @@ -276,6 +263,10 @@ void PostGLCommand(const GLRRenderData& data) { } } +void SetVRCompat(VRCompat flag, long value) { + vrCompat[flag] = value; +} + /* ================================================================================ diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index d177e3455a..5a7bc4ac9c 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -4,6 +4,20 @@ #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" +enum VRCompat { + //compatibility tweaks + VR_COMPAT_SKYPLANE, + + //render state + VR_COMPAT_GEOMETRY, VR_USE_CLIP, + + //uniforms + VR_COMPAT_FOG_COLOR, + + //end + VR_COMPAT_MAX +}; + #ifdef OPENXR // VR app flow integration @@ -18,6 +32,7 @@ void UpdateVRScreenKey(const KeyInput &key); void PreGLRenderPass(const GLRStep& step); void PreGLCommand(const GLRRenderData& data); void PostGLCommand(const GLRRenderData& data); +void SetVRCompat(VRCompat flag, long value); // VR rendering integration void BindVRFramebuffer(); @@ -46,6 +61,7 @@ inline void UpdateVRScreenKey(const KeyInput &key) {} inline void PreGLRenderPass(const GLRStep& step) {} inline void PreGLCommand(const GLRRenderData& data) {} inline void PostGLCommand(const GLRRenderData& data) {} +inline void SetVRCompat(VRCompat flag, long value) {} // VR rendering integration inline void BindVRFramebuffer() {} diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index a1ae7edb61..8b0abb8b37 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -354,6 +354,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu if (IsVRBuild()) { dirty |= DIRTY_VIEWMATRIX; + SetVRCompat(VR_COMPAT_FOG_COLOR, gstate.fogcolor); } if (!dirty) return; From 214a5572c33c4d4f63c363791cdad832b4157b35 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 8 Sep 2022 19:00:41 +0200 Subject: [PATCH 006/326] OpenXR - Compile fix --- Common/GPU/OpenGL/GLQueueRunner.cpp | 8 ++++---- Common/VR/PPSSPPVR.cpp | 27 ++++++++++++++++----------- Common/VR/PPSSPPVR.h | 13 ++++++------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 5646fa16b4..79b3efa0b6 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -770,8 +770,8 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last PerformBindFramebufferAsRenderTarget(step); - bool isVR = IsVRBuild(); - if (isVR) PreGLRenderPass(step); + const bool isVR = IsVRBuild(); + if (isVR) PreGLRenderPass(&step); if (first) { glDisable(GL_DEPTH_TEST); @@ -820,7 +820,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last CHECK_GL_ERROR_IF_DEBUG(); auto &commands = step.commands; for (const auto &c : commands) { - if (isVR) PreGLCommand(c); + if (isVR) PreGLCommand(&c); switch (c.cmd) { case GLRRenderCommand::DEPTH: if (c.depth.enabled) { @@ -1328,7 +1328,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last Crash(); break; } - if (isVR) PostGLCommand(c); + if (isVR) PostGLCommand(&c); } for (int i = 0; i < 7; i++) { diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 1ff55eaceb..af828ebe18 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -1,3 +1,5 @@ +#include "Common/GPU/OpenGL/GLRenderManager.h" + #include "Common/VR/PPSSPPVR.h" #include "Common/VR/VRBase.h" #include "Common/VR/VRInput.h" @@ -217,22 +219,23 @@ void UpdateVRScreenKey(const KeyInput &key) { ================================================================================ */ -bool IsSkyPlane(const GLRRenderData& data) { - if ((data.drawIndexed.count <= 60) && !vrCompat[VR_USE_CLIP]) { +bool IsSkyPlane(const GLRRenderData* data) { + if ((data->drawIndexed.count <= 60) && !vrCompat[VR_USE_CLIP]) { //TODO:fix HUD items return true; } return false; } -void PreGLRenderPass(const GLRStep& step) { +void PreGLRenderPass(const void* step) { if (IsFlatVRScene()) { return; } - vrCompat[VR_COMPAT_GEOMETRY] = strcmp(step.tag, "FramebufferSwitch") == 0; + const auto* glrStep = (const GLRStep*)step; + vrCompat[VR_COMPAT_GEOMETRY] = strcmp(glrStep->tag, "FramebufferSwitch") == 0; // Clear the screen with fog color, only for passes containing geometry (and no lens flares) - if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY] && (step.commands.size() > 50)) { + if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY] && (glrStep->commands.size() > 50)) { float color[4]; Uint8x3ToFloat4(color, vrCompat[VR_COMPAT_FOG_COLOR]); glClearColor(color[0], color[1], color[2], 1.0f); @@ -241,23 +244,25 @@ void PreGLRenderPass(const GLRStep& step) { } } -void PreGLCommand(const GLRRenderData& data) { +void PreGLCommand(const void* data) { if (IsFlatVRScene()) { return; } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { - if (data.cmd == GLRRenderCommand::BINDPROGRAM) { - vrCompat[VR_USE_CLIP] = data.program.program->use_clip_distance0; - } else if ((data.cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(data)) { + const auto* glrData = (const GLRRenderData*)data; + if (glrData->cmd == GLRRenderCommand::BINDPROGRAM) { + vrCompat[VR_USE_CLIP] = glrData->program.program->use_clip_distance0; + } else if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(glrData)) { glColorMask(false, false, false, false); } } } -void PostGLCommand(const GLRRenderData& data) { +void PostGLCommand(const void* data) { if (IsFlatVRScene()) { return; } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { - if ((data.cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(data)) { + const auto* glrData = (const GLRRenderData*)data; + if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(glrData)) { glColorMask(true, true, true, true); } } diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 5a7bc4ac9c..5a944419a9 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -1,6 +1,5 @@ #pragma once -#include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" @@ -29,9 +28,9 @@ void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(con void UpdateVRScreenKey(const KeyInput &key); // VR games compatibility -void PreGLRenderPass(const GLRStep& step); -void PreGLCommand(const GLRRenderData& data); -void PostGLCommand(const GLRRenderData& data); +void PreGLRenderPass(const void* step); +void PreGLCommand(const void* data); +void PostGLCommand(const void* data); void SetVRCompat(VRCompat flag, long value); // VR rendering integration @@ -58,9 +57,9 @@ inline void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTou inline void UpdateVRScreenKey(const KeyInput &key) {} // VR games compatibility -inline void PreGLRenderPass(const GLRStep& step) {} -inline void PreGLCommand(const GLRRenderData& data) {} -inline void PostGLCommand(const GLRRenderData& data) {} +inline void PreGLRenderPass(const void* step) {} +inline void PreGLCommand(const void* data) {} +inline void PostGLCommand(const void* data) {} inline void SetVRCompat(VRCompat flag, long value) {} // VR rendering integration From 9ede9bd82232947861fa46255e6181e3c346a2e4 Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 12 Sep 2022 23:01:44 +0200 Subject: [PATCH 007/326] OpenXR - Refactor variable name --- Common/VR/PPSSPPVR.cpp | 2 +- Common/VR/PPSSPPVR.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index af828ebe18..6426d19ba4 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -268,7 +268,7 @@ void PostGLCommand(const void* data) { } } -void SetVRCompat(VRCompat flag, long value) { +void SetVRCompat(VRCompatFlag flag, long value) { vrCompat[flag] = value; } diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 5a944419a9..c6c52b8512 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -3,7 +3,7 @@ #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" -enum VRCompat { +enum VRCompatFlag { //compatibility tweaks VR_COMPAT_SKYPLANE, @@ -31,7 +31,7 @@ void UpdateVRScreenKey(const KeyInput &key); void PreGLRenderPass(const void* step); void PreGLCommand(const void* data); void PostGLCommand(const void* data); -void SetVRCompat(VRCompat flag, long value); +void SetVRCompat(VRCompatFlag flag, long value); // VR rendering integration void BindVRFramebuffer(); @@ -60,7 +60,7 @@ inline void UpdateVRScreenKey(const KeyInput &key) {} inline void PreGLRenderPass(const void* step) {} inline void PreGLCommand(const void* data) {} inline void PostGLCommand(const void* data) {} -inline void SetVRCompat(VRCompat flag, long value) {} +inline void SetVRCompat(VRCompatFlag flag, long value) {} // VR rendering integration inline void BindVRFramebuffer() {} From e4f44e680df193ccea55538cc2a2e0792733c83a Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 13 Sep 2022 21:16:03 +0200 Subject: [PATCH 008/326] OpenXR - Move skyplane compat flag into compatvr.ini --- Common/VR/PPSSPPVR.cpp | 2 +- Core/Compatibility.cpp | 2 +- Core/Compatibility.h | 2 +- assets/compat.ini | 17 ----------------- assets/compatvr.ini | 18 ++++++++++++++++++ 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 9353fdfb6c..88101e7838 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -301,7 +301,7 @@ bool PreVRRender() { VR_SetConfig(VR_CONFIG_3D_GEOMETRY_COUNT, VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) / 2); // Set compatibility - vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.flags().VRSkyplane; + vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.vrCompat().Skyplane; // Set customizations VR_SetConfig(VR_CONFIG_6DOF_ENABLED, g_Config.bEnable6DoF); diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index a99756d7bd..f86ebbbcea 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -46,6 +46,7 @@ void Compatibility::Load(const std::string &gameID) { IniFile compat; // This loads from assets. if (compat.LoadFromVFS("compatvr.ini")) { + CheckSetting(compat, gameID, "Skyplane", &vrCompat_.Skyplane); CheckSetting(compat, gameID, "UnitsPerMeter", &vrCompat_.UnitsPerMeter); } } @@ -108,7 +109,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin); CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn); CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT); - CheckSetting(iniFile, gameID, "VRSkyplane", &flags_.VRSkyplane); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 97396180b0..0fee77b805 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -89,10 +89,10 @@ struct CompatFlags { bool SplitFramebufferMargin; bool ForceLowerResolutionForEffectsOn; bool AllowDownloadCLUT; - bool VRSkyplane; }; struct VRCompat { + bool Skyplane; float UnitsPerMeter; }; diff --git a/assets/compat.ini b/assets/compat.ini index 33d5028a92..3d2846d377 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1302,20 +1302,3 @@ ULKS46087 = true [AllowDownloadCLUT] # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. - -[VRSkyplane] -# Grand Theft Auto: Liberty City Stories -ULUS10041 = true -ULES00151 = true -ULJM05255 = true -ULJM05359 = true -ULJM05885 = true -NPJH50825 = true - -# Grand Theft Auto: Vice City Stories -ULES00502 = true -ULUS10160 = true -ULJM05297 = true -ULJM05395 = true -ULJM05884 = true -NPJH50827 = true diff --git a/assets/compatvr.ini b/assets/compatvr.ini index 4c47ddffa9..c3ec3f926a 100644 --- a/assets/compatvr.ini +++ b/assets/compatvr.ini @@ -29,6 +29,24 @@ # Issue numbers refer to issues on https://github.com/hrydgard/ppsspp/issues # ======================================================================================== + +[Skyplane] +# Grand Theft Auto: Liberty City Stories +ULUS10041 = true +ULES00151 = true +ULJM05255 = true +ULJM05359 = true +ULJM05885 = true +NPJH50825 = true + +# Grand Theft Auto: Vice City Stories +ULES00502 = true +ULUS10160 = true +ULJM05297 = true +ULJM05395 = true +ULJM05884 = true +NPJH50827 = true + [UnitsPerMeter] # Values exported from https://github.com/CarlKenner/ppsspp/tree/VR/Sys/GameSettings ELF000000 = 10.0 From 4803825f836701ed1341e5104a64b814d5a50a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Sep 2022 11:16:30 +0200 Subject: [PATCH 009/326] Buffer copy matching workaround --- GPU/Common/FramebufferManagerCommon.cpp | 35 ++++++++++++++++++++----- GPU/Common/FramebufferManagerCommon.h | 2 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index bac8514562..22a0907518 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1017,7 +1017,7 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { // TODO: This doesn't seem quite right anymore. fmt = displayFormat_; } - DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height); + DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height, RASTER_COLOR, "UpdateFromMemory_DrawPixels"); SetColorUpdated(vfb, gstate_c.skipDrawReason); } else { INFO_LOG(FRAMEBUF, "Invalidating FBO for %08x (%dx%d %s)", vfb->fb_address, vfb->width, vfb->height, GeBufferFormatToString(vfb->fb_format)); @@ -1033,7 +1033,10 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } -void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height) { +void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag) { + // Add depth support later for depth uploads. + _dbg_assert_(channel == RASTER_COLOR); + textureCache_->ForgetLastTexture(); shaderManager_->DirtyLastShader(); // On GL, important that this is BEFORE drawing float u0 = 0.0f, u1 = 1.0f; @@ -1042,7 +1045,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int DrawTextureFlags flags; if (useBufferedRendering_ && vfb && vfb->fbo) { flags = DRAWTEX_LINEAR; - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "DrawPixels"); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag); gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); @@ -1063,6 +1066,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height); if (pixelsTex) { draw_->BindTextures(0, 1, &pixelsTex); + // TODO: Replace with BlitUsingRaster for simplicity. DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); gpuStats.numUploads++; pixelsTex->Release(); @@ -1210,7 +1214,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G // better be fast. Draw::Texture *tex = draw_->CreateTexture(desc); if (!tex) - ERROR_LOG(G3D, "Failed to create drawpixels texture"); + ERROR_LOG(G3D, "Failed to create DrawPixels texture"); return tex; } @@ -1654,7 +1658,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, WARN_LOG_ONCE(btucpy, G3D, "Memcpy fbo upload %08x -> %08x (size: %x)", src, dst, size); FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(src); - DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH); + DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH, RASTER_COLOR, "MemcpyFboUpload_DrawPixels"); SetColorUpdated(dstBuffer, skipDrawReason); RebindFramebuffer("RebindFramebuffer - Memcpy fbo upload"); // This is a memcpy, let's still copy just in case. @@ -1700,7 +1704,16 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p // that of their buffers. Then after matching we try to map the copy to the simplest operation that does // what we need. + // We are only looking at color for now, have not found any block transfers of depth data (although it's plausible). + for (auto vfb : vfbs_) { + // Check for easily detected depth copies for logging purposes. + // Depth copies are not that useful though because you manually need to account for swizzle, so + // not sure if games will use them. + if ((vfb->z_address & 0x3FFFFFFF) == basePtr) { + WARN_LOG_N_TIMES(z_xfer, 5, G3D, "FindTransferFramebuffer: found matching depth buffer, %08x (dest=%d, bpp=%d)", basePtr, (int)destination, bpp); + } + const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF; const u32 vfb_size = ColorBufferByteSize(vfb); @@ -1773,7 +1786,14 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p // Sort candidates by just recency for now, we might add other. for (size_t i = 0; i < candidates.size(); i++) { const BlockTransferRect *candidate = &candidates[i]; - if (!best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq) { + + bool better = !best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq; + if ((candidate->vfb->usageFlags & FB_USAGE_CLUT) && candidate->x_bytes == 0 && candidate->y == 0 && destination) { + // Hack to prioritize copies to clut buffers. + best = candidate; + break; + } + if (better) { best = candidate; } } @@ -2153,6 +2173,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x", srcBasePtr, dstBasePtr); FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(srcBasePtr) + (srcX + srcY * srcStride) * bpp; + int dstBpp = BufferFormatBytesPerPixel(dstRect.vfb->fb_format); float dstXFactor = (float)bpp / dstBpp; if (dstRect.w_bytes / bpp > dstRect.vfb->width || dstRect.h > dstRect.vfb->height) { @@ -2166,7 +2187,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS // Resizing may change the viewport/etc. gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE); } - DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h); + DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h, RASTER_COLOR, "BlockTransferCopy_DrawPixels"); SetColorUpdated(dstRect.vfb, skipDrawReason); RebindFramebuffer("RebindFramebuffer - NotifyBlockTransferAfter"); } diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 905996979b..f7107dc029 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -327,7 +327,7 @@ public: void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes); void DrawFramebufferToOutput(const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat); - void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height); + void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag); size_t NumVFBs() const { return vfbs_.size(); } From 8d00df12a40c8a3db5eec6d70809a257e5f878e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 11 Sep 2022 18:23:57 +0200 Subject: [PATCH 010/326] Detect the CLUT8/8888 situation Implement CLUT8 depal from 8888 textures. Hard to tell if working --- GPU/Common/FragmentShaderGenerator.cpp | 22 ++++++++++++++++++++-- GPU/Common/ShaderId.cpp | 2 +- GPU/Common/TextureCacheCommon.cpp | 21 +++++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 9586d01b48..74acc84886 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -734,8 +734,26 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " }\n"); break; case ShaderDepalMode::CLUT8_8888: - // Not yet implemented. - WRITE(p, " vec4 t = vec4(0.0, 0.0, 0.0, 0.0);\n"); + if (doTextureProjection) { + // We don't use textureProj because we need better control and it's probably not much of a savings anyway. + // However it is good for precision on older hardware like PowerVR. + p.F(" vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord); + } else { + p.F(" vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord); + } + p.C(" vec2 tsize = vec2(textureSize(tex, 0).xy);\n"); + p.C(" uv_round = floor(uv * tsize);\n"); + p.C(" int component = int(uv_round.x) & 3;\n"); + p.C(" uv_round.x *= 0.25;\n"); + p.C(" vec4 t = ivec4(").LoadTexture2D("tex", "ivec2(uv_round)", 0).C(");\n"); + p.C(" int index;\n"); + p.C(" switch (component) {\n"); + p.C(" case 0: index = int(t.x * 255.99); break;\n"); + p.C(" case 1: index = int(t.y * 255.99); break;\n"); + p.C(" case 2: index = int(t.z * 255.99); break;\n"); + p.C(" case 3: index = int(t.w * 255.99); break;\n"); + p.C(" }\n"); + p.C(" t = ").LoadTexture2D("pal", "ivec2(index, 0)", 0).C(";\n"); break; } diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index f873024c39..7ca42ccba6 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -180,7 +180,7 @@ std::string FragmentShaderDesc(const FShaderID &id) { if (id.Bit(FS_BIT_COLOR_DOUBLE)) desc << "2x "; if (id.Bit(FS_BIT_FLATSHADE)) desc << "Flat "; if (id.Bit(FS_BIT_BGRA_TEXTURE)) desc << "BGRA "; - switch ((ShaderDepalMode)id.Bit(FS_BIT_SHADER_DEPAL_MODE)) { + switch ((ShaderDepalMode)id.Bits(FS_BIT_SHADER_DEPAL_MODE, 2)) { case ShaderDepalMode::OFF: break; case ShaderDepalMode::NORMAL: desc << "Depal "; break; case ShaderDepalMode::SMOOTHED: desc << "SmoothDepal "; break; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 77b47e8b9e..65869ad428 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -947,12 +947,13 @@ bool TextureCacheCommon::MatchFramebuffer( return false; } - // Check works for D16 too (???) + // Check works for D16 too. const bool matchingClutFormat = (fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_CLUT16) || (fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_5650) || (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT32) || - (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16); + (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16) || + (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8); const int texBitsPerPixel = std::max(1U, (u32)textureBitsPerPixel[entry.format]); const int byteOffset = texaddr - addr; @@ -991,6 +992,10 @@ bool TextureCacheCommon::MatchFramebuffer( if (fb_stride_in_bytes != tex_stride_in_bytes) { // Probably irrelevant. Although, as we shall see soon, there are exceptions. + // Burnout Dominator lens flare trick special case. + if (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8 && texWidth == 4 && texHeight == 1) { + return true; + } return false; } @@ -1876,7 +1881,7 @@ static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferForma } break; case GE_FORMAT_8888: - if (texFormat == GE_TFMT_CLUT32) { + if (texFormat == GE_TFMT_CLUT32 || texFormat == GE_TFMT_CLUT8) { // clut8 takes a special depal mode. return true; } break; @@ -1964,9 +1969,17 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer samplerKey.mipEnable = false; ApplySamplingParams(samplerKey); + ShaderDepalMode mode = ShaderDepalMode::NORMAL; + if (texFormat == GE_TFMT_CLUT8 && framebuffer->fb_format == GE_FORMAT_8888) { + mode = ShaderDepalMode::CLUT8_8888; + smoothedDepal = false; // just in case + } else if (smoothedDepal) { + mode = ShaderDepalMode::SMOOTHED; + } + // Since we started/ended render passes, might need these. gstate_c.Dirty(DIRTY_DEPAL); - gstate_c.SetUseShaderDepal(smoothedDepal ? ShaderDepalMode::SMOOTHED : ShaderDepalMode::NORMAL); + gstate_c.SetUseShaderDepal(mode); gstate_c.depalFramebufferFormat = framebuffer->fb_format; const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); From d551d0dd8f9cbd326ebde0fc268037029b12986d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 11 Sep 2022 18:51:12 +0200 Subject: [PATCH 011/326] Enable DownloadCLUT and ShaderBitmask for Burnout Dominator --- assets/compat.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index 3d2846d377..ce3afae275 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1152,6 +1152,10 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true +# Burnout Dominator - lens flare effect (issue )#11100) +ULUS10236 = true +ULES00703 = true + [DisableFirstFrameReadback] # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344 UCUS98612 = true @@ -1302,3 +1306,7 @@ ULKS46087 = true [AllowDownloadCLUT] # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. + +# Burnout Dominator - lens flare effect (issue )#11100) +ULUS10236 = true +ULES00703 = true From a52e62a187a595ffab2088144eab41f68da65358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Sep 2022 15:34:32 +0200 Subject: [PATCH 012/326] Enable depth uploads on render-to-clut-buffer. Esoteric but needed for #11100. Compat flag for now. --- Common/GPU/D3D11/thin3d_d3d11.cpp | 1 + Common/GPU/D3D9/thin3d_d3d9.cpp | 1 + Common/GPU/DataFormat.h | 2 + Common/GPU/OpenGL/DataFormatGL.cpp | 8 +++- Common/GPU/OpenGL/DataFormatGL.h | 2 +- Common/GPU/OpenGL/GLQueueRunner.cpp | 4 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 8 ++++ Common/GPU/thin3d.cpp | 3 ++ Common/GPU/thin3d.h | 1 + Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + GPU/Common/FragmentShaderGenerator.cpp | 11 +++-- GPU/Common/FramebufferManagerCommon.cpp | 63 ++++++++++++++++++------- GPU/Common/FramebufferManagerCommon.h | 1 + assets/compat.ini | 8 +++- 15 files changed, 87 insertions(+), 28 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 2bce9ebaff..99e2eb2577 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -471,6 +471,7 @@ static DXGI_FORMAT dataFormatToD3D11(DataFormat format) { case DataFormat::R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; case DataFormat::B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM; case DataFormat::B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + case DataFormat::R16_UNORM: return DXGI_FORMAT_R16_UNORM; case DataFormat::R16_FLOAT: return DXGI_FORMAT_R16_FLOAT; case DataFormat::R16G16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT; case DataFormat::R16G16B16A16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 8554961119..9bfe824c09 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -114,6 +114,7 @@ static const D3DSTENCILOP stencilOpToD3D9[] = { D3DFORMAT FormatToD3DFMT(DataFormat fmt) { switch (fmt) { + case DataFormat::R16_UNORM: return D3DFMT_L16; // closest match, should be a fine substitution if we ignore channels except R. case DataFormat::R8G8B8A8_UNORM: return D3DFMT_A8R8G8B8; case DataFormat::B8G8R8A8_UNORM: return D3DFMT_A8R8G8B8; case DataFormat::R4G4B4A4_UNORM_PACK16: return D3DFMT_A4R4G4B4; // emulated diff --git a/Common/GPU/DataFormat.h b/Common/GPU/DataFormat.h index b07d0a2f34..0c8a93e627 100644 --- a/Common/GPU/DataFormat.h +++ b/Common/GPU/DataFormat.h @@ -32,6 +32,8 @@ enum class DataFormat : uint8_t { A1R5G5B5_UNORM_PACK16, // A1 in the UPPER bit. A1B5G5R5_UNORM_PACK16, // A1 in the UPPER bit. OpenGL-only. + R16_UNORM, + R16_FLOAT, R16G16_FLOAT, R16G16B16A16_FLOAT, diff --git a/Common/GPU/OpenGL/DataFormatGL.cpp b/Common/GPU/OpenGL/DataFormatGL.cpp index 1b8ba63acc..859624b28f 100644 --- a/Common/GPU/OpenGL/DataFormatGL.cpp +++ b/Common/GPU/OpenGL/DataFormatGL.cpp @@ -4,9 +4,15 @@ namespace Draw { // TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage. -bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { +bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { alignment = 4; switch (fmt) { + case DataFormat::R16_UNORM: + internalFormat = GL_R16; + format = GL_R; + type = GL_UNSIGNED_SHORT; + break; + case DataFormat::R8G8B8A8_UNORM: internalFormat = GL_RGBA; format = GL_RGBA; diff --git a/Common/GPU/OpenGL/DataFormatGL.h b/Common/GPU/OpenGL/DataFormatGL.h index 63fc0a22ec..163f155db3 100644 --- a/Common/GPU/OpenGL/DataFormatGL.h +++ b/Common/GPU/OpenGL/DataFormatGL.h @@ -5,6 +5,6 @@ namespace Draw { -bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment); +bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment); } diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index e19075f109..c680062b78 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -385,7 +385,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski GLenum internalFormat, format, type; int alignment; - Thin3DFormatToFormatAndType(step.texture_image.format, internalFormat, format, type, alignment); + Thin3DFormatToGLFormatAndType(step.texture_image.format, internalFormat, format, type, alignment); if (step.texture_image.depth == 1) { glTexImage2D(tex->target, step.texture_image.level, internalFormat, @@ -1279,7 +1279,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last // For things to show in RenderDoc, need to split into glTexImage2D(..., nullptr) and glTexSubImage. GLuint internalFormat, format, type; int alignment; - Thin3DFormatToFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment); + Thin3DFormatToGLFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment); glTexSubImage2D(tex->target, c.texture_subimage.level, c.texture_subimage.x, c.texture_subimage.y, c.texture_subimage.width, c.texture_subimage.height, format, type, c.texture_subimage.data); if (c.texture_subimage.allocType == GLRAllocType::ALIGNED) { FreeAlignedMemory(c.texture_subimage.data); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index f673691224..c836b41499 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -563,6 +563,11 @@ static int GetBpp(VkFormat format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM: return 32; + case VK_FORMAT_R8_UNORM: + return 8; + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R16_UNORM: + return 16; case VK_FORMAT_R4G4B4A4_UNORM_PACK16: case VK_FORMAT_B4G4R4A4_UNORM_PACK16: case VK_FORMAT_R5G5B5A1_UNORM_PACK16: @@ -586,6 +591,9 @@ static VkFormat DataFormatToVulkan(DataFormat format) { case DataFormat::D32F: return VK_FORMAT_D32_SFLOAT; case DataFormat::D32F_S8: return VK_FORMAT_D32_SFLOAT_S8_UINT; case DataFormat::S8: return VK_FORMAT_S8_UINT; + + case DataFormat::R16_UNORM: return VK_FORMAT_R16_UNORM; + case DataFormat::R16_FLOAT: return VK_FORMAT_R16_SFLOAT; case DataFormat::R16G16_FLOAT: return VK_FORMAT_R16G16_SFLOAT; case DataFormat::R16G16B16A16_FLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT; diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index d712195c7b..1cc3c11fa0 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -33,6 +33,9 @@ size_t DataFormatSizeInBytes(DataFormat fmt) { case DataFormat::R8G8B8A8_SNORM: return 4; case DataFormat::R8G8B8A8_UINT: return 4; case DataFormat::R8G8B8A8_SINT: return 4; + + case DataFormat::R16_UNORM: return 2; + case DataFormat::R16_FLOAT: return 2; case DataFormat::R16G16_FLOAT: return 4; case DataFormat::R16G16B16A16_FLOAT: return 8; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 6196f494f9..46b6305fd9 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -557,6 +557,7 @@ typedef std::functionfb_format = params.fb_format; vfb->usageFlags = FB_USAGE_RENDER_COLOR; - u32 byteSize = ColorBufferByteSize(vfb); - if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + byteSize > framebufRangeEnd_) { - framebufRangeEnd_ = params.fb_address + byteSize; + u32 colorByteSize = ColorBufferByteSize(vfb); + if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + colorByteSize > framebufRangeEnd_) { + framebufRangeEnd_ = params.fb_address + colorByteSize; } // This is where we actually create the framebuffer. The true is "force". @@ -499,9 +499,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer // Assume that if we're clearing right when switching to a new framebuffer, we don't need to upload. if (useBufferedRendering_ && params.isDrawing) { - gpu->PerformMemoryUpload(params.fb_address, byteSize); + gpu->PerformMemoryUpload(params.fb_address, colorByteSize); // Alpha was already done by PerformMemoryUpload. - PerformStencilUpload(params.fb_address, byteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA); + PerformStencilUpload(params.fb_address, colorByteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA); // TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?) } @@ -551,9 +551,20 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // by copying from any overlapping buffers with fresher content. if (!isClearingDepth) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); - } + // Special compatibility trick for Burnout Dominator lens flares. Not sure how to best generalize this. See issue #11100 + if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && (currentRenderVfb_->usageFlags & FB_USAGE_CLUT) != 0) { + // Set the flag, then upload memory contents to depth channel. + // Sanity check the depth buffer pointer. + if (currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { + const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); + DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); + } + } + } + // First time use of this framebuffer's depth buffer. currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; + currentRenderVfb_->depthBindSeq = GetBindSeqCount(); } @@ -1034,22 +1045,19 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { } void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag) { - // Add depth support later for depth uploads. - _dbg_assert_(channel == RASTER_COLOR); - textureCache_->ForgetLastTexture(); - shaderManager_->DirtyLastShader(); // On GL, important that this is BEFORE drawing + shaderManager_->DirtyLastShader(); float u0 = 0.0f, u1 = 1.0f; float v0 = 0.0f, v1 = 1.0f; DrawTextureFlags flags; if (useBufferedRendering_ && vfb && vfb->fbo) { - flags = DRAWTEX_LINEAR; + flags = channel == RASTER_COLOR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag); - gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); } else { + _dbg_assert_(channel == RASTER_COLOR); // We are drawing directly to the back buffer so need to flip. // Should more of this be handled by the presentation engine? if (needBackBufferYSwap_) @@ -1063,11 +1071,18 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); } + if (channel == RASTER_DEPTH) { + _dbg_assert_(srcPixelFormat == GE_FORMAT_DEPTH16); + flags = flags | DRAWTEX_DEPTH; + } + Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height); if (pixelsTex) { draw_->BindTextures(0, 1, &pixelsTex); - // TODO: Replace with BlitUsingRaster for simplicity. + + // TODO: Replace with draw2D_.Blit() directly. DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); + gpuStats.numUploads++; pixelsTex->Release(); draw_->InvalidateCachedState(); @@ -1157,6 +1172,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; u32 *dst = (u32 *)(data + byteStride * y); + u16 *dst16 = (u16 *)(data + byteStride * y); switch (srcPixelFormat) { case GE_FORMAT_565: if (preferredPixelsFormat_ == Draw::DataFormat::B8G8R8A8_UNORM) @@ -1189,18 +1205,28 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G memcpy(dst, src32, width * 4); break; - case GE_FORMAT_INVALID: case GE_FORMAT_DEPTH16: - _dbg_assert_msg_(false, "Invalid pixelFormat passed to DrawPixels()."); + // TODO: Must take the depth range into account, unless it's already 0-1. + // TODO: Depending on the color buffer format used with this depth buffer, we need + // to do one of two different swizzle operations. However, for the only use of this so far, + // the Burnout lens flare trickery, swizzle doesn't matter since it's just a 0, 7fff, 0, 7fff pattern + // which comes out the same. + memcpy(dst16, src16, w * 2); + break; + + case GE_FORMAT_INVALID: + // Bad break; } } return true; }; + // Note: For depth, we create an R16_UNORM texture, that'll be just fine for uploading depth through a shader, + // and likely more efficient. Draw::TextureDesc desc{ Draw::TextureType::LINEAR2D, - preferredPixelsFormat_, + srcPixelFormat == GE_FORMAT_DEPTH16 ? Draw::DataFormat::R16_UNORM : preferredPixelsFormat_, width, height, 1, @@ -1210,6 +1236,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G { (uint8_t *)srcPixels }, generateTexture, }; + // Hot Shots Golf (#12355) does tons of these in a frame in some situations! So creating textures // better be fast. Draw::Texture *tex = draw_->CreateTexture(desc); @@ -1233,7 +1260,7 @@ void FramebufferManagerCommon::DrawFramebufferToOutput(const u8 *srcPixels, int if (needBackBufferYSwap_) { flags |= OutputFlags::BACKBUFFER_FLIPPED; } - // DrawActiveTexture reverses these, probably to match "up". + // CopyToOutput reverses these, probably to match "up". if (GetGPUBackend() == GPUBackend::DIRECT3D9 || GetGPUBackend() == GPUBackend::DIRECT3D11) { flags |= OutputFlags::POSITION_FLIPPED; } @@ -2727,7 +2754,7 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa // Rearrange to strip form. std::swap(coord[2], coord[3]); - draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline(DRAW2D_COPY_COLOR)); + draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline((flags & DRAWTEX_DEPTH) ? DRAW2D_COPY_DEPTH : DRAW2D_COPY_COLOR)); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); } diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index f7107dc029..9337a92b2e 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -192,6 +192,7 @@ enum DrawTextureFlags { DRAWTEX_NEAREST = 0, DRAWTEX_LINEAR = 1, DRAWTEX_TO_BACKBUFFER = 8, + DRAWTEX_DEPTH = 16, }; inline DrawTextureFlags operator | (const DrawTextureFlags &lhs, const DrawTextureFlags &rhs) { diff --git a/assets/compat.ini b/assets/compat.ini index ce3afae275..5d5be53e44 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1307,6 +1307,12 @@ ULKS46087 = true [AllowDownloadCLUT] # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. -# Burnout Dominator - lens flare effect (issue )#11100) +# Burnout Dominator - lens flare effect (issue #11100) +ULUS10236 = true +ULES00703 = true + +[UploadDepthForCLUTTextures] +# Burnout Dominator - lens flare effect (issue #11100) +# We need a preinitialized depth buffer ULUS10236 = true ULES00703 = true From 650ce22359802516a4c38f2912c7305b2688ac0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Sep 2022 21:10:27 +0200 Subject: [PATCH 013/326] Force low resolution for effects in Burnout Dominator. Fixes lens flare at high res --- GPU/Common/ShaderUniforms.h | 10 +++++----- assets/compat.ini | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 691835a262..f64fc3417b 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -36,14 +36,14 @@ struct UB_VS_FS_Base { uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. uint32_t colorWriteMask; float mipBias; // Fragment data - float fogColor[4]; - float texEnvColor[4]; + float fogColor[4]; // .w is unused + float texEnvColor[4]; // .w is unused int alphaColorRef[4]; int colorTestMask[4]; - float blendFixA[4]; - float blendFixB[4]; + float blendFixA[4]; // .w is unused + float blendFixB[4]; // .w is unused float texClamp[4]; - float texClampOffset[4]; + float texClampOffset[4]; // .zw are unused }; static const char *ub_baseStr = diff --git a/assets/compat.ini b/assets/compat.ini index 5d5be53e44..ba515164e2 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1304,6 +1304,11 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true +# Burnout Dominator - lens flare effect (issue #11100) +# Some of the steps don't work at high resolution yet. +ULUS10236 = true +ULES00703 = true + [AllowDownloadCLUT] # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. From 7213e9b7c2507a5d1dd3ee56963aca241feb5b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Sep 2022 21:26:13 +0200 Subject: [PATCH 014/326] Comment --- GPU/Common/FramebufferManagerCommon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 88fa93fb25..9ce5305fd9 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -2974,6 +2974,10 @@ static void ApplyKillzoneFramebufferSplit(FramebufferHeuristicParams *params, in margin = true; } } + + // TODO: Implement this for Burnout Dominator. It has to handle self-reads inside + // the margin framebuffer though, so framebuffer copies are still needed, just smaller. + // It uses 0x0080019f (through, float texcoords, ABGR 8888 colors, float positions). } if (margin) { From 6744fe3da15f9a8cef03e50326afcd7a6574a621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Sep 2022 23:32:38 +0200 Subject: [PATCH 015/326] OpenGL fix --- Common/GPU/OpenGL/DataFormatGL.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/GPU/OpenGL/DataFormatGL.cpp b/Common/GPU/OpenGL/DataFormatGL.cpp index 859624b28f..f34bebd193 100644 --- a/Common/GPU/OpenGL/DataFormatGL.cpp +++ b/Common/GPU/OpenGL/DataFormatGL.cpp @@ -8,8 +8,8 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin alignment = 4; switch (fmt) { case DataFormat::R16_UNORM: - internalFormat = GL_R16; - format = GL_R; + internalFormat = GL_RGBA; + format = GL_RED; type = GL_UNSIGNED_SHORT; break; From 431f142413960c185b62094c60394b63c8b31404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 13 Sep 2022 10:00:48 +0200 Subject: [PATCH 016/326] Cleanup in ge_constants.h, add virtual CLUT8 buffer format --- GPU/ge_constants.h | 65 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/GPU/ge_constants.h b/GPU/ge_constants.h index 91ef35b3ec..cdb7cbce58 100644 --- a/GPU/ge_constants.h +++ b/GPU/ge_constants.h @@ -278,20 +278,6 @@ enum GECommand { GE_CMD_NOP_FF = 0xFF, }; -enum GEBufferFormat : uint8_t { - GE_FORMAT_565 = 0, - GE_FORMAT_5551 = 1, - GE_FORMAT_4444 = 2, - GE_FORMAT_8888 = 3, - GE_FORMAT_DEPTH16 = 4, // Virtual format, just used to pass into Depal - GE_FORMAT_INVALID = 0xFF, -}; - -const char *GeBufferFormatToString(GEBufferFormat fmt); -inline bool IsGeBufferFormat16BitColor(GEBufferFormat fmt) { - return (int)fmt < 3; -} - #define GE_VTYPE_TRANSFORM (0<<23) #define GE_VTYPE_THROUGH (1<<23) #define GE_VTYPE_THROUGH_MASK (1<<23) @@ -413,8 +399,10 @@ enum GELightComputation GE_LIGHTCOMP_ONLYPOWDIFFUSE = 2, }; -enum GETextureFormat : uint8_t -{ +// TODO: Consolidate the below three to one enum? The first four are the same in all, +// and we sometimes need to interpret back and forth between them. + +enum GETextureFormat : uint8_t { GE_TFMT_5650 = 0, GE_TFMT_5551 = 1, GE_TFMT_4444 = 2, @@ -428,6 +416,32 @@ enum GETextureFormat : uint8_t GE_TFMT_DXT5 = 10, }; +enum GEBufferFormat : uint8_t { + GE_FORMAT_565 = 0, + GE_FORMAT_5551 = 1, + GE_FORMAT_4444 = 2, + GE_FORMAT_8888 = 3, + GE_FORMAT_DEPTH16 = 4, // Virtual format, just used to pass into Depal + GE_FORMAT_CLUT8 = 5, // Virtual format, for pre-decoded static textures with dynamic CLUT + GE_FORMAT_INVALID = 0xFF, +}; + +enum GEPaletteFormat : uint8_t { + GE_CMODE_16BIT_BGR5650, + GE_CMODE_16BIT_ABGR5551, + GE_CMODE_16BIT_ABGR4444, + GE_CMODE_32BIT_ABGR8888, +}; + +const char *GEPaletteFormatToString(GEPaletteFormat pfmt); +const char *GeTextureFormatToString(GETextureFormat tfmt); +const char *GeTextureFormatToString(GETextureFormat tfmt, GEPaletteFormat pfmt); + +const char *GeBufferFormatToString(GEBufferFormat fmt); +inline bool IsGeBufferFormat16BitColor(GEBufferFormat fmt) { + return (int)fmt < 3; +} + inline bool IsClutFormat(GETextureFormat tfmt) { return tfmt == GE_TFMT_CLUT4 || tfmt == GE_TFMT_CLUT8 || tfmt == GE_TFMT_CLUT16 || tfmt == GE_TFMT_CLUT32; } @@ -445,7 +459,12 @@ inline bool IsTextureFormat16Bit(GETextureFormat tfmt) { } inline int BufferFormatBytesPerPixel(GEBufferFormat format) { - return format == GE_FORMAT_8888 ? 4 : 2; // applies to depth as well. + switch (format) { + case GE_FORMAT_8888: return 4; // applies to depth as well. + case GE_FORMAT_CLUT8: return 1; + default: + return 2; + } } inline bool TextureFormatMatchesBufferFormat(GETextureFormat fmt, GEBufferFormat bfmt) { @@ -613,15 +632,3 @@ inline GEPrimitiveType PatchPrimToPrim(GEPatchPrimType type) { case GE_PATCHPRIM_UNKNOWN: default: return GE_PRIM_POINTS; // Treated as points. } } - -enum GEPaletteFormat -{ - GE_CMODE_16BIT_BGR5650, - GE_CMODE_16BIT_ABGR5551, - GE_CMODE_16BIT_ABGR4444, - GE_CMODE_32BIT_ABGR8888, -}; - -const char *GEPaletteFormatToString(GEPaletteFormat pfmt); -const char *GeTextureFormatToString(GETextureFormat tfmt); -const char *GeTextureFormatToString(GETextureFormat tfmt, GEPaletteFormat pfmt); From 9907957242400d2ead3f3dc56122ba5737714373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 13 Sep 2022 23:55:57 +0200 Subject: [PATCH 017/326] Start implementing ApplyTextureDepal --- GPU/Common/DepalettizeShaderCommon.cpp | 6 ++ GPU/Common/FramebufferManagerCommon.h | 15 +-- GPU/Common/TextureCacheCommon.cpp | 121 ++++++++++++++++++++++++- GPU/Common/TextureCacheCommon.h | 7 ++ GPU/Common/TextureShaderCommon.cpp | 2 +- 5 files changed, 142 insertions(+), 9 deletions(-) diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp index ee04359135..ee62064d3d 100644 --- a/GPU/Common/DepalettizeShaderCommon.cpp +++ b/GPU/Common/DepalettizeShaderCommon.cpp @@ -84,6 +84,9 @@ void GenerateDepalShader300(ShaderWriter &writer, const DepalConfig &config) { int shiftedMask = mask << shift; switch (config.bufferFormat) { + case GE_FORMAT_CLUT8: + writer.C(" int index = int(color.r * 255.99);\n"); + break; case GE_FORMAT_8888: if (shiftedMask & 0xFF) writer.C(" int r = int(color.r * 255.99);\n"); else writer.C(" int r = 0;\n"); if (shiftedMask & 0xFF00) writer.C(" int g = int(color.g * 255.99);\n"); else writer.C(" int g = 0;\n"); @@ -168,6 +171,9 @@ void GenerateDepalShaderFloat(ShaderWriter &writer, const DepalConfig &config) { // pixelformat is the format of the texture we are sampling. bool formatOK = true; switch (config.bufferFormat) { + case GE_FORMAT_CLUT8: + _dbg_assert_(false); // to be implemented + break; case GE_FORMAT_8888: if ((mask & (mask + 1)) == 0) { // If the value has all bits contiguous (bitmask check above), we can mod by it + 1. diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 9337a92b2e..04c604972b 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -425,6 +425,14 @@ public: Draw2DPipeline *Get2DPipeline(Draw2DShader shader); Draw2DPipeline *GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX); + // Public to be used from the texture cache's depal shenanigans. + void BlitUsingRaster( + Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2, + Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2, + bool linearFilter, + int scaleFactor, // usually unused, except for swizzle... + Draw2DPipeline *pipeline, const char *tag); + protected: virtual void PackFramebufferSync(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel); void SetViewport2D(int x, int y, int w, int h); @@ -442,13 +450,6 @@ 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 BlitUsingRaster( - Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2, - Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2, - bool linearFilter, - int scaleFactor, // usually unused, except for swizzle... - Draw2DPipeline *pipeline, const char *tag); - void CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags); 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); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 65869ad428..22fcedca98 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1923,7 +1923,6 @@ static bool CanUseSmoothDepal(const GPUgstate &gstate, GEBufferFormat framebuffe return false; } - void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel) { Draw2DPipeline *textureShader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; @@ -2079,6 +2078,126 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); } +void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { + Draw2DPipeline *textureShader = nullptr; + uint32_t clutMode = gstate.clutformat & 0xFFFFFF; + + switch (entry->format) { + case GE_TFMT_CLUT4: + case GE_TFMT_CLUT8: + break; // These are OK + default: + _dbg_assert_(false); + return; + } + + // Create GPU resources. + if (!dynamicClutFbo_) { + Draw::FramebufferDesc desc{}; + desc.width = 512; + desc.height = 1; + desc.depth = 1; + desc.z_stencil = false; + desc.numColorAttachments = 1; + dynamicClutFbo_ = draw_->CreateFramebuffer(desc); + dynamicClutReinterpreted_ = draw_->CreateFramebuffer(desc); + } + + const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); + u32 depthUpperBits = 0; + + // The CLUT texture is dynamic, it's the framebuffer pointed to by clutRenderAddress. + // Instead of texturing directly from that, we copy to a temporary CLUT texture. + GEBufferFormat expectedCLUTBufferFormat = (GEBufferFormat)clutFormat; // All entries from clutFormat correspond directly to buffer formats. + + VirtualFramebuffer *src = framebufferManager_->GetVFBAt(clutRenderAddress_); + if (!src) { + // What do we do? + return; + } + + Draw::Framebuffer *clutFbo = dynamicClutFbo_; + + // First we use a blit (with nearest interpolation, so we don't mash pixels together) + // to shrink to the correct size, if we are running with scaling. + // We can always blit 512 pixels even if we only need less, the cost will be negligible. + framebufferManager_->BlitUsingRaster( + src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); + + // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. + if (expectedCLUTBufferFormat != src->fb_format) { + float scaleFactorX = 1.0f; + Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); + framebufferManager_->BlitUsingRaster( + dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutReinterpreted_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); + clutFbo = dynamicClutReinterpreted_; + } + + textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0); + gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF); + + int texWidth = gstate.getTextureWidth(0); + int texHeight = gstate.getTextureHeight(0); + + // If min is not < max, then we don't have values (wasn't set during decode.) + const KnownVertexBounds &bounds = gstate_c.vertBounds; + float u1 = 0.0f; + float v1 = 0.0f; + float u2 = 1.0f; + float v2 = 1.0f; + if (bounds.minV < bounds.maxV) { + u1 = (bounds.minU + gstate_c.curTextureXOffset) * texWidth; + v1 = (bounds.minV + gstate_c.curTextureYOffset) * texHeight; + u2 = (bounds.maxU + gstate_c.curTextureXOffset) * texWidth; + v2 = (bounds.maxV + gstate_c.curTextureYOffset) * texHeight; + // We need to reapply the texture next time since we cropped UV. + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + } + + Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, texWidth, texHeight); + draw_->BindTexture(0, nullptr); + draw_->BindTexture(1, nullptr); + 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_->BindTexture(0, framebuffer->fbo); + draw_->BindFramebufferAsTexture(clutFbo, 1, Draw::FB_COLOR_BIT, 0); + Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false); + Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false); + draw_->BindSamplerStates(0, 1, &nearest); + draw_->BindSamplerStates(1, 1, &clutSampler); + + draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, texWidth, texHeight, texWidth, texHeight, false, 1); + + gpuStats.numDepal++; + + gstate_c.curTextureWidth = texWidth; + + draw_->BindTexture(0, nullptr); + framebufferManager_->RebindFramebuffer("ApplyTextureFramebuffer"); + + draw_->BindFramebufferAsTexture(depalFBO, 0, Draw::FB_COLOR_BIT, 0); + BoundFramebufferTexture(); + + const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); + const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; + + CheckAlphaResult alphaStatus = CheckCLUTAlpha((const uint8_t *)clutBufRaw_, clutFormat, clutTotalColors); + gstate_c.SetTextureFullAlpha(alphaStatus == CHECKALPHA_FULL); + + draw_->InvalidateCachedState(); + shaderManager_->DirtyLastShader(); + + SamplerCacheKey samplerKey = GetFramebufferSamplingParams(texWidth, texHeight); + ApplySamplingParams(samplerKey); + + // Since we started/ended render passes, might need these. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); +} + void TextureCacheCommon::Clear(bool delete_them) { textureShaderCache_->Clear(); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 78ad53df1f..f864b1a9e0 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -144,6 +144,8 @@ struct TexCacheEntry { STATUS_FORCE_REBUILD = 0x2000, STATUS_3D = 0x4000, + + STATUS_CLUT_GPU = 0x8000, }; // Status, but int so we can zero initialize. @@ -352,6 +354,7 @@ protected: void Decimate(bool forcePressure = false); void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel); + void ApplyTextureDepal(TexCacheEntry *entry); void HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); virtual void BuildTexture(TexCacheEntry *const entry) = 0; @@ -480,6 +483,10 @@ protected: bool clutAlphaLinear_ = false; u16 clutAlphaLinearColor_; + // Facilities for GPU depal of static textures. + Draw::Framebuffer *dynamicClutFbo_ = nullptr; + Draw::Framebuffer *dynamicClutReinterpreted_ = nullptr; + int standardScaleFactor_; int shaderScaleFactor_ = 0; diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp index f364016e72..1c67818a85 100644 --- a/GPU/Common/TextureShaderCommon.cpp +++ b/GPU/Common/TextureShaderCommon.cpp @@ -102,7 +102,7 @@ ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const int lastA = 0; int rampLength = 0; - // Quick check for how many continouosly growing entries we have at the start. + // Quick check for how many continuously growing entries we have at the start. // Bilinearly filtering CLUTs only really makes sense for this kind of ramp. for (int i = 0; i < maxClutEntries; i++) { rampLength = i; From f745e9489974e64d458765300f82749782ca1ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 00:13:29 +0200 Subject: [PATCH 018/326] Allow binding "native" texture objects to thin3d --- Common/GPU/D3D11/thin3d_d3d11.cpp | 7 +++++++ Common/GPU/D3D9/thin3d_d3d9.cpp | 7 +++++++ Common/GPU/OpenGL/thin3d_gl.cpp | 6 ++++++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 6 ++++++ Common/GPU/thin3d.h | 7 +++++++ GPU/Common/TextureCacheCommon.cpp | 2 +- 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 99e2eb2577..c17bb39919 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -105,6 +105,7 @@ public: void InvalidateCachedState() override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int index, void *nativeTexture) override; void BindSamplerStates(int start, int count, SamplerState **states) override; void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override; void BindIndexBuffer(Buffer *indexBuffer, int offset) override; @@ -1389,6 +1390,12 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) { context_->PSSetShaderResources(start, count, views); } +void D3D11DrawContext::BindNativeTexture(int index, void *nativeTexture) { + // Collect the resource views from the textures. + ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)nativeTexture; + context_->PSSetShaderResources(index, 1, &view); +} + void D3D11DrawContext::BindSamplerStates(int start, int count, SamplerState **states) { ID3D11SamplerState *samplers[MAX_BOUND_TEXTURES]; _assert_(start + count <= ARRAY_SIZE(samplers)); diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 9bfe824c09..c47d8235a1 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -521,6 +521,8 @@ public: void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int index, void *nativeTexture) override; + void BindSamplerStates(int start, int count, SamplerState **states) override { _assert_(start + count <= MAX_BOUND_TEXTURES); for (int i = 0; i < count; ++i) { @@ -814,6 +816,11 @@ void D3D9Context::BindTextures(int start, int count, Texture **textures) { } } +void D3D9Context::BindNativeTexture(int index, void *nativeTexture) { + LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)nativeTexture; + device_->SetTexture(index, texture); +} + void D3D9Context::EndFrame() { curPipeline_ = nullptr; } diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 2e93fc8791..908a764ecc 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -401,6 +401,8 @@ public: } void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int sampler, void *nativeTexture) override; + void BindPipeline(Pipeline *pipeline) override; void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override { _assert_(start + count <= ARRAY_SIZE(curVBuffers_)); @@ -1138,6 +1140,10 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) { } } +void OpenGLContext::BindNativeTexture(int index, void *nativeTexture) { + boundTextures_[index] = (GLRTexture *)nativeTexture; +} + void OpenGLContext::ApplySamplers() { for (int i = 0; i < MAX_TEXTURE_SLOTS; i++) { const OpenGLSamplerState *samp = boundSamplers_[i]; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index c836b41499..8420c01450 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -414,6 +414,7 @@ public: void BindSamplerStates(int start, int count, SamplerState **state) override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int sampler, void *nativeTexture) override; void BindPipeline(Pipeline *pipeline) override { curPipeline_ = (VKPipeline *)pipeline; @@ -1292,6 +1293,11 @@ void VKContext::BindTextures(int start, int count, Texture **textures) { } } +void VKContext::BindNativeTexture(int sampler, void *nativeTexture) { + boundTextures_[sampler] = nullptr; + boundImageView_[sampler] = (VkImageView)nativeTexture; +} + ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t size, const char *tag) { VKShaderModule *shader = new VKShaderModule(stage, tag); if (shader->Compile(vulkan_, language, data, size)) { diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 46b6305fd9..843e6e8813 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -675,6 +675,13 @@ public: virtual void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) = 0; virtual void BindIndexBuffer(Buffer *indexBuffer, int offset) = 0; + // Sometimes it's necessary to bind a texture not created by thin3d, and use with a thin3d pipeline. + // Not pretty, and one way in the future could be to create all textures through thin3d. + // Data types: + // * Vulkan: VkImageView + // * D3D11: ID3D11ShaderResourceView* + virtual void BindNativeTexture(int sampler, void *nativeTexture) = 0; + // Only supports a single dynamic uniform buffer, for maximum compatibility with the old APIs and ease of emulation. // More modern methods will be added later. virtual void UpdateDynamicUniformBuffer(const void *ub, size_t size) = 0; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 22fcedca98..ca83804954 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2163,7 +2163,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { Draw::Viewport vp{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f }; draw_->SetViewports(1, &vp); - draw_->BindTexture(0, framebuffer->fbo); + draw_->BindNativeTexture(0, framebuffer->fbo); draw_->BindFramebufferAsTexture(clutFbo, 1, Draw::FB_COLOR_BIT, 0); Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false); Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false); From abd58199ce9a05e207c5e6ff4d1ce8bc7027d6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 08:42:25 +0200 Subject: [PATCH 019/326] Add way to bind cached textures to a DrawContext --- GPU/Common/TextureCacheCommon.cpp | 6 +++--- GPU/Common/TextureCacheCommon.h | 3 +++ GPU/D3D11/TextureCacheD3D11.cpp | 5 +++++ GPU/D3D11/TextureCacheD3D11.h | 5 +++-- GPU/Directx9/TextureCacheDX9.cpp | 5 +++++ GPU/Directx9/TextureCacheDX9.h | 3 ++- GPU/GLES/TextureCacheGLES.cpp | 5 +++++ GPU/GLES/TextureCacheGLES.h | 1 + GPU/Vulkan/TextureCacheVulkan.cpp | 5 +++++ GPU/Vulkan/TextureCacheVulkan.h | 1 + 10 files changed, 33 insertions(+), 6 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index ca83804954..31ee03e82b 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2163,7 +2163,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { Draw::Viewport vp{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f }; draw_->SetViewports(1, &vp); - draw_->BindNativeTexture(0, framebuffer->fbo); + draw_->BindNativeTexture(0, GetNativeTextureView(entry)); draw_->BindFramebufferAsTexture(clutFbo, 1, Draw::FB_COLOR_BIT, 0); Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false); Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false); @@ -2185,8 +2185,8 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; - CheckAlphaResult alphaStatus = CheckCLUTAlpha((const uint8_t *)clutBufRaw_, clutFormat, clutTotalColors); - gstate_c.SetTextureFullAlpha(alphaStatus == CHECKALPHA_FULL); + // We don't know about alpha at all. + gstate_c.SetTextureFullAlpha(false); draw_->InvalidateCachedState(); shaderManager_->DirtyLastShader(); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index f864b1a9e0..84b03683ac 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -108,6 +108,8 @@ struct TextureDefinition { // NOTE: These only handle textures loaded directly from PSP memory contents. // Framebuffer textures do not have entries, we bind the framebuffers directly. +// At one point we might merge the concepts of framebuffers and textures, but that +// moment is far away. struct TexCacheEntry { ~TexCacheEntry() { if (texturePtr || textureName || vkTex) @@ -345,6 +347,7 @@ public: virtual bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { return false; } protected: + virtual void *GetNativeTextureView(const TexCacheEntry *entry) = 0; bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry); virtual void BindTexture(TexCacheEntry *entry) = 0; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index fb6cfba8b7..47c7b40eee 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -525,3 +525,8 @@ bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level stagingCopy->Release(); return true; } + +void *TextureCacheD3D11::GetNativeTextureView(const TexCacheEntry *entry) { + ID3D11ShaderResourceView *textureView = DxView(entry); + return (void *)textureView; +} diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 03ec04a481..e1be7d3aaf 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -61,6 +61,7 @@ protected: void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; void ApplySamplingParams(const SamplerCacheKey &key) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; @@ -72,10 +73,10 @@ private: ID3D11Device *device_; ID3D11DeviceContext *context_; - ID3D11Resource *&DxTex(TexCacheEntry *entry) { + ID3D11Resource *&DxTex(const TexCacheEntry *entry) { return (ID3D11Resource *&)entry->texturePtr; } - ID3D11ShaderResourceView *DxView(TexCacheEntry *entry) { + ID3D11ShaderResourceView *DxView(const TexCacheEntry *entry) { return (ID3D11ShaderResourceView *)entry->textureView; } diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index bfb859bb40..3a3feda7f3 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -454,3 +454,8 @@ bool TextureCacheDX9::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) return success; } + +void *TextureCacheDX9::GetNativeTextureView(const TexCacheEntry *entry) { + LPDIRECT3DBASETEXTURE9 tex = DxTex(entry); + return (void *)tex; +} diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index e8f4d9e58f..58c8e17db6 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -50,6 +50,7 @@ protected: void Unbind() override; void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void ApplySamplingParams(const SamplerCacheKey &key) override; @@ -60,7 +61,7 @@ private: void BuildTexture(TexCacheEntry *const entry) override; - LPDIRECT3DBASETEXTURE9 &DxTex(TexCacheEntry *entry) { + LPDIRECT3DBASETEXTURE9 &DxTex(const TexCacheEntry *entry) const { return *(LPDIRECT3DBASETEXTURE9 *)&entry->texturePtr; } diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index c28fc6d1e5..5ea95a9eed 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -443,3 +443,8 @@ void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) { render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); textureShaderCache_->DeviceRestore(draw); } + +void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry) { + GLRTexture *tex = entry->textureName; + return (void *)tex; +} diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 1eae540b87..2a4d04574d 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -66,6 +66,7 @@ protected: void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void ApplySamplingParams(const SamplerCacheKey &key) override; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index a67210cfc7..8a9e9f5a6a 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -857,3 +857,8 @@ std::vector TextureCacheVulkan::DebugGetSamplerIDs() const { std::string TextureCacheVulkan::DebugGetSamplerString(std::string id, DebugShaderStringType stringType) { return samplerCache_.DebugGetSamplerString(id, stringType); } + +void *TextureCacheVulkan::GetNativeTextureView(const TexCacheEntry *entry) { + VkImageView view = entry->vkTex->GetImageView(); + return (void *)view; +} diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 64b9da9eab..7dfb38fdc4 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -97,6 +97,7 @@ protected: void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; void ApplySamplingParams(const SamplerCacheKey &key) override; void BoundFramebufferTexture() override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); From 51c97c7a7f7c0bee7c9d5b8b82b99f1869b8b0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 10:26:54 +0200 Subject: [PATCH 020/326] Set texcache entry status flag if CLUT needs to be dynamic --- GPU/Common/TextureCacheCommon.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 31ee03e82b..6b72287407 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -377,13 +377,20 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { } bool hasClut = gstate.isTextureFormatIndexed(); + bool hasClutGPU = false; u32 cluthash; if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); + if (clutRenderAddress_ != 0xFFFFFFFF) { + hasClutGPU = true; + cluthash = 0; // Or should we use some other marker value? + } else { + if (clutLastFormat_ != gstate.clutformat) { + // We update here because the clut format can be specified after the load. + // TODO: Unify this as far as possible (I think only GLES backend really needs its own implementation due to different component order). + UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); + } + cluthash = clutHash_ ^ gstate.clutformat; } - cluthash = clutHash_ ^ gstate.clutformat; } else { cluthash = 0; } @@ -412,6 +419,13 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { bool match = entry->Matches(dim, texFormat, maxLevel); const char *reason = "different params"; + // Check for dynamic CLUT status + if (((entry->status & TexCacheEntry::STATUS_CLUT_GPU) != 0) != hasClutGPU) { + // Need to recreate, suddenly a CLUT GPU texture was used without it, or vice versa. + // I think this can only happen on a clut hash collision with the marker value, so highly unlikely. + match = false; + } + // Check for FBO changes. if (entry->status & TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP) { // Fall through to the end where we'll delete the entry if there's a framebuffer. @@ -553,10 +567,6 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { entry = new TexCacheEntry{}; cache_[cachekey].reset(entry); - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - if (PPGeIsFontTextureAddress(texaddr)) { // It's the builtin font texture. entry->status = TexCacheEntry::STATUS_RELIABLE; @@ -566,6 +576,11 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { entry->status = TexCacheEntry::STATUS_UNRELIABLE; } + if (hasClutGPU) { + WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with dynamic CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); + entry->status |= TexCacheEntry::STATUS_CLUT_GPU; + } + if (hasClut && clutRenderAddress_ == 0xFFFFFFFF) { const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32; const u64 cachekeyMax = cachekeyMin + (1ULL << 32); From d6d7a15d25261a02dc20689e36a2f10e339a502f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 21:57:09 +0200 Subject: [PATCH 021/326] Get depal-from-dynamic-CLUT working --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 2 + GPU/Common/TextureCacheCommon.cpp | 91 +++++++++++++++++++++-- GPU/Common/TextureCacheCommon.h | 4 + GPU/Vulkan/TextureCacheVulkan.cpp | 22 +++++- GPU/Vulkan/VulkanUtil.h | 1 + assets/compat.ini | 4 +- 6 files changed, 111 insertions(+), 13 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 10cc890009..56f40655f7 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -150,6 +150,8 @@ VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRe width = _width; height = _height; + _dbg_assert_(tag); + CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 6b72287407..b1ddc448aa 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -381,6 +381,8 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { u32 cluthash; if (hasClut) { if (clutRenderAddress_ != 0xFFFFFFFF) { + gstate_c.curTextureXOffset = 0.0f; + gstate_c.curTextureYOffset = 0.0f; hasClutGPU = true; cluthash = 0; // Or should we use some other marker value? } else { @@ -1491,12 +1493,28 @@ inline u32 TfmtRawToFullAlpha(GETextureFormat fmt) { } } +// Used for converting CLUT4 to CLUT8. +// Could SIMD or whatever, though will hardly be a bottleneck. +static void Expand4To8Bits(u8 *dest, const u8 *src, int srcWidth) { + for (int i = 0; i < (srcWidth + 1) / 2; i++) { + u8 lower = src[i] & 0xF; + u8 upper = src[i] >> 4; + dest[i * 2] = lower; + dest[i * 2 + 1] = upper; + } +} + CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, TexDecodeFlags flags) { u32 alphaSum = 0xFFFFFFFF; u32 fullAlphaMask = 0x0; bool expandTo32bit = (flags & TexDecodeFlags::EXPAND32) != 0; bool reverseColors = (flags & TexDecodeFlags::REVERSE_COLORS) != 0; + bool toClut8 = (flags & TexDecodeFlags::TO_CLUT8) != 0; + + if (toClut8 && format != GE_TFMT_CLUT8 && format != GE_TFMT_CLUT4) { + _dbg_assert_(false); + } bool swizzled = gstate.isTextureSwizzled(); if ((texaddr & 0x00600000) != 0 && Memory::IsVRAMAddress(texaddr)) { @@ -1531,6 +1549,15 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G texptr = (u8 *)tmpTexBuf32_.data(); } + if (toClut8) { + // We just need to expand from 4 to 8 bits. + for (int y = 0; y < h; ++y) { + Expand4To8Bits((u8 *)out + outPitch * y, texptr + (bufw * y) / 2, w); + } + // We can't know anything about alpha. + return CHECKALPHA_ANY; + } + switch (clutformat) { case GE_CMODE_16BIT_BGR5650: case GE_CMODE_16BIT_ABGR5551: @@ -1593,6 +1620,19 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G break; case GE_TFMT_CLUT8: + if (toClut8) { + if (gstate.isTextureSwizzled()) { + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw, texptr, bufw, h, 1); + texptr = (u8 *)tmpTexBuf32_.data(); + } + // After deswizzling, we are in the correct format and can just copy. + for (int y = 0; y < h; ++y) { + memcpy((u8 *)out + outPitch * y, texptr + (bufw * y), w); + } + // We can't know anything about alpha. + return CHECKALPHA_ANY; + } return ReadIndexedTex(out, outPitch, level, texptr, 1, bufw, reverseColors, expandTo32bit); case GE_TFMT_CLUT16: @@ -1878,10 +1918,18 @@ void TextureCacheCommon::ApplyTexture() { InvalidateLastTexture(); } - entry->lastFrame = gpuStats.numFlips; - BindTexture(entry); - gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL); - gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0); + if (entry->status & TexCacheEntry::STATUS_CLUT_GPU) { + // Special process. + ApplyTextureDepal(entry); + entry->lastFrame = gpuStats.numFlips; + gstate_c.SetTextureFullAlpha(false); + gstate_c.SetTextureIs3D(false); + } else { + entry->lastFrame = gpuStats.numFlips; + BindTexture(entry); + gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL); + gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0); + } } static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferFormat) { @@ -2093,6 +2141,7 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); } +// Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format. void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { Draw2DPipeline *textureShader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; @@ -2114,6 +2163,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { desc.depth = 1; desc.z_stencil = false; desc.numColorAttachments = 1; + desc.tag = "dynamic_clut"; dynamicClutFbo_ = draw_->CreateFramebuffer(desc); dynamicClutReinterpreted_ = draw_->CreateFramebuffer(desc); } @@ -2144,7 +2194,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { float scaleFactorX = 1.0f; Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); framebufferManager_->BlitUsingRaster( - dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutReinterpreted_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); + dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutReinterpreted_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); clutFbo = dynamicClutReinterpreted_; } @@ -2158,8 +2208,8 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { const KnownVertexBounds &bounds = gstate_c.vertBounds; float u1 = 0.0f; float v1 = 0.0f; - float u2 = 1.0f; - float v2 = 1.0f; + float u2 = texWidth; + float v2 = texHeight; if (bounds.minV < bounds.maxV) { u1 = (bounds.minU + gstate_c.curTextureXOffset) * texWidth; v1 = (bounds.minV + gstate_c.curTextureYOffset) * texHeight; @@ -2232,6 +2282,15 @@ void TextureCacheCommon::Clear(bool delete_them) { secondCacheSizeEstimate_ = 0; } videos_.clear(); + + if (dynamicClutFbo_) { + dynamicClutFbo_->Release(); + dynamicClutFbo_ = nullptr; + } + if (dynamicClutReinterpreted_) { + dynamicClutReinterpreted_->Release(); + dynamicClutReinterpreted_ = nullptr; + } } void TextureCacheCommon::DeleteTexture(TexCache::iterator it) { @@ -2598,6 +2657,21 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt plan.maxPossibleLevels = log2i(std::min(plan.createW, plan.createH)) + 1; } + if (entry->status & TexCacheEntry::TexStatus::STATUS_CLUT_GPU) { + _dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8); + plan.decodeToClut8 = true; + // We only support 1 mip level when doing CLUT on GPU for now. + // Supporting more would be possible, just not very interesting until we need it. + plan.levelsToCreate = 1; + plan.levelsToLoad = 1; + plan.maxPossibleLevels = 1; + plan.scaleFactor = 1; + plan.saveTexture = false; // Can't yet save these properly. + // TODO: Also forcibly disable replacement, or check that the replacement is a 8-bit paletted texture. + } else { + plan.decodeToClut8 = false; + } + if (plan.levelsToCreate == 1) { entry->status |= TexCacheEntry::STATUS_NO_MIPS; } else { @@ -2639,6 +2713,9 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i if (!gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || dstFmt == Draw::DataFormat::R8G8B8A8_UNORM) { texDecFlags |= TexDecodeFlags::EXPAND32; } + if (entry.status & TexCacheEntry::STATUS_CLUT_GPU) { + texDecFlags |= TexDecodeFlags::TO_CLUT8; + } CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, srcLevel, bufw, texDecFlags); entry.SetAlphaStatus(alphaResult, srcLevel); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 84b03683ac..b24c50b95c 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -53,6 +53,7 @@ class ShaderManagerCommon; enum class TexDecodeFlags { EXPAND32 = 1, REVERSE_COLORS = 2, + TO_CLUT8 = 4, }; ENUM_CLASS_BITOPS(TexDecodeFlags); @@ -285,6 +286,9 @@ struct BuildTexturePlan { bool replaceValid; bool saveTexture; + // TODO: Expand32 should probably also be decided in PrepareBuildTexture. + bool decodeToClut8; + void GetMipSize(int level, int *w, int *h) const { if (replaceValid) { replaced->GetSize(level, *w, *h); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 8a9e9f5a6a..56345d4db5 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -107,6 +107,15 @@ void main() { )"; +static int VkFormatBytesPerPixel(VkFormat format) { + switch (format) { + case VULKAN_8888_FORMAT: return 4; + case VULKAN_CLUT8_FORMAT: return 1; + default: break; + } + return 2; +} + SamplerCache::~SamplerCache() { DeviceLost(); } @@ -448,6 +457,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { if (plan.scaleFactor > 1) { // Whether hardware or software scaling, this is the dest format. dstFmt = VULKAN_8888_FORMAT; + } else if (plan.decodeToClut8) { + dstFmt = VULKAN_CLUT8_FORMAT; } // We don't generate mipmaps for 512x512 textures because they're almost exclusively used for menu backgrounds @@ -479,7 +490,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { case VULKAN_4444_FORMAT: mapping = &VULKAN_4444_SWIZZLE; break; case VULKAN_1555_FORMAT: mapping = &VULKAN_1555_SWIZZLE; break; case VULKAN_565_FORMAT: mapping = &VULKAN_565_SWIZZLE; break; - default: mapping = &VULKAN_8888_SWIZZLE; break; + default: mapping = &VULKAN_8888_SWIZZLE; break; // no swizzle } VkImageLayout imageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; @@ -562,7 +573,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { int mipHeight; plan.GetMipSize(i, &mipWidth, &mipHeight); - int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; // output bpp + int bpp = VkFormatBytesPerPixel(actualFmt); int stride = (mipWidth * bpp + 15) & ~15; // output stride int uploadSize = stride * mipHeight; @@ -602,7 +613,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { loadLevel(uploadSize, i, stride, plan.scaleFactor); entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, stride / bpp); } else if (computeUpload) { - int srcBpp = dstFmt == VULKAN_8888_FORMAT ? 4 : 2; + int srcBpp = VkFormatBytesPerPixel(dstFmt); int srcStride = mipUnscaledWidth * srcBpp; int srcSize = srcStride * mipUnscaledHeight; loadLevel(srcSize, i == 0 ? plan.baseLevelSrc : i, srcStride, 1); @@ -723,7 +734,7 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt _assert_msg_(texaddr != 0, "Can't load a texture from address null") int bufw = GetTextureBufw(level, texaddr, tfmt); - int bpp = dstFmt == VULKAN_8888_FORMAT ? 4 : 2; + int bpp = VkFormatBytesPerPixel(dstFmt); u32 *pixelData; int decPitch; @@ -732,6 +743,9 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt if (!gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || scaleFactor > 1 || dstFmt == VULKAN_8888_FORMAT) { texDecFlags |= TexDecodeFlags::EXPAND32; } + if (entry.status & TexCacheEntry::STATUS_CLUT_GPU) { + texDecFlags |= TexDecodeFlags::TO_CLUT8; + } if (scaleFactor > 1) { tmpTexBufRearrange_.resize(std::max(bufw, w) * h); diff --git a/GPU/Vulkan/VulkanUtil.h b/GPU/Vulkan/VulkanUtil.h index 2753fd79eb..b31fdb75f9 100644 --- a/GPU/Vulkan/VulkanUtil.h +++ b/GPU/Vulkan/VulkanUtil.h @@ -36,6 +36,7 @@ extern const VkComponentMapping VULKAN_8888_SWIZZLE; #define VULKAN_1555_FORMAT VK_FORMAT_A1R5G5B5_UNORM_PACK16 #define VULKAN_565_FORMAT VK_FORMAT_B5G6R5_UNORM_PACK16 // TODO: Does not actually have mandatory support, though R5G6B5 does! See #14602 #define VULKAN_8888_FORMAT VK_FORMAT_R8G8B8A8_UNORM +#define VULKAN_CLUT8_FORMAT VK_FORMAT_R8_UNORM // Manager for compute shaders that upload things (and those have two bindings: a storage buffer to read from and an image to write to). class VulkanComputeShaderManager { diff --git a/assets/compat.ini b/assets/compat.ini index ba515164e2..3345917f5c 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1313,8 +1313,8 @@ ULES00703 = true # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. # Burnout Dominator - lens flare effect (issue #11100) -ULUS10236 = true -ULES00703 = true +# ULUS10236 = true +# ULES00703 = true [UploadDepthForCLUTTextures] # Burnout Dominator - lens flare effect (issue #11100) From 1f6d27bd552ecc1bfdae916288df88802b61da29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 22:46:17 +0200 Subject: [PATCH 022/326] Ridge Racer fix --- GPU/Common/TextureCacheCommon.cpp | 43 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index b1ddc448aa..f4a8dff574 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1190,29 +1190,40 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { if (Memory::IsValidAddress(clutAddr)) { if (Memory::IsVRAMAddress(clutAddr)) { // Clear the uncached bit, etc. to match framebuffers. - const u32 clutFramebufAddr = clutAddr & 0x3FFFFFFF; - const u32 clutFramebufEnd = clutFramebufAddr + loadBytes; + const u32 clutLoadAddr = clutAddr & 0x3FFFFFFF; + const u32 clutLoadEnd = clutLoadAddr + loadBytes; static const u32 MAX_CLUT_OFFSET = 4096; clutRenderOffset_ = MAX_CLUT_OFFSET; const std::vector &framebuffers = framebufferManager_->Framebuffers(); for (VirtualFramebuffer *framebuffer : framebuffers) { const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF; - const u32 bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); - u32 offset = clutFramebufAddr - fb_address; + const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); + int offset = clutLoadAddr - fb_address; - // Is this inside the framebuffer at all? - bool matchRange = fb_address + framebuffer->fb_stride * bpp > clutFramebufAddr && fb_address < clutFramebufEnd; - // And is it inside the rendered area? Sometimes games pack data outside. - bool matchRegion = ((offset / bpp) % framebuffer->fb_stride) < framebuffer->width; - if (matchRange && matchRegion && offset < clutRenderOffset_) { - WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); - framebuffer->last_frame_clut = gpuStats.numFlips; - framebuffer->usageFlags |= FB_USAGE_CLUT; - clutRenderAddress_ = framebuffer->fb_address; - clutRenderOffset_ = offset; - if (offset == 0) { - break; + // Is this inside the framebuffer at all? Note that we only check the first line here, this should + // be changed. + bool matchRange = offset >= 0 && offset < framebuffer->fb_stride * fb_bpp; + if (matchRange) { + // And is it inside the rendered area? Sometimes games pack data in the margin between width and stride. + // If the framebuffer width was detected as 512, we're gonna assume it's really 480. + int fbMatchWidth = framebuffer->width; + if (fbMatchWidth == 512) { + fbMatchWidth = 480; + } + bool inMargin = ((offset / fb_bpp) % framebuffer->fb_stride) == fbMatchWidth; + if (matchRange && !inMargin && offset < clutRenderOffset_) { + WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); + framebuffer->last_frame_clut = gpuStats.numFlips; + framebuffer->usageFlags |= FB_USAGE_CLUT; + clutRenderAddress_ = framebuffer->fb_address; + clutRenderOffset_ = offset; + if (offset == 0) { + break; + } + + // TODO: Could automatically trigger the copy here and make it partial. + // That way, multiple partial copies would work. Though games don't really do that. } } } From 85bb6786a1315ab4a866af0e1ab0eb145db5d3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 23:13:46 +0200 Subject: [PATCH 023/326] Log improvements --- GPU/Common/FramebufferManagerCommon.cpp | 2 +- GPU/Common/TextureCacheCommon.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 9ce5305fd9..c735bad062 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -2197,7 +2197,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS } if (dstBuffer && !srcBuffer) { - WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x", srcBasePtr, dstBasePtr); + WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x (%dx%d %d,%d bpp=%d)", srcBasePtr, dstBasePtr, width, height, dstX, dstY, bpp); FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(srcBasePtr) + (srcX + srcY * srcStride) * bpp; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index f4a8dff574..68db4b8e45 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1039,8 +1039,8 @@ bool TextureCacheCommon::MatchFramebuffer( return true; } } else { - WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x", - GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address); + WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x (+%dx%d)", + GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address, matchInfo->xOffset, matchInfo->yOffset); return false; } } From a208afb7b88fd561e05100c78eaf08b0c25f08fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 23:35:15 +0200 Subject: [PATCH 024/326] OpenGL and D3D11 fixes --- Common/GPU/OpenGL/DataFormatGL.cpp | 8 ++++++++ Common/GPU/OpenGL/thin3d_gl.cpp | 7 +++++-- GPU/D3D11/TextureCacheD3D11.cpp | 4 ++++ GPU/GLES/TextureCacheGLES.cpp | 6 ++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Common/GPU/OpenGL/DataFormatGL.cpp b/Common/GPU/OpenGL/DataFormatGL.cpp index f34bebd193..48e439a76d 100644 --- a/Common/GPU/OpenGL/DataFormatGL.cpp +++ b/Common/GPU/OpenGL/DataFormatGL.cpp @@ -11,6 +11,14 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin internalFormat = GL_RGBA; format = GL_RED; type = GL_UNSIGNED_SHORT; + alignment = 2; + break; + + case DataFormat::R8_UNORM: + internalFormat = GL_RGBA; + format = GL_RED; + type = GL_UNSIGNED_BYTE; + alignment = 1; break; case DataFormat::R8G8B8A8_UNORM: diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 908a764ecc..047cc8b8fb 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -1141,7 +1141,9 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) { } void OpenGLContext::BindNativeTexture(int index, void *nativeTexture) { - boundTextures_[index] = (GLRTexture *)nativeTexture; + GLRTexture *tex = (GLRTexture *)nativeTexture; + boundTextures_[index] = tex; + renderManager_.BindTexture(index, tex); } void OpenGLContext::ApplySamplers() { @@ -1489,7 +1491,8 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const { return FMT_INPUTLAYOUT; case DataFormat::R8_UNORM: - return 0; + return FMT_TEXTURE; + case DataFormat::BC1_RGBA_UNORM_BLOCK: case DataFormat::BC2_UNORM_BLOCK: case DataFormat::BC3_UNORM_BLOCK: diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 47c7b40eee..0930aec655 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -65,6 +65,8 @@ Draw::DataFormat FromD3D11Format(u32 fmt) { return Draw::DataFormat::A1R5G5B5_UNORM_PACK16; case DXGI_FORMAT_B5G6R5_UNORM: return Draw::DataFormat::R5G6B5_UNORM_PACK16; + case DXGI_FORMAT_R8_UNORM: + return Draw::DataFormat::R8_UNORM; case DXGI_FORMAT_B8G8R8A8_UNORM: default: return Draw::DataFormat::R8G8B8A8_UNORM; @@ -290,6 +292,8 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { dstFmt = ToDXGIFormat(plan.replaced->Format(plan.baseLevelSrc)); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = DXGI_FORMAT_B8G8R8A8_UNORM; + } else if (plan.decodeToClut8) { + dstFmt = DXGI_FORMAT_R8_UNORM; } int levels; diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 5ea95a9eed..83309208b8 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -266,6 +266,8 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { dstFmt = plan.replaced->Format(plan.baseLevelSrc); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = Draw::DataFormat::R8G8B8A8_UNORM; + } else if (plan.decodeToClut8) { + dstFmt = Draw::DataFormat::R8_UNORM; } if (plan.depth == 1) { @@ -313,7 +315,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { if (plan.scaleFactor > 1) { bpp = 4; } else { - bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2; + bpp = (int)Draw::DataFormatSizeInBytes(dstFmt); } } @@ -335,7 +337,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips); } else { - int bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2; + int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt); int stride = bpp * (plan.w * plan.scaleFactor); int levelStride = stride * (plan.h * plan.scaleFactor); From 9c2bfad7492091e50fc8a9e1099fb925d18d81dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Sep 2022 00:18:26 +0200 Subject: [PATCH 025/326] Seems Need for Speed: Shift has exactly the same lens flare, enable the same compat settings. --- GPU/Common/TextureCacheCommon.cpp | 6 +++--- assets/compat.ini | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 68db4b8e45..70be614c85 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1203,7 +1203,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { // Is this inside the framebuffer at all? Note that we only check the first line here, this should // be changed. - bool matchRange = offset >= 0 && offset < framebuffer->fb_stride * fb_bpp; + bool matchRange = offset >= 0 && offset < (int)(framebuffer->fb_stride * fb_bpp); if (matchRange) { // And is it inside the rendered area? Sometimes games pack data in the margin between width and stride. // If the framebuffer width was detected as 512, we're gonna assume it's really 480. @@ -1212,12 +1212,12 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { fbMatchWidth = 480; } bool inMargin = ((offset / fb_bpp) % framebuffer->fb_stride) == fbMatchWidth; - if (matchRange && !inMargin && offset < clutRenderOffset_) { + if (matchRange && !inMargin && offset < (int)clutRenderOffset_) { WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); framebuffer->last_frame_clut = gpuStats.numFlips; framebuffer->usageFlags |= FB_USAGE_CLUT; clutRenderAddress_ = framebuffer->fb_address; - clutRenderOffset_ = offset; + clutRenderOffset_ = (u32)offset; if (offset == 0) { break; } diff --git a/assets/compat.ini b/assets/compat.ini index 3345917f5c..7bc570faaf 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1156,6 +1156,13 @@ ULKS46087 = true ULUS10236 = true ULES00703 = true +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true + [DisableFirstFrameReadback] # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344 UCUS98612 = true @@ -1309,6 +1316,13 @@ ULKS46087 = true ULUS10236 = true ULES00703 = true +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true + [AllowDownloadCLUT] # Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. @@ -1321,3 +1335,10 @@ ULES00703 = true # We need a preinitialized depth buffer ULUS10236 = true ULES00703 = true + +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true From 36c98ab367b4a1c0bbb874dc6fd4fdc876a599d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Sep 2022 09:15:02 +0200 Subject: [PATCH 026/326] Address feedback, change roles of the two dynamic clut fbos to eliminate the need for state (see next commit) --- GPU/Common/FramebufferManagerCommon.cpp | 7 +++++-- GPU/Common/TextureCacheCommon.cpp | 27 +++++++++++++------------ GPU/Common/TextureCacheCommon.h | 2 +- assets/compat.ini | 9 +++------ 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index c735bad062..006452ac9c 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -557,11 +557,14 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // Set the flag, then upload memory contents to depth channel. // Sanity check the depth buffer pointer. if (currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { - const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); - DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); + if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { + const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); + DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); + } } } } + // First time use of this framebuffer's depth buffer. currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 70be614c85..ec730a5a59 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2176,7 +2176,8 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { desc.numColorAttachments = 1; desc.tag = "dynamic_clut"; dynamicClutFbo_ = draw_->CreateFramebuffer(desc); - dynamicClutReinterpreted_ = draw_->CreateFramebuffer(desc); + desc.tag = "dynamic_clut_temp"; + dynamicClutTemp_ = draw_->CreateFramebuffer(desc); } const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); @@ -2192,21 +2193,21 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { return; } - Draw::Framebuffer *clutFbo = dynamicClutFbo_; - // First we use a blit (with nearest interpolation, so we don't mash pixels together) // to shrink to the correct size, if we are running with scaling. // We can always blit 512 pixels even if we only need less, the cost will be negligible. - framebufferManager_->BlitUsingRaster( - src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); - // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. - if (expectedCLUTBufferFormat != src->fb_format) { + if (expectedCLUTBufferFormat == src->fb_format) { + framebufferManager_->BlitUsingRaster( + src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); + } else { + framebufferManager_->BlitUsingRaster( + src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); + // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. float scaleFactorX = 1.0f; Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); framebufferManager_->BlitUsingRaster( - dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutReinterpreted_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); - clutFbo = dynamicClutReinterpreted_; + dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); } textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0); @@ -2240,7 +2241,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { draw_->SetViewports(1, &vp); draw_->BindNativeTexture(0, GetNativeTextureView(entry)); - draw_->BindFramebufferAsTexture(clutFbo, 1, Draw::FB_COLOR_BIT, 0); + draw_->BindFramebufferAsTexture(dynamicClutFbo_, 1, Draw::FB_COLOR_BIT, 0); Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false); Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false); draw_->BindSamplerStates(0, 1, &nearest); @@ -2298,9 +2299,9 @@ void TextureCacheCommon::Clear(bool delete_them) { dynamicClutFbo_->Release(); dynamicClutFbo_ = nullptr; } - if (dynamicClutReinterpreted_) { - dynamicClutReinterpreted_->Release(); - dynamicClutReinterpreted_ = nullptr; + if (dynamicClutTemp_) { + dynamicClutTemp_->Release(); + dynamicClutTemp_ = nullptr; } } diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index b24c50b95c..126a497bb6 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -491,8 +491,8 @@ protected: u16 clutAlphaLinearColor_; // Facilities for GPU depal of static textures. + Draw::Framebuffer *dynamicClutTemp_ = nullptr; Draw::Framebuffer *dynamicClutFbo_ = nullptr; - Draw::Framebuffer *dynamicClutReinterpreted_ = nullptr; int standardScaleFactor_; int shaderScaleFactor_ = 0; diff --git a/assets/compat.ini b/assets/compat.ini index 7bc570faaf..f79978e9fd 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1152,7 +1152,7 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true -# Burnout Dominator - lens flare effect (issue )#11100) +# Burnout Dominator - lens flare effect (issue #11100) ULUS10236 = true ULES00703 = true @@ -1324,11 +1324,8 @@ NPJH50143 = true ULJM05738 = true [AllowDownloadCLUT] -# Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. - -# Burnout Dominator - lens flare effect (issue #11100) -# ULUS10236 = true -# ULES00703 = true +# Temporary compatibility option, while working on the GPU CLUT-from-framebuffer path. +# Not required for any games now that it works, but might be useful for development. [UploadDepthForCLUTTextures] # Burnout Dominator - lens flare effect (issue #11100) From 662126a0a16b966cad97dc490431ad63b7a574a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Sep 2022 16:57:03 +0200 Subject: [PATCH 027/326] Small simplification --- GPU/Common/FramebufferManagerCommon.cpp | 7 +++++++ GPU/Common/FramebufferManagerCommon.h | 2 ++ GPU/Common/TextureCacheCommon.cpp | 24 +++++++++--------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 006452ac9c..ed43996676 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -825,6 +825,11 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra } Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX) { + if (from == to) { + *scaleFactorX = 1.0f; + return Get2DPipeline(DRAW2D_COPY_COLOR); + } + if (IsBufferFormat16Bit(from) && !IsBufferFormat16Bit(to)) { // We halve the X coordinates in the destination framebuffer. // The shader will collect two pixels worth of input data and merge into one. @@ -833,6 +838,8 @@ Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat // We double the X coordinates in the destination framebuffer. // The shader will sample and depending on the X coordinate & 1, use the upper or lower bits. *scaleFactorX = 2.0f; + } else { + *scaleFactorX = 1.0f; } Draw2DPipeline *pipeline = reinterpretFromTo_[(int)from][(int)to]; diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 04c604972b..5ef9159868 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -423,6 +423,8 @@ public: VirtualFramebuffer *ResolveFramebufferColorToFormat(VirtualFramebuffer *vfb, GEBufferFormat newFormat); Draw2DPipeline *Get2DPipeline(Draw2DShader shader); + + // If from==to, returns a copy pipeline. Draw2DPipeline *GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX); // Public to be used from the texture cache's depal shenanigans. diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index ec730a5a59..8d6b5ea5d3 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2154,7 +2154,6 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer // Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format. void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { - Draw2DPipeline *textureShader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; switch (entry->format) { @@ -2196,21 +2195,16 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { // First we use a blit (with nearest interpolation, so we don't mash pixels together) // to shrink to the correct size, if we are running with scaling. // We can always blit 512 pixels even if we only need less, the cost will be negligible. + framebufferManager_->BlitUsingRaster( + src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); + // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. + // If no reinterpretation is needed, we'll automatically just get a copy shader. + float scaleFactorX = 1.0f; + Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); + framebufferManager_->BlitUsingRaster( + dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); - if (expectedCLUTBufferFormat == src->fb_format) { - framebufferManager_->BlitUsingRaster( - src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut"); - } else { - framebufferManager_->BlitUsingRaster( - src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); - // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. - float scaleFactorX = 1.0f; - Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); - framebufferManager_->BlitUsingRaster( - dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); - } - - textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0); + Draw2DPipeline *textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0); gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF); int texWidth = gstate.getTextureWidth(0); From 5d4075376d291ae668b6f4e2849b53d1b0610997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Sep 2022 17:09:03 +0200 Subject: [PATCH 028/326] Copy the texels directly in LoadCLUT --- GPU/Common/TextureCacheCommon.cpp | 60 ++++++++++++++++--------------- GPU/Common/TextureCacheCommon.h | 2 ++ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 8d6b5ea5d3..66d70f7c02 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1196,6 +1196,8 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { clutRenderOffset_ = MAX_CLUT_OFFSET; const std::vector &framebuffers = framebufferManager_->Framebuffers(); + + VirtualFramebuffer *chosenFramebuffer = nullptr; for (VirtualFramebuffer *framebuffer : framebuffers) { const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF; const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); @@ -1212,22 +1214,47 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { fbMatchWidth = 480; } bool inMargin = ((offset / fb_bpp) % framebuffer->fb_stride) == fbMatchWidth; + + // The offset check here means, in the context of the loop, that we'll pick + // the framebuffer with the smallest offset. This is yet another framebuffer matching + // loop with its own rules, eventually we'll probably want to do something + // more systematic. if (matchRange && !inMargin && offset < (int)clutRenderOffset_) { WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); framebuffer->last_frame_clut = gpuStats.numFlips; framebuffer->usageFlags |= FB_USAGE_CLUT; clutRenderAddress_ = framebuffer->fb_address; clutRenderOffset_ = (u32)offset; + chosenFramebuffer = framebuffer; if (offset == 0) { + // Not gonna find a better match according to the smallest-offset rule, so we'll go with this one. break; } - - // TODO: Could automatically trigger the copy here and make it partial. - // That way, multiple partial copies would work. Though games don't really do that. } } } + if (chosenFramebuffer) { + if (!dynamicClutTemp_) { + Draw::FramebufferDesc desc{}; + desc.width = 512; + desc.height = 1; + desc.depth = 1; + desc.z_stencil = false; + desc.numColorAttachments = 1; + desc.tag = "dynamic_clut"; + dynamicClutFbo_ = draw_->CreateFramebuffer(desc); + desc.tag = "dynamic_clut_temp"; + dynamicClutTemp_ = draw_->CreateFramebuffer(desc); + } + + // Download the pixels to our temp clut, scaling down if needed. + framebufferManager_->BlitUsingRaster( + chosenFramebuffer->fbo, 0.0f, 0.0f, 512.0f * chosenFramebuffer->renderScaleFactor, 1.0f, + dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, + false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); + clutRenderFormat_ = chosenFramebuffer->fb_format; + } NotifyMemInfo(MemBlockFlags::ALLOC, clutAddr, loadBytes, "CLUT"); } @@ -2165,20 +2192,6 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { return; } - // Create GPU resources. - if (!dynamicClutFbo_) { - Draw::FramebufferDesc desc{}; - desc.width = 512; - desc.height = 1; - desc.depth = 1; - desc.z_stencil = false; - desc.numColorAttachments = 1; - desc.tag = "dynamic_clut"; - dynamicClutFbo_ = draw_->CreateFramebuffer(desc); - desc.tag = "dynamic_clut_temp"; - dynamicClutTemp_ = draw_->CreateFramebuffer(desc); - } - const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); u32 depthUpperBits = 0; @@ -2186,21 +2199,10 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { // Instead of texturing directly from that, we copy to a temporary CLUT texture. GEBufferFormat expectedCLUTBufferFormat = (GEBufferFormat)clutFormat; // All entries from clutFormat correspond directly to buffer formats. - VirtualFramebuffer *src = framebufferManager_->GetVFBAt(clutRenderAddress_); - if (!src) { - // What do we do? - return; - } - - // First we use a blit (with nearest interpolation, so we don't mash pixels together) - // to shrink to the correct size, if we are running with scaling. - // We can always blit 512 pixels even if we only need less, the cost will be negligible. - framebufferManager_->BlitUsingRaster( - src->fbo, 0.0f, 0.0f, 512.0f * src->renderScaleFactor, 1.0f, dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. // If no reinterpretation is needed, we'll automatically just get a copy shader. float scaleFactorX = 1.0f; - Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(src->fb_format, expectedCLUTBufferFormat, &scaleFactorX); + Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(clutRenderFormat_, expectedCLUTBufferFormat, &scaleFactorX); framebufferManager_->BlitUsingRaster( dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 126a497bb6..9922e9070a 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -486,6 +486,8 @@ protected: u32 clutMaxBytes_ = 0; u32 clutRenderAddress_ = 0xFFFFFFFF; u32 clutRenderOffset_; + GEBufferFormat clutRenderFormat_; + // True if the clut is just alpha values in the same order (RGBA4444-bit only.) bool clutAlphaLinear_ = false; u16 clutAlphaLinearColor_; From e8aaf221635e68c72ab92b35aa1e98f1aa96cfb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Sep 2022 17:22:09 +0200 Subject: [PATCH 029/326] D3D9 crashfix (flare doesn't render correctly though) --- Common/GPU/D3D9/thin3d_d3d9.cpp | 11 +++++++++++ GPU/Common/DepalettizeShaderCommon.cpp | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index c47d8235a1..a84d42f64e 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -443,6 +443,17 @@ void D3D9Texture::SetImageData(int x, int y, int z, int width, int height, int d if (data != rect.pBits) memcpy(dest, source, sizeof(uint32_t) * width); break; + + case DataFormat::R8_UNORM: + if (data != rect.pBits) + memcpy(dest, source, width); + break; + + case DataFormat::R16_UNORM: + if (data != rect.pBits) + memcpy(dest, source, sizeof(uint16_t) * width); + break; + default: // Unhandled data format copy. DebugBreak(); diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp index ee62064d3d..a1e593e626 100644 --- a/GPU/Common/DepalettizeShaderCommon.cpp +++ b/GPU/Common/DepalettizeShaderCommon.cpp @@ -172,7 +172,14 @@ void GenerateDepalShaderFloat(ShaderWriter &writer, const DepalConfig &config) { bool formatOK = true; switch (config.bufferFormat) { case GE_FORMAT_CLUT8: - _dbg_assert_(false); // to be implemented + if (shift == 0 && mask == 0xFF) { + // Easy peasy. + sprintf(lookupMethod, "index.r"); + formatOK = true; + } else { + // Deal with this if we find it. + formatOK = false; + } break; case GE_FORMAT_8888: if ((mask & (mask + 1)) == 0) { From 0ed86188b0e5e294f09352d6847c78e4bc65d882 Mon Sep 17 00:00:00 2001 From: Sprite Date: Thu, 15 Sep 2022 22:50:57 +0800 Subject: [PATCH 030/326] Fix const pointer conversion error for RISC-V --- Common/RiscVEmitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index 2fc0966c1f..3f0d47499b 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -628,7 +628,7 @@ void RiscVEmitter::FlushIcache() { void RiscVEmitter::FlushIcacheSection(const u8 *start, const u8 *end) { #if PPSSPP_ARCH(RISCV64) - __builtin___clear_cache(start, end); + __builtin___clear_cache((void *)start, (void *)end); #endif } From 9f3851678e246250408f28239f91be4518369325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Sep 2022 10:14:00 +0200 Subject: [PATCH 031/326] Add tags to all our UI screens. Useful to debug UI problems. --- Common/UI/Screen.h | 2 +- Common/UI/UIScreen.h | 8 +++++- UI/ComboKeyMappingScreen.cpp | 4 +++ UI/ComboKeyMappingScreen.h | 2 ++ UI/ControlMappingScreen.h | 24 ++++++++++++----- UI/CwCheatScreen.h | 2 ++ UI/DevScreens.cpp | 40 ++++++++++++++-------------- UI/DevScreens.h | 44 ++++++++++++++++++++++--------- UI/DisplayLayoutScreen.h | 2 +- UI/EmuScreen.cpp | 2 +- UI/EmuScreen.h | 2 ++ UI/GPUDriverTestScreen.h | 2 ++ UI/GameScreen.cpp | 1 + UI/GameScreen.h | 2 +- UI/GameSettingsScreen.h | 9 +++++-- UI/InstallZipScreen.h | 2 ++ UI/MainScreen.h | 6 ++++- UI/MemStickScreen.h | 5 +++- UI/MiscScreens.h | 12 +++++++++ UI/PauseScreen.cpp | 4 +-- UI/PauseScreen.h | 2 ++ UI/RemoteISOScreen.h | 8 ++++++ UI/ReportScreen.h | 4 +++ UI/SavedataScreen.cpp | 5 ++-- UI/SavedataScreen.h | 2 ++ UI/Store.h | 2 +- UI/TiltAnalogSettingsScreen.h | 2 ++ UI/TouchControlLayoutScreen.h | 2 ++ UI/TouchControlVisibilityScreen.h | 4 +++ 29 files changed, 152 insertions(+), 54 deletions(-) diff --git a/Common/UI/Screen.h b/Common/UI/Screen.h index de24c74985..42f0830627 100644 --- a/Common/UI/Screen.h +++ b/Common/UI/Screen.h @@ -71,7 +71,7 @@ public: // what screen it is. virtual void *dialogData() { return 0; } - virtual std::string tag() const { return std::string(""); } + virtual const char *tag() const = 0; virtual bool isTransparent() const { return false; } virtual bool isTopLevel() const { return false; } diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index 43ec130d9d..d667873e8c 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -136,7 +136,7 @@ public: void SetHiddenChoices(std::set hidden) { hidden_ = hidden; } - virtual std::string tag() const override { return std::string("listpopup"); } + const char *tag() const override { return "listpopup"; } UI::Event OnChoice; @@ -187,6 +187,8 @@ public: disabled_ = *value_ < 0; } + const char *tag() const { return "SliderPopup"; } + Event OnChange; private: @@ -214,6 +216,8 @@ public: : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false), liveUpdate_(liveUpdate) {} void CreatePopupContents(UI::ViewGroup *parent) override; + const char *tag() const { return "SliderFloatPopup"; } + Event OnChange; private: @@ -241,6 +245,8 @@ public: : PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {} virtual void CreatePopupContents(ViewGroup *parent) override; + const char *tag() const { return "TextEditPopup"; } + Event OnChange; private: diff --git a/UI/ComboKeyMappingScreen.cpp b/UI/ComboKeyMappingScreen.cpp index 3a4bc277d1..d4c8a814b6 100644 --- a/UI/ComboKeyMappingScreen.cpp +++ b/UI/ComboKeyMappingScreen.cpp @@ -56,6 +56,8 @@ public: parent->Add(scroll); } + const char *tag() const override { return "ButtonShape"; } + private: int *setting_; }; @@ -84,6 +86,8 @@ public: parent->Add(scroll); } + const char *tag() const override { return "ButtonIcon"; } + private: int *setting_; }; diff --git a/UI/ComboKeyMappingScreen.h b/UI/ComboKeyMappingScreen.h index 6eb271211d..c2ed6396d8 100644 --- a/UI/ComboKeyMappingScreen.h +++ b/UI/ComboKeyMappingScreen.h @@ -28,6 +28,8 @@ class ComboKeyScreen : public UIDialogScreenWithBackground { public: ComboKeyScreen(int id): id_(id) {} + const char *tag() const override { return "ComboKey"; } + void CreateViews() override; void onFinish(DialogResult result) override; diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index add89bfdfa..29f1360fbe 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -35,7 +35,7 @@ class SingleControlMapper; class ControlMappingScreen : public UIDialogScreenWithBackground { public: ControlMappingScreen() {} - std::string tag() const override { return "control mapping"; } + const char *tag() const override { return "ControlMapping"; } protected: void CreateViews() override; @@ -47,7 +47,7 @@ private: UI::EventReturn OnAutoConfigure(UI::EventParams ¶ms); UI::EventReturn OnVisualizeMapping(UI::EventParams ¶ms); - virtual void dialogFinished(const Screen *dialog, DialogResult result) override; + void dialogFinished(const Screen *dialog, DialogResult result) override; UI::ScrollView *rightScroll_; std::vector mappers_; @@ -61,17 +61,19 @@ public: pspBtn_ = btn; } - virtual bool key(const KeyInput &key) override; - virtual bool axis(const AxisInput &axis) override; + const char *tag() const override { return "KeyMappingNewKey"; } + + bool key(const KeyInput &key) override; + bool axis(const AxisInput &axis) override; void SetDelay(float t); protected: void CreatePopupContents(UI::ViewGroup *parent) override; - virtual bool FillVertical() const override { return false; } - virtual bool ShowButtons() const override { return true; } - virtual void OnCompleted(DialogResult result) override {} + bool FillVertical() const override { return false; } + bool ShowButtons() const override { return true; } + void OnCompleted(DialogResult result) override {} private: int pspBtn_; @@ -87,6 +89,8 @@ public: pspBtn_ = btn; } + const char *tag() const override { return "KeyMappingNewMouseKey"; } + bool key(const KeyInput &key) override; bool axis(const AxisInput &axis) override; @@ -114,6 +118,8 @@ public: void update() override; + const char *tag() const override { return "AnalogSetup"; } + protected: void CreateViews() override; @@ -144,6 +150,8 @@ public: bool key(const KeyInput &key) override; bool axis(const AxisInput &axis) override; + const char *tag() const override { return "TouchTest"; } + protected: struct TrackedTouch { int id; @@ -171,6 +179,8 @@ class VisualMappingScreen : public UIDialogScreenWithBackground { public: VisualMappingScreen() {} + const char *tag() const override { return "VisualMapping"; } + protected: void CreateViews() override; diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index e2e167c771..27a28a1b51 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -41,6 +41,8 @@ public: void update() override; void onFinish(DialogResult result) override; + const char *tag() const override { return "CwCheat"; } + protected: void CreateViews() override; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index a804eb98f0..8077875010 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -85,7 +85,7 @@ static const char *logLevelList[] = { "Verb." }; -void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { +void DevMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; auto dev = GetI18NCategory("Developer"); auto sy = GetI18NCategory("System"); @@ -94,25 +94,25 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { LinearLayout *items = new LinearLayout(ORIENT_VERTICAL); #if !defined(MOBILE_DEVICE) - items->Add(new Choice(dev->T("Log View")))->OnClick.Handle(this, &DevMenu::OnLogView); + items->Add(new Choice(dev->T("Log View")))->OnClick.Handle(this, &DevMenuScreen::OnLogView); #endif - items->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DevMenu::OnLogConfig); - items->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &DevMenu::OnDeveloperTools); - items->Add(new Choice(dev->T("Jit Compare")))->OnClick.Handle(this, &DevMenu::OnJitCompare); - items->Add(new Choice(dev->T("Shader Viewer")))->OnClick.Handle(this, &DevMenu::OnShaderView); + items->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DevMenuScreen::OnLogConfig); + items->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &DevMenuScreen::OnDeveloperTools); + 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.bShowGpuProfile, dev->T("GPU Profile"))); } - items->Add(new Choice(dev->T("Toggle Freeze")))->OnClick.Handle(this, &DevMenu::OnFreezeFrame); - items->Add(new Choice(dev->T("Dump Frame GPU Commands")))->OnClick.Handle(this, &DevMenu::OnDumpFrame); - items->Add(new Choice(dev->T("Toggle Audio Debug")))->OnClick.Handle(this, &DevMenu::OnToggleAudioDebug); + items->Add(new Choice(dev->T("Toggle Freeze")))->OnClick.Handle(this, &DevMenuScreen::OnFreezeFrame); + items->Add(new Choice(dev->T("Dump Frame GPU Commands")))->OnClick.Handle(this, &DevMenuScreen::OnDumpFrame); + items->Add(new Choice(dev->T("Toggle Audio Debug")))->OnClick.Handle(this, &DevMenuScreen::OnToggleAudioDebug); #ifdef USE_PROFILER items->Add(new CheckBox(&g_Config.bShowFrameProfiler, dev->T("Frame Profiler"), "")); #endif items->Add(new CheckBox(&g_Config.bDrawFrameGraph, dev->T("Draw Frametimes Graph"))); - items->Add(new Choice(dev->T("Reset limited logging")))->OnClick.Handle(this, &DevMenu::OnResetLimitedLogging); + items->Add(new Choice(dev->T("Reset limited logging")))->OnClick.Handle(this, &DevMenuScreen::OnResetLimitedLogging); scroll->Add(items); parent->Add(scroll); @@ -123,48 +123,48 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { } } -UI::EventReturn DevMenu::OnToggleAudioDebug(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnToggleAudioDebug(UI::EventParams &e) { g_Config.bShowAudioDebug = !g_Config.bShowAudioDebug; return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnResetLimitedLogging(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnResetLimitedLogging(UI::EventParams &e) { Reporting::ResetCounts(); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnLogView(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnLogView(UI::EventParams &e) { UpdateUIState(UISTATE_PAUSEMENU); screenManager()->push(new LogScreen()); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnLogConfig(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnLogConfig(UI::EventParams &e) { UpdateUIState(UISTATE_PAUSEMENU); screenManager()->push(new LogConfigScreen()); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnDeveloperTools(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnDeveloperTools(UI::EventParams &e) { UpdateUIState(UISTATE_PAUSEMENU); screenManager()->push(new DeveloperToolsScreen()); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnJitCompare(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnJitCompare(UI::EventParams &e) { UpdateUIState(UISTATE_PAUSEMENU); screenManager()->push(new JitCompareScreen()); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnShaderView(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnShaderView(UI::EventParams &e) { UpdateUIState(UISTATE_PAUSEMENU); if (gpu) // Avoid crashing if chosen while the game is being loaded. screenManager()->push(new ShaderListScreen()); return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnFreezeFrame(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnFreezeFrame(UI::EventParams &e) { if (PSP_CoreParameter().frozen) { PSP_CoreParameter().frozen = false; } else { @@ -173,12 +173,12 @@ UI::EventReturn DevMenu::OnFreezeFrame(UI::EventParams &e) { return UI::EVENT_DONE; } -UI::EventReturn DevMenu::OnDumpFrame(UI::EventParams &e) { +UI::EventReturn DevMenuScreen::OnDumpFrame(UI::EventParams &e) { gpu->DumpNextFrame(); return UI::EVENT_DONE; } -void DevMenu::dialogFinished(const Screen *dialog, DialogResult result) { +void DevMenuScreen::dialogFinished(const Screen *dialog, DialogResult result) { UpdateUIState(UISTATE_INGAME); // Close when a subscreen got closed. // TODO: a bug in screenmanager causes this not to work here. diff --git a/UI/DevScreens.h b/UI/DevScreens.h index 4e7065307e..92e7cc77f5 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -28,9 +28,11 @@ #include "UI/MiscScreens.h" #include "GPU/Common/ShaderCommon.h" -class DevMenu : public PopupScreen { +class DevMenuScreen : public PopupScreen { public: - DevMenu(std::shared_ptr i18n) : PopupScreen(i18n->T("Dev Tools")) {} + DevMenuScreen(std::shared_ptr i18n) : PopupScreen(i18n->T("Dev Tools")) {} + + const char *tag() const override { return "DevMenu"; } void CreatePopupContents(UI::ViewGroup *parent) override; void dialogFinished(const Screen *dialog, DialogResult result) override; @@ -50,7 +52,9 @@ protected: class JitDebugScreen : public UIDialogScreenWithBackground { public: JitDebugScreen() {} - virtual void CreateViews() override; + void CreateViews() override; + + const char *tag() const override { return "JitDebug"; } private: UI::EventReturn OnEnableAll(UI::EventParams &e); @@ -60,7 +64,9 @@ private: class LogConfigScreen : public UIDialogScreenWithBackground { public: LogConfigScreen() {} - virtual void CreateViews() override; + void CreateViews() override; + + const char *tag() const override { return "LogConfig"; } private: UI::EventReturn OnToggleAll(UI::EventParams &e); @@ -76,6 +82,8 @@ public: void CreateViews() override; void update() override; + const char *tag() const override { return "Log"; } + private: void UpdateLog(); UI::EventReturn OnSubmit(UI::EventParams &e); @@ -89,6 +97,8 @@ class LogLevelScreen : public ListPopupScreen { public: LogLevelScreen(const std::string &title); + const char *tag() const override { return "LogLevel"; } + private: virtual void OnCompleted(DialogResult result); @@ -96,7 +106,8 @@ private: class SystemInfoScreen : public UIDialogScreenWithBackground { public: - SystemInfoScreen() {} + const char *tag() const override { return "SystemInfo"; } + void CreateViews() override; }; @@ -106,13 +117,15 @@ public: memset(buttons_, 0, sizeof(buttons_)); } - virtual bool key(const KeyInput &key) override; + const char *tag() const override { return "AddressPrompt"; } + + bool key(const KeyInput &key) override; UI::Event OnChoice; protected: - virtual void CreatePopupContents(UI::ViewGroup *parent) override; - virtual void OnCompleted(DialogResult result) override; + void CreatePopupContents(UI::ViewGroup *parent) override; + void OnCompleted(DialogResult result) override; UI::EventReturn OnDigitButton(UI::EventParams &e); UI::EventReturn OnBackspace(UI::EventParams &e); @@ -128,8 +141,9 @@ private: class JitCompareScreen : public UIDialogScreenWithBackground { public: - JitCompareScreen() : currentBlock_(-1) {} - virtual void CreateViews() override; + void CreateViews() override; + + const char *tag() const override { return "JitCompare"; } private: void UpdateDisasm(); @@ -146,7 +160,7 @@ private: UI::EventReturn OnAddressChange(UI::EventParams &e); UI::EventReturn OnShowStats(UI::EventParams &e); - int currentBlock_; + int currentBlock_ = -1; UI::TextView *blockName_; UI::TextEdit *blockAddr_; @@ -158,9 +172,10 @@ private: class ShaderListScreen : public UIDialogScreenWithBackground { public: - ShaderListScreen() {} void CreateViews() override; + const char *tag() const override { return "ShaderList"; } + private: int ListShaders(DebugShaderType shaderType, UI::LinearLayout *view); @@ -175,6 +190,9 @@ public: : id_(id), type_(type) {} void CreateViews() override; + + const char *tag() const override { return "ShaderView"; } + private: std::string id_; DebugShaderType type_; @@ -188,6 +206,8 @@ public: void CreateViews() override; void update() override; + const char *tag() const override { return "FrameDumpTest"; } + private: UI::EventReturn OnLoadDump(UI::EventParams &e); diff --git a/UI/DisplayLayoutScreen.h b/UI/DisplayLayoutScreen.h index 15835258eb..06730742d4 100644 --- a/UI/DisplayLayoutScreen.h +++ b/UI/DisplayLayoutScreen.h @@ -31,7 +31,7 @@ public: virtual void dialogFinished(const Screen *dialog, DialogResult result) override; virtual void onFinish(DialogResult reason) override; virtual void resized() override; - std::string tag() const override { return "display layout screen"; } + const char *tag() const override { return "DisplayLayout"; } protected: virtual UI::EventReturn OnCenter(UI::EventParams &e); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 1b98b91bb8..14ee1ced6c 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -955,7 +955,7 @@ void EmuScreen::CreateViews() { UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) { auto dev = GetI18NCategory("Developer"); - DevMenu *devMenu = new DevMenu(dev); + DevMenuScreen *devMenu = new DevMenuScreen(dev); if (params.v) devMenu->SetPopupOrigin(params.v); screenManager()->push(devMenu); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index ff41bd81a4..1fd41ffb9d 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -40,6 +40,8 @@ public: EmuScreen(const Path &filename); ~EmuScreen(); + const char *tag() const override { return "Emu"; } + void update() override; void render() override; void preRender() override; diff --git a/UI/GPUDriverTestScreen.h b/UI/GPUDriverTestScreen.h index f2c5796be1..38ec2c33e1 100644 --- a/UI/GPUDriverTestScreen.h +++ b/UI/GPUDriverTestScreen.h @@ -17,6 +17,8 @@ public: void CreateViews() override; void render() override; + const char *tag() const override { return "GPUDriverTest"; } + private: void DiscardTest(); void ShaderTest(); diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 15d4073f98..edf519f46b 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -443,6 +443,7 @@ UI::EventReturn GameScreen::OnRemoveFromRecent(UI::EventParams &e) { class SetBackgroundPopupScreen : public PopupScreen { public: SetBackgroundPopupScreen(const std::string &title, const Path &gamePath); + const char *tag() const override { return "SetBackgroundPopup"; } protected: bool FillVertical() const override { return false; } diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 9379c8f134..cff0c48366 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -38,7 +38,7 @@ public: void render() override; - std::string tag() const override { return "game"; } + const char *tag() const override { return "Game"; } protected: void CreateViews() override; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 087b05740e..82b4cd859e 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -32,7 +32,7 @@ public: void update() override; void onFinish(DialogResult result) override; - std::string tag() const override { return "settings"; } + const char *tag() const override { return "GameSettings"; } protected: void sendMessage(const char *message, const char *value) override; @@ -157,10 +157,11 @@ private: class DeveloperToolsScreen : public UIDialogScreenWithBackground { public: - DeveloperToolsScreen() {} void update() override; void onFinish(DialogResult result) override; + const char *tag() const override { return "DeveloperTools"; } + protected: void CreateViews() override; @@ -205,6 +206,8 @@ public: void CreatePopupContents(UI::ViewGroup *parent) override; + const char *tag() const override { return "HostnameSelect"; } + protected: void OnCompleted(DialogResult result) override; bool CanComplete(DialogResult result) override; @@ -247,4 +250,6 @@ private: class GestureMappingScreen : public UIDialogScreenWithBackground { public: void CreateViews() override; + + const char *tag() const override { return "GestureMapping"; } }; diff --git a/UI/InstallZipScreen.h b/UI/InstallZipScreen.h index 6daa13bf7f..a7073ca73f 100644 --- a/UI/InstallZipScreen.h +++ b/UI/InstallZipScreen.h @@ -30,6 +30,8 @@ public: virtual void update() override; virtual bool key(const KeyInput &key) override; + const char *tag() const { return "install_zip"; } + protected: virtual void CreateViews() override; diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 6b15f06427..b81e193b93 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -102,6 +102,8 @@ public: bool isTopLevel() const override { return true; } + const char *tag() const override { return "Main"; } + // Horrible hack to show the demos & homebrew tab after having installed a game from a zip file. static bool showHomebrewTab; @@ -153,7 +155,7 @@ protected: class UmdReplaceScreen : public UIDialogScreenWithBackground { public: - UmdReplaceScreen() {} + const char *tag() const override { return "UmdReplace"; } protected: void CreateViews() override; @@ -174,6 +176,8 @@ public: void CreatePopupContents(UI::ViewGroup *parent) override; UI::Event OnRecentChanged; + const char *tag() const override { return "GridSettings"; } + private: UI::EventReturn GridPlusClick(UI::EventParams &e); UI::EventReturn GridMinusClick(UI::EventParams &e); diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index f315467c56..6cab2a34a4 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -36,7 +36,7 @@ public: MemStickScreen(bool initialSetup); ~MemStickScreen() {} - std::string tag() const override { return "game"; } + const char *tag() const override { return "memstick"; } enum Choice { CHOICE_BROWSE_FOLDER, @@ -112,6 +112,9 @@ class ConfirmMemstickMoveScreen : public UIDialogScreenWithBackground { public: ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup); ~ConfirmMemstickMoveScreen(); + + const char *tag() const override { return "ConfirmMemstickMove"; } + protected: void update() override; void CreateViews() override; diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index d094b6d3db..164a06cfec 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -82,6 +82,8 @@ public: void TriggerFinish(DialogResult result) override; + const char *tag() const override { return "Prompt"; } + private: UI::EventReturn OnYes(UI::EventParams &e); UI::EventReturn OnNo(UI::EventParams &e); @@ -96,6 +98,8 @@ class NewLanguageScreen : public ListPopupScreen { public: NewLanguageScreen(const std::string &title); + const char *tag() const override { return "NewLanguage"; } + private: void OnCompleted(DialogResult result) override; bool ShowButtons() const override { return true; } @@ -110,6 +114,8 @@ public: void CreateViews() override; + const char *tag() const override { return "PostProc"; } + private: void OnCompleted(DialogResult result) override; bool ShowButtons() const override { return true; } @@ -123,6 +129,8 @@ public: void CreateViews() override; + const char *tag() const override { return "TextureShader"; } + private: void OnCompleted(DialogResult result) override; bool ShowButtons() const override { return true; } @@ -146,6 +154,8 @@ public: void sendMessage(const char *message, const char *value) override; void CreateViews() override {} + const char *tag() const override { return "Logo"; } + private: void Next(); int frames_ = 0; @@ -162,6 +172,8 @@ public: void CreateViews() override; + const char *tag() const override { return "Credits"; } + private: UI::EventReturn OnOK(UI::EventParams &e); diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 339409ed6e..f358fa1e06 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -172,9 +172,7 @@ public: return slot_; } - std::string tag() const override { - return "screenshot"; - } + const char *tag() const override { return "screenshot_view"; } protected: bool FillVertical() const override { return false; } diff --git a/UI/PauseScreen.h b/UI/PauseScreen.h index 15b22174e2..3c66609285 100644 --- a/UI/PauseScreen.h +++ b/UI/PauseScreen.h @@ -33,6 +33,8 @@ public: virtual void dialogFinished(const Screen *dialog, DialogResult dr) override; + const char *tag() const override { return "GamePause"; } + protected: virtual void CreateViews() override; virtual void update() override; diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index bfcac815be..b8676e4832 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -29,6 +29,8 @@ class RemoteISOScreen : public UIScreenWithBackground { public: RemoteISOScreen(); + const char *tag() const override { return "RemoteISO"; } + protected: void update() override; void CreateViews() override; @@ -57,6 +59,8 @@ public: RemoteISOConnectScreen(); ~RemoteISOConnectScreen() override; + const char *tag() const override { return "RemoteISOConnect"; } + protected: void update() override; void CreateViews() override; @@ -83,6 +87,8 @@ class RemoteISOBrowseScreen : public MainScreen { public: RemoteISOBrowseScreen(const std::string &url, const std::vector &games); + const char *tag() const override { return "RemoteISOBrowse"; } + protected: void CreateViews() override; @@ -94,6 +100,8 @@ class RemoteISOSettingsScreen : public UIDialogScreenWithBackground { public: RemoteISOSettingsScreen(); + const char *tag() const override { return "RemoteISOSettings"; } + UI::EventReturn OnClickRemoteISOSubdir(UI::EventParams &e); UI::EventReturn OnClickRemoteServer(UI::EventParams &e); protected: diff --git a/UI/ReportScreen.h b/UI/ReportScreen.h index a7d132b2c8..006d90e6f5 100644 --- a/UI/ReportScreen.h +++ b/UI/ReportScreen.h @@ -37,6 +37,8 @@ class ReportScreen : public UIDialogScreenWithGameBackground { public: ReportScreen(const Path &gamePath); + const char *tag() const override { return "Report"; } + protected: void postRender() override; void update() override; @@ -75,6 +77,8 @@ class ReportFinishScreen : public UIDialogScreenWithGameBackground { public: ReportFinishScreen(const Path &gamePath, ReportingOverallScore score); + const char *tag() const override { return "ReportFinish"; } + protected: void update() override; void CreateViews() override; diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 731e3d5d95..0172b6a19b 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -78,8 +78,9 @@ static std::string TrimString(const std::string &str) { class SavedataPopupScreen : public PopupScreen { public: - SavedataPopupScreen(std::string savePath, std::string title) : PopupScreen(TrimString(title)), savePath_(savePath) { - } + SavedataPopupScreen(std::string savePath, std::string title) : PopupScreen(TrimString(title)), savePath_(savePath) { } + + const char *tag() const override { return "SavedataPopup"; } void CreatePopupContents(UI::ViewGroup *parent) override { using namespace UI; diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index 1f9509e687..1bb2f42140 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -74,6 +74,8 @@ public: void dialogFinished(const Screen *dialog, DialogResult result) override; void sendMessage(const char *message, const char *value) override; + const char *tag() const override { return "Savedata"; } + protected: UI::EventReturn OnSavedataButtonClick(UI::EventParams &e); UI::EventReturn OnSortClick(UI::EventParams &e); diff --git a/UI/Store.h b/UI/Store.h index 263e5dbde1..8f02f1a615 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -67,7 +67,7 @@ public: ~StoreScreen(); void update() override; - std::string tag() const override { return "store"; } + const char *tag() const override { return "Store"; } protected: void CreateViews() override; diff --git a/UI/TiltAnalogSettingsScreen.h b/UI/TiltAnalogSettingsScreen.h index 3aeece6eb9..832a436842 100644 --- a/UI/TiltAnalogSettingsScreen.h +++ b/UI/TiltAnalogSettingsScreen.h @@ -27,6 +27,8 @@ public: void CreateViews() override; bool axis(const AxisInput &axis) override; + const char *tag() const override { return "TiltAnalogSettings"; } + private: UI::EventReturn OnCalibrate(UI::EventParams &e); float currentTiltX_ = 0.0f; diff --git a/UI/TouchControlLayoutScreen.h b/UI/TouchControlLayoutScreen.h index 3db9394258..9834f217f0 100644 --- a/UI/TouchControlLayoutScreen.h +++ b/UI/TouchControlLayoutScreen.h @@ -33,6 +33,8 @@ public: virtual void update() override; virtual void resized() override; + const char *tag() const override { return "TouchControlLayout"; } + protected: virtual UI::EventReturn OnReset(UI::EventParams &e); virtual UI::EventReturn OnVisibility(UI::EventParams &e); diff --git a/UI/TouchControlVisibilityScreen.h b/UI/TouchControlVisibilityScreen.h index f44e333395..52d6d00eea 100644 --- a/UI/TouchControlVisibilityScreen.h +++ b/UI/TouchControlVisibilityScreen.h @@ -36,6 +36,8 @@ public: void CreateViews() override; void onFinish(DialogResult result) override; + const char *tag() const override { return "TouchControlVisibility"; } + protected: UI::EventReturn OnToggleAll(UI::EventParams &e); @@ -47,4 +49,6 @@ private: class RightAnalogMappingScreen : public UIDialogScreenWithBackground { public: void CreateViews() override; + + const char *tag() const override { return "RightAnalogMapping"; } }; From 0b9d6f9cb07167066e3746210639452f9af40e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Sep 2022 10:23:32 +0200 Subject: [PATCH 032/326] Log the screen tag when logging scissor errors. --- Common/UI/Context.cpp | 2 +- Common/UI/Context.h | 7 ++++++- Common/UI/UIScreen.cpp | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index e9fe8ed0c5..21c687bd2b 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -170,7 +170,7 @@ void UIContext::ActivateTopScissor() { int h = std::max(0.0f, ceilf(scale_y * bounds.h)); if (x < 0 || y < 0 || x + w > pixel_xres || y + h > pixel_yres) { // This won't actually report outside a game, but we can try. - ERROR_LOG_REPORT(G3D, "UI scissor out of bounds: %d,%d-%d,%d / %d,%d", x, y, w, h, pixel_xres, pixel_yres); + ERROR_LOG_REPORT(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); x = std::max(0, x); y = std::max(0, y); w = std::min(w, pixel_xres - x); diff --git a/Common/UI/Context.h b/Common/UI/Context.h index ad02b4027c..ac26ab0202 100644 --- a/Common/UI/Context.h +++ b/Common/UI/Context.h @@ -74,7 +74,6 @@ public: const UI::Theme *theme; // Utility methods - TextDrawer *Text() const { return textDrawer_; } void SetFontStyle(const UI::FontStyle &style); @@ -103,6 +102,10 @@ public: void setUIAtlas(const std::string &name); + void SetScreenTag(const char *tag) { + screenTag_ = tag; + } + private: Draw::DrawContext *draw_ = nullptr; Bounds bounds_; @@ -126,4 +129,6 @@ private: std::string lastUIAtlas_; std::string UIAtlas_ = "ui_atlas.zim"; + + const char *screenTag_ = nullptr; }; diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index 8026de37bc..ecebae3a81 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -117,6 +117,9 @@ void UIScreen::render() { if (root_) { UIContext *uiContext = screenManager()->getUIContext(); + + uiContext->SetScreenTag(tag()); + UI::LayoutViewHierarchy(*uiContext, root_, ignoreInsets_); uiContext->PushTransform({translation_, scale_, alpha_}); From 9ec35d1464941747d11777b41df17aafca21f84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Sep 2022 12:34:27 +0200 Subject: [PATCH 033/326] Logic op fix, min filter fix. Also remove some unnecessary indentation. Fixes the water on Adreno (no logic) --- GPU/Common/GPUStateUtils.cpp | 119 +++++++++++++++--------------- GPU/Common/TextureCacheCommon.cpp | 5 +- 2 files changed, 65 insertions(+), 59 deletions(-) diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index fb90850cb9..04f8240a61 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -859,66 +859,63 @@ static inline bool blendColorSimilar(uint32_t a, uint32_t b, int margin = 25) { static bool SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) { // Note: our shader solution applies logic ops BEFORE blending, not correctly after. // This is however fine for the most common ones, like CLEAR/NOOP/SET, etc. - if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { - if (gstate.isLogicOpEnabled()) { - switch (gstate.getLogicOp()) { - case GE_LOGIC_CLEAR: - srcBlend = BlendFactor::ZERO; - dstBlend = BlendFactor::ZERO; - blendEq = BlendEq::ADD; - return true; - case GE_LOGIC_AND: - case GE_LOGIC_AND_REVERSE: - WARN_LOG_REPORT_ONCE(d3dLogicOpAnd, G3D, "Unsupported AND logic op: %x", gstate.getLogicOp()); - break; - case GE_LOGIC_COPY: - // This is the same as off. - break; - case GE_LOGIC_COPY_INVERTED: - // Handled in the shader. - break; - case GE_LOGIC_AND_INVERTED: - case GE_LOGIC_NOR: - case GE_LOGIC_NAND: - case GE_LOGIC_EQUIV: - // Handled in the shader. - WARN_LOG_REPORT_ONCE(d3dLogicOpAndInverted, G3D, "Attempted invert for logic op: %x", gstate.getLogicOp()); - break; - case GE_LOGIC_INVERTED: - srcBlend = BlendFactor::ONE; - dstBlend = BlendFactor::ONE; - blendEq = BlendEq::SUBTRACT; - WARN_LOG_REPORT_ONCE(d3dLogicOpInverted, G3D, "Attempted inverse for logic op: %x", gstate.getLogicOp()); - return true; - case GE_LOGIC_NOOP: - srcBlend = BlendFactor::ZERO; - dstBlend = BlendFactor::ONE; - blendEq = BlendEq::ADD; - return true; - case GE_LOGIC_XOR: - WARN_LOG_REPORT_ONCE(d3dLogicOpOrXor, G3D, "Unsupported XOR logic op: %x", gstate.getLogicOp()); - break; - case GE_LOGIC_OR: - case GE_LOGIC_OR_INVERTED: - // Inverted in shader. - srcBlend = BlendFactor::ONE; - dstBlend = BlendFactor::ONE; - blendEq = BlendEq::ADD; - WARN_LOG_REPORT_ONCE(d3dLogicOpOr, G3D, "Attempted or for logic op: %x", gstate.getLogicOp()); - return true; - case GE_LOGIC_OR_REVERSE: - WARN_LOG_REPORT_ONCE(d3dLogicOpOrReverse, G3D, "Unsupported OR REVERSE logic op: %x", gstate.getLogicOp()); - break; - case GE_LOGIC_SET: - srcBlend = BlendFactor::ONE; - dstBlend = BlendFactor::ONE; - blendEq = BlendEq::ADD; - WARN_LOG_REPORT_ONCE(d3dLogicOpSet, G3D, "Attempted set for logic op: %x", gstate.getLogicOp()); - return true; - } + if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled()) { + switch (gstate.getLogicOp()) { + case GE_LOGIC_CLEAR: + srcBlend = BlendFactor::ZERO; + dstBlend = BlendFactor::ZERO; + blendEq = BlendEq::ADD; + return true; + case GE_LOGIC_AND: + case GE_LOGIC_AND_REVERSE: + WARN_LOG_REPORT_ONCE(d3dLogicOpAnd, G3D, "Unsupported AND logic op: %x", gstate.getLogicOp()); + break; + case GE_LOGIC_COPY: + // This is the same as off. + break; + case GE_LOGIC_COPY_INVERTED: + // Handled in the shader. + break; + case GE_LOGIC_AND_INVERTED: + case GE_LOGIC_NOR: + case GE_LOGIC_NAND: + case GE_LOGIC_EQUIV: + // Handled in the shader. + WARN_LOG_REPORT_ONCE(d3dLogicOpAndInverted, G3D, "Attempted invert for logic op: %x", gstate.getLogicOp()); + break; + case GE_LOGIC_INVERTED: + srcBlend = BlendFactor::ONE; + dstBlend = BlendFactor::ONE; + blendEq = BlendEq::SUBTRACT; + WARN_LOG_REPORT_ONCE(d3dLogicOpInverted, G3D, "Attempted inverse for logic op: %x", gstate.getLogicOp()); + return true; + case GE_LOGIC_NOOP: + srcBlend = BlendFactor::ZERO; + dstBlend = BlendFactor::ONE; + blendEq = BlendEq::ADD; + return true; + case GE_LOGIC_XOR: + WARN_LOG_REPORT_ONCE(d3dLogicOpOrXor, G3D, "Unsupported XOR logic op: %x", gstate.getLogicOp()); + break; + case GE_LOGIC_OR: + case GE_LOGIC_OR_INVERTED: + // Inverted in shader. + srcBlend = BlendFactor::ONE; + dstBlend = BlendFactor::ONE; + blendEq = BlendEq::ADD; + WARN_LOG_REPORT_ONCE(d3dLogicOpOr, G3D, "Attempted or for logic op: %x", gstate.getLogicOp()); + return true; + case GE_LOGIC_OR_REVERSE: + WARN_LOG_REPORT_ONCE(d3dLogicOpOrReverse, G3D, "Unsupported OR REVERSE logic op: %x", gstate.getLogicOp()); + break; + case GE_LOGIC_SET: + srcBlend = BlendFactor::ONE; + dstBlend = BlendFactor::ONE; + blendEq = BlendEq::ADD; + WARN_LOG_REPORT_ONCE(d3dLogicOpSet, G3D, "Attempted set for logic op: %x", gstate.getLogicOp()); + return true; } } - return false; } @@ -1080,6 +1077,12 @@ static void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBl case REPLACE_BLEND_NO: // We may still want to do something about stencil -> alpha. ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState); + + if (forceReplaceBlend) { + // If this is true, the logic and mask replacements will be applied, at least. In that case, + // we should not apply any logic op simulation. + blendState.simulateLogicOpType = LOGICOPTYPE_NORMAL; + } return; case REPLACE_BLEND_BLUE_TO_ALPHA: diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 66d70f7c02..ff31455fae 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -292,11 +292,14 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac SamplerCacheKey TextureCacheCommon::GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) { SamplerCacheKey key = GetSamplingParams(0, nullptr); - // Kill any mipmapping settings. + // Kill any mipmapping settings, and reset min filtering. + int minFilt = gstate.texfilter & 0x7; + key.minFilt = minFilt & 1; key.mipEnable = false; key.mipFilt = false; key.aniso = 0.0; key.maxLevel = 0.0f; + key.lodBias = 0.0f; // Often the framebuffer will not match the texture size. We'll wrap/clamp in the shader in that case. int w = gstate.getTextureWidth(0); From ac9677dcc045035bc1d428614342a110b5c0149a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Sep 2022 13:14:44 +0200 Subject: [PATCH 034/326] Ah, need to enable the Mali workaround for Outrun's water, too. --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 10 +++++++--- assets/compat.ini | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 8420c01450..91a6f5ff3e 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -840,15 +840,19 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) // Workaround for Intel driver bug. TODO: Re-enable after some driver version bugs_.Infest(Bugs::DUAL_SOURCE_BLENDING_BROKEN); } else if (caps_.vendor == GPUVendor::VENDOR_ARM) { + int majorVersion = VK_API_VERSION_MAJOR(deviceProps.driverVersion); + // These GPUs (up to some certain hardware version?) have a bug where draws where gl_Position.w == .z // corrupt the depth buffer. This is easily worked around by simply scaling Z down a tiny bit when this case // is detected. See: https://github.com/hrydgard/ppsspp/issues/11937 bugs_.Infest(Bugs::EQUAL_WZ_CORRUPTS_DEPTH); - // At least one driver at the upper end of the range is known to be likely to suffer from the bug causing issue #13833 (Midnight Club map broken). + + // Similar to the Adreno bug, see #13833 (Midnight Club map broken) and other issues. + // Reported fixed in major version 40 - let's add a check once confirmed. bugs_.Infest(Bugs::MALI_STENCIL_DISCARD_BUG); - // This started in driver 31 or 32. - if (VK_API_VERSION_MAJOR(deviceProps.driverVersion) >= 32) { + // This started in driver 31 or 32, fixed in 40 - let's add a check once confirmed. + if (majorVersion >= 32) { bugs_.Infest(Bugs::MALI_CONSTANT_LOAD_BUG); // See issue #15661 } } diff --git a/assets/compat.ini b/assets/compat.ini index f79978e9fd..c4986ae888 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -632,6 +632,11 @@ ULJM05886 = true ULJM08063 = true NPJH50535 = true +# Outrun 2006: Coast to Coast - water +ULES00262 = true +ULUS10064 = true +ULKS46087 = true + [RequireDefaultCPUClock] # GOW : Ghost of Sparta UCUS98737 = true From 976b502c825bf3cb0f3a665893968f24f1142fcc Mon Sep 17 00:00:00 2001 From: sum2012 Date: Fri, 16 Sep 2022 21:05:55 +0800 Subject: [PATCH 035/326] Increase log level of ppsspp version Default log level is Error so that cannot see it, --- Core/System.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/System.cpp b/Core/System.cpp index 29b1765fb0..9c7519edd6 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -413,11 +413,11 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { } #if defined(_WIN32) && PPSSPP_ARCH(AMD64) - INFO_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION); + NOTICE_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION); #elif defined(_WIN32) && !PPSSPP_ARCH(AMD64) - INFO_LOG(BOOT, "PPSSPP %s Windows 32 bit", PPSSPP_GIT_VERSION); + NOTICE_LOG(BOOT, "PPSSPP %s Windows 32 bit", PPSSPP_GIT_VERSION); #else - INFO_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION); + NOTICE_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION); #endif Core_NotifyLifecycle(CoreLifecycle::STARTING); From 370a7304a6ce196e04c938e03c77b6593d5351e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Sep 2022 23:49:52 +0200 Subject: [PATCH 036/326] Careful with min filtering of framebuffer textures only if auto max quality is on. --- GPU/Common/TextureCacheCommon.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index ff31455fae..cba174d416 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -292,9 +292,13 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac SamplerCacheKey TextureCacheCommon::GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) { SamplerCacheKey key = GetSamplingParams(0, nullptr); - // Kill any mipmapping settings, and reset min filtering. - int minFilt = gstate.texfilter & 0x7; - key.minFilt = minFilt & 1; + // In case auto max quality was on, restore min filt. Another fix for water in Outrun. + if (g_Config.iTexFiltering == TEX_FILTER_AUTO_MAX_QUALITY) { + int minFilt = gstate.texfilter & 0x7; + key.minFilt = minFilt & 1; + } + + // Kill any mipmapping settings. key.mipEnable = false; key.mipFilt = false; key.aniso = 0.0; From 30e3f3b47a2fe8efb78b14e82fa952256f869cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 19 Feb 2022 20:40:11 +0100 Subject: [PATCH 037/326] Add two more extensions to device extension lookup --- Common/GPU/Vulkan/VulkanContext.cpp | 3 +++ Common/GPU/Vulkan/VulkanLoader.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 55edc0f31f..0d29b518df 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -667,7 +667,10 @@ VkResult VulkanContext::CreateDevice() { extensionsLookup_.KHR_create_renderpass2 = true; extensionsLookup_.KHR_depth_stencil_resolve = EnableDeviceExtension(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); } + extensionsLookup_.EXT_shader_stencil_export = EnableDeviceExtension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); + extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME); + extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME); VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_info.queueCreateInfoCount = 1; diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h index 1f3d7d7704..b0c14570da 100644 --- a/Common/GPU/Vulkan/VulkanLoader.h +++ b/Common/GPU/Vulkan/VulkanLoader.h @@ -241,6 +241,8 @@ struct VulkanExtensions { bool KHR_depth_stencil_resolve; bool EXT_shader_stencil_export; bool EXT_swapchain_colorspace; + bool ARM_rasterization_order_attachment_access; + bool EXT_fragment_shader_interlock; // bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers. }; From ce82fce8dea423f50f0f7487d7351bf60b7998b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 19 Feb 2022 20:40:27 +0100 Subject: [PATCH 038/326] Use subpass dependencies to implement shader framebuffer read in Vulkan. --- Common/GPU/Vulkan/VulkanBarrier.cpp | 2 +- Common/GPU/Vulkan/VulkanBarrier.h | 2 + Common/GPU/Vulkan/VulkanQueueRunner.cpp | 91 ++++++++++++++++++++--- Common/GPU/Vulkan/VulkanQueueRunner.h | 20 +++-- Common/GPU/Vulkan/VulkanRenderManager.cpp | 12 ++- Common/GPU/Vulkan/VulkanRenderManager.h | 4 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 62 ++++++++------- Common/GPU/thin3d.h | 4 + GPU/Common/DrawEngineCommon.cpp | 6 +- GPU/Common/DrawEngineCommon.h | 8 +- GPU/Common/FragmentShaderGenerator.cpp | 31 +++++--- GPU/Common/FragmentShaderGenerator.h | 2 +- GPU/D3D11/StateMappingD3D11.cpp | 9 ++- GPU/Directx9/DrawEngineDX9.h | 2 + GPU/Directx9/StateMappingDX9.cpp | 13 ++-- GPU/GLES/StateMappingGLES.cpp | 10 ++- GPU/GPUState.h | 3 +- GPU/Vulkan/DrawEngineVulkan.cpp | 20 +++-- GPU/Vulkan/DrawEngineVulkan.h | 3 +- GPU/Vulkan/FramebufferManagerVulkan.h | 2 +- GPU/Vulkan/GPU_Vulkan.cpp | 3 + GPU/Vulkan/PipelineManagerVulkan.cpp | 17 +++-- GPU/Vulkan/PipelineManagerVulkan.h | 9 ++- GPU/Vulkan/StateMappingVulkan.cpp | 10 ++- 24 files changed, 248 insertions(+), 97 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanBarrier.cpp b/Common/GPU/Vulkan/VulkanBarrier.cpp index 125d51ea6d..e4f2d09089 100644 --- a/Common/GPU/Vulkan/VulkanBarrier.cpp +++ b/Common/GPU/Vulkan/VulkanBarrier.cpp @@ -4,7 +4,7 @@ void VulkanBarrier::Flush(VkCommandBuffer cmd) { if (!imageBarriers_.empty()) { - vkCmdPipelineBarrier(cmd, srcStageMask_, dstStageMask_, 0, 0, nullptr, 0, nullptr, (uint32_t)imageBarriers_.size(), imageBarriers_.data()); + vkCmdPipelineBarrier(cmd, srcStageMask_, dstStageMask_, dependencyFlags_, 0, nullptr, 0, nullptr, (uint32_t)imageBarriers_.size(), imageBarriers_.data()); } imageBarriers_.clear(); srcStageMask_ = 0; diff --git a/Common/GPU/Vulkan/VulkanBarrier.h b/Common/GPU/Vulkan/VulkanBarrier.h index eb949dd2f0..0d5754b3f4 100644 --- a/Common/GPU/Vulkan/VulkanBarrier.h +++ b/Common/GPU/Vulkan/VulkanBarrier.h @@ -21,6 +21,7 @@ public: ) { srcStageMask_ |= srcStageMask; dstStageMask_ |= dstStageMask; + dependencyFlags_ |= VK_DEPENDENCY_BY_REGION_BIT; VkImageMemoryBarrier imageBarrier; imageBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -112,4 +113,5 @@ private: VkPipelineStageFlags srcStageMask_ = 0; VkPipelineStageFlags dstStageMask_ = 0; std::vector imageBarriers_; + VkDependencyFlags dependencyFlags_ = 0; }; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 10a88e623a..a327880886 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -34,8 +34,11 @@ RenderPassType MergeRPTypes(RenderPassType a, RenderPassType b) { if (a == b) { // Trivial merging case. return a; + } else if (a == RP_TYPE_COLOR_DEPTH && b == RP_TYPE_COLOR_DEPTH_INPUT) { + return RP_TYPE_COLOR_DEPTH_INPUT; + } else if (a == RP_TYPE_COLOR_DEPTH_INPUT && b == RP_TYPE_COLOR_DEPTH) { + return RP_TYPE_COLOR_DEPTH_INPUT; } - // More cases to be added later. return a; } @@ -155,7 +158,12 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) { return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning } +// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827 +// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies + VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) { + bool selfDependency = rpType == RP_TYPE_COLOR_DEPTH_INPUT; + VkAttachmentDescription attachments[2] = {}; attachments[0].format = rpType == RP_TYPE_BACKBUFFER ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; @@ -179,7 +187,7 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp VkAttachmentReference color_reference{}; color_reference.attachment = 0; - color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_reference{}; depth_reference.attachment = 1; @@ -188,8 +196,13 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.flags = 0; - subpass.inputAttachmentCount = 0; - subpass.pInputAttachments = nullptr; + if (selfDependency) { + subpass.inputAttachmentCount = 1; + subpass.pInputAttachments = &color_reference; + } else { + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + } subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_reference; subpass.pResolveAttachments = nullptr; @@ -198,22 +211,40 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp subpass.pPreserveAttachments = nullptr; // Not sure if this is really necessary. - VkSubpassDependency dep{}; - dep.srcSubpass = VK_SUBPASS_EXTERNAL; - dep.dstSubpass = 0; - dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dep.srcAccessMask = 0; - dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkSubpassDependency deps[2]{}; + size_t numDeps = 0; VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; rp.attachmentCount = 2; rp.pAttachments = attachments; rp.subpassCount = 1; rp.pSubpasses = &subpass; + if (rpType == RP_TYPE_BACKBUFFER) { + deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL; + deps[numDeps].dstSubpass = 0; + deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[numDeps].srcAccessMask = 0; + deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + numDeps++; rp.dependencyCount = 1; - rp.pDependencies = &dep; + } + + if (selfDependency) { + deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + deps[numDeps].srcSubpass = 0; + deps[numDeps].dstSubpass = 0; + numDeps++; + } + + if (numDeps > 0) { + rp.dependencyCount = (u32)numDeps; + rp.pDependencies = deps; } VkRenderPass pass; @@ -246,6 +277,30 @@ VKRRenderPass *VulkanQueueRunner::GetRenderPass(const RPKey &key) { return pass; } +// Must match the subpass self-dependency declared above. +void VulkanQueueRunner::SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) { + if (aspect & VK_IMAGE_ASPECT_COLOR_BIT) { + VkAccessFlags srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkAccessFlags dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + recordBarrier->TransitionImage( + img.image, + 0, + 1, + aspect, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_GENERAL, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask + ); + } else { + _assert_msg_(false, "Depth self-dependencies not yet supported"); + } +} + void VulkanQueueRunner::PreprocessSteps(std::vector &steps) { // Optimizes renderpasses, then sequences them. // Planned optimizations: @@ -817,6 +872,9 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) { case VKRRenderCommand::REMOVED: INFO_LOG(G3D, " (Removed)"); break; + case VKRRenderCommand::SELF_DEPENDENCY_BARRIER: + INFO_LOG(G3D, " SelfBarrier()"); + break; case VKRRenderCommand::BIND_GRAPHICS_PIPELINE: INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline); break; @@ -1235,6 +1293,15 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c break; } + case VKRRenderCommand::SELF_DEPENDENCY_BARRIER: + { + _assert_(step.render.pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT); + VulkanBarrier barrier; + SelfDependencyBarrier(step.render.framebuffer->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier); + barrier.Flush(cmd); + break; + } + case VKRRenderCommand::PUSH_CONSTANTS: vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data); break; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index adb9e7d96e..8f0533331a 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -20,7 +20,6 @@ struct VKRImage; enum { QUEUE_HACK_MGS2_ACID = 1, QUEUE_HACK_SONIC = 2, - // Killzone PR = 4. QUEUE_HACK_RENDERPASS_MERGE = 8, }; @@ -36,20 +35,24 @@ enum class VKRRenderCommand : uint8_t { DRAW, DRAW_INDEXED, PUSH_CONSTANTS, + SELF_DEPENDENCY_BARRIER, NUM_RENDER_COMMANDS, }; -enum PipelineFlags { - PIPELINE_FLAG_NONE = 0, - PIPELINE_FLAG_USES_LINES = (1 << 2), - PIPELINE_FLAG_USES_BLEND_CONSTANT = (1 << 3), - PIPELINE_FLAG_USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer. +enum class PipelineFlags { + NONE = 0, + USES_LINES = (1 << 2), + USES_BLEND_CONSTANT = (1 << 3), + USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer. + USES_INPUT_ATTACHMENT = (1 << 5), }; +ENUM_CLASS_BITOPS(PipelineFlags); // Pipelines need to be created for the right type of render pass. enum RenderPassType { RP_TYPE_BACKBUFFER, RP_TYPE_COLOR_DEPTH, + RP_TYPE_COLOR_DEPTH_INPUT, // Later will add pure-color render passes. RP_TYPE_COUNT, }; @@ -168,7 +171,6 @@ struct VKRStep { union { struct { VKRFramebuffer *framebuffer; - // TODO: Look these up through renderPass? VKRRenderPassLoadAction colorLoad; VKRRenderPassLoadAction depthLoad; VKRRenderPassLoadAction stencilLoad; @@ -183,7 +185,7 @@ struct VKRStep { int numReads; VkImageLayout finalColorLayout; VkImageLayout finalDepthStencilLayout; - u32 pipelineFlags; + PipelineFlags pipelineFlags; // contains the self dependency flag, in the form of USES_INPUT_ATTACHMENT VkRect2D renderArea; // Render pass type. Deduced after finishing recording the pass, from the used pipelines. // NOTE: Storing the render pass here doesn't do much good, we change the compatible parameters (load/store ops) during step optimization. @@ -324,6 +326,8 @@ private: static void SetupTransitionToTransferSrc(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); static void SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); + static void SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); + VulkanContext *vulkan_; VkFramebuffer backbuffer_ = VK_NULL_HANDLE; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 56f40655f7..7984824a26 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -223,7 +223,7 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int // Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers. ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; if (color) { - ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; } else { ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; } @@ -774,6 +774,9 @@ void VulkanRenderManager::EndCurRenderStep() { curRenderStep_->render.pipelineFlags = curPipelineFlags_; if (!curRenderStep_->render.framebuffer) { rpType = RP_TYPE_BACKBUFFER; + } else if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) { + // Not allowed on backbuffers. + rpType = RP_TYPE_COLOR_DEPTH_INPUT; } VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key); @@ -806,7 +809,12 @@ void VulkanRenderManager::EndCurRenderStep() { // We no longer have a current render step. curRenderStep_ = nullptr; - curPipelineFlags_ = 0; + curPipelineFlags_ = (PipelineFlags)0; +} + +void VulkanRenderManager::BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits) { + _dbg_assert_(curRenderStep_); + curRenderStep_->commands.push_back(VkRenderData{ VKRRenderCommand::SELF_DEPENDENCY_BARRIER }); } void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 4cc7aafdfc..b29b8bdcbd 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -236,6 +236,8 @@ public: // as the other backends, even though there's no actual binding happening here. VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int attachment); + void BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits); + bool CopyFramebufferToMemorySync(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag); void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag); @@ -544,7 +546,7 @@ private: VKRStep *curRenderStep_ = nullptr; bool curStepHasViewport_ = false; bool curStepHasScissor_ = false; - u32 curPipelineFlags_ = 0; + PipelineFlags curPipelineFlags_{}; BoundingRect curRenderArea_; std::vector steps_; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 91a6f5ff3e..b36c4e90ea 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -401,9 +401,10 @@ public: // These functions should be self explanatory. void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override; Framebuffer *GetCurrentRenderTarget() override { - return curFramebuffer_; + return (Framebuffer *)curFramebuffer_.ptr; } void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override; + void BindCurrentFramebufferForColorInput() override; void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; @@ -473,27 +474,7 @@ public: std::vector GetFeatureList() const override; std::vector GetExtensionList() const override; - uint64_t GetNativeObject(NativeObject obj, void *srcObject) override { - switch (obj) { - case NativeObject::CONTEXT: - return (uint64_t)vulkan_; - case NativeObject::INIT_COMMANDBUFFER: - return (uint64_t)renderManager_.GetInitCmd(); - case NativeObject::BOUND_TEXTURE0_IMAGEVIEW: - return (uint64_t)boundImageView_[0]; - case NativeObject::BOUND_TEXTURE1_IMAGEVIEW: - return (uint64_t)boundImageView_[1]; - case NativeObject::RENDER_MANAGER: - return (uint64_t)(uintptr_t)&renderManager_; - case NativeObject::NULL_IMAGEVIEW: - return (uint64_t)GetNullTexture()->GetImageView(); - case NativeObject::TEXTURE_VIEW: - return (uint64_t)(((VKTexture *)srcObject)->GetImageView()); - default: - Crash(); - return 0; - } - } + uint64_t GetNativeObject(NativeObject obj, void *srcObject) override; void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override; @@ -522,7 +503,7 @@ private: VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE; VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE; VkPipelineCache pipelineCache_ = VK_NULL_HANDLE; - AutoRef curFramebuffer_; + AutoRef curFramebuffer_; VkDevice device_; VkQueue queue_; @@ -808,6 +789,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) caps_.textureNPOTFullySupported = true; caps_.fragmentShaderDepthWriteSupported = true; caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.logicOp != 0; + caps_.framebufferFetchSupported = true; // Limited, through input attachments and self-dependencies. auto deviceProps = vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDeviceIndex()).properties; switch (deviceProps.vendorID) { @@ -1062,12 +1044,12 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char VKDepthStencilState *depth = (VKDepthStencilState *)desc.depthStencil; VKRasterState *raster = (VKRasterState *)desc.raster; - u32 pipelineFlags = 0; + PipelineFlags pipelineFlags = (PipelineFlags)0; if (depth->info.depthTestEnable || depth->info.stencilTestEnable) { - pipelineFlags |= PIPELINE_FLAG_USES_DEPTH_STENCIL; + pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL; } - VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), (PipelineFlags)pipelineFlags, tag); + VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), pipelineFlags, tag); VKRGraphicsPipelineDesc &gDesc = pipeline->vkrDesc; @@ -1588,6 +1570,10 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, attachment); } +void VKContext::BindCurrentFramebufferForColorInput() { + renderManager_.BindCurrentFramebufferAsInputAttachment0(VK_IMAGE_ASPECT_COLOR_BIT); +} + void VKContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { VKFramebuffer *fb = (VKFramebuffer *)fbo; if (fb) { @@ -1628,4 +1614,28 @@ void VKContext::InvalidateFramebuffer(FBInvalidationStage stage, uint32_t channe } } +uint64_t VKContext::GetNativeObject(NativeObject obj, void *srcObject) { + switch (obj) { + case NativeObject::CONTEXT: + return (uint64_t)vulkan_; + case NativeObject::INIT_COMMANDBUFFER: + return (uint64_t)renderManager_.GetInitCmd(); + case NativeObject::BOUND_TEXTURE0_IMAGEVIEW: + return (uint64_t)boundImageView_[0]; + case NativeObject::BOUND_TEXTURE1_IMAGEVIEW: + return (uint64_t)boundImageView_[1]; + case NativeObject::RENDER_MANAGER: + return (uint64_t)(uintptr_t)&renderManager_; + case NativeObject::NULL_IMAGEVIEW: + return (uint64_t)GetNullTexture()->GetImageView(); + case NativeObject::TEXTURE_VIEW: + return (uint64_t)(((VKTexture *)srcObject)->GetImageView()); + case NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW: + return (uint64_t)curFramebuffer_->GetFB()->color.imageView; + default: + Crash(); + return 0; + } +} + } // namespace Draw diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 843e6e8813..7ec8de9685 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -242,6 +242,7 @@ enum class NativeObject { INIT_COMMANDBUFFER, BOUND_TEXTURE0_IMAGEVIEW, BOUND_TEXTURE1_IMAGEVIEW, + BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW, RENDER_MANAGER, TEXTURE_VIEW, NULL_IMAGEVIEW, @@ -651,6 +652,9 @@ public: // binding must be < MAX_TEXTURE_SLOTS (0, 1 are okay if it's 2). virtual void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) = 0; + // Framebuffer fetch / input attachment support, needs to be explicit in Vulkan. + virtual void BindCurrentFramebufferForColorInput() {} + // deprecated, only used by D3D9 virtual uintptr_t GetFramebufferAPITexture(Framebuffer *fbo, int channelBits, int attachment) { return 0; diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index d361458111..06bf6513ff 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -484,12 +484,12 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, return GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_NRM_FLOAT | GE_VTYPE_POS_FLOAT | (vertType & (GE_VTYPE_IDX_MASK | GE_VTYPE_THROUGH)); } -void DrawEngineCommon::ApplyFramebufferRead(bool *fboTexNeedsBind) { +void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) { if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { - *fboTexNeedsBind = false; + *fboTexState = FBO_TEX_READ_FRAMEBUFFER; } else { gpuStats.numCopiesForShaderBlend++; - *fboTexNeedsBind = true; + *fboTexState = FBO_TEX_COPY_BIND_TEX; } gstate_c.Dirty(DIRTY_SHADERBLEND); diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index a8997454d9..e6ba0b37d9 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -46,6 +46,12 @@ enum { TEX_SLOT_SPLINE_WEIGHTS_V = 6, }; +enum FBOTexState { + FBO_TEX_NONE, + FBO_TEX_COPY_BIND_TEX, + FBO_TEX_READ_FRAMEBUFFER, +}; + inline uint32_t GetVertTypeID(uint32_t vertType, int uvGenMode) { // As the decoder depends on the UVGenMode when we use UV prescale, we simply mash it // into the top of the verttype where there are unused bits. @@ -130,7 +136,7 @@ protected: // Vertex decoding void DecodeVertsStep(u8 *dest, int &i, int &decodedVerts); - void ApplyFramebufferRead(bool *fboTexNeedsBind); + void ApplyFramebufferRead(FBOTexState *fboTexState); inline int IndexSize(u32 vtype) const { const u32 indexType = (vtype & GE_VTYPE_IDX_MASK); diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index d37b4a772d..e2088731af 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -134,10 +134,12 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu GELogicOp replaceLogicOpType = isModeClear ? GE_LOGIC_COPY : (GELogicOp)id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4); bool replaceLogicOp = replaceLogicOpType != GE_LOGIC_COPY && compat.bitwiseOps; - bool readFramebuffer = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask || replaceLogicOp; - bool readFramebufferTex = readFramebuffer && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); + bool needFramebufferRead = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask || replaceLogicOp; - bool needFragCoord = readFramebuffer || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); + bool fetchFramebuffer = needFramebufferRead && gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); + bool readFramebufferTex = needFramebufferRead && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); + + bool needFragCoord = readFramebufferTex || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); bool writeDepth = gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); if (shaderDepalMode != ShaderDepalMode::OFF && !doTexture) { @@ -157,6 +159,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (readFramebufferTex) { WRITE(p, "layout (binding = 1) uniform sampler2D fbotex;\n"); + } else if (fetchFramebuffer) { + WRITE(p, "layout (input_attachment_index = 0, binding = 9) uniform subpassInput inputColor;\n"); + if (fragmentShaderFlags) { + *fragmentShaderFlags |= FragmentShaderFlags::INPUT_ATTACHMENT; + } } if (shaderDepalMode != ShaderDepalMode::OFF) { @@ -416,7 +423,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (!strcmp(compat.fragColor0, "fragColor0")) { const char *qualifierColor0 = "out"; - if (readFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) { + if (fetchFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) { qualifierColor0 = "inout"; } // Output the output color definitions. @@ -492,20 +499,26 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } // Two things read from the old framebuffer - shader replacement blending and bit-level masking. - if (readFramebuffer) { + if (readFramebufferTex) { if (compat.shaderLanguage == HLSL_D3D11) { WRITE(p, " vec4 destColor = fbotex.Load(int3((int)gl_FragCoord.x, (int)gl_FragCoord.y, 0));\n"); } else if (compat.shaderLanguage == HLSL_D3D9) { WRITE(p, " vec4 destColor = tex2D(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", compat.texture); - } else if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { - // If we have EXT_shader_framebuffer_fetch / ARM_shader_framebuffer_fetch, we skip the blit. - // We can just read the prev value more directly. - WRITE(p, " lowp vec4 destColor = %s;\n", compat.lastFragData); } else if (!compat.texelFetch) { WRITE(p, " lowp vec4 destColor = %s(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", compat.texture); } else { WRITE(p, " lowp vec4 destColor = %s(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n", compat.texelFetch); } + } else if (fetchFramebuffer) { + // If we have EXT_shader_framebuffer_fetch / ARM_shader_framebuffer_fetch, we skip the blit. + // We can just read the prev value more directly. + if (compat.shaderLanguage == GLSL_3xx) { + WRITE(p, " lowp vec4 destColor = %s;\n", compat.lastFragData); + } else if (compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, " lowp vec4 destColor = subpassLoad(inputColor);\n", compat.lastFragData); + } else { + _assert_msg_(false, "Need fetch destColor, but not a compatible language"); + } } if (isModeClear) { diff --git a/GPU/Common/FragmentShaderGenerator.h b/GPU/Common/FragmentShaderGenerator.h index 85c651cf8b..8f358fa7ff 100644 --- a/GPU/Common/FragmentShaderGenerator.h +++ b/GPU/Common/FragmentShaderGenerator.h @@ -42,7 +42,7 @@ struct FShaderID; // Can technically be deduced from the fragment shader ID, but this is safer. enum class FragmentShaderFlags : u32 { - FS_FLAG_INPUT_ATTACHMENT = 1, + INPUT_ATTACHMENT = 1, }; ENUM_CLASS_BITOPS(FragmentShaderFlags); diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index 0cee52a155..e594042abd 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -153,20 +153,23 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { // We ignore the logicState on D3D since there's no support, the emulation of it is blend-and-shader only. if (pipelineState_.FramebufferRead()) { - bool fboTexNeedsBind = false; - ApplyFramebufferRead(&fboTexNeedsBind); + FBOTexState fboTexBindState_ = FBO_TEX_NONE; + ApplyFramebufferRead(&fboTexBindState_); // The shader takes over the responsibility for blending, so recompute. ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); - if (fboTexNeedsBind) { + if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) { framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // No sampler required, we do a plain Load in the pixel shader. fboTexBound_ = true; + fboTexBindState_ = FBO_TEX_NONE; framebufferManager_->RebindFramebuffer("RebindFramebuffer - ApplyDrawState"); // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE; gstate_c.Dirty(DIRTY_BLEND_STATE); + } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) { + fboTexBindState_ = FBO_TEX_NONE; } dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE; diff --git a/GPU/Directx9/DrawEngineDX9.h b/GPU/Directx9/DrawEngineDX9.h index a0ee23e60d..9ef5b37c65 100644 --- a/GPU/Directx9/DrawEngineDX9.h +++ b/GPU/Directx9/DrawEngineDX9.h @@ -170,6 +170,8 @@ private: // Hardware tessellation TessellationDataTransferDX9 *tessDataTransferDX9; + FBOTexState fboTexBindState_ = FBO_TEX_NONE; + int lastRenderStepId_ = -1; bool fboTexNeedsBind_ = false; diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index 2ebadb3974..0dfa352f00 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -99,14 +99,14 @@ void DrawEngineDX9::ApplyDrawState(int prim) { if (!gstate.isModeClear()) { textureCache_->ApplyTexture(); - if (fboTexNeedsBind_) { + if (fboTexBindState_ = FBO_TEX_COPY_BIND_TEX) { // Note that this is positions, not UVs, that we need the copy from. framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // If we are rendering at a higher resolution, linear is probably best for the dest color. device_->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); device_->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); fboTexBound_ = true; - fboTexNeedsBind_ = false; + fboTexBindState_ = FBO_TEX_NONE; } // TODO: Test texture? @@ -133,20 +133,23 @@ void DrawEngineDX9::ApplyDrawState(int prim) { // We ignore the logicState on D3D since there's no support, the emulation of it is blend-and-shader only. if (pipelineState_.FramebufferRead()) { - bool fboTexNeedsBind = false; - ApplyFramebufferRead(&fboTexNeedsBind); + ApplyFramebufferRead(&fboTexBindState_); // The shader takes over the responsibility for blending, so recompute. ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); - if (fboTexNeedsBind) { + if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) { // Note that this is positions, not UVs, that we need the copy from. framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // If we are rendering at a higher resolution, linear is probably best for the dest color. device_->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); device_->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); fboTexBound_ = true; + fboTexBindState_ = FBO_TEX_NONE; dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE; gstate_c.Dirty(DIRTY_BLEND_STATE); + } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) { + // Not supported. + fboTexBindState_ = FBO_TEX_NONE; } dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE; diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index dbfc115a91..cdcc9e5069 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -149,13 +149,14 @@ void DrawEngineGLES::ApplyDrawState(int prim) { GenericLogicState &logicState = pipelineState_.logicState; if (pipelineState_.FramebufferRead()) { - bool fboTexNeedsBind = false; - ApplyFramebufferRead(&fboTexNeedsBind); + FBOTexState fboTexBindState = FBO_TEX_NONE; + ApplyFramebufferRead(&fboTexBindState); // The shader takes over the responsibility for blending, so recompute. ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); // We copy the framebuffer here, as doing so will wipe any blend state if we do it later. - if (fboTexNeedsBind) { + // fboTexNeedsBind_ won't be set if we can read directly from the target. + if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) { // Note that this is positions, not UVs, that we need the copy from. framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // If we are rendering at a higher resolution, linear is probably best for the dest color. @@ -166,6 +167,9 @@ void DrawEngineGLES::ApplyDrawState(int prim) { // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE; gstate_c.Dirty(DIRTY_BLEND_STATE); + } else if (fboTexBindState == FBO_TEX_READ_FRAMEBUFFER) { + // No action needed here. + fboTexBindState = FBO_TEX_NONE; } dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE; gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); diff --git a/GPU/GPUState.h b/GPU/GPUState.h index cbfb58395d..191a670376 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -485,7 +485,8 @@ enum { // Free bit: 15 GPU_SUPPORTS_DEPTH_TEXTURE = FLAG_BIT(16), GPU_SUPPORTS_ACCURATE_DEPTH = FLAG_BIT(17), - // Free bits: 18-19 + GPU_SUPPORTS_FRAGMENT_SHADER_INTERLOCK = FLAG_BIT(18), + // Free bits: 19 GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20), GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT = FLAG_BIT(21), GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22), diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index e8ebd485d1..c67eb59ea2 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -71,6 +71,7 @@ enum { DRAW_BINDING_TESS_STORAGE_BUF = 6, DRAW_BINDING_TESS_STORAGE_BUF_WU = 7, DRAW_BINDING_TESS_STORAGE_BUF_WV = 8, + DRAW_BINDING_INPUT_ATTACHMENT = 9, }; enum { @@ -94,7 +95,10 @@ DrawEngineVulkan::DrawEngineVulkan(Draw::DrawContext *draw) void DrawEngineVulkan::InitDeviceObjects() { // All resources we need for PSP drawing. Usually only bindings 0 and 2-4 are populated. - VkDescriptorSetLayoutBinding bindings[9]{}; + + // TODO: Make things more flexible, so we at least have specialized layouts for input attachments and tess. + // Note that it becomes a support matrix.. + VkDescriptorSetLayoutBinding bindings[10]{}; bindings[0].descriptorCount = 1; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -132,6 +136,10 @@ void DrawEngineVulkan::InitDeviceObjects() { bindings[8].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[8].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; bindings[8].binding = DRAW_BINDING_TESS_STORAGE_BUF_WV; + bindings[9].descriptorCount = 1; + bindings[9].descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + bindings[9].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[9].binding = DRAW_BINDING_INPUT_ATTACHMENT; VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); VkDevice device = vulkan->GetDevice(); @@ -417,15 +425,15 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView } if (boundSecondary_) { - tex[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + tex[1].imageLayout = key.secondaryIsInputAttachment ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; tex[1].imageView = boundSecondary_; tex[1].sampler = samplerSecondaryNearest_; writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writes[n].pNext = nullptr; - writes[n].dstBinding = DRAW_BINDING_2ND_TEXTURE; + writes[n].dstBinding = key.secondaryIsInputAttachment ? DRAW_BINDING_INPUT_ATTACHMENT : DRAW_BINDING_2ND_TEXTURE; writes[n].pImageInfo = &tex[1]; writes[n].descriptorCount = 1; - writes[n].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writes[n].descriptorType = key.secondaryIsInputAttachment ? VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writes[n].dstSet = desc; n++; } @@ -788,7 +796,7 @@ void DrawEngineVulkan::DoFlush() { lastRenderStepId_ = curRenderStepId; } - renderManager->BindPipeline(pipeline->pipeline, (PipelineFlags)pipeline->flags, pipelineLayout_); + renderManager->BindPipeline(pipeline->pipeline, pipeline->pipelineFlags, pipelineLayout_); if (pipeline != lastPipeline_) { if (lastPipeline_ && !(lastPipeline_->UsesBlendConstant() && pipeline->UsesBlendConstant())) { gstate_c.Dirty(DIRTY_BLEND_STATE); @@ -916,7 +924,7 @@ void DrawEngineVulkan::DoFlush() { lastRenderStepId_ = curRenderStepId; } - renderManager->BindPipeline(pipeline->pipeline, (PipelineFlags)pipeline->flags, pipelineLayout_); + renderManager->BindPipeline(pipeline->pipeline, pipeline->pipelineFlags, pipelineLayout_); if (pipeline != lastPipeline_) { if (lastPipeline_ && !lastPipeline_->UsesBlendConstant() && pipeline->UsesBlendConstant()) { gstate_c.Dirty(DIRTY_BLEND_STATE); diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 531e05c4ed..653dfd3f1f 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -234,6 +234,7 @@ private: VkSampler sampler_; VkBuffer base_, light_, bone_; // All three UBO slots will be set to this. This will usually be identical // for all draws in a frame, except when the buffer has to grow. + bool secondaryIsInputAttachment; }; // We alternate between these. @@ -281,7 +282,7 @@ private: VulkanDynamicState dynState_{}; int tessOffset_ = 0; - bool fboTexNeedsBind_ = false; + FBOTexState fboTexBindState_ = FBO_TEX_NONE; // Hardware tessellation TessellationDataTransferVulkan *tessDataTransferVulkan; diff --git a/GPU/Vulkan/FramebufferManagerVulkan.h b/GPU/Vulkan/FramebufferManagerVulkan.h index 0f5d7c4f53..d3370fafb7 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.h +++ b/GPU/Vulkan/FramebufferManagerVulkan.h @@ -33,7 +33,7 @@ class VulkanPushBuffer; class FramebufferManagerVulkan : public FramebufferManagerCommon { public: - FramebufferManagerVulkan(Draw::DrawContext *draw); + explicit FramebufferManagerVulkan(Draw::DrawContext *draw); ~FramebufferManagerVulkan(); // If within a render pass, this will just issue a regular clear. If beginning a new render pass, diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index f96d282fc7..039f855d86 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -229,6 +229,9 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_TEXTURE_FLOAT; features |= GPU_SUPPORTS_DEPTH_TEXTURE; + // input attachments + features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; + auto &enabledFeatures = vulkan->GetDeviceFeatures().enabled; if (enabledFeatures.depthClamp) { features |= GPU_SUPPORTS_DEPTH_CLAMP; diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index a88fe235a5..e950dfc457 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -170,7 +170,7 @@ static std::string CutFromMain(std::string str) { } static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache, - VkPipelineLayout layout, const VulkanPipelineRasterStateKey &key, + VkPipelineLayout layout, PipelineFlags pipelineFlags, const VulkanPipelineRasterStateKey &key, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask) { VulkanPipeline *vulkanPipeline = new VulkanPipeline(); VKRGraphicsPipelineDesc *desc = &vulkanPipeline->desc; @@ -299,14 +299,14 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, variantBitmask, "game"); vulkanPipeline->pipeline = pipeline; - vulkanPipeline->flags = 0; if (useBlendConstant) - vulkanPipeline->flags |= PIPELINE_FLAG_USES_BLEND_CONSTANT; + pipelineFlags |= PipelineFlags::USES_BLEND_CONSTANT; if (key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP) - vulkanPipeline->flags |= PIPELINE_FLAG_USES_LINES; + pipelineFlags |= PipelineFlags::USES_LINES; if (dss.depthTestEnable || dss.stencilTestEnable) { - vulkanPipeline->flags |= PIPELINE_FLAG_USES_DEPTH_STENCIL; + pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL; } + vulkanPipeline->pipelineFlags = pipelineFlags; return vulkanPipeline; } @@ -329,8 +329,13 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager * if (iter) return iter; + PipelineFlags pipelineFlags = (PipelineFlags)0; + if (fs->Flags() & FragmentShaderFlags::INPUT_ATTACHMENT) { + pipelineFlags |= PipelineFlags::USES_INPUT_ATTACHMENT; + } + VulkanPipeline *pipeline = CreateVulkanPipeline( - renderManager, pipelineCache_, layout, + renderManager, pipelineCache_, layout, pipelineFlags, rasterKey, decFmt, vs, fs, useHwTransform, variantBitmask); pipelines_.Insert(key, pipeline); diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index af32aa81bd..08907e3b3e 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -55,11 +55,12 @@ struct VulkanPipelineKey { struct VulkanPipeline { VKRGraphicsPipeline *pipeline; VKRGraphicsPipelineDesc desc; - int flags; // PipelineFlags enum above. + PipelineFlags pipelineFlags; // PipelineFlags enum above. - bool UsesBlendConstant() const { return (flags & PIPELINE_FLAG_USES_BLEND_CONSTANT) != 0; } - bool UsesLines() const { return (flags & PIPELINE_FLAG_USES_LINES) != 0; } - bool UsesDepthStencil() const { return (flags & PIPELINE_FLAG_USES_DEPTH_STENCIL) != 0; } + bool UsesBlendConstant() const { return (pipelineFlags & PipelineFlags::USES_BLEND_CONSTANT) != 0; } + bool UsesLines() const { return (pipelineFlags & PipelineFlags::USES_LINES) != 0; } + bool UsesDepthStencil() const { return (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) != 0; } + bool UsesInputAttachment() const { return (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) != 0; } u32 GetVariantsBitmask() const; }; diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index 6457b99ad9..7377b059a0 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -153,7 +153,7 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag GenericLogicState &logicState = pipelineState_.logicState; if (pipelineState_.FramebufferRead()) { - ApplyFramebufferRead(&fboTexNeedsBind_); + ApplyFramebufferRead(&fboTexBindState_); // The shader takes over the responsibility for blending, so recompute. // We might still end up using blend to write something to alpha. ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); @@ -364,15 +364,19 @@ void DrawEngineVulkan::BindShaderBlendTex() { // TODO: At this point, we know if the vertices are full alpha or not. // Set the nearest/linear here (since we correctly know if alpha/color tests are needed)? if (!gstate.isModeClear()) { - if (fboTexNeedsBind_) { + if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) { bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); _dbg_assert_(bindResult); boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW); fboTexBound_ = true; - fboTexNeedsBind_ = false; + fboTexBindState_ = FBO_TEX_NONE; // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE; + } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) { + draw_->BindCurrentFramebufferForColorInput(); + boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW); + fboTexBindState_ = FBO_TEX_NONE; } } } From eadb144cc470da6d7ff527a262ac3495db9572e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 8 Sep 2022 00:50:24 +0200 Subject: [PATCH 039/326] Show render pass types in GPU profiler --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index a327880886..2a7f81690f 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -683,6 +683,7 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { switch (step.render.renderPassType) { case RP_TYPE_BACKBUFFER: renderCmd = "BACKBUF"; break; case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER"; break; + case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_INPUT"; break; default: renderCmd = "N/A"; } snprintf(buffer, sizeof(buffer), "%s %s (draws: %d, %dx%d/%dx%d, fb: %p, )", renderCmd, step.tag, step.render.numDraws, actual_w, actual_h, w, h, step.render.framebuffer); From a0f7a4ee2f4ac24546f75fcae53a35e95b93393b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 9 Sep 2022 09:54:10 +0200 Subject: [PATCH 040/326] Remove logspam --- Common/GPU/Vulkan/VulkanDebug.cpp | 2 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp index d4052e970a..b52e4396cb 100644 --- a/Common/GPU/Vulkan/VulkanDebug.cpp +++ b/Common/GPU/Vulkan/VulkanDebug.cpp @@ -86,7 +86,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( } else { WARN_LOG(G3D, "VKDEBUG: %s", msg.c_str()); } - // false indicates that layer should not bail-out of an // API call that had validation failures. This may mean that the // app dies inside the driver due to invalid parameter(s). @@ -94,3 +93,4 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( // keep that behavior here. return false; } + diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 7984824a26..8dcd0985fa 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -534,7 +534,9 @@ void VulkanRenderManager::CompileThreadFunc() { break; } - INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size()); + if (!toCompile.empty()) { + INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size()); + } // TODO: Here we can sort the pending pipelines by vertex and fragment shaders, // and split up further. From 5f5277a40938ef45200e60b933d63879f3632b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 10 Sep 2022 01:11:02 +0200 Subject: [PATCH 041/326] Need to have input attachments in the descriptor pool. --- GPU/Vulkan/DrawEngineVulkan.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index c67eb59ea2..ae5146c188 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -153,13 +153,15 @@ void DrawEngineVulkan::InitDeviceObjects() { static constexpr int DEFAULT_DESC_POOL_SIZE = 512; std::vector dpTypes; - dpTypes.resize(3); + dpTypes.resize(4); dpTypes[0].descriptorCount = DEFAULT_DESC_POOL_SIZE * 3; dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; dpTypes[1].descriptorCount = DEFAULT_DESC_POOL_SIZE * 3; // Don't use these for tess anymore, need max three per set. dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; dpTypes[2].descriptorCount = DEFAULT_DESC_POOL_SIZE * 3; // TODO: Use a separate layout when no spline stuff is needed to reduce the need for these. dpTypes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + dpTypes[3].descriptorCount = DEFAULT_DESC_POOL_SIZE; // TODO: Use a separate layout when no spline stuff is needed to reduce the need for these. + dpTypes[3].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; VkDescriptorPoolCreateInfo dp{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; // Don't want to mess around with individually freeing these. From 262a306b9a5edcb732c7533e4c17fc7d6f670f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Sep 2022 19:18:38 +0200 Subject: [PATCH 042/326] Flag shader blending as broken on Adreno for now. --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 10 +++++++++- Common/GPU/thin3d.cpp | 1 + Common/GPU/thin3d.h | 1 + Common/UI/UIScreen.h | 6 +++--- GPU/D3D11/StateMappingD3D11.cpp | 10 ++++------ GPU/Vulkan/DrawEngineVulkan.cpp | 1 + GPU/Vulkan/DrawEngineVulkan.h | 2 ++ GPU/Vulkan/GPU_Vulkan.cpp | 6 ++++-- GPU/Vulkan/StateMappingVulkan.cpp | 4 ++++ UI/InstallZipScreen.h | 2 +- 10 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index b36c4e90ea..7d07e6a3cb 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -789,7 +789,6 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) caps_.textureNPOTFullySupported = true; caps_.fragmentShaderDepthWriteSupported = true; caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.logicOp != 0; - caps_.framebufferFetchSupported = true; // Limited, through input attachments and self-dependencies. auto deviceProps = vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDeviceIndex()).properties; switch (deviceProps.vendorID) { @@ -813,6 +812,11 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) // Color write mask not masking write in certain scenarios with a depth test, see #10421. // Known still present on driver 0x80180000 and Adreno 5xx (possibly more.) bugs_.Infest(Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST); + + // Trying to follow all the rules in https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies + // and https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#renderpass-feedbackloop, but still it doesn't + // quite work - artifacts on triangle boundaries on Adreno. + bugs_.Infest(Bugs::SUBPASS_FEEDBACK_BROKEN); } else if (caps_.vendor == GPUVendor::VENDOR_AMD) { // See issue #10074, and also #10065 (AMD) and #10109 for the choice of the driver version to check for. if (deviceProps.driverVersion < 0x00407000) { @@ -839,6 +843,10 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) } } + // Limited, through input attachments and self-dependencies. + // We turn it off here already if buggy. + caps_.framebufferFetchSupported = !bugs_.Has(Bugs::SUBPASS_FEEDBACK_BROKEN); + caps_.deviceID = deviceProps.deviceID; device_ = vulkan->GetDevice(); diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index 1cc3c11fa0..2560765f43 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -681,6 +681,7 @@ const char *Bugs::GetBugName(uint32_t bug) { case MALI_STENCIL_DISCARD_BUG: return "MALI_STENCIL_DISCARD_BUG"; case RASPBERRY_SHADER_COMP_HANG: return "RASPBERRY_SHADER_COMP_HANG"; case MALI_CONSTANT_LOAD_BUG: return "MALI_CONSTANT_LOAD_BUG"; + case SUBPASS_FEEDBACK_BROKEN: return "SUBPASS_FEEDBACK_BROKEN"; default: return "(N/A)"; } } diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 7ec8de9685..389640ec9e 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -332,6 +332,7 @@ public: MALI_STENCIL_DISCARD_BUG = 8, RASPBERRY_SHADER_COMP_HANG = 9, MALI_CONSTANT_LOAD_BUG = 10, + SUBPASS_FEEDBACK_BROKEN = 11, MAX_BUG, }; diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index d667873e8c..4b113d3001 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -187,7 +187,7 @@ public: disabled_ = *value_ < 0; } - const char *tag() const { return "SliderPopup"; } + const char *tag() const override { return "SliderPopup"; } Event OnChange; @@ -216,7 +216,7 @@ public: : PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false), liveUpdate_(liveUpdate) {} void CreatePopupContents(UI::ViewGroup *parent) override; - const char *tag() const { return "SliderFloatPopup"; } + const char *tag() const override { return "SliderFloatPopup"; } Event OnChange; @@ -245,7 +245,7 @@ public: : PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {} virtual void CreatePopupContents(ViewGroup *parent) override; - const char *tag() const { return "TextEditPopup"; } + const char *tag() const override { return "TextEditPopup"; } Event OnChange; diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index e594042abd..f4ce888f7b 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -153,23 +153,21 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { // We ignore the logicState on D3D since there's no support, the emulation of it is blend-and-shader only. if (pipelineState_.FramebufferRead()) { - FBOTexState fboTexBindState_ = FBO_TEX_NONE; - ApplyFramebufferRead(&fboTexBindState_); + FBOTexState fboTexBindState = FBO_TEX_NONE; + ApplyFramebufferRead(&fboTexBindState); // The shader takes over the responsibility for blending, so recompute. ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); - if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) { + if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) { framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // No sampler required, we do a plain Load in the pixel shader. fboTexBound_ = true; - fboTexBindState_ = FBO_TEX_NONE; + fboTexBindState = FBO_TEX_NONE; framebufferManager_->RebindFramebuffer("RebindFramebuffer - ApplyDrawState"); // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE; gstate_c.Dirty(DIRTY_BLEND_STATE); - } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) { - fboTexBindState_ = FBO_TEX_NONE; } dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index ae5146c188..f992df082f 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -389,6 +389,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView key.base_ = base; key.light_ = light; key.bone_ = bone; + key.secondaryIsInputAttachment = boundSecondaryIsInputAttachment_; FrameData &frame = GetCurFrame(); // See if we already have this descriptor set cached. diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 653dfd3f1f..0de67940de 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -217,6 +217,8 @@ private: // Secondary texture for shader blending VkImageView boundSecondary_ = VK_NULL_HANDLE; + bool boundSecondaryIsInputAttachment_ = false; + // CLUT texture for shader depal VkImageView boundDepal_ = VK_NULL_HANDLE; bool boundDepalSmoothed_ = false; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 039f855d86..eed7c8b9d5 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -229,8 +229,10 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_TEXTURE_FLOAT; features |= GPU_SUPPORTS_DEPTH_TEXTURE; - // input attachments - features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; + // through input attachments, when not broken. + if (draw_->GetDeviceCaps().framebufferFetchSupported) { + features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; + } auto &enabledFeatures = vulkan->GetDeviceFeatures().enabled; if (enabledFeatures.depthClamp) { diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index 7377b059a0..290c7b010c 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -368,6 +368,7 @@ void DrawEngineVulkan::BindShaderBlendTex() { bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); _dbg_assert_(bindResult); boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW); + boundSecondaryIsInputAttachment_ = false; fboTexBound_ = true; fboTexBindState_ = FBO_TEX_NONE; @@ -376,7 +377,10 @@ void DrawEngineVulkan::BindShaderBlendTex() { } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) { draw_->BindCurrentFramebufferForColorInput(); boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW); + boundSecondaryIsInputAttachment_ = true; fboTexBindState_ = FBO_TEX_NONE; + } else { + boundSecondary_ = VK_NULL_HANDLE; } } } diff --git a/UI/InstallZipScreen.h b/UI/InstallZipScreen.h index a7073ca73f..2f705b5510 100644 --- a/UI/InstallZipScreen.h +++ b/UI/InstallZipScreen.h @@ -30,7 +30,7 @@ public: virtual void update() override; virtual bool key(const KeyInput &key) override; - const char *tag() const { return "install_zip"; } + const char *tag() const override { return "install_zip"; } protected: virtual void CreateViews() override; From 242efba6f79739f557b349dcb03d24b09575cacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 00:36:43 +0200 Subject: [PATCH 043/326] Remove splitSubmit setting. Now we submit init commands before the acquire. --- Common/GPU/OpenGL/GLRenderManager.cpp | 1 - Common/GPU/Vulkan/VulkanQueueRunner.cpp | 2 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 70 ++++++++++++++--------- Common/GPU/Vulkan/VulkanRenderManager.h | 11 ++-- Common/GPU/Vulkan/thin3d_vulkan.cpp | 10 ++-- Common/GPU/thin3d_create.h | 2 +- Core/Config.cpp | 1 - Core/Config.h | 2 +- SDL/SDLVulkanGraphicsContext.cpp | 2 +- Windows/GPU/WindowsVulkanContext.cpp | 4 +- android/jni/AndroidVulkanContext.cpp | 2 +- libretro/LibretroVulkanContext.cpp | 2 +- 12 files changed, 59 insertions(+), 50 deletions(-) diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index ae5cb9dd17..01a94fb8b4 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -579,7 +579,6 @@ void GLRenderManager::EndSubmitFrame(int frame) { void GLRenderManager::Run(int frame) { BeginSubmitFrame(frame); - FrameData &frameData = frameData_[frame]; auto &stepsOnThread = frameData_[frame].steps; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 2a7f81690f..7d3c5adf71 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -1129,7 +1129,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c } } - // Don't execute empty renderpasses that keep the contents. if (step.commands.empty() && step.render.colorLoad == VKRRenderPassLoadAction::KEEP && step.render.depthLoad == VKRRenderPassLoadAction::KEEP && step.render.stencilLoad == VKRRenderPassLoadAction::KEEP) { // Flush the pending barrier @@ -1179,6 +1178,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c // This reads the layout of the color and depth images, and chooses a render pass using them that // will transition to the desired final layout. + // // NOTE: Flushes recordBarrier_. VKRRenderPass *renderPass = PerformBindFramebufferAsRenderTarget(step, cmd); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 8dcd0985fa..1f9f306faa 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1412,6 +1412,9 @@ void VulkanRenderManager::Wipe() { // the backbuffer image acquisition. void VulkanRenderManager::BeginSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; + + SubmitInitCommands(frame); + if (!frameData.hasBegun) { // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. VkResult res = vkAcquireNextImageKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &frameData.curSwapchainImage); @@ -1438,38 +1441,53 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { } } +void VulkanRenderManager::SubmitInitCommands(int frame) { + FrameData &frameData = frameData_[frame]; + if (!frameData.hasInitCommands) { + return; + } + + if (frameData.profilingEnabled_) { + // Pre-allocated query ID 1. + vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.profile.queryPool, 1); + } + + VkResult res = vkEndCommandBuffer(frameData.initCmd); + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); + + VkCommandBuffer cmdBufs[1]; + int numCmdBufs = 0; + + cmdBufs[numCmdBufs++] = frameData.initCmd; + // Send the init commands off separately, so they can be processed while we're building the rest of the list. + // (Likely the CPU will be more than a frame ahead anyway, but this will help when we try to work on latency). + VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit_info.commandBufferCount = (uint32_t)numCmdBufs; + submit_info.pCommandBuffers = cmdBufs; + res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE); + if (res == VK_ERROR_DEVICE_LOST) { + _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan"); + } else { + _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res)); + } + numCmdBufs = 0; + + frameData.hasInitCommands = false; +} + void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { FrameData &frameData = frameData_[frame]; - if (frameData.hasInitCommands) { - if (frameData.profilingEnabled_ && triggerFrameFence) { - // Pre-allocated query ID 1. - vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.profile.queryPool, 1); - } - VkResult res = vkEndCommandBuffer(frameData.initCmd); - _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); - } VkResult res = vkEndCommandBuffer(frameData.mainCmd); _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res)); - VkCommandBuffer cmdBufs[2]; + SubmitInitCommands(frame); + + // Submit the main and final cmdbuf, ending by signalling the fence. + + VkCommandBuffer cmdBufs[1]; int numCmdBufs = 0; - if (frameData.hasInitCommands) { - cmdBufs[numCmdBufs++] = frameData.initCmd; - if (splitSubmit_) { - // Send the init commands off separately. Used this once to confirm that the cause of a device loss was in the init cmdbuf. - VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submit_info.commandBufferCount = (uint32_t)numCmdBufs; - submit_info.pCommandBuffers = cmdBufs; - res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE); - if (res == VK_ERROR_DEVICE_LOST) { - _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan"); - } else { - _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res)); - } - numCmdBufs = 0; - } - } + cmdBufs[numCmdBufs++] = frameData.mainCmd; VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; @@ -1489,7 +1507,7 @@ void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { if (res == VK_ERROR_DEVICE_LOST) { _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan"); } else { - _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main, split=%d)! result=%s", (int)splitSubmit_, VulkanResultToString(res)); + _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main)! result=%s", VulkanResultToString(res)); } // When !triggerFence, we notify after syncing with Vulkan. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index b29b8bdcbd..87f037528c 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -445,10 +445,6 @@ public: return !framebuffers_.empty(); } - void SetSplitSubmit(bool split) { - splitSubmit_ = split; - } - void SetInflightFrames(int f) { newInflightFrames_ = f < 1 || f > VulkanContext::MAX_INFLIGHT_FRAMES ? VulkanContext::MAX_INFLIGHT_FRAMES : f; } @@ -479,6 +475,7 @@ private: void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); void Submit(int frame, bool triggerFence); + void SubmitInitCommands(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void FlushSync(); @@ -508,10 +505,11 @@ private: bool readbackFenceUsed = false; // These are on different threads so need separate pools. - VkCommandPool cmdPoolInit; - VkCommandPool cmdPoolMain; + VkCommandPool cmdPoolInit; // Written to from main thread + VkCommandPool cmdPoolMain; // Written to from render thread, which also submits VkCommandBuffer initCmd; VkCommandBuffer mainCmd; + bool hasInitCommands = false; std::vector steps; @@ -550,7 +548,6 @@ private: BoundingRect curRenderArea_; std::vector steps_; - bool splitSubmit_ = false; // Execution time state bool run_ = true; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 7d07e6a3cb..d276ba9868 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -361,7 +361,7 @@ class VKFramebuffer; class VKContext : public DrawContext { public: - VKContext(VulkanContext *vulkan, bool splitSubmit); + VKContext(VulkanContext *vulkan); virtual ~VKContext(); const DeviceCaps &GetDeviceCaps() const override { @@ -762,7 +762,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur return true; } -VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) +VKContext::VKContext(VulkanContext *vulkan) : vulkan_(vulkan), renderManager_(vulkan) { shaderLanguageDesc_.Init(GLSL_VULKAN); @@ -914,8 +914,6 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO }; res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_); _assert_(VK_SUCCESS == res); - - renderManager_.SetSplitSubmit(splitSubmit); } VKContext::~VKContext() { @@ -1395,8 +1393,8 @@ void VKContext::Clear(int clearMask, uint32_t colorval, float depthVal, int sten renderManager_.Clear(colorval, depthVal, stencilVal, mask); } -DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan, bool split) { - return new VKContext(vulkan, split); +DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan) { + return new VKContext(vulkan); } void AddFeature(std::vector &features, const char *name, VkBool32 available, VkBool32 enabled) { diff --git a/Common/GPU/thin3d_create.h b/Common/GPU/thin3d_create.h index 702e7f45c2..fccb3b076c 100644 --- a/Common/GPU/thin3d_create.h +++ b/Common/GPU/thin3d_create.h @@ -31,6 +31,6 @@ DrawContext *T3DCreateDX9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapt DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector adapterNames); #endif -DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool splitSubmit); +DrawContext *T3DCreateVulkanContext(VulkanContext *context); } // namespace Draw diff --git a/Core/Config.cpp b/Core/Config.cpp index 27f97e89c5..3798362ef8 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -938,7 +938,6 @@ static ConfigSetting graphicsSettings[] = { ReportedConfigSetting("FragmentTestCache", &g_Config.bFragmentTestCache, true, true, true), ConfigSetting("GfxDebugOutput", &g_Config.bGfxDebugOutput, false, false, false), - ConfigSetting("GfxDebugSplitSubmit", &g_Config.bGfxDebugSplitSubmit, false, false, false), ConfigSetting("LogFrameDrops", &g_Config.bLogFrameDrops, false, true, false), ConfigSetting("InflightFrames", &g_Config.iInflightFrames, 3, true, false), diff --git a/Core/Config.h b/Core/Config.h index 6ae172bfba..36ec3a760d 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -176,6 +176,7 @@ public: bool bSustainedPerformanceMode; // Android: Slows clocks down to avoid overheating/speed fluctuations. bool bIgnoreScreenInsets; // Android: Center screen disregarding insets if this is enabled. bool bVSync; + int iFrameSkip; int iFrameSkipType; int iFastForwardMode; // See FastForwardMode in ConfigValues.h. @@ -242,7 +243,6 @@ public: bool bShaderChainRequires60FPS; std::string sTextureShaderName; bool bGfxDebugOutput; - bool bGfxDebugSplitSubmit; int iInflightFrames; bool bRenderDuplicateFrames; diff --git a/SDL/SDLVulkanGraphicsContext.cpp b/SDL/SDLVulkanGraphicsContext.cpp index 433f35b03f..b7eb932b11 100644 --- a/SDL/SDLVulkanGraphicsContext.cpp +++ b/SDL/SDLVulkanGraphicsContext.cpp @@ -116,7 +116,7 @@ bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, return false; } - draw_ = Draw::T3DCreateVulkanContext(vulkan_, false); + draw_ = Draw::T3DCreateVulkanContext(vulkan_); SetGPUBackend(GPUBackend::VULKAN); bool success = draw_->CreatePresets(); _assert_(success); diff --git a/Windows/GPU/WindowsVulkanContext.cpp b/Windows/GPU/WindowsVulkanContext.cpp index 5618e2f1f5..818f398420 100644 --- a/Windows/GPU/WindowsVulkanContext.cpp +++ b/Windows/GPU/WindowsVulkanContext.cpp @@ -131,9 +131,7 @@ bool WindowsVulkanContext::Init(HINSTANCE hInst, HWND hWnd, std::string *error_m return false; } - bool splitSubmit = g_Config.bGfxDebugSplitSubmit; - - draw_ = Draw::T3DCreateVulkanContext(vulkan_, splitSubmit); + draw_ = Draw::T3DCreateVulkanContext(vulkan_); SetGPUBackend(GPUBackend::VULKAN, vulkan_->GetPhysicalDeviceProperties(deviceNum).properties.deviceName); bool success = draw_->CreatePresets(); _assert_msg_(success, "Failed to compile preset shaders"); diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index dc68a8d75a..af8a87e751 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -101,7 +101,7 @@ bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredB bool success = true; if (g_Vulkan->InitSwapchain()) { - draw_ = Draw::T3DCreateVulkanContext(g_Vulkan, g_Config.bGfxDebugSplitSubmit); + draw_ = Draw::T3DCreateVulkanContext(g_Vulkan); SetGPUBackend(GPUBackend::VULKAN); success = draw_->CreatePresets(); // Doesn't fail, we ship the compiler. _assert_msg_(success, "Failed to compile preset shaders"); diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index 571b7bba9d..ff9180cecf 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -137,7 +137,7 @@ void LibretroVulkanContext::CreateDrawContext() { return; } - draw_ = Draw::T3DCreateVulkanContext(vk, false); + draw_ = Draw::T3DCreateVulkanContext(vk); ((VulkanRenderManager*)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER))->SetInflightFrames(g_Config.iInflightFrames); SetGPUBackend(GPUBackend::VULKAN); } From 415a795b115f4765ee6d01263b879975bed92b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 01:11:41 +0200 Subject: [PATCH 044/326] Move back buffer rendering to a separate "present" command buffer --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 29 ++++++++++++++++------- Common/GPU/Vulkan/VulkanQueueRunner.h | 2 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 16 ++++++++++--- Common/GPU/Vulkan/VulkanRenderManager.h | 4 ++++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 7d3c5adf71..307b2241e5 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -376,7 +376,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) { } } -void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile) { +void VulkanQueueRunner::RunSteps(VkCommandBuffer mainCmd, VkCommandBuffer presentCmd, bool *hasPresentCommands, std::vector &steps, QueueProfileContext *profile) { if (profile) profile->cpuStartTime = time_now_d(); @@ -388,36 +388,47 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st if (emitLabels) { VkDebugUtilsLabelEXT labelInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; labelInfo.pLabelName = step.tag; - vkCmdBeginDebugUtilsLabelEXT(cmd, &labelInfo); + vkCmdBeginDebugUtilsLabelEXT(mainCmd, &labelInfo); } switch (step.stepType) { case VKRStepType::RENDER: - PerformRenderPass(step, cmd); + if (!step.render.framebuffer) { + _dbg_assert_(!*hasPresentCommands); + if (!*hasPresentCommands) { + *hasPresentCommands = true; + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(presentCmd, &begin); + } + PerformRenderPass(step, presentCmd); + } else { + PerformRenderPass(step, mainCmd); + } break; case VKRStepType::COPY: - PerformCopy(step, cmd); + PerformCopy(step, mainCmd); break; case VKRStepType::BLIT: - PerformBlit(step, cmd); + PerformBlit(step, mainCmd); break; case VKRStepType::READBACK: - PerformReadback(step, cmd); + PerformReadback(step, mainCmd); break; case VKRStepType::READBACK_IMAGE: - PerformReadbackImage(step, cmd); + PerformReadbackImage(step, mainCmd); break; case VKRStepType::RENDER_SKIP: break; } if (profile && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) { - vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); + vkCmdWriteTimestamp(mainCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); profile->timestampDescriptions.push_back(StepToString(step)); } if (emitLabels) { - vkCmdEndDebugUtilsLabelEXT(cmd); + vkCmdEndDebugUtilsLabelEXT(mainCmd); } } diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 8f0533331a..fc30a0554a 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -257,7 +257,7 @@ public: } void PreprocessSteps(std::vector &steps); - void RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile); + void RunSteps(VkCommandBuffer cmd, VkCommandBuffer presentCmd, bool *hasPresentCommands, std::vector &steps, QueueProfileContext *profile); void LogSteps(const std::vector &steps, bool verbose); std::string StepToString(const VKRStep &step) const; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 1f9f306faa..4ce9ca1941 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -309,11 +309,11 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan cmd_alloc.commandPool = frameData_[i].cmdPoolInit; cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_alloc.commandBufferCount = 1; - res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].initCmd); _dbg_assert_(res == VK_SUCCESS); cmd_alloc.commandPool = frameData_[i].cmdPoolMain; res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].mainCmd); + res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].presentCmd); _dbg_assert_(res == VK_SUCCESS); // Creating the frame fence with true so they can be instantly waited on the first frame @@ -1481,14 +1481,22 @@ void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { VkResult res = vkEndCommandBuffer(frameData.mainCmd); _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res)); + if (frameData.hasPresentCommands) { + VkResult res = vkEndCommandBuffer(frameData.presentCmd); + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res)); + } + SubmitInitCommands(frame); // Submit the main and final cmdbuf, ending by signalling the fence. - VkCommandBuffer cmdBufs[1]; + VkCommandBuffer cmdBufs[2]; int numCmdBufs = 0; cmdBufs[numCmdBufs++] = frameData.mainCmd; + if (frameData.hasPresentCommands) { + cmdBufs[numCmdBufs++] = frameData.presentCmd; + } VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; @@ -1519,6 +1527,7 @@ void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { } frameData.hasInitCommands = false; + frameData.hasPresentCommands = false; } void VulkanRenderManager::EndSubmitFrame(int frame) { @@ -1564,7 +1573,7 @@ void VulkanRenderManager::Run(int frame) { VkCommandBuffer cmd = frameData.mainCmd; queueRunner_.PreprocessSteps(stepsOnThread); //queueRunner_.LogSteps(stepsOnThread, false); - queueRunner_.RunSteps(cmd, stepsOnThread, frameData.profilingEnabled_ ? &frameData.profile : nullptr); + queueRunner_.RunSteps(cmd, frameData.presentCmd, &frameData.hasPresentCommands, stepsOnThread, frameData.profilingEnabled_ ? &frameData.profile : nullptr); stepsOnThread.clear(); switch (frameData.type) { @@ -1586,6 +1595,7 @@ void VulkanRenderManager::Run(int frame) { void VulkanRenderManager::EndSyncFrame(int frame) { FrameData &frameData = frameData_[frame]; + _dbg_assert_(!frameData.hasPresentCommands); frameData.readbackFenceUsed = true; // The submit will trigger the readbackFence. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 87f037528c..362e94bcac 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -507,10 +507,14 @@ private: // These are on different threads so need separate pools. VkCommandPool cmdPoolInit; // Written to from main thread VkCommandPool cmdPoolMain; // Written to from render thread, which also submits + VkCommandBuffer initCmd; VkCommandBuffer mainCmd; + VkCommandBuffer presentCmd; bool hasInitCommands = false; + bool hasPresentCommands = false; + std::vector steps; // Swapchain. From fabd50b1780067ff3854e0ea68af9763170ca995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 01:34:38 +0200 Subject: [PATCH 045/326] Move FrameData out of VulkanRenderManager --- CMakeLists.txt | 2 + Common/Common.vcxproj | 2 + Common/Common.vcxproj.filters | 6 +++ Common/GPU/Vulkan/VulkanFrameData.cpp | 15 ++++++ Common/GPU/Vulkan/VulkanFrameData.h | 65 +++++++++++++++++++++++ Common/GPU/Vulkan/VulkanQueueRunner.h | 9 +--- Common/GPU/Vulkan/VulkanRenderManager.cpp | 19 +++---- Common/GPU/Vulkan/VulkanRenderManager.h | 44 --------------- android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 10 files changed, 99 insertions(+), 65 deletions(-) create mode 100644 Common/GPU/Vulkan/VulkanFrameData.cpp create mode 100644 Common/GPU/Vulkan/VulkanFrameData.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bc81d9be2b..cc31004ebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,6 +623,8 @@ add_library(Common STATIC Common/GPU/Vulkan/VulkanRenderManager.h Common/GPU/Vulkan/VulkanQueueRunner.cpp Common/GPU/Vulkan/VulkanQueueRunner.h + Common/GPU/Vulkan/VulkanFrameData.cpp + Common/GPU/Vulkan/VulkanFrameData.h Common/Input/GestureDetector.cpp Common/Input/GestureDetector.h Common/Input/KeyCodes.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 2895cdc837..d232c01534 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -441,6 +441,7 @@ + @@ -861,6 +862,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index ff9fd9eaa6..991b03a71f 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -419,6 +419,9 @@ GPU\Vulkan + + GPU\Vulkan + @@ -791,6 +794,9 @@ GPU\Vulkan + + GPU\Vulkan + diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp new file mode 100644 index 0000000000..58ce42deaf --- /dev/null +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -0,0 +1,15 @@ +#include "VulkanFrameData.h" + +void FrameData::AcquireNextImage(VulkanContext *vulkan) { + // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. + VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); + if (res == VK_SUBOPTIMAL_KHR) { + // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something. + WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring"); + } else if (res == VK_ERROR_OUT_OF_DATE_KHR) { + WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned - processing the frame, but not presenting"); + skipSwap = true; + } else { + _assert_msg_(res == VK_SUCCESS, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res)); + } +} diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h new file mode 100644 index 0000000000..21c44404b9 --- /dev/null +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include +#include + +#include "Common/GPU/Vulkan/VulkanContext.h" + +struct VKRStep; + +enum class VKRRunType { + END, + SYNC, +}; + +struct QueueProfileContext { + VkQueryPool queryPool; + std::vector timestampDescriptions; + std::string profileSummary; + double cpuStartTime; + double cpuEndTime; +}; + +// Per-frame data, round-robin so we can overlap submission with execution of the previous frame. +struct FrameData { + std::mutex push_mutex; + std::condition_variable push_condVar; + + std::mutex pull_mutex; + std::condition_variable pull_condVar; + + bool readyForFence = true; + bool readyForRun = false; + bool skipSwap = false; + VKRRunType type = VKRRunType::END; + + VkFence fence; + VkFence readbackFence; // Strictly speaking we might only need one of these. + bool readbackFenceUsed = false; + + // These are on different threads so need separate pools. + VkCommandPool cmdPoolInit; // Written to from main thread + VkCommandPool cmdPoolMain; // Written to from render thread, which also submits + + VkCommandBuffer initCmd; + VkCommandBuffer mainCmd; + VkCommandBuffer presentCmd; + + bool hasInitCommands = false; + bool hasPresentCommands = false; + + std::vector steps; + + // Swapchain. + bool hasBegun = false; + uint32_t curSwapchainImage = -1; + VkSemaphore acquireSemaphore; // Not owned, shared between all FrameData. + + // Profiling. + QueueProfileContext profile; + bool profilingEnabled_; + + void AcquireNextImage(VulkanContext *vulkan); +}; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index fc30a0554a..d7a40000d3 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -8,6 +8,7 @@ #include "Common/Data/Collections/Hashmaps.h" #include "Common/GPU/Vulkan/VulkanContext.h" #include "Common/GPU/Vulkan/VulkanBarrier.h" +#include "Common/GPU/Vulkan/VulkanFrameData.h" #include "Common/Data/Convert/SmallDataConvert.h" #include "Common/Data/Collections/TinySet.h" #include "Common/GPU/DataFormat.h" @@ -149,14 +150,6 @@ struct TransitionRequest { VkImageLayout targetLayout; }; -struct QueueProfileContext { - VkQueryPool queryPool; - std::vector timestampDescriptions; - std::string profileSummary; - double cpuStartTime; - double cpuEndTime; -}; - class VKRRenderPass; struct VKRStep { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 4ce9ca1941..01180a2a44 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -326,6 +326,8 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan query_ci.queryCount = MAX_TIMESTAMP_QUERIES; query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP; res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].profile.queryPool); + + frameData_[i].acquireSemaphore = acquireSemaphore_; } queueRunner_.CreateDeviceObjects(); @@ -1416,23 +1418,14 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { SubmitInitCommands(frame); if (!frameData.hasBegun) { - // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. - VkResult res = vkAcquireNextImageKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &frameData.curSwapchainImage); - if (res == VK_SUBOPTIMAL_KHR) { - // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something. - WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring"); - } else if (res == VK_ERROR_OUT_OF_DATE_KHR) { - WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned - processing the frame, but not presenting"); - frameData.skipSwap = true; - } else { - _assert_msg_(res == VK_SUCCESS, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res)); - } + frameData.AcquireNextImage(vulkan_); + // Effectively resets both main and present command buffers, since they both live in this pool. vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0); + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(frameData.mainCmd, &begin); - + VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 362e94bcac..e34c1a6550 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -65,11 +65,6 @@ private: std::string tag_; }; -enum class VKRRunType { - END, - SYNC, -}; - enum { MAX_TIMESTAMP_QUERIES = 128, }; @@ -487,45 +482,6 @@ private: VkSemaphore acquireSemaphore_; VkSemaphore renderingCompleteSemaphore_; - // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. - struct FrameData { - std::mutex push_mutex; - std::condition_variable push_condVar; - - std::mutex pull_mutex; - std::condition_variable pull_condVar; - - bool readyForFence = true; - bool readyForRun = false; - bool skipSwap = false; - VKRRunType type = VKRRunType::END; - - VkFence fence; - VkFence readbackFence; // Strictly speaking we might only need one of these. - bool readbackFenceUsed = false; - - // These are on different threads so need separate pools. - VkCommandPool cmdPoolInit; // Written to from main thread - VkCommandPool cmdPoolMain; // Written to from render thread, which also submits - - VkCommandBuffer initCmd; - VkCommandBuffer mainCmd; - VkCommandBuffer presentCmd; - - bool hasInitCommands = false; - bool hasPresentCommands = false; - - std::vector steps; - - // Swapchain. - bool hasBegun = false; - uint32_t curSwapchainImage = -1; - - // Profiling. - QueueProfileContext profile; - bool profilingEnabled_; - }; - FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; int newInflightFrames_ = -1; int inflightFramesAtStart_ = 0; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index b47db9d5cc..f317315e0a 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -51,6 +51,7 @@ VULKAN_FILES := \ $(SRC)/Common/GPU/Vulkan/thin3d_vulkan.cpp \ $(SRC)/Common/GPU/Vulkan/VulkanQueueRunner.cpp \ $(SRC)/Common/GPU/Vulkan/VulkanRenderManager.cpp \ + $(SRC)/Common/GPU/Vulkan/VulkanFrameData.cpp \ $(SRC)/Common/GPU/Vulkan/VulkanLoader.cpp \ $(SRC)/Common/GPU/Vulkan/VulkanContext.cpp \ $(SRC)/Common/GPU/Vulkan/VulkanDebug.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index ab2c4bfe1c..f08bd45633 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -253,6 +253,7 @@ SOURCES_CXX += \ $(COMMONDIR)/GPU/Vulkan/thin3d_vulkan.cpp \ $(COMMONDIR)/GPU/Vulkan/VulkanQueueRunner.cpp \ $(COMMONDIR)/GPU/Vulkan/VulkanRenderManager.cpp \ + $(COMMONDIR)/GPU/Vulkan/VulkanFrameData.cpp \ $(COMMONDIR)/GPU/Vulkan/VulkanLoader.cpp \ $(COMMONDIR)/GPU/Vulkan/VulkanContext.cpp \ $(COMMONDIR)/GPU/Vulkan/VulkanDebug.cpp \ From a369252a298d209e907c0183f78fa95b81dadc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 01:41:26 +0200 Subject: [PATCH 046/326] RunSteps cleanup --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 40 +++++++++++++---------- Common/GPU/Vulkan/VulkanQueueRunner.h | 3 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 6 ++-- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 307b2241e5..166c279f1c 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -376,68 +376,72 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) { } } -void VulkanQueueRunner::RunSteps(VkCommandBuffer mainCmd, VkCommandBuffer presentCmd, bool *hasPresentCommands, std::vector &steps, QueueProfileContext *profile) { +void VulkanQueueRunner::RunSteps(FrameData &frameData) { + QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr; + if (profile) profile->cpuStartTime = time_now_d(); bool emitLabels = vulkan_->Extensions().EXT_debug_utils; - for (size_t i = 0; i < steps.size(); i++) { - const VKRStep &step = *steps[i]; + for (size_t i = 0; i < frameData.steps.size(); i++) { + const VKRStep &step = *frameData.steps[i]; if (emitLabels) { VkDebugUtilsLabelEXT labelInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; labelInfo.pLabelName = step.tag; - vkCmdBeginDebugUtilsLabelEXT(mainCmd, &labelInfo); + vkCmdBeginDebugUtilsLabelEXT(frameData.mainCmd, &labelInfo); } switch (step.stepType) { case VKRStepType::RENDER: if (!step.render.framebuffer) { - _dbg_assert_(!*hasPresentCommands); - if (!*hasPresentCommands) { - *hasPresentCommands = true; + _dbg_assert_(!frameData.hasPresentCommands); + if (!frameData.hasPresentCommands) { + frameData.hasPresentCommands = true; VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - vkBeginCommandBuffer(presentCmd, &begin); + vkBeginCommandBuffer(frameData.presentCmd, &begin); } - PerformRenderPass(step, presentCmd); + PerformRenderPass(step, frameData.presentCmd); } else { - PerformRenderPass(step, mainCmd); + PerformRenderPass(step, frameData.mainCmd); } break; case VKRStepType::COPY: - PerformCopy(step, mainCmd); + PerformCopy(step, frameData.mainCmd); break; case VKRStepType::BLIT: - PerformBlit(step, mainCmd); + PerformBlit(step, frameData.mainCmd); break; case VKRStepType::READBACK: - PerformReadback(step, mainCmd); + PerformReadback(step, frameData.mainCmd); break; case VKRStepType::READBACK_IMAGE: - PerformReadbackImage(step, mainCmd); + PerformReadbackImage(step, frameData.mainCmd); break; case VKRStepType::RENDER_SKIP: break; } if (profile && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) { - vkCmdWriteTimestamp(mainCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); + vkCmdWriteTimestamp(frameData.mainCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); profile->timestampDescriptions.push_back(StepToString(step)); } if (emitLabels) { - vkCmdEndDebugUtilsLabelEXT(mainCmd); + vkCmdEndDebugUtilsLabelEXT(frameData.mainCmd); } } // Deleting all in one go should be easier on the instruction cache than deleting // them as we go - and easier to debug because we can look backwards in the frame. - for (size_t i = 0; i < steps.size(); i++) { - delete steps[i]; + for (auto step : frameData.steps) { + delete step; } + frameData.steps.clear(); + if (profile) profile->cpuEndTime = time_now_d(); } diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index d7a40000d3..efe5b1d5a6 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -17,6 +17,7 @@ class VKRFramebuffer; struct VKRGraphicsPipeline; struct VKRComputePipeline; struct VKRImage; +struct FrameData; enum { QUEUE_HACK_MGS2_ACID = 1, @@ -250,7 +251,7 @@ public: } void PreprocessSteps(std::vector &steps); - void RunSteps(VkCommandBuffer cmd, VkCommandBuffer presentCmd, bool *hasPresentCommands, std::vector &steps, QueueProfileContext *profile); + void RunSteps(FrameData &frameData); void LogSteps(const std::vector &steps, bool verbose); std::string StepToString(const VKRStep &step) const; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 01180a2a44..598948137c 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1562,12 +1562,10 @@ void VulkanRenderManager::Run(int frame) { BeginSubmitFrame(frame); FrameData &frameData = frameData_[frame]; - auto &stepsOnThread = frameData_[frame].steps; VkCommandBuffer cmd = frameData.mainCmd; - queueRunner_.PreprocessSteps(stepsOnThread); + queueRunner_.PreprocessSteps(frameData_[frame].steps); //queueRunner_.LogSteps(stepsOnThread, false); - queueRunner_.RunSteps(cmd, frameData.presentCmd, &frameData.hasPresentCommands, stepsOnThread, frameData.profilingEnabled_ ? &frameData.profile : nullptr); - stepsOnThread.clear(); + queueRunner_.RunSteps(frameData); switch (frameData.type) { case VKRRunType::END: From 9d9f2c539e054853073430431bb87336c5b52c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 08:43:13 +0200 Subject: [PATCH 047/326] Finish the switch to late acquire, moving the swapchain responsibility to the queue runner. --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 167 ++++++++++++++++++++++ Common/GPU/Vulkan/VulkanQueueRunner.h | 27 ++++ Common/GPU/Vulkan/VulkanRenderManager.cpp | 159 +------------------- Common/GPU/Vulkan/VulkanRenderManager.h | 21 +-- 4 files changed, 198 insertions(+), 176 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 166c279f1c..2056463261 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -141,6 +141,171 @@ void VulkanQueueRunner::DestroyDeviceObjects() { renderPasses_.Clear(); } +bool VulkanQueueRunner::CreateSwapchain(VkCommandBuffer cmdInit) { + VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, nullptr); + _dbg_assert_(res == VK_SUCCESS); + + VkImage *swapchainImages = new VkImage[swapchainImageCount_]; + res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, swapchainImages); + if (res != VK_SUCCESS) { + ERROR_LOG(G3D, "vkGetSwapchainImagesKHR failed"); + delete[] swapchainImages; + return false; + } + + for (uint32_t i = 0; i < swapchainImageCount_; i++) { + SwapchainImageData sc_buffer{}; + sc_buffer.image = swapchainImages[i]; + + VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + color_image_view.format = vulkan_->GetSwapchainFormat(); + color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + color_image_view.subresourceRange.baseMipLevel = 0; + color_image_view.subresourceRange.levelCount = 1; + color_image_view.subresourceRange.baseArrayLayer = 0; + color_image_view.subresourceRange.layerCount = 1; + color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D; + color_image_view.flags = 0; + color_image_view.image = sc_buffer.image; + + // We leave the images as UNDEFINED, there's no need to pre-transition them as + // the backbuffer renderpass starts out with them being auto-transitioned from UNDEFINED anyway. + // Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417. + + res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view); + swapchainImages_.push_back(sc_buffer); + _dbg_assert_(res == VK_SUCCESS); + } + delete[] swapchainImages; + + // Must be before InitBackbufferRenderPass. + if (InitDepthStencilBuffer(cmdInit)) { + InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight()); + } + return true; +} + + +bool VulkanQueueRunner::InitBackbufferFramebuffers(int width, int height) { + VkResult res; + // We share the same depth buffer but have multiple color buffers, see the loop below. + VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view }; + + VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; + fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RP_TYPE_BACKBUFFER); + fb_info.attachmentCount = 2; + fb_info.pAttachments = attachments; + fb_info.width = width; + fb_info.height = height; + fb_info.layers = 1; + + framebuffers_.resize(swapchainImageCount_); + + for (uint32_t i = 0; i < swapchainImageCount_; i++) { + attachments[0] = swapchainImages_[i].view; + res = vkCreateFramebuffer(vulkan_->GetDevice(), &fb_info, nullptr, &framebuffers_[i]); + _dbg_assert_(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + framebuffers_.clear(); + return false; + } + } + + return true; +} + +bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd) { + const VkFormat depth_format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; + int aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + VkImageCreateInfo image_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = depth_format; + image_info.extent.width = vulkan_->GetBackbufferWidth(); + image_info.extent.height = vulkan_->GetBackbufferHeight(); + image_info.extent.depth = 1; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.queueFamilyIndexCount = 0; + image_info.pQueueFamilyIndices = nullptr; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + image_info.flags = 0; + + depth_.format = depth_format; + + VmaAllocationCreateInfo allocCreateInfo{}; + VmaAllocationInfo allocInfo{}; + + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_info, &allocCreateInfo, &depth_.image, &depth_.alloc, &allocInfo); + _dbg_assert_(res == VK_SUCCESS); + if (res != VK_SUCCESS) + return false; + + vulkan_->SetDebugName(depth_.image, VK_OBJECT_TYPE_IMAGE, "BackbufferDepth"); + + TransitionImageLayout2(cmd, depth_.image, 0, 1, + aspectMask, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); + + VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + depth_view_info.image = depth_.image; + depth_view_info.format = depth_format; + depth_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + depth_view_info.subresourceRange.aspectMask = aspectMask; + depth_view_info.subresourceRange.baseMipLevel = 0; + depth_view_info.subresourceRange.levelCount = 1; + depth_view_info.subresourceRange.baseArrayLayer = 0; + depth_view_info.subresourceRange.layerCount = 1; + depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + depth_view_info.flags = 0; + + VkDevice device = vulkan_->GetDevice(); + + res = vkCreateImageView(device, &depth_view_info, NULL, &depth_.view); + _dbg_assert_(res == VK_SUCCESS); + if (res != VK_SUCCESS) + return false; + + return true; +} + + +void VulkanQueueRunner::DestroyBackBuffers() { + for (auto &image : swapchainImages_) { + vulkan_->Delete().QueueDeleteImageView(image.view); + } + swapchainImages_.clear(); + + if (depth_.view) { + vulkan_->Delete().QueueDeleteImageView(depth_.view); + } + if (depth_.image) { + _dbg_assert_(depth_.alloc); + vulkan_->Delete().QueueDeleteImageAllocation(depth_.image, depth_.alloc); + } + depth_ = {}; + for (uint32_t i = 0; i < framebuffers_.size(); i++) { + _dbg_assert_(framebuffers_[i] != VK_NULL_HANDLE); + vulkan_->Delete().QueueDeleteFramebuffer(framebuffers_[i]); + } + framebuffers_.clear(); + + INFO_LOG(G3D, "Backbuffers destroyed"); +} + static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) { switch (action) { case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -399,6 +564,8 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData) { _dbg_assert_(!frameData.hasPresentCommands); if (!frameData.hasPresentCommands) { frameData.hasPresentCommands = true; + frameData.AcquireNextImage(vulkan_); + SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(frameData.presentCmd, &begin); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index efe5b1d5a6..de3108c742 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -259,6 +259,14 @@ public: void CreateDeviceObjects(); void DestroyDeviceObjects(); + // Swapchain + void DestroyBackBuffers(); + bool CreateSwapchain(VkCommandBuffer cmdInit); + + bool HasBackbuffers() const { + return !framebuffers_.empty(); + } + // Get a render pass that's compatible with all our framebuffers. // Note that it's precached, cannot look up in the map as this might be on another thread. VKRRenderPass *GetCompatibleRenderPass() const { @@ -298,6 +306,9 @@ public: } private: + bool InitBackbufferFramebuffers(int width, int height); + bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. + VKRRenderPass *PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd); void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd); void PerformCopy(const VKRStep &pass, VkCommandBuffer cmd); @@ -352,4 +363,20 @@ private: // Stored here to help reuse the allocation. VulkanBarrier recordBarrier_; + + // Swap chain management + struct SwapchainImageData { + VkImage image; + VkImageView view; + }; + std::vector framebuffers_; + std::vector swapchainImages_; + uint32_t swapchainImageCount_ = 0; + struct DepthBufferInfo { + VkFormat format = VK_FORMAT_UNDEFINED; + VkImage image = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + }; + DepthBufferInfo depth_; }; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 598948137c..1ac269c515 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -344,52 +344,14 @@ bool VulkanRenderManager::CreateBackbuffers() { ERROR_LOG(G3D, "No swapchain - can't create backbuffers"); return false; } - VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, nullptr); - _dbg_assert_(res == VK_SUCCESS); - VkImage *swapchainImages = new VkImage[swapchainImageCount_]; - res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, swapchainImages); - if (res != VK_SUCCESS) { - ERROR_LOG(G3D, "vkGetSwapchainImagesKHR failed"); - delete[] swapchainImages; - return false; - } VkCommandBuffer cmdInit = GetInitCmd(); - for (uint32_t i = 0; i < swapchainImageCount_; i++) { - SwapchainImageData sc_buffer{}; - sc_buffer.image = swapchainImages[i]; - - VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - color_image_view.format = vulkan_->GetSwapchainFormat(); - color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - color_image_view.subresourceRange.baseMipLevel = 0; - color_image_view.subresourceRange.levelCount = 1; - color_image_view.subresourceRange.baseArrayLayer = 0; - color_image_view.subresourceRange.layerCount = 1; - color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D; - color_image_view.flags = 0; - color_image_view.image = sc_buffer.image; - - // We leave the images as UNDEFINED, there's no need to pre-transition them as - // the backbuffer renderpass starts out with them being auto-transitioned from UNDEFINED anyway. - // Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417. - - res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view); - swapchainImages_.push_back(sc_buffer); - _dbg_assert_(res == VK_SUCCESS); + if (!queueRunner_.CreateSwapchain(cmdInit)) { + return false; } - delete[] swapchainImages; - // Must be before InitBackbufferRenderPass. - if (InitDepthStencilBuffer(cmdInit)) { - InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight()); - } curWidthRaw_ = -1; curHeightRaw_ = -1; @@ -477,26 +439,7 @@ void VulkanRenderManager::DestroyBackbuffers() { StopThread(); vulkan_->WaitUntilQueueIdle(); - for (auto &image : swapchainImages_) { - vulkan_->Delete().QueueDeleteImageView(image.view); - } - swapchainImages_.clear(); - - if (depth_.view) { - vulkan_->Delete().QueueDeleteImageView(depth_.view); - } - if (depth_.image) { - _dbg_assert_(depth_.alloc); - vulkan_->Delete().QueueDeleteImageAllocation(depth_.image, depth_.alloc); - } - depth_ = {}; - for (uint32_t i = 0; i < framebuffers_.size(); i++) { - _dbg_assert_(framebuffers_[i] != VK_NULL_HANDLE); - vulkan_->Delete().QueueDeleteFramebuffer(framebuffers_[i]); - } - framebuffers_.clear(); - - INFO_LOG(G3D, "Backbuffers destroyed"); + queueRunner_.DestroyBackBuffers(); } VulkanRenderManager::~VulkanRenderManager() { @@ -1040,98 +983,6 @@ void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int queueRunner_.CopyReadbackBuffer(w, h, destFormat, destFormat, pixelStride, pixels); } -bool VulkanRenderManager::InitBackbufferFramebuffers(int width, int height) { - VkResult res; - // We share the same depth buffer but have multiple color buffers, see the loop below. - VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view }; - - VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; - fb_info.renderPass = queueRunner_.GetCompatibleRenderPass()->Get(vulkan_, RP_TYPE_BACKBUFFER); - fb_info.attachmentCount = 2; - fb_info.pAttachments = attachments; - fb_info.width = width; - fb_info.height = height; - fb_info.layers = 1; - - framebuffers_.resize(swapchainImageCount_); - - for (uint32_t i = 0; i < swapchainImageCount_; i++) { - attachments[0] = swapchainImages_[i].view; - res = vkCreateFramebuffer(vulkan_->GetDevice(), &fb_info, nullptr, &framebuffers_[i]); - _dbg_assert_(res == VK_SUCCESS); - if (res != VK_SUCCESS) { - framebuffers_.clear(); - return false; - } - } - - return true; -} - -bool VulkanRenderManager::InitDepthStencilBuffer(VkCommandBuffer cmd) { - const VkFormat depth_format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; - int aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - VkImageCreateInfo image_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - image_info.imageType = VK_IMAGE_TYPE_2D; - image_info.format = depth_format; - image_info.extent.width = vulkan_->GetBackbufferWidth(); - image_info.extent.height = vulkan_->GetBackbufferHeight(); - image_info.extent.depth = 1; - image_info.mipLevels = 1; - image_info.arrayLayers = 1; - image_info.samples = VK_SAMPLE_COUNT_1_BIT; - image_info.queueFamilyIndexCount = 0; - image_info.pQueueFamilyIndices = nullptr; - image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - image_info.flags = 0; - - depth_.format = depth_format; - - VmaAllocationCreateInfo allocCreateInfo{}; - VmaAllocationInfo allocInfo{}; - - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_info, &allocCreateInfo, &depth_.image, &depth_.alloc, &allocInfo); - _dbg_assert_(res == VK_SUCCESS); - if (res != VK_SUCCESS) - return false; - - vulkan_->SetDebugName(depth_.image, VK_OBJECT_TYPE_IMAGE, "BackbufferDepth"); - - TransitionImageLayout2(cmd, depth_.image, 0, 1, - aspectMask, - VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); - - VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - depth_view_info.image = depth_.image; - depth_view_info.format = depth_format; - depth_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - depth_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - depth_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - depth_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - depth_view_info.subresourceRange.aspectMask = aspectMask; - depth_view_info.subresourceRange.baseMipLevel = 0; - depth_view_info.subresourceRange.levelCount = 1; - depth_view_info.subresourceRange.baseArrayLayer = 0; - depth_view_info.subresourceRange.layerCount = 1; - depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; - depth_view_info.flags = 0; - - VkDevice device = vulkan_->GetDevice(); - - res = vkCreateImageView(device, &depth_view_info, NULL, &depth_.view); - _dbg_assert_(res == VK_SUCCESS); - if (res != VK_SUCCESS) - return false; - - return true; -} - static void RemoveDrawCommands(std::vector *cmds) { // Here we remove any DRAW type commands when we hit a CLEAR. for (auto &c : *cmds) { @@ -1418,8 +1269,6 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { SubmitInitCommands(frame); if (!frameData.hasBegun) { - frameData.AcquireNextImage(vulkan_); - // Effectively resets both main and present command buffers, since they both live in this pool. vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0); @@ -1428,8 +1277,6 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); - queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); - frameData.hasBegun = true; } } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index e34c1a6550..a91669ce74 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -437,7 +437,7 @@ public: void DestroyBackbuffers(); bool HasBackbuffers() { - return !framebuffers_.empty(); + return queueRunner_.HasBackbuffers(); } void SetInflightFrames(int f) { @@ -463,8 +463,6 @@ public: } private: - bool InitBackbufferFramebuffers(int width, int height); - bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. void EndCurRenderStep(); void BeginSubmitFrame(int frame); @@ -528,22 +526,5 @@ private: // pipelines to check and possibly create at the end of the current render pass. std::vector pipelinesToCheck_; - // Swap chain management - struct SwapchainImageData { - VkImage image; - VkImageView view; - }; - std::vector framebuffers_; - std::vector swapchainImages_; - uint32_t swapchainImageCount_ = 0; - struct DepthBufferInfo { - VkFormat format = VK_FORMAT_UNDEFINED; - VkImage image = VK_NULL_HANDLE; - VmaAllocation alloc = VK_NULL_HANDLE; - VkImageView view = VK_NULL_HANDLE; - }; - DepthBufferInfo depth_; - - // This works great - except see issue #10097. WTF? bool useThread_ = true; }; From 48162858d8547c1d13eb7d1604d362a79e920237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Sep 2022 09:30:08 +0200 Subject: [PATCH 048/326] Remove assert (this happens when playing framedumps) --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 1ac269c515..588e8eb7fb 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1433,7 +1433,6 @@ void VulkanRenderManager::Run(int frame) { void VulkanRenderManager::EndSyncFrame(int frame) { FrameData &frameData = frameData_[frame]; - _dbg_assert_(!frameData.hasPresentCommands); frameData.readbackFenceUsed = true; // The submit will trigger the readbackFence. From 6576b853e467fa7b5ce11d6fc9a2a96479ceeb02 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 13:29:33 -0700 Subject: [PATCH 049/326] UI: Fix some logic dependent on screen tags. 9f38516 changed various tags but not code referencing them. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 1 - UI/InstallZipScreen.h | 2 +- UI/MainScreen.cpp | 5 +++-- UI/MemStickScreen.h | 2 +- UI/MiscScreens.cpp | 6 +++--- UI/PauseScreen.cpp | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 588e8eb7fb..d2973abb5a 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1409,7 +1409,6 @@ void VulkanRenderManager::Run(int frame) { BeginSubmitFrame(frame); FrameData &frameData = frameData_[frame]; - VkCommandBuffer cmd = frameData.mainCmd; queueRunner_.PreprocessSteps(frameData_[frame].steps); //queueRunner_.LogSteps(stepsOnThread, false); queueRunner_.RunSteps(frameData); diff --git a/UI/InstallZipScreen.h b/UI/InstallZipScreen.h index 2f705b5510..140b82fec2 100644 --- a/UI/InstallZipScreen.h +++ b/UI/InstallZipScreen.h @@ -30,7 +30,7 @@ public: virtual void update() override; virtual bool key(const KeyInput &key) override; - const char *tag() const override { return "install_zip"; } + const char *tag() const override { return "InstallZip"; } protected: virtual void CreateViews() override; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 3e025d06ba..d5ed3ab0a4 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -1432,11 +1432,12 @@ UI::EventReturn MainScreen::OnExit(UI::EventParams &e) { } void MainScreen::dialogFinished(const Screen *dialog, DialogResult result) { - if (dialog->tag() == "store") { + std::string tag = dialog->tag(); + if (tag == "Store") { backFromStore_ = true; RecreateViews(); } - if (dialog->tag() == "game") { + if (tag == "Game") { if (!restoreFocusGamePath_.empty() && UI::IsFocusMovementEnabled()) { // Prevent the background from fading, since we just were displaying it. highlightedGamePath_ = restoreFocusGamePath_; diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 6cab2a34a4..378f4f3ed0 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -36,7 +36,7 @@ public: MemStickScreen(bool initialSetup); ~MemStickScreen() {} - const char *tag() const override { return "memstick"; } + const char *tag() const override { return "MemStick"; } enum Choice { CHOICE_BROWSE_FOLDER, diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 2a0ea9bfe7..d90d92f76d 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -387,13 +387,13 @@ void HandleCommonMessages(const char *message, const char *value, ScreenManager MIPSComp::jit->ClearCache(); } currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore); - } else if (!strcmp(message, "control mapping") && isActiveScreen && activeScreen->tag() != "control mapping") { + } else if (!strcmp(message, "control mapping") && isActiveScreen && std::string(activeScreen->tag()) != "ControlMapping") { UpdateUIState(UISTATE_MENU); manager->push(new ControlMappingScreen()); - } else if (!strcmp(message, "display layout editor") && isActiveScreen && activeScreen->tag() != "display layout screen") { + } else if (!strcmp(message, "display layout editor") && isActiveScreen && std::string(activeScreen->tag()) != "DisplayLayout") { UpdateUIState(UISTATE_MENU); manager->push(new DisplayLayoutScreen()); - } else if (!strcmp(message, "settings") && isActiveScreen && activeScreen->tag() != "settings") { + } else if (!strcmp(message, "settings") && isActiveScreen && std::string(activeScreen->tag()) != "GameSettings") { UpdateUIState(UISTATE_MENU); manager->push(new GameSettingsScreen(Path())); } else if (!strcmp(message, "language screen") && isActiveScreen) { diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index f358fa1e06..748fcdf86d 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -172,7 +172,7 @@ public: return slot_; } - const char *tag() const override { return "screenshot_view"; } + const char *tag() const override { return "ScreenshotView"; } protected: bool FillVertical() const override { return false; } @@ -458,7 +458,7 @@ UI::EventReturn GamePauseScreen::OnState(UI::EventParams &e) { void GamePauseScreen::dialogFinished(const Screen *dialog, DialogResult dr) { std::string tag = dialog->tag(); - if (tag == "screenshot" && dr == DR_OK) { + if (tag == "ScreenshotView" && dr == DR_OK) { finishNextFrame_ = true; } else { // There may have been changes to our savestates, so let's recreate. From 9b01fce5b54936ab3028d93367b7192cbca6c546 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 11:04:01 -0700 Subject: [PATCH 050/326] softgpu: Run early Z tests in fast rect path. Needed for some 2D games, like Criminal Girls. --- GPU/Software/RasterizerRectangle.cpp | 50 ++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 4c2e4ad1a9..f07f66092a 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -116,8 +116,12 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran DrawingCoords scissorTL = TransformUnit::ScreenToDrawing(range.x1, range.y1); DrawingCoords scissorBR = TransformUnit::ScreenToDrawing(range.x2, range.y2); - int z = v1.screenpos.z; - int fog = 255; + const int z = v1.screenpos.z; + constexpr int fog = 255; + + // Since it's flat, we can check depth range early. Matters for earlyZChecks. + if (pixelID.applyDepthRange && (z < pixelID.cached.minz || z > pixelID.cached.maxz)) + return; bool isWhite = v1.color0 == 0xFFFFFFFF; @@ -204,15 +208,31 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran float t = tf_start; const Vec4 c0 = Vec4::FromRGBA(v1.color0); - for (int y = pos0.y; y < pos1.y; y++) { - float s = sf_start; - // Not really that fast but faster than triangle. - for (int x = pos0.x; x < pos1.x; x++) { - Vec4 prim_color = state.nearest(s, t, xoff, yoff, ToVec4IntArg(c0), &texptr, &texbufw, 0, 0, state.samplerID); - state.drawPixel(x, y, z, 255, ToVec4IntArg(prim_color), pixelID); - s += dsf; + if (pixelID.earlyZChecks) { + for (int y = pos0.y; y < pos1.y; y++) { + float s = sf_start; + // Not really that fast but faster than triangle. + for (int x = pos0.x; x < pos1.x; x++) { + if (CheckDepthTestPassed(pixelID.DepthTestFunc(), x, y, pixelID.cached.depthbufStride, z)) { + Vec4 prim_color = state.nearest(s, t, xoff, yoff, ToVec4IntArg(c0), &texptr, &texbufw, 0, 0, state.samplerID); + state.drawPixel(x, y, z, fog, ToVec4IntArg(prim_color), pixelID); + } + + s += dsf; + } + t += dtf; + } + } else { + for (int y = pos0.y; y < pos1.y; y++) { + float s = sf_start; + // Not really that fast but faster than triangle. + for (int x = pos0.x; x < pos1.x; x++) { + Vec4 prim_color = state.nearest(s, t, xoff, yoff, ToVec4IntArg(c0), &texptr, &texbufw, 0, 0, state.samplerID); + state.drawPixel(x, y, z, fog, ToVec4IntArg(prim_color), pixelID); + s += dsf; + } + t += dtf; } - t += dtf; } } } else { @@ -239,6 +259,16 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran pixel++; } } + } else if (pixelID.earlyZChecks) { + const Vec4 prim_color = Vec4::FromRGBA(v1.color0); + for (int y = pos0.y; y < pos1.y; y++) { + for (int x = pos0.x; x < pos1.x; x++) { + if (!CheckDepthTestPassed(pixelID.DepthTestFunc(), x, y, pixelID.cached.depthbufStride, z)) + continue; + + state.drawPixel(x, y, z, fog, ToVec4IntArg(prim_color), pixelID); + } + } } else { const Vec4 prim_color = Vec4::FromRGBA(v1.color0); for (int y = pos0.y; y < pos1.y; y++) { From d703c934ddeebf2c4ac090c943a9f5b856a39485 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 10:05:11 -0700 Subject: [PATCH 051/326] Headless: Small cleanup of options handling. To be able to add additional options. --- headless/Headless.cpp | 53 ++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 0be0518e8b..ab88d3395d 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -128,13 +128,9 @@ int printUsage(const char *progname, const char *reason) fprintf(stderr, " -l, --log full log output, not just emulated printfs\n"); fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n"); -#if defined(HEADLESSHOST_CLASS) - { - fprintf(stderr, " --graphics=BACKEND use the full gpu backend (slower)\n"); - fprintf(stderr, " options: gles, software, directx9, etc.\n"); - fprintf(stderr, " --screenshot=FILE compare against a screenshot\n"); - } -#endif + fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n"); + fprintf(stderr, " options: gles, software, directx9, etc.\n"); + fprintf(stderr, " --screenshot=FILE compare against a screenshot\n"); fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n"); fprintf(stderr, " -v, --verbose show the full passed/failed result\n"); @@ -161,13 +157,18 @@ static HeadlessHost *getHost(GPUCore gpuCore) { } } -bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool autoCompare, bool verbose, double timeout) -{ +struct AutoTestOptions { + double timeout; + bool compare : 1; + bool verbose : 1; +}; + +bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) { // Kinda ugly, trying to guesstimate the test name from filename... currentTestName = GetTestName(coreParameter.fileToStart); std::string output; - if (autoCompare) + if (opt.compare) coreParameter.collectEmuLog = &output; std::string error_string; @@ -183,7 +184,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool host->BootDone(); - if (autoCompare) + if (opt.compare) headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart)); while (!PSP_InitUpdate(&error_string)) @@ -196,8 +197,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool } bool passed = true; - double deadline; - deadline = time_now_d() + timeout; + double deadline = time_now_d() + opt.timeout; Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops); @@ -239,8 +239,8 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool headlessHost->FlushDebugOutput(); - if (autoCompare && passed) - passed = CompareOutput(coreParameter.fileToStart, output, verbose); + if (opt.compare && passed) + passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose); TeamCityPrint("testFinished name='%s'", currentTestName.c_str()); @@ -263,9 +263,9 @@ int main(int argc, const char* argv[]) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif + AutoTestOptions testOptions{}; + testOptions.timeout = std::numeric_limits::infinity(); bool fullLog = false; - bool autoCompare = false; - bool verbose = false; const char *stateToLoad = 0; GPUCore gpuCore = GPUCORE_SOFTWARE; CPUCore cpuCore = CPUCore::JIT; @@ -275,7 +275,6 @@ int main(int argc, const char* argv[]) const char *mountIso = nullptr; const char *mountRoot = nullptr; const char *screenshotFilename = nullptr; - float timeout = std::numeric_limits::infinity(); for (int i = 1; i < argc; i++) { @@ -300,9 +299,9 @@ int main(int argc, const char* argv[]) else if (!strcmp(argv[i], "--ir")) cpuCore = CPUCore::IR_JIT; else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare")) - autoCompare = true; + testOptions.compare = true; else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) - verbose = true; + testOptions.verbose = true; else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics=")) { const char *gpuName = argv[i] + strlen("--graphics="); @@ -330,7 +329,7 @@ int main(int argc, const char* argv[]) } else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot=")) screenshotFilename = argv[i] + strlen("--screenshot="); else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout=")) - timeout = (float)strtod(argv[i] + strlen("--timeout="), NULL); + testOptions.timeout = strtod(argv[i] + strlen("--timeout="), nullptr); else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger=")) debuggerPort = (int)strtoul(argv[i] + strlen("--debugger="), NULL, 10); else if (!strcmp(argv[i], "--teamcity")) @@ -388,7 +387,7 @@ int main(int argc, const char* argv[]) coreParameter.mountIso = mountIso ? Path(std::string(mountIso)) : Path(); coreParameter.mountRoot = mountRoot ? Path(std::string(mountRoot)) : Path(); coreParameter.startBreak = false; - coreParameter.printfEmuLog = !autoCompare; + coreParameter.printfEmuLog = !testOptions.compare; coreParameter.headLess = true; coreParameter.renderScaleFactor = 1; coreParameter.renderWidth = 480; @@ -487,11 +486,10 @@ int main(int argc, const char* argv[]) for (size_t i = 0; i < testFilenames.size(); ++i) { coreParameter.fileToStart = Path(testFilenames[i]); - if (autoCompare) + if (testOptions.compare) printf("%s:\n", coreParameter.fileToStart.c_str()); - bool passed = RunAutoTest(headlessHost, coreParameter, autoCompare, verbose, timeout); - if (autoCompare) - { + bool passed = RunAutoTest(headlessHost, coreParameter, testOptions); + if (testOptions.compare) { std::string testName = GetTestName(coreParameter.fileToStart); if (passed) { @@ -503,8 +501,7 @@ int main(int argc, const char* argv[]) } } - if (autoCompare) - { + if (testOptions.compare) { printf("%d tests passed, %d tests failed.\n", (int)passedTests.size(), (int)failedTests.size()); if (!failedTests.empty()) { From f32e8e11abdf3d95a9e14b5904cda5ea8a10711f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 11:14:09 -0700 Subject: [PATCH 052/326] headless: Switch screenshot error to MSE. This is more useful than 1/0 on whether a pixel doesn't match, when many are close but not exact. --- headless/Compare.cpp | 23 +++++++++++++++-------- headless/StubHost.cpp | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/headless/Compare.cpp b/headless/Compare.cpp index f5127e5ee0..76254d005a 100644 --- a/headless/Compare.cpp +++ b/headless/Compare.cpp @@ -305,11 +305,18 @@ bool CompareOutput(const Path &bootFilename, const std::string &output, bool ver } } -inline int ComparePixel(u32 pix1, u32 pix2) { - // For now, if they're different at all except alpha, it's an error. - if ((pix1 & 0xFFFFFF) != (pix2 & 0xFFFFFF)) - return 1; - return 0; +static inline double CompareChannel(int pix1, int pix2) { + double diff = pix1 - pix2; + return diff * diff; +} + +static inline double ComparePixel(u32 pix1, u32 pix2) { + // Ignore alpha. + double r = CompareChannel(pix1 & 0xFF, pix2 & 0xFF); + double g = CompareChannel((pix1 >> 8) & 0xFF, (pix2 >> 8) & 0xFF); + double b = CompareChannel((pix1 >> 16) & 0xFF, (pix2 >> 16) & 0xFF); + + return r + g + b; } std::vector TranslateDebugBufferToCompare(const GPUDebugBuffer *buffer, u32 stride, u32 h) { @@ -338,7 +345,6 @@ std::vector TranslateDebugBufferToCompare(const GPUDebugBuffer *buffer, u32 dst += (h - safeH) * stride; } - u32 errors = 0; for (u32 y = 0; y < safeH; ++y) { switch (buffer->GetFormat()) { case GPU_DBG_FORMAT_8888: @@ -429,7 +435,7 @@ double ScreenshotComparer::Compare(const Path &screenshotFilename) { return -1.0f; } - u32 errors = 0; + double errors = 0; if (asBitmap_) { // The reference is flipped and BGRA by default for the common BMP compare case. for (u32 y = 0; y < h_; ++y) { @@ -447,7 +453,8 @@ double ScreenshotComparer::Compare(const Path &screenshotFilename) { } } - return (double) errors / (double) (w_ * h_); + // Convert to MSE, accounting for all three channels (RGB.) + return errors / (double)(w_ * h_ * 3); } bool ScreenshotComparer::SaveActualBitmap(const Path &resultFilename) { diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index e7fbd03f9a..a7f3496d0d 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -55,7 +55,7 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) { SendOrCollectDebugOutput(comparer.GetError() + "\n"); if (errors > 0) - SendOrCollectDebugOutput(StringFromFormat("Screenshot error: %f%%\n", errors * 100.0f)); + SendOrCollectDebugOutput(StringFromFormat("Screenshot MSE: %f\n", errors)); if (errors > 0 && !teamCityMode && !getenv("GITHUB_ACTIONS")) { if (comparer.SaveActualBitmap(Path("__testfailure.bmp"))) From 25d92c8a845c46941a04fbdd3995c261002566e2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 11:24:15 -0700 Subject: [PATCH 053/326] headless: Add option for MSE pass threshold. This makes it easier to allow a certain amount of deviation across devices. --- headless/Headless.cpp | 10 +++++++--- headless/StubHost.cpp | 4 ++-- headless/StubHost.h | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index ab88d3395d..89272aa406 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -131,6 +131,7 @@ int printUsage(const char *progname, const char *reason) fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n"); fprintf(stderr, " options: gles, software, directx9, etc.\n"); fprintf(stderr, " --screenshot=FILE compare against a screenshot\n"); + fprintf(stderr, " --max-mse=NUMBER maximum allowed MSE error for screenshot\n"); fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n"); fprintf(stderr, " -v, --verbose show the full passed/failed result\n"); @@ -159,6 +160,7 @@ static HeadlessHost *getHost(GPUCore gpuCore) { struct AutoTestOptions { double timeout; + double maxScreenshotError; bool compare : 1; bool verbose : 1; }; @@ -185,7 +187,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const host->BootDone(); if (opt.compare) - headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart)); + headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart), opt.maxScreenshotError); while (!PSP_InitUpdate(&error_string)) sleep_ms(1); @@ -330,6 +332,8 @@ int main(int argc, const char* argv[]) screenshotFilename = argv[i] + strlen("--screenshot="); else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout=")) testOptions.timeout = strtod(argv[i] + strlen("--timeout="), nullptr); + else if (!strncmp(argv[i], "--max-mse=", strlen("--max-mse=")) && strlen(argv[i]) > strlen("--max-mse=")) + testOptions.maxScreenshotError = strtod(argv[i] + strlen("--max-mse="), nullptr); else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger=")) debuggerPort = (int)strtoul(argv[i] + strlen("--debugger="), NULL, 10); else if (!strcmp(argv[i], "--teamcity")) @@ -455,8 +459,8 @@ int main(int argc, const char* argv[]) if (!File::Exists(g_Config.flash0Directory)) g_Config.flash0Directory = File::GetExeDirectory() / "assets/flash0"; - if (screenshotFilename != 0) - headlessHost->SetComparisonScreenshot(Path(std::string(screenshotFilename))); + if (screenshotFilename) + headlessHost->SetComparisonScreenshot(Path(std::string(screenshotFilename)), testOptions.maxScreenshotError); #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index a7f3496d0d..7a886c4827 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -54,10 +54,10 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) { if (errors < 0) SendOrCollectDebugOutput(comparer.GetError() + "\n"); - if (errors > 0) + if (errors > maxScreenshotError_) SendOrCollectDebugOutput(StringFromFormat("Screenshot MSE: %f\n", errors)); - if (errors > 0 && !teamCityMode && !getenv("GITHUB_ACTIONS")) { + if (errors > maxScreenshotError_ && !teamCityMode && !getenv("GITHUB_ACTIONS")) { if (comparer.SaveActualBitmap(Path("__testfailure.bmp"))) SendOrCollectDebugOutput("Actual output written to: __testfailure.bmp\n"); comparer.SaveVisualComparisonPNG(Path("__testcompare.png")); diff --git a/headless/StubHost.h b/headless/StubHost.h index c914f025b2..ddfc6cd29d 100644 --- a/headless/StubHost.h +++ b/headless/StubHost.h @@ -68,8 +68,9 @@ public: } } - virtual void SetComparisonScreenshot(const Path &filename) { + void SetComparisonScreenshot(const Path &filename, double maxError) { comparisonScreenshot_ = filename; + maxScreenshotError_ = maxError; } void SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) override; @@ -83,6 +84,7 @@ protected: void SendOrCollectDebugOutput(const std::string &output); Path comparisonScreenshot_; + double maxScreenshotError_ = 0.0; std::string debugOutputBuffer_; GPUCore gpuCore_; GraphicsContext *gfx_ = nullptr; From 749268c7f76418cee5cf93f9e85664d162ed88eb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 12:30:16 -0700 Subject: [PATCH 054/326] headless: Benchmark a test or frame dump. This outputs the average time for comparison. --- headless/Headless.cpp | 51 +++++++++++++++++++++++++++++++------------ headless/StubHost.cpp | 2 +- headless/StubHost.h | 4 ++++ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 89272aa406..96a6820087 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -139,6 +139,7 @@ int printUsage(const char *progname, const char *reason) fprintf(stderr, " --ir use ir interpreter\n"); fprintf(stderr, " -j use jit (default)\n"); fprintf(stderr, " -c, --compare compare with output in file.expected\n"); + fprintf(stderr, " --bench run multiple times and output speed\n"); fprintf(stderr, "\nSee headless.txt for details.\n"); return 1; @@ -163,6 +164,7 @@ struct AutoTestOptions { double maxScreenshotError; bool compare : 1; bool verbose : 1; + bool bench : 1; }; bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) { @@ -170,7 +172,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const currentTestName = GetTestName(coreParameter.fileToStart); std::string output; - if (opt.compare) + if (opt.compare || opt.bench) coreParameter.collectEmuLog = &output; std::string error_string; @@ -184,8 +186,6 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const TeamCityPrint("testStarted name='%s' captureStandardOutput='true'", currentTestName.c_str()); - host->BootDone(); - if (opt.compare) headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart), opt.maxScreenshotError); @@ -194,12 +194,11 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const if (!PSP_IsInited()) { TeamCityPrint("testFailed name='%s' message='Startup failed'", currentTestName.c_str()); TeamCityPrint("testFinished name='%s'", currentTestName.c_str()); - GitHubActionsPrint("error", "Test timeout for %s", currentTestName.c_str()); + GitHubActionsPrint("error", "Test init failed for %s", currentTestName.c_str()); return false; } - bool passed = true; - double deadline = time_now_d() + opt.timeout; + host->BootDone(); Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops); @@ -207,6 +206,8 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const if (coreParameter.graphicsContext && coreParameter.graphicsContext->GetDrawContext()) coreParameter.graphicsContext->GetDrawContext()->BeginFrame(); + bool passed = true; + double deadline = time_now_d() + opt.timeout; coreState = coreParameter.startBreak ? CORE_STEPPING : CORE_RUNNING; while (coreState == CORE_RUNNING || coreState == CORE_STEPPING) { @@ -223,12 +224,15 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const } if (time_now_d() > deadline) { // Don't compare, print the output at least up to this point, and bail. - printf("%s", output.c_str()); - passed = false; + if (!opt.bench) { + printf("%s", output.c_str()); - host->SendDebugOutput("TIMEOUT\n"); - TeamCityPrint("testFailed name='%s' message='Test timeout'", currentTestName.c_str()); - GitHubActionsPrint("error", "Test timeout for %s", currentTestName.c_str()); + host->SendDebugOutput("TIMEOUT\n"); + TeamCityPrint("testFailed name='%s' message='Test timeout'", currentTestName.c_str()); + GitHubActionsPrint("error", "Test timeout for %s", currentTestName.c_str()); + } + + passed = false; Core_Stop(); } } @@ -239,7 +243,8 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const PSP_Shutdown(); - headlessHost->FlushDebugOutput(); + if (!opt.bench) + headlessHost->FlushDebugOutput(); if (opt.compare && passed) passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose); @@ -302,6 +307,8 @@ int main(int argc, const char* argv[]) cpuCore = CPUCore::IR_JIT; else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare")) testOptions.compare = true; + else if (!strcmp(argv[i], "--bench")) + testOptions.bench = true; else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) testOptions.verbose = true; else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics=")) @@ -461,6 +468,7 @@ int main(int argc, const char* argv[]) if (screenshotFilename) headlessHost->SetComparisonScreenshot(Path(std::string(screenshotFilename)), testOptions.maxScreenshotError); + headlessHost->SetWriteFailureScreenshot(!teamCityMode && !getenv("GITHUB_ACTIONS") && !testOptions.bench); #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? @@ -493,10 +501,25 @@ int main(int argc, const char* argv[]) if (testOptions.compare) printf("%s:\n", coreParameter.fileToStart.c_str()); bool passed = RunAutoTest(headlessHost, coreParameter, testOptions); + if (testOptions.bench) { + double st = time_now_d(); + double deadline = st + testOptions.timeout; + double runs = 0.0; + for (int i = 0; i < 100; ++i) { + RunAutoTest(headlessHost, coreParameter, testOptions); + runs++; + + if (time_now_d() > deadline) + break; + } + double et = time_now_d(); + + std::string testName = GetTestName(coreParameter.fileToStart); + printf(" %s - %f seconds average\n", testName.c_str(), (et - st) / runs); + } if (testOptions.compare) { std::string testName = GetTestName(coreParameter.fileToStart); - if (passed) - { + if (passed) { passedTests.push_back(testName); printf(" %s - passed!\n", testName.c_str()); } diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index 7a886c4827..b3896df478 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -57,7 +57,7 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) { if (errors > maxScreenshotError_) SendOrCollectDebugOutput(StringFromFormat("Screenshot MSE: %f\n", errors)); - if (errors > maxScreenshotError_ && !teamCityMode && !getenv("GITHUB_ACTIONS")) { + if (errors > maxScreenshotError_ && writeFailureScreenshot_) { if (comparer.SaveActualBitmap(Path("__testfailure.bmp"))) SendOrCollectDebugOutput("Actual output written to: __testfailure.bmp\n"); comparer.SaveVisualComparisonPNG(Path("__testcompare.png")); diff --git a/headless/StubHost.h b/headless/StubHost.h index ddfc6cd29d..a77a42424d 100644 --- a/headless/StubHost.h +++ b/headless/StubHost.h @@ -72,6 +72,9 @@ public: comparisonScreenshot_ = filename; maxScreenshotError_ = maxError; } + void SetWriteFailureScreenshot(bool flag) { + writeFailureScreenshot_ = flag; + } void SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) override; @@ -88,4 +91,5 @@ protected: std::string debugOutputBuffer_; GPUCore gpuCore_; GraphicsContext *gfx_ = nullptr; + bool writeFailureScreenshot_ = true; }; From 915b734cdee0cece298cc957b55e2c43a9d67a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Sep 2022 10:10:48 +0200 Subject: [PATCH 055/326] Show GPU device ID in system information if available. --- UI/DevScreens.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 8077875010..ce5bd3fada 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -514,7 +514,15 @@ void SystemInfoScreen::CreateViews() { const std::string apiNameKey = draw->GetInfoString(InfoField::APINAME); const char *apiName = gr->T(apiNameKey); deviceSpecs->Add(new InfoItem(si->T("3D API"), apiName)); - deviceSpecs->Add(new InfoItem(si->T("Vendor"), draw->GetInfoString(InfoField::VENDORSTRING))); + + // TODO: Not really vendor, on most APIs it's a device name (GL calls it vendor though). + std::string vendorString; + if (draw->GetDeviceCaps().deviceID != 0) { + vendorString = StringFromFormat("%s (%08x)", draw->GetInfoString(InfoField::VENDORSTRING).c_str(), draw->GetDeviceCaps().deviceID); + } else { + vendorString = draw->GetInfoString(InfoField::VENDORSTRING); + } + deviceSpecs->Add(new InfoItem(si->T("Vendor"), vendorString)); std::string vendor = draw->GetInfoString(InfoField::VENDOR); if (vendor.size()) deviceSpecs->Add(new InfoItem(si->T("Vendor (detected)"), vendor)); From 4045de8d565d215e7dd1e399040bad1e51397c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Sep 2022 10:34:43 +0200 Subject: [PATCH 056/326] Consider the Adreno and Mali stencil-discard bugs the same. Use the better check from the Mali bug. Should fix #11980 --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 4 +-- Common/GPU/thin3d.cpp | 1 - Common/GPU/thin3d.h | 7 ++-- GPU/Common/ShaderId.cpp | 11 +++--- assets/compat.ini | 53 ----------------------------- 5 files changed, 9 insertions(+), 67 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index d276ba9868..d64fd7ae7d 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -833,9 +833,9 @@ VKContext::VKContext(VulkanContext *vulkan) // is detected. See: https://github.com/hrydgard/ppsspp/issues/11937 bugs_.Infest(Bugs::EQUAL_WZ_CORRUPTS_DEPTH); - // Similar to the Adreno bug, see #13833 (Midnight Club map broken) and other issues. + // Nearly identical to the the Adreno bug, see #13833 (Midnight Club map broken) and other issues. // Reported fixed in major version 40 - let's add a check once confirmed. - bugs_.Infest(Bugs::MALI_STENCIL_DISCARD_BUG); + bugs_.Infest(Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL); // This started in driver 31 or 32, fixed in 40 - let's add a check once confirmed. if (majorVersion >= 32) { diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index 2560765f43..acaa3dac0b 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -678,7 +678,6 @@ const char *Bugs::GetBugName(uint32_t bug) { case COLORWRITEMASK_BROKEN_WITH_DEPTHTEST: return "COLORWRITEMASK_BROKEN_WITH_DEPTHTEST"; case BROKEN_FLAT_IN_SHADER: return "BROKEN_FLAT_IN_SHADER"; case EQUAL_WZ_CORRUPTS_DEPTH: return "EQUAL_WZ_CORRUPTS_DEPTH"; - case MALI_STENCIL_DISCARD_BUG: return "MALI_STENCIL_DISCARD_BUG"; case RASPBERRY_SHADER_COMP_HANG: return "RASPBERRY_SHADER_COMP_HANG"; case MALI_CONSTANT_LOAD_BUG: return "MALI_CONSTANT_LOAD_BUG"; case SUBPASS_FEEDBACK_BROKEN: return "SUBPASS_FEEDBACK_BROKEN"; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 389640ec9e..b4eca9d0bc 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -329,10 +329,9 @@ public: COLORWRITEMASK_BROKEN_WITH_DEPTHTEST = 5, BROKEN_FLAT_IN_SHADER = 6, EQUAL_WZ_CORRUPTS_DEPTH = 7, - MALI_STENCIL_DISCARD_BUG = 8, - RASPBERRY_SHADER_COMP_HANG = 9, - MALI_CONSTANT_LOAD_BUG = 10, - SUBPASS_FEEDBACK_BROKEN = 11, + RASPBERRY_SHADER_COMP_HANG = 8, + MALI_CONSTANT_LOAD_BUG = 9, + SUBPASS_FEEDBACK_BROKEN = 10, MAX_BUG, }; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 7ca42ccba6..6345816604 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -349,13 +349,10 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip id.SetBit(FS_BIT_COLOR_WRITEMASK, colorWriteMask); - if (g_Config.bVendorBugChecksEnabled) { - if (bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL)) { - id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, !IsStencilTestOutputDisabled() && !gstate.isDepthWriteEnabled()); - } else if (bugs.Has(Draw::Bugs::MALI_STENCIL_DISCARD_BUG) && PSP_CoreParameter().compat.flags().MaliDepthStencilBugWorkaround) { - // Very similar driver bug to the Adreno one, with the same workaround (though might look into if there are cheaper ones!) - // Keeping the conditions separate since it can probably be made tighter. - id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, !IsStencilTestOutputDisabled() && (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled())); + if (g_Config.bVendorBugChecksEnabled && bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL)) { + bool stencilWithoutDepth = !IsStencilTestOutputDisabled() && (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled()); + if (stencilWithoutDepth) { + id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, stencilWithoutDepth); } } } diff --git a/assets/compat.ini b/assets/compat.ini index c4986ae888..f153b93933 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -584,59 +584,6 @@ NPJH50304 = true ULES00703 = true ULAS42095 = true -[MaliDepthStencilBugWorkaround] -# See issue #13833 where the map is supposed to be round but is not. - -# Midnight Club: LA Remix -ULUS10383 = true -ULES01144 = true -ULJS00180 = true -ULJS00267 = true -ULJM05904 = true -NPJH50440 = true -# Midnight Club 3 : DUB edition -ULUS10021 = true -ULES00108 = true - -# Tales of Phantasia - Narikiri Dungeon X. See #15526 -ULJS00293 = true - -# The Simpsons Game - issue #9830 -ULUS10295 = true -ULES00975 = true -ULES00979 = true -ULES00978 = true -ULES00977 = true -ULES00976 = true - -# Surf's Up - see issue #15016 -ULES00816 = true -ULES00817 = true -ULUS10262 = true - -# Kenka Bancho - Badass Rumble - see issue #15016 -ULUS10442 = true -ULJS00166 = true -UCAS40231 = true -ULJS19030 = true -NPJH50223 = true -ULJS19082 = true - -# Nanoha Magical Girl - see issue #15016 -ULJS00384 = true -ULJS00385 = true -ULJS19078 = true - -# Suikoden Woven Web of The Centuries / Genso Suikoden: Tsumugareshi Hyakunen no Toki -ULJM05886 = true -ULJM08063 = true -NPJH50535 = true - -# Outrun 2006: Coast to Coast - water -ULES00262 = true -ULUS10064 = true -ULKS46087 = true - [RequireDefaultCPUClock] # GOW : Ghost of Sparta UCUS98737 = true From 3493e722f0ca51bd1fc35b768daedc5f4bb17713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Sep 2022 10:37:42 +0200 Subject: [PATCH 057/326] Update ffmpeg submodule (no big news, just readme updates and a gcc buildfix) --- ffmpeg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffmpeg b/ffmpeg index 3ad7ddb9eb..cea6dd17be 160000 --- a/ffmpeg +++ b/ffmpeg @@ -1 +1 @@ -Subproject commit 3ad7ddb9eb2af898dce8c4b5e9a28b77b3f7ddd7 +Subproject commit cea6dd17be4ea045946dc991ef0bca91f9005345 From 3b1893600c43fc8da7540ce2d3d76068d584dc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Sep 2022 10:38:37 +0200 Subject: [PATCH 058/326] Update pspautotest submodule --- pspautotests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pspautotests b/pspautotests index 682a4303ab..d83d49800a 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 682a4303aba63a50c91ae0fa6928c9dac8ca9b92 +Subproject commit d83d49800a64bdb344211e4d1a8153ab99de8ee2 From fc84f258187d2cd5dc447f62e00e5adcc1f78ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Sep 2022 12:09:56 +0200 Subject: [PATCH 059/326] Quick GE debugger stepping fix, after the recent vulkan swapchain acquire change --- Common/GPU/Vulkan/VulkanFrameData.h | 1 + Common/GPU/Vulkan/VulkanQueueRunner.cpp | 13 +++++++------ Common/GPU/Vulkan/VulkanRenderManager.cpp | 3 +++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 21c44404b9..d7093c2f42 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -49,6 +49,7 @@ struct FrameData { bool hasInitCommands = false; bool hasPresentCommands = false; + bool hasAcquired = false; std::vector steps; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 2056463261..5d9b1b1adf 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -561,15 +561,16 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData) { switch (step.stepType) { case VKRStepType::RENDER: if (!step.render.framebuffer) { - _dbg_assert_(!frameData.hasPresentCommands); - if (!frameData.hasPresentCommands) { - frameData.hasPresentCommands = true; + // When stepping in the GE debugger, we can end up here multiple times in a "frame". + if (!frameData.hasAcquired) { + frameData.hasAcquired = true; frameData.AcquireNextImage(vulkan_); SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); - VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - vkBeginCommandBuffer(frameData.presentCmd, &begin); } + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(frameData.presentCmd, &begin); + frameData.hasPresentCommands = true; PerformRenderPass(step, frameData.presentCmd); } else { PerformRenderPass(step, frameData.mainCmd); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index d2973abb5a..1bf93caebc 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1385,6 +1385,8 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { present.pWaitSemaphores = &renderingCompleteSemaphore_; present.waitSemaphoreCount = 1; + _dbg_assert_(frameData.hasAcquired); + VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); if (res == VK_ERROR_OUT_OF_DATE_KHR) { // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above. @@ -1398,6 +1400,7 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { // Success outOfDateFrames_ = 0; } + frameData.hasAcquired = false; } else { // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE. outOfDateFrames_++; From f740fcdbe7178a7bfab6923329330d4c6c5536c3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 16:27:51 -0700 Subject: [PATCH 060/326] GPU: Minor cleanup of unnecessary virtual. --- GPU/GPUCommon.h | 2 +- GPU/GPUInterface.h | 1 - GPU/Software/SoftGpu.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 3a5674127f..26dd599164 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -103,7 +103,7 @@ public: void ExecuteOp(u32 op, u32 diff) override; void PreExecuteOp(u32 op, u32 diff) override; - bool InterpretList(DisplayList &list) override; + bool InterpretList(DisplayList &list); void ProcessDLQueue(); u32 UpdateStall(int listid, u32 newstall) override; u32 EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 832897f598..2ce7c48620 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -205,7 +205,6 @@ public: virtual void PreExecuteOp(u32 op, u32 diff) = 0; virtual void ExecuteOp(u32 op, u32 diff) = 0; - virtual bool InterpretList(DisplayList& list) = 0; // Framebuffer management virtual void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) = 0; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 18cdb0b314..9de5fe5cbd 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -650,7 +650,7 @@ void SoftGPU::MarkDirty(uint32_t addr, uint32_t stride, uint32_t height, GEBuffe } void SoftGPU::MarkDirty(uint32_t addr, uint32_t bytes, SoftGPUVRAMDirty value) { - // Don't bother tracking if frameskipping. + // Only bother tracking if frameskipping. if (g_Config.iFrameSkip == 0) return; if (!Memory::IsVRAMAddress(addr) || !Memory::IsVRAMAddress(addr + bytes - 1)) From 028a341cc8f5715f764291c8c24c118c0a7cf260 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 16:28:15 -0700 Subject: [PATCH 061/326] softgpu: Explicitly flush on sync and output. We could in theory skip flush on FinishDeferred, and allow some CPU/GPU overlap. If we did, we'd still want to flush at these times. --- GPU/Software/SoftGpu.cpp | 13 +++++++++++++ GPU/Software/SoftGpu.h | 2 ++ GPU/Software/TransformUnit.cpp | 8 ++++++++ GPU/Software/TransformUnit.h | 1 + 4 files changed, 24 insertions(+) diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 9de5fe5cbd..48c5258cd5 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -639,6 +639,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { } void SoftGPU::CopyDisplayToOutput(bool reallyDirty) { + drawEngine_->transformUnit.Flush("output"); // The display always shows 480x272. CopyToCurrentFboFromDisplayRam(FB_WIDTH, FB_HEIGHT); MarkDirty(displayFramebuf_, displayStride_, 272, displayFormat_, SoftGPUVRAMDirty::CLEAR); @@ -1138,6 +1139,18 @@ void SoftGPU::FinishDeferred() { drawEngine_->transformUnit.Flush("finish"); } +int SoftGPU::ListSync(int listid, int mode) { + // Take this as a cue that we need to finish drawing. + drawEngine_->transformUnit.Flush("listsync"); + return GPUCommon::ListSync(listid, mode); +} + +u32 SoftGPU::DrawSync(int mode) { + // Take this as a cue that we need to finish drawing. + drawEngine_->transformUnit.Flush("drawsync"); + return GPUCommon::DrawSync(mode); +} + void SoftGPU::GetStats(char *buffer, size_t bufsize) { drawEngine_->transformUnit.GetStats(buffer, bufsize); } diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 11d5dd16f3..54880bd477 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -131,6 +131,8 @@ public: void InitClear() override {} void ExecuteOp(u32 op, u32 diff) override; void FinishDeferred() override; + int ListSync(int listid, int mode) override; + u32 DrawSync(int mode) override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput(bool reallyDirty) override; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 0cb91dd280..dfc8c6a58e 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -484,6 +484,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // then resolve the indices. This lets us avoid transforming shared vertices twice. binner_->UpdateState(vreader.isThrough()); + hasDraws_ = true; static TransformState transformState; if (binner_->HasDirty(SoftDirty::LIGHT_ALL | SoftDirty::TRANSFORM_ALL)) { @@ -826,8 +827,12 @@ void TransformUnit::SendTriangle(CullType cullType, const VertexData *verts, int } void TransformUnit::Flush(const char *reason) { + if (!hasDraws_) + return; + binner_->Flush(reason); GPUDebug::NotifyDraw(); + hasDraws_ = false; } void TransformUnit::GetStats(char *buffer, size_t bufsize) { @@ -836,6 +841,9 @@ void TransformUnit::GetStats(char *buffer, size_t bufsize) { } void TransformUnit::FlushIfOverlap(const char *reason, bool modifying, uint32_t addr, uint32_t stride, uint32_t w, uint32_t h) { + if (!hasDraws_) + return; + if (binner_->HasPendingWrite(addr, stride, w, h)) Flush(reason); if (modifying && binner_->HasPendingRead(addr, stride, w, h)) diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index fd6e047350..2b35501b44 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -149,6 +149,7 @@ private: // This is the index of the next vert in data (or higher, may need modulus.) int data_index_ = 0; GEPrimitiveType prev_prim_ = GE_PRIM_POINTS; + bool hasDraws_ = false; }; class SoftwareDrawEngine : public DrawEngineCommon { From de080e259488fb5d102a47c1a49a816a2780cb50 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 17:19:34 -0700 Subject: [PATCH 062/326] softgpu: Simplify vertex range culling. The previous logic was harder to understand and easier to get wrong. Just drop them when clipping the primitive. --- GPU/Software/Clipper.cpp | 18 ++++++ GPU/Software/RasterizerRectangle.cpp | 9 ++- GPU/Software/TransformUnit.cpp | 83 ++++++---------------------- GPU/Software/TransformUnit.h | 6 +- 4 files changed, 47 insertions(+), 69 deletions(-) diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp index 166947ef14..6fcb2fe47b 100644 --- a/GPU/Software/Clipper.cpp +++ b/GPU/Software/Clipper.cpp @@ -133,6 +133,10 @@ static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) { void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner) { if (!binner.State().throughMode) { + // If any verts were outside range, throw the entire prim away. + if (v0.OutsideRange() || v1.OutsideRange()) + return; + // We may discard the entire rect based on depth values. int outsidePos = 0, outsideNeg = 0; CheckOutsideZ(v0.clippos, outsidePos, outsideNeg); @@ -176,6 +180,12 @@ void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner) } void ProcessPoint(const VertexData &v0, BinManager &binner) { + // If any verts were outside range, throw the entire prim away. + if (!binner.State().throughMode) { + if (v0.OutsideRange()) + return; + } + // Points need no clipping. Will be bounds checked in the rasterizer (which seems backwards?) binner.AddPoint(v0); } @@ -187,6 +197,10 @@ void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) return; } + // If any verts were outside range, throw the entire prim away. + if (v0.OutsideRange() || v1.OutsideRange()) + return; + int outsidePos = 0, outsideNeg = 0; CheckOutsideZ(v0.clippos, outsidePos, outsideNeg); CheckOutsideZ(v1.clippos, outsidePos, outsideNeg); @@ -222,6 +236,10 @@ void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner) { int mask = 0; if (!binner.State().throughMode) { + // If any verts were outside range, throw the entire prim away. + if (v0.OutsideRange() || v1.OutsideRange() || v2.OutsideRange()) + return; + mask |= CalcClipMask(v0.clippos); mask |= CalcClipMask(v1.clippos); mask |= CalcClipMask(v2.clippos); diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index f07f66092a..ff5b9c85d9 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -355,15 +355,18 @@ bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &b } static bool AreCoordsRectangleCompatible(const RasterizerState &state, const VertexData &data0, const VertexData &data1) { - if (!(data1.color0 == data0.color0)) + if (data1.color0 != data0.color0) return false; - if (!(data1.screenpos.z == data0.screenpos.z)) { + if (data1.screenpos.z != data0.screenpos.z) { // Sometimes, we don't actually care about z. if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS) return false; } if (!state.throughMode) { - if (!state.throughMode && !(data1.color1 == data0.color1)) + if (data1.color1 != data0.color1) + return false; + // This means it should be culled, outside range. + if (data1.OutsideRange() || data0.OutsideRange()) return false; // Do we have to think about perspective correction or slope mip level? if (state.enableTextures && data1.clippos.w != data0.clippos.w) { diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index dfc8c6a58e..28c4d468d6 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -273,7 +273,7 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { state->roundToScreen = &ClipToScreenInternal; } -VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState &state, bool &outside_range_flag) { +VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState &state) { PROFILE_THIS_SCOPE("read_vert"); VertexData vertex; @@ -362,9 +362,13 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState #else screenScaled = vertex.clippos.xyz() * state.screenScale / vertex.clippos.w + state.screenAdd; #endif + bool outside_range_flag = false; vertex.screenpos = state.roundToScreen(screenScaled, vertex.clippos, &outside_range_flag); - if (outside_range_flag) + if (outside_range_flag) { + // We use this, essentially, as the flag. + vertex.screenpos.x = 0x7FFFFFFF; return vertex; + } if (state.enableFog) { vertex.fogdepth = (viewpos.z + state.fogEnd) * state.fogSlope; @@ -495,9 +499,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G bool skipCull = !gstate.isCullEnabled() || gstate.isModeClear(); const CullType cullType = skipCull ? CullType::OFF : (gstate.getCullMode() ? CullType::CCW : CullType::CW); - bool outside_range_flag = false; - - if (vreader.isThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ + vertex_count >= 6 && ((data_index_ + vertex_count) % 6) == 0) { + if (vreader.isThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ == 0 && vertex_count >= 6 && ((vertex_count) % 6) == 0) { // Some games send rectangles as a series of regular triangles. // We look for this, but only in throughmode. VertexData buf[6]; @@ -513,14 +515,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G vreader.Goto(vtx); } - buf[buf_index++] = ReadVertex(vreader, transformState, outside_range_flag); - if (buf_index >= 3 && outside_range_flag) { - // Cull, just pretend it didn't happen. - buf_index -= 3; - outside_range_flag = false; - continue; - } - + buf[buf_index++] = ReadVertex(vreader, transformState); if (buf_index < 6) continue; @@ -565,19 +560,13 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G vreader.Goto(vtx); } - data_[data_index_++] = ReadVertex(vreader, transformState, outside_range_flag); + data_[data_index_++] = ReadVertex(vreader, transformState); if (data_index_ < vtcs_per_prim) { // Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS. continue; } - // Okay, we've got enough verts. Reset the index for next time. data_index_ = 0; - if (outside_range_flag) { - // Cull the prim if it was outside, and move to the next prim. - outside_range_flag = false; - continue; - } switch (prim_type) { case GE_PRIM_TRIANGLES: @@ -607,19 +596,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G vreader.Goto(vtx); } - data_[data_index_++] = ReadVertex(vreader, transformState, outside_range_flag); - if (outside_range_flag) { - outside_range_flag = false; - // Note: this is the post increment index. If odd, we set the first vert. - if (data_index_ & 1) { - // Skip the next one and forget this one. - vtx++; - data_index_--; - } else { - // Forget both of the last 2. - data_index_ -= 2; - } - } + data_[data_index_++] = ReadVertex(vreader, transformState); if (data_index_ == 4 && vreader.isThrough() && cullType == CullType::OFF) { if (Rasterizer::DetectRectangleThroughModeSlices(binner_->State(), data_)) { @@ -653,13 +630,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G vreader.Goto(vtx); } - data_[(data_index_++) & 1] = ReadVertex(vreader, transformState, outside_range_flag); - if (outside_range_flag) { - // Drop all primitives containing the current vertex - skip_count = 2; - outside_range_flag = false; - continue; - } + data_[(data_index_++) & 1] = ReadVertex(vreader, transformState); if (skip_count) { --skip_count; @@ -687,12 +658,12 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } else { vreader.Goto(base + vtx); } - data_[vtx] = ReadVertex(vreader, transformState, outside_range_flag); + data_[vtx] = ReadVertex(vreader, transformState); } // If a strip is effectively a rectangle, draw it as such! int tl = -1, br = -1; - if (!outside_range_flag && Rasterizer::DetectRectangleFromStrip(binner_->State(), data_, &tl, &br)) { + if (Rasterizer::DetectRectangleFromStrip(binner_->State(), data_, &tl, &br)) { Clipper::ProcessRect(data_[tl], data_[br], *binner_); start_vtx += 2; if (base + 4 >= vertex_count) { @@ -711,7 +682,6 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } } - outside_range_flag = false; for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { if (indices) { vreader.Goto(ConvertIndex(vtx) - index_lower_bound); @@ -720,13 +690,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } int provoking_index = (data_index_++) % 3; - data_[provoking_index] = ReadVertex(vreader, transformState, outside_range_flag); - if (outside_range_flag) { - // Drop all primitives containing the current vertex - skip_count = 2; - outside_range_flag = false; - continue; - } + data_[provoking_index] = ReadVertex(vreader, transformState); if (skip_count) { --skip_count; @@ -754,13 +718,9 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } else { vreader.Goto(0); } - data_[0] = ReadVertex(vreader, transformState, outside_range_flag); + data_[0] = ReadVertex(vreader, transformState); data_index_++; start_vtx = 1; - - // If the central vertex is outside range, all the points are toast. - if (outside_range_flag) - break; } if (data_index_ == 1 && vertex_count == 4 && cullType == CullType::OFF) { @@ -770,17 +730,16 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } else { vreader.Goto(vtx); } - data_[vtx] = ReadVertex(vreader, transformState, outside_range_flag); + data_[vtx] = ReadVertex(vreader, transformState); } int tl = -1, br = -1; - if (!outside_range_flag && Rasterizer::DetectRectangleFromFan(binner_->State(), data_, vertex_count, &tl, &br)) { + if (Rasterizer::DetectRectangleFromFan(binner_->State(), data_, vertex_count, &tl, &br)) { Clipper::ProcessRect(data_[tl], data_[br], *binner_); break; } } - outside_range_flag = false; for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { if (indices) { vreader.Goto(ConvertIndex(vtx) - index_lower_bound); @@ -789,13 +748,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } int provoking_index = 2 - ((data_index_++) % 2); - data_[provoking_index] = ReadVertex(vreader, transformState, outside_range_flag); - if (outside_range_flag) { - // Drop all primitives containing the current vertex - skip_count = 2; - outside_range_flag = false; - continue; - } + data_[provoking_index] = ReadVertex(vreader, transformState); if (skip_count) { --skip_count; diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index 2b35501b44..dcfe2ecd0b 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -90,6 +90,10 @@ struct VertexData { color1 = LerpInt, 256>(Vec3::FromRGB(a.color1), Vec3::FromRGB(b.color1), t_int).ToRGB(); } + bool OutsideRange() const { + return screenpos.x == 0x7FFFFFFF; + } + ClipCoords clippos; Vec2 texturecoords; uint32_t color0; @@ -138,7 +142,7 @@ public: SoftDirty GetDirty(); private: - VertexData ReadVertex(VertexReader &vreader, const TransformState &lstate, bool &outside_range_flag); + VertexData ReadVertex(VertexReader &vreader, const TransformState &state); void SendTriangle(CullType cullType, const VertexData *verts, int provoking = 2); u8 *decoded_ = nullptr; From 799a9ae95bb427db39035f5780f3ab3ca1272075 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 17:48:18 -0700 Subject: [PATCH 063/326] softgpu: Simplify vertex reading. --- GPU/Software/TransformUnit.cpp | 79 ++++++++-------------------------- 1 file changed, 19 insertions(+), 60 deletions(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 28c4d468d6..81ff27c4c1 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -499,6 +499,16 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G bool skipCull = !gstate.isCullEnabled() || gstate.isModeClear(); const CullType cullType = skipCull ? CullType::OFF : (gstate.getCullMode() ? CullType::CCW : CullType::CW); + auto readVertexAt = [&](VertexReader &vreader, const TransformState &transformState, int vtx) { + if (indices) { + vreader.Goto(ConvertIndex(vtx) - index_lower_bound); + } else { + vreader.Goto(vtx); + } + + return ReadVertex(vreader, transformState); + }; + if (vreader.isThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ == 0 && vertex_count >= 6 && ((vertex_count) % 6) == 0) { // Some games send rectangles as a series of regular triangles. // We look for this, but only in throughmode. @@ -509,13 +519,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } for (int vtx = 0; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - - buf[buf_index++] = ReadVertex(vreader, transformState); + buf[buf_index++] = readVertexAt(vreader, transformState, vtx); if (buf_index < 6) continue; @@ -554,13 +558,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G case GE_PRIM_TRIANGLES: { for (int vtx = 0; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - - data_[data_index_++] = ReadVertex(vreader, transformState); + data_[data_index_++] = readVertexAt(vreader, transformState, vtx); if (data_index_ < vtcs_per_prim) { // Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS. continue; @@ -590,13 +588,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G case GE_PRIM_RECTANGLES: for (int vtx = 0; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - - data_[data_index_++] = ReadVertex(vreader, transformState); + data_[data_index_++] = readVertexAt(vreader, transformState, vtx); if (data_index_ == 4 && vreader.isThrough() && cullType == CullType::OFF) { if (Rasterizer::DetectRectangleThroughModeSlices(binner_->State(), data_)) { @@ -624,13 +616,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // If data_index_ is 1 or 2, etc., it means we're continuing a line strip. int skip_count = data_index_ == 0 ? 1 : 0; for (int vtx = 0; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - - data_[(data_index_++) & 1] = ReadVertex(vreader, transformState); + data_[(data_index_++) & 1] = readVertexAt(vreader, transformState, vtx); if (skip_count) { --skip_count; @@ -653,12 +639,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (data_index_ == 0 && vertex_count >= 4 && (vertex_count & 1) == 0 && cullType == CullType::OFF) { for (int base = 0; base < vertex_count - 2; base += 2) { for (int vtx = base == 0 ? 0 : 2; vtx < 4; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(base + vtx) - index_lower_bound); - } else { - vreader.Goto(base + vtx); - } - data_[vtx] = ReadVertex(vreader, transformState); + data_[vtx] = readVertexAt(vreader, transformState, base + vtx); } // If a strip is effectively a rectangle, draw it as such! @@ -683,14 +664,8 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - int provoking_index = (data_index_++) % 3; - data_[provoking_index] = ReadVertex(vreader, transformState); + data_[provoking_index] = readVertexAt(vreader, transformState, vtx); if (skip_count) { --skip_count; @@ -713,24 +688,14 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // Only read the central vertex if we're not continuing. if (data_index_ == 0) { - if (indices) { - vreader.Goto(ConvertIndex(0) - index_lower_bound); - } else { - vreader.Goto(0); - } - data_[0] = ReadVertex(vreader, transformState); + data_[0] = readVertexAt(vreader, transformState, 0); data_index_++; start_vtx = 1; } if (data_index_ == 1 && vertex_count == 4 && cullType == CullType::OFF) { for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - data_[vtx] = ReadVertex(vreader, transformState); + data_[vtx] = readVertexAt(vreader, transformState, vtx); } int tl = -1, br = -1; @@ -741,14 +706,8 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - int provoking_index = 2 - ((data_index_++) % 2); - data_[provoking_index] = ReadVertex(vreader, transformState); + data_[provoking_index] = readVertexAt(vreader, transformState, vtx); if (skip_count) { --skip_count; From 35ba01e01f053ff3ac1e49d5a96dd98fc2b61c36 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 18:42:59 -0700 Subject: [PATCH 064/326] softgpu: Refactor imm draws to bypass vert read. --- GPU/Common/DrawEngineCommon.cpp | 37 ++++++++ GPU/Common/DrawEngineCommon.h | 5 +- GPU/GPUCommon.cpp | 36 +------ GPU/Software/TransformUnit.cpp | 161 ++++++++++++++++++++++---------- GPU/Software/TransformUnit.h | 3 +- 5 files changed, 152 insertions(+), 90 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 06bf6513ff..31707fa76d 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -19,6 +19,7 @@ #include "Common/Data/Convert/ColorConv.h" #include "Common/Profiler/Profiler.h" +#include "Common/LogReporting.h" #include "Core/Config.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/SplineCommon.h" @@ -188,6 +189,42 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, return DrawEngineCommon::NormalizeVertices(outPtr, bufPtr, inPtr, dec, lowerBound, upperBound, vertType); } +void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) { + // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle + // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into + // through vertices. + // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos. + struct ImmVertex { + float uv[2]; + uint32_t color; + float xyz[3]; + }; + std::vector temp; + temp.resize(vertexCount); + uint32_t color1Used = 0; + for (int i = 0; i < vertexCount; i++) { + // Since we're sending through, scale back up to w/h. + temp[i].uv[0] = buffer[i].u * gstate.getTextureWidth(0); + temp[i].uv[1] = buffer[i].v * gstate.getTextureHeight(0); + temp[i].color = buffer[i].color0_32; + temp[i].xyz[0] = buffer[i].pos[0]; + temp[i].xyz[1] = buffer[i].pos[1]; + temp[i].xyz[2] = buffer[i].pos[2]; + color1Used |= buffer[i].color1_32; + } + int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; + // TODO: Handle fog and secondary color somehow? + + if (color1Used != 0 && gstate.isUsingSecondaryColor()) { + WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color"); + } + + int bytesRead; + uint32_t vertTypeID = GetVertTypeID(vtype, 0); + SubmitPrim(&temp[0], nullptr, prim, vertexCount, vertTypeID, cullMode, &bytesRead); + DispatchFlush(); +} + // This code has plenty of potential for optimization. // // It does the simplest and safest test possible: If all points of a bbox is outside a single of diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index e6ba0b37d9..49c5e73200 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -90,10 +90,7 @@ public: SubmitPrim(verts, inds, prim, vertexCount, vertTypeID, cullMode, bytesRead); } - virtual void DispatchSubmitImm(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) { - SubmitPrim(verts, inds, prim, vertexCount, vertTypeID, cullMode, bytesRead); - DispatchFlush(); - } + virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode); bool TestBoundingBox(const void* control_points, int vertexCount, u32 vertType, int *bytesRead); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 107398b7c8..bda1f7e51a 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2416,8 +2416,7 @@ void GPUCommon::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { immFlags_ = op & 0x00FFF800; } else if (prim == GE_PRIM_KEEP_PREVIOUS && immPrim_ != GE_PRIM_INVALID) { static constexpr int flushPrimCount[] = { 1, 2, 0, 3, 0, 0, 2, 0 }; - // Instead of finding a proper point to flush, we just emit a full rectangle every time one - // is finished. + // Instead of finding a proper point to flush, we just emit prims when we can. if (immCount_ == flushPrimCount[immPrim_ & 7]) FlushImm(); } else { @@ -2439,31 +2438,6 @@ void GPUCommon::FlushImm() { } UpdateUVScaleOffset(); - // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle - // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into - // through vertices. - // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos. - struct ImmVertex { - float uv[2]; - uint32_t color; - float xyz[3]; - }; - ImmVertex temp[MAX_IMMBUFFER_SIZE]; - uint32_t color1Used = 0; - for (int i = 0; i < immCount_; i++) { - // Since we're sending through, scale back up to w/h. - temp[i].uv[0] = immBuffer_[i].u * gstate.getTextureWidth(0); - temp[i].uv[1] = immBuffer_[i].v * gstate.getTextureHeight(0); - temp[i].color = immBuffer_[i].color0_32; - temp[i].xyz[0] = immBuffer_[i].pos[0]; - temp[i].xyz[1] = immBuffer_[i].pos[1]; - temp[i].xyz[2] = immBuffer_[i].pos[2]; - color1Used |= immBuffer_[i].color1_32; - } - int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; - - // TODO: Handle fog and secondary color somehow? - bool antialias = (immFlags_ & GE_IMM_ANTIALIAS) != 0; bool prevAntialias = gstate.isAntiAliasEnabled(); bool shading = (immFlags_ & GE_IMM_SHADING) != 0; @@ -2480,8 +2454,6 @@ void GPUCommon::FlushImm() { WARN_LOG_REPORT_ONCE(geimmclipvalue, G3D, "Imm vertex used clip value, flags=%06x", immFlags_); } else if ((immFlags_ & GE_IMM_FOG) != 0) { WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog, flags=%06x", immFlags_); - } else if (color1Used != 0 && gstate.isUsingSecondaryColor()) { - WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color, flags=%06x", immFlags_); } if (texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither || prevShading != shading) { @@ -2494,11 +2466,7 @@ void GPUCommon::FlushImm() { gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); } - int bytesRead; - uint32_t vertTypeID = GetVertTypeID(vtype, 0); - drawEngineCommon_->DispatchSubmitImm(temp, nullptr, immPrim_, immCount_, vertTypeID, cullMode, &bytesRead); - // TODO: In the future, make a special path for these. - // drawEngineCommon_->DispatchSubmitImm(immBuffer_, immCount_); + drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode); immCount_ = 0; gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 81ff27c4c1..c052a472cd 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -70,12 +70,45 @@ void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, transformUnit.SubmitPrimitive(verts, inds, prim, vertexCount, vertTypeID, bytesRead, this); } -void SoftwareDrawEngine::DispatchSubmitImm(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) { +void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) { + // TODO: Handle fog and secondary color somehow? + int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; + uint32_t vertTypeID = GetVertTypeID(vtype, 0); + int flipCull = cullMode != gstate.getCullMode() ? 1 : 0; // TODO: For now, just setting all dirty. transformUnit.SetDirty(SoftDirty(-1)); gstate.cullmode ^= flipCull; - transformUnit.SubmitPrimitive(verts, inds, prim, vertexCount, vertTypeID, bytesRead, this); + + // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle + // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into + // through vertices. + // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos. + struct ImmVertex { + float uv[2]; + uint32_t color; + float xyz[3]; + }; + std::vector temp; + temp.resize(vertexCount); + uint32_t color1Used = 0; + for (int i = 0; i < vertexCount; i++) { + // Since we're sending through, scale back up to w/h. + temp[i].uv[0] = buffer[i].u * gstate.getTextureWidth(0); + temp[i].uv[1] = buffer[i].v * gstate.getTextureHeight(0); + temp[i].color = buffer[i].color0_32; + temp[i].xyz[0] = buffer[i].pos[0]; + temp[i].xyz[1] = buffer[i].pos[1]; + temp[i].xyz[2] = buffer[i].pos[2]; + color1Used |= buffer[i].color1_32; + } + + if (color1Used != 0 && gstate.isUsingSecondaryColor()) { + WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color"); + } + + transformUnit.SubmitPrimitive(&temp[0], nullptr, prim, vertexCount, vertTypeID, nullptr, this); + gstate.cullmode ^= flipCull; // TODO: Should really clear, but the vertex type is faked so things might need resetting... transformUnit.SetDirty(SoftDirty(-1)); @@ -475,15 +508,6 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G prim_type = prev_prim_; } - int vtcs_per_prim; - switch (prim_type) { - case GE_PRIM_POINTS: vtcs_per_prim = 1; break; - case GE_PRIM_LINES: vtcs_per_prim = 2; break; - case GE_PRIM_TRIANGLES: vtcs_per_prim = 3; break; - case GE_PRIM_RECTANGLES: vtcs_per_prim = 2; break; - default: vtcs_per_prim = 0; break; - } - // TODO: Do this in two passes - first process the vertices (before indexing/stripping), // then resolve the indices. This lets us avoid transforming shared vertices twice. @@ -552,39 +576,50 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G return; } + // Note: intentionally, these allow for the case of vertex_count == 0, but data_index_ > 0. + // This is used for immediate-mode primitives. switch (prim_type) { case GE_PRIM_POINTS: - case GE_PRIM_LINES: - case GE_PRIM_TRIANGLES: - { - for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[data_index_++] = readVertexAt(vreader, transformState, vtx); - if (data_index_ < vtcs_per_prim) { - // Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS. - continue; - } - // Okay, we've got enough verts. Reset the index for next time. - data_index_ = 0; - - switch (prim_type) { - case GE_PRIM_TRIANGLES: - SendTriangle(cullType, &data_[0]); - break; - - case GE_PRIM_LINES: - Clipper::ProcessLine(data_[0], data_[1], *binner_); - break; - - case GE_PRIM_POINTS: - Clipper::ProcessPoint(data_[0], *binner_); - break; - - default: - _dbg_assert_msg_(false, "Unexpected prim type: %d", prim_type); - } - } - break; + for (int i = 0; i < data_index_; ++i) + Clipper::ProcessPoint(data_[i], *binner_); + data_index_ = 0; + for (int vtx = 0; vtx < vertex_count; ++vtx) { + data_[0] = readVertexAt(vreader, transformState, vtx); + Clipper::ProcessPoint(data_[0], *binner_); } + break; + + case GE_PRIM_LINES: + for (int i = 0; i < data_index_ - 1; i += 2) + Clipper::ProcessLine(data_[i + 0], data_[i + 1], *binner_); + data_index_ &= 1; + for (int vtx = 0; vtx < vertex_count; ++vtx) { + data_[data_index_++] = readVertexAt(vreader, transformState, vtx); + if (data_index_ == 2) { + Clipper::ProcessLine(data_[0], data_[1], *binner_); + data_index_ = 0; + } + } + break; + + case GE_PRIM_TRIANGLES: + for (int vtx = 0; vtx < vertex_count; ++vtx) { + data_[data_index_++] = readVertexAt(vreader, transformState, vtx); + if (data_index_ < 3) { + // Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS. + continue; + } + // Okay, we've got enough verts. Reset the index for next time. + data_index_ = 0; + + SendTriangle(cullType, &data_[0]); + } + // In case vertex_count was 0. + if (data_index_ >= 3) { + SendTriangle(cullType, &data_[0]); + data_index_ = 0; + } + break; case GE_PRIM_RECTANGLES: for (int vtx = 0; vtx < vertex_count; ++vtx) { @@ -625,6 +660,9 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G Clipper::ProcessLine(data_[data_index_ & 1], data_[(data_index_ & 1) ^ 1], *binner_); } } + // If this is from immediate-mode drawing, we always had one new vert (already in data_.) + if (isImmDraw_ && data_index_ >= 2) + Clipper::ProcessLine(data_[data_index_ & 1], data_[(data_index_ & 1) ^ 1], *binner_); break; } @@ -647,6 +685,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (Rasterizer::DetectRectangleFromStrip(binner_->State(), data_, &tl, &br)) { Clipper::ProcessRect(data_[tl], data_[br], *binner_); start_vtx += 2; + skip_count = 0; if (base + 4 >= vertex_count) { start_vtx = vertex_count; break; @@ -663,15 +702,25 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } } + for (int vtx = start_vtx; vtx < vertex_count && skip_count > 0; ++vtx) { + int provoking_index = (data_index_++) % 3; + data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + --skip_count; + ++start_vtx; + } + for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { int provoking_index = (data_index_++) % 3; data_[provoking_index] = readVertexAt(vreader, transformState, vtx); - if (skip_count) { - --skip_count; - continue; - } + int wind = (data_index_ - 1) % 2; + CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); + SendTriangle(altCullType, &data_[0], provoking_index); + } + // If this is from immediate-mode drawing, we always had one new vert (already in data_.) + if (isImmDraw_ && data_index_ >= 3) { + int provoking_index = (data_index_ - 1) % 3; int wind = (data_index_ - 1) % 2; CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); SendTriangle(altCullType, &data_[0], provoking_index); @@ -687,7 +736,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G int start_vtx = 0; // Only read the central vertex if we're not continuing. - if (data_index_ == 0) { + if (data_index_ == 0 && vertex_count > 0) { data_[0] = readVertexAt(vreader, transformState, 0); data_index_++; start_vtx = 1; @@ -705,19 +754,29 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } } + for (int vtx = start_vtx; vtx < vertex_count && skip_count > 0; ++vtx) { + int provoking_index = 2 - ((data_index_++) % 2); + data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + --skip_count; + ++start_vtx; + } + for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { int provoking_index = 2 - ((data_index_++) % 2); data_[provoking_index] = readVertexAt(vreader, transformState, vtx); - if (skip_count) { - --skip_count; - continue; - } - int wind = (data_index_ - 1) % 2; CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); SendTriangle(altCullType, &data_[0], provoking_index); } + + // If this is from immediate-mode drawing, we always had one new vert (already in data_.) + if (isImmDraw_ && data_index_ >= 3) { + int wind = (data_index_ - 1) % 2; + int provoking_index = 2 - wind; + CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); + SendTriangle(altCullType, &data_[0], provoking_index); + } break; } diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index dcfe2ecd0b..e19d69fff7 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -154,6 +154,7 @@ private: int data_index_ = 0; GEPrimitiveType prev_prim_ = GE_PRIM_POINTS; bool hasDraws_ = false; + bool isImmDraw_ = false; }; class SoftwareDrawEngine : public DrawEngineCommon { @@ -163,7 +164,7 @@ public: void DispatchFlush() override; void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int cullMode, int *bytesRead) override; - void DispatchSubmitImm(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) override; + void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) override; VertexDecoder *FindVertexDecoder(u32 vtype); From 596b07bd2eb484d59b05ca0679be81664c95db2e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 19:42:41 -0700 Subject: [PATCH 065/326] softgpu: Support fog and color1 on imm verts. --- GPU/Common/DrawEngineCommon.cpp | 3 + GPU/GPUCommon.cpp | 8 ++- GPU/Software/BinManager.cpp | 2 +- GPU/Software/FuncId.cpp | 6 +- GPU/Software/FuncId.h | 2 +- GPU/Software/Rasterizer.cpp | 6 +- GPU/Software/Rasterizer.h | 2 +- GPU/Software/TransformUnit.cpp | 122 ++++++++++++++++++++++++-------- GPU/Software/TransformUnit.h | 1 + 9 files changed, 109 insertions(+), 43 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 31707fa76d..df66890248 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -215,6 +215,9 @@ void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; // TODO: Handle fog and secondary color somehow? + if (gstate.isFogEnabled()) { + WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog"); + } if (color1Used != 0 && gstate.isUsingSecondaryColor()) { WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color"); } diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index bda1f7e51a..e91d32f5d6 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2447,21 +2447,22 @@ void GPUCommon::FlushImm() { int cullMode = (immFlags_ & GE_IMM_CULLFACE) != 0 ? 1 : 0; bool texturing = (immFlags_ & GE_IMM_TEXTURE) != 0; bool prevTexturing = gstate.isTextureMapEnabled(); + bool fog = (immFlags_ & GE_IMM_FOG) != 0; + bool prevFog = gstate.isFogEnabled(); bool dither = (immFlags_ & GE_IMM_DITHER) != 0; bool prevDither = gstate.isDitherEnabled(); if ((immFlags_ & GE_IMM_CLIPMASK) != 0) { WARN_LOG_REPORT_ONCE(geimmclipvalue, G3D, "Imm vertex used clip value, flags=%06x", immFlags_); - } else if ((immFlags_ & GE_IMM_FOG) != 0) { - WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog, flags=%06x", immFlags_); } - if (texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither || prevShading != shading) { + if (texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither || prevShading != shading || prevFog != fog) { DispatchFlush(); gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)antialias; gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)shading; gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)cullEnable; gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)texturing; + gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)fog; gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)dither; gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); } @@ -2473,6 +2474,7 @@ void GPUCommon::FlushImm() { gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading; gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)prevCullEnable; gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing; + gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)prevFog; gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither; gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); } diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 3438cb222d..7fee77b64c 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -167,7 +167,7 @@ void BinManager::UpdateState(bool throughMode) { if (states_.Full()) Flush("states"); stateIndex_ = (uint16_t)states_.Push(RasterizerState()); - ComputeRasterizerState(&states_[stateIndex_], throughMode); + ComputeRasterizerState(&states_[stateIndex_]); states_[stateIndex_].samplerID.cached.clut = cluts_[clutIndex_].readable; ClearDirty(SoftDirty::PIXEL_ALL | SoftDirty::SAMPLER_ALL | SoftDirty::RAST_ALL); diff --git a/GPU/Software/FuncId.cpp b/GPU/Software/FuncId.cpp index 501b5aaf27..0d194d0012 100644 --- a/GPU/Software/FuncId.cpp +++ b/GPU/Software/FuncId.cpp @@ -48,11 +48,11 @@ static inline PixelBlendFactor OptimizeAlphaFactor(uint32_t color) { return PixelBlendFactor::FIX; } -void ComputePixelFuncID(PixelFuncID *id, bool throughMode) { +void ComputePixelFuncID(PixelFuncID *id) { id->fullKey = 0; // TODO: Could this be minz > 0x0000 || maxz < 0xFFFF? Maybe unsafe, depending on verts... - id->applyDepthRange = !throughMode; + id->applyDepthRange = !gstate.isModeThrough(); // Dither happens even in clear mode. id->dithering = gstate.isDitherEnabled(); id->fbFormat = gstate.FrameBufFormat(); @@ -169,7 +169,7 @@ void ComputePixelFuncID(PixelFuncID *id, bool throughMode) { } id->applyLogicOp = gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY; - id->applyFog = gstate.isFogEnabled() && !throughMode; + id->applyFog = gstate.isFogEnabled() && !gstate.isModeThrough(); id->earlyZChecks = id->DepthTestFunc() != GE_COMP_ALWAYS; if (id->stencilTest && id->earlyZChecks) { diff --git a/GPU/Software/FuncId.h b/GPU/Software/FuncId.h index 46307fc55c..d9d8e51573 100644 --- a/GPU/Software/FuncId.h +++ b/GPU/Software/FuncId.h @@ -244,7 +244,7 @@ struct hash { }; -void ComputePixelFuncID(PixelFuncID *id, bool throughMode); +void ComputePixelFuncID(PixelFuncID *id); std::string DescribePixelFuncID(const PixelFuncID &id); void ComputeSamplerID(SamplerID *id); diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 1f4039d05a..fa7e0af3f6 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -93,8 +93,8 @@ static inline Vec4 Interpolate(const float &c0, const float &c1, const fl return Interpolate(c0, c1, c2, w0.Cast(), w1.Cast(), w2.Cast(), wsum_recip); } -void ComputeRasterizerState(RasterizerState *state, bool throughMode) { - ComputePixelFuncID(&state->pixelID, throughMode); +void ComputeRasterizerState(RasterizerState *state) { + ComputePixelFuncID(&state->pixelID); state->drawPixel = Rasterizer::GetSingleFunc(state->pixelID); state->enableTextures = gstate.isTextureMapEnabled() && !state->pixelID.clearMode; @@ -132,7 +132,7 @@ void ComputeRasterizerState(RasterizerState *state, bool throughMode) { } state->shadeGouraud = gstate.getShadeMode() == GE_SHADE_GOURAUD; - state->throughMode = throughMode; + state->throughMode = gstate.isModeThrough(); state->antialiasLines = gstate.isAntiAliasEnabled(); #if defined(SOFTGPU_MEMORY_TAGGING_DETAILED) || defined(SOFTGPU_MEMORY_TAGGING_BASIC) diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index 089f99a657..93e89f9596 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -65,7 +65,7 @@ struct RasterizerState { } }; -void ComputeRasterizerState(RasterizerState *state, bool throughMode); +void ComputeRasterizerState(RasterizerState *state); // Draws a triangle if its vertices are specified in counter-clockwise order void DrawTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const BinCoords &range, const RasterizerState &state); diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index c052a472cd..1278d93483 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -71,46 +71,64 @@ void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, } void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) { - // TODO: Handle fog and secondary color somehow? - int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; - uint32_t vertTypeID = GetVertTypeID(vtype, 0); + uint32_t vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode()); int flipCull = cullMode != gstate.getCullMode() ? 1 : 0; // TODO: For now, just setting all dirty. transformUnit.SetDirty(SoftDirty(-1)); gstate.cullmode ^= flipCull; - // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle - // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into - // through vertices. - // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos. - struct ImmVertex { - float uv[2]; - uint32_t color; - float xyz[3]; - }; - std::vector temp; - temp.resize(vertexCount); - uint32_t color1Used = 0; + // TODO: This is a bit ugly. Should bypass when clipping... + uint32_t xScale = gstate.viewportxscale; + uint32_t xCenter = gstate.viewportxcenter; + uint32_t yScale = gstate.viewportyscale; + uint32_t yCenter = gstate.viewportycenter; + uint32_t zScale = gstate.viewportzscale; + uint32_t zCenter = gstate.viewportzcenter; + + // Force scale to 1 and center to zero. + gstate.viewportxscale = (GE_CMD_VIEWPORTXSCALE << 24) | 0x3F8000; + gstate.viewportxcenter = (GE_CMD_VIEWPORTXCENTER << 24) | 0x000000; + gstate.viewportyscale = (GE_CMD_VIEWPORTYSCALE << 24) | 0x3F8000; + gstate.viewportycenter = (GE_CMD_VIEWPORTYCENTER << 24) | 0x000000; + // Z we scale to 65535 for neg z clipping. + gstate.viewportzscale = (GE_CMD_VIEWPORTZSCALE << 24) | 0x477FFF; + gstate.viewportzcenter = (GE_CMD_VIEWPORTZCENTER << 24) | 0x000000; + + // Before we start, submit 0 prims to reset the prev prim type. + // Following submits will always be KEEP_PREVIOUS. + transformUnit.SubmitPrimitive(nullptr, nullptr, prim, 0, vertTypeID, nullptr, this); + for (int i = 0; i < vertexCount; i++) { - // Since we're sending through, scale back up to w/h. - temp[i].uv[0] = buffer[i].u * gstate.getTextureWidth(0); - temp[i].uv[1] = buffer[i].v * gstate.getTextureHeight(0); - temp[i].color = buffer[i].color0_32; - temp[i].xyz[0] = buffer[i].pos[0]; - temp[i].xyz[1] = buffer[i].pos[1]; - temp[i].xyz[2] = buffer[i].pos[2]; - color1Used |= buffer[i].color1_32; + VertexData vert; + vert.clippos = ClipCoords(buffer[i].pos); + vert.texturecoords.x = buffer[i].u; + vert.texturecoords.y = buffer[i].v; + if (gstate.isModeThrough()) { + vert.texturecoords.x *= gstate.getTextureWidth(0); + vert.texturecoords.y *= gstate.getTextureHeight(0); + } else { + vert.clippos.z *= 1.0f / 65535.0f; + } + vert.color0 = buffer[i].color0_32; + vert.color1 = gstate.isUsingSecondaryColor() ? buffer[i].color1_32 : 0; + vert.fogdepth = buffer[i].fog; + vert.screenpos.x = (int)(buffer[i].x * 16.0f); + vert.screenpos.y = (int)(buffer[i].y * 16.0f); + vert.screenpos.z = (u16)(u32)buffer[i].z; + + transformUnit.SubmitImmVertex(vert, this); } - if (color1Used != 0 && gstate.isUsingSecondaryColor()) { - WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color"); - } - - transformUnit.SubmitPrimitive(&temp[0], nullptr, prim, vertexCount, vertTypeID, nullptr, this); + gstate.viewportxscale = xScale; + gstate.viewportxcenter = xCenter; + gstate.viewportyscale = yScale; + gstate.viewportycenter = yCenter; + gstate.viewportzscale = zScale; + gstate.viewportzcenter = zCenter; gstate.cullmode ^= flipCull; - // TODO: Should really clear, but the vertex type is faked so things might need resetting... + // TODO: Should really clear, but a bunch of values are forced so we this is safest. transformUnit.SetDirty(SoftDirty(-1)); } @@ -492,12 +510,13 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G return; u16 index_lower_bound = 0; - u16 index_upper_bound = vertex_count - 1; + u16 index_upper_bound = vertex_count == 0 ? 0 : vertex_count - 1; IndexConverter ConvertIndex(vertex_type, indices); if (indices) GetIndexBounds(indices, vertex_count, vertex_type, &index_lower_bound, &index_upper_bound); - vdecoder.DecodeVerts(decoded_, vertices, index_lower_bound, index_upper_bound); + if (vertex_count != 0) + vdecoder.DecodeVerts(decoded_, vertices, index_lower_bound, index_upper_bound); VertexReader vreader(decoded_, vtxfmt, vertex_type); @@ -786,6 +805,47 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } } +void TransformUnit::SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine *drawEngine) { + // Where we put it is different for STRIP/FAN types. + switch (prev_prim_) { + case GE_PRIM_POINTS: + case GE_PRIM_LINES: + case GE_PRIM_TRIANGLES: + case GE_PRIM_RECTANGLES: + // This is the easy one. SubmitPrimitive resets data_index_. + data_[data_index_++] = vert; + break; + + case GE_PRIM_LINE_STRIP: + // This one alternates, and data_index_ > 0 means it draws a segment. + data_[(data_index_++) & 1] = vert; + break; + + case GE_PRIM_TRIANGLE_STRIP: + data_[(data_index_++) % 3] = vert; + break; + + case GE_PRIM_TRIANGLE_FAN: + if (data_index_ == 0) { + data_[data_index_++] = vert; + } else { + int provoking_index = 2 - ((data_index_++) % 2); + data_[provoking_index] = vert; + } + break; + + default: + _assert_msg_(false, "Invalid prim type: %d", (int)prev_prim_); + break; + } + + uint32_t vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode()); + // This now processes the step with shared logic, given the existing data_. + isImmDraw_ = true; + SubmitPrimitive(nullptr, nullptr, GE_PRIM_KEEP_PREVIOUS, 0, vertTypeID, nullptr, drawEngine); + isImmDraw_ = false; +} + void TransformUnit::SendTriangle(CullType cullType, const VertexData *verts, int provoking) { if (cullType == CullType::OFF) { Clipper::ProcessTriangle(verts[0], verts[1], verts[2], verts[provoking], *binner_); diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index e19d69fff7..bcaf5859c7 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -129,6 +129,7 @@ public: static ScreenCoords DrawingToScreen(const DrawingCoords &coords, u16 z); void SubmitPrimitive(const void* vertices, const void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine); + void SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine *drawEngine); bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices); From 6877ff1af25c754c70ea55e6f25d3ef9921fb317 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 20:15:40 -0700 Subject: [PATCH 066/326] softgpu: Fix state/continuation for imm prims. --- GPU/Common/DrawEngineCommon.cpp | 2 +- GPU/Common/DrawEngineCommon.h | 2 +- GPU/GPUCommon.cpp | 4 +++- GPU/GPUCommon.h | 1 + GPU/Software/SoftGpu.cpp | 8 +++++++- GPU/Software/SoftGpu.h | 2 ++ GPU/Software/TransformUnit.cpp | 5 +++-- GPU/Software/TransformUnit.h | 2 +- 8 files changed, 19 insertions(+), 7 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index df66890248..9f405b8ccc 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -189,7 +189,7 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, return DrawEngineCommon::NormalizeVertices(outPtr, bufPtr, inPtr, dec, lowerBound, upperBound, vertType); } -void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) { +void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) { // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into // through vertices. diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 49c5e73200..40c397649b 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -90,7 +90,7 @@ public: SubmitPrim(verts, inds, prim, vertexCount, vertTypeID, cullMode, bytesRead); } - virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode); + virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation); bool TestBoundingBox(const void* control_points, int vertexCount, u32 vertType, int *bytesRead); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index e91d32f5d6..0654c5ad89 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2414,6 +2414,7 @@ void GPUCommon::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { immPrim_ = (GEPrimitiveType)prim; // Flags seem to only be respected from the first prim. immFlags_ = op & 0x00FFF800; + immFirstSent_ = false; } else if (prim == GE_PRIM_KEEP_PREVIOUS && immPrim_ != GE_PRIM_INVALID) { static constexpr int flushPrimCount[] = { 1, 2, 0, 3, 0, 0, 2, 0 }; // Instead of finding a proper point to flush, we just emit prims when we can. @@ -2467,8 +2468,9 @@ void GPUCommon::FlushImm() { gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); } - drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode); + drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode, immFirstSent_); immCount_ = 0; + immFirstSent_ = true; gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias; gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 26dd599164..cddb1ccdc1 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -355,6 +355,7 @@ protected: int immCount_ = 0; GEPrimitiveType immPrim_ = GE_PRIM_INVALID; uint32_t immFlags_ = 0; + bool immFirstSent_ = false; std::string reportingPrimaryInfo_; std::string reportingFullInfo_; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 48c5258cd5..01c0a46023 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -361,7 +361,7 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = { { GE_CMD_VTCT }, { GE_CMD_VTCQ }, { GE_CMD_VCV }, - { GE_CMD_VAP, FLAG_EXECUTE, SoftDirty::NONE, &GPUCommon::Execute_ImmVertexAlphaPrim }, + { GE_CMD_VAP, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_ImmVertexAlphaPrim }, { GE_CMD_VFC }, { GE_CMD_VSCV }, @@ -1110,6 +1110,12 @@ void SoftGPU::Execute_BoneMtxData(u32 op, u32 diff) { gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; } +void SoftGPU::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { + GPUCommon::Execute_ImmVertexAlphaPrim(op, diff); + // We won't flush as often as hardware renderers, so we want to flush right away. + FlushImm(); +} + void SoftGPU::Execute_Call(u32 op, u32 diff) { PROFILE_THIS_SCOPE("gpu_call"); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 54880bd477..3ce69dcc18 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -187,6 +187,8 @@ public: void Execute_TgenMtxData(u32 op, u32 diff); void Execute_BoneMtxData(u32 op, u32 diff); + void Execute_ImmVertexAlphaPrim(u32 op, u32 diff); + typedef void (SoftGPU::*CmdFunc)(u32 op, u32 diff); protected: diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 1278d93483..a57769f5dd 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -70,7 +70,7 @@ void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, transformUnit.SubmitPrimitive(verts, inds, prim, vertexCount, vertTypeID, bytesRead, this); } -void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) { +void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) { uint32_t vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode()); int flipCull = cullMode != gstate.getCullMode() ? 1 : 0; @@ -97,7 +97,8 @@ void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVert // Before we start, submit 0 prims to reset the prev prim type. // Following submits will always be KEEP_PREVIOUS. - transformUnit.SubmitPrimitive(nullptr, nullptr, prim, 0, vertTypeID, nullptr, this); + if (!continuation) + transformUnit.SubmitPrimitive(nullptr, nullptr, prim, 0, vertTypeID, nullptr, this); for (int i = 0; i < vertexCount; i++) { VertexData vert; diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index bcaf5859c7..465f4bd8ab 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -165,7 +165,7 @@ public: void DispatchFlush() override; void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, int cullMode, int *bytesRead) override; - void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode) override; + void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) override; VertexDecoder *FindVertexDecoder(u32 vtype); From 97002692c44534bc763332e32274ef80612c3e2d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 20:51:39 -0700 Subject: [PATCH 067/326] softgpu: Correct secondary color on imm verts. Only with through set, and we have to ignore position. --- GPU/Software/TransformUnit.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index a57769f5dd..4822a5a215 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -71,7 +71,7 @@ void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, } void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) { - uint32_t vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode()); + uint32_t vertTypeID = GetVertTypeID(gstate.vertType | GE_VTYPE_POS_FLOAT, gstate.getUVGenMode()); int flipCull = cullMode != gstate.getCullMode() ? 1 : 0; // TODO: For now, just setting all dirty. @@ -112,7 +112,7 @@ void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVert vert.clippos.z *= 1.0f / 65535.0f; } vert.color0 = buffer[i].color0_32; - vert.color1 = gstate.isUsingSecondaryColor() ? buffer[i].color1_32 : 0; + vert.color1 = gstate.isUsingSecondaryColor() && !gstate.isModeThrough() ? buffer[i].color1_32 : 0; vert.fogdepth = buffer[i].fog; vert.screenpos.x = (int)(buffer[i].x * 16.0f); vert.screenpos.y = (int)(buffer[i].y * 16.0f); @@ -840,7 +840,7 @@ void TransformUnit::SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine * break; } - uint32_t vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode()); + uint32_t vertTypeID = GetVertTypeID(gstate.vertType | GE_VTYPE_POS_FLOAT, gstate.getUVGenMode()); // This now processes the step with shared logic, given the existing data_. isImmDraw_ = true; SubmitPrimitive(nullptr, nullptr, GE_PRIM_KEEP_PREVIOUS, 0, vertTypeID, nullptr, drawEngine); From 9f84cde062e83471ddc8cb68b897b98897bacd63 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 17 Sep 2022 21:08:26 -0700 Subject: [PATCH 068/326] GPU: Fix crash on imm vert triangles. Was crashing because the frag and vert shaders didn't match up. --- GPU/Common/DrawEngineCommon.cpp | 16 ++++++++++++++-- GPU/GPUCommon.cpp | 25 +++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 9f405b8ccc..5f4256158c 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -215,17 +215,29 @@ void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH; // TODO: Handle fog and secondary color somehow? - if (gstate.isFogEnabled()) { + if (gstate.isFogEnabled() && !gstate.isModeThrough()) { WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog"); } - if (color1Used != 0 && gstate.isUsingSecondaryColor()) { + if (color1Used != 0 && gstate.isUsingSecondaryColor() && !gstate.isModeThrough()) { WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color"); } + bool prevThrough = gstate.isModeThrough(); + // Code checks this reg directly, not just the vtype ID. + if (!prevThrough) { + gstate.vertType |= GE_VTYPE_THROUGH; + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE); + } + int bytesRead; uint32_t vertTypeID = GetVertTypeID(vtype, 0); SubmitPrim(&temp[0], nullptr, prim, vertexCount, vertTypeID, cullMode, &bytesRead); DispatchFlush(); + + if (!prevThrough) { + gstate.vertType &= ~GE_VTYPE_THROUGH; + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE); + } } // This code has plenty of potential for optimization. diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 0654c5ad89..6d73d1d532 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -89,7 +89,7 @@ const CommonCommandTableEntry commonCommandTable[] = { // 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 }, { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE}, + { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, { 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. @@ -2457,7 +2457,9 @@ void GPUCommon::FlushImm() { WARN_LOG_REPORT_ONCE(geimmclipvalue, G3D, "Imm vertex used clip value, flags=%06x", immFlags_); } - if (texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither || prevShading != shading || prevFog != fog) { + bool changed = texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither; + changed = changed || prevShading != shading || prevFog != fog; + if (changed) { DispatchFlush(); gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)antialias; gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)shading; @@ -2465,20 +2467,23 @@ void GPUCommon::FlushImm() { gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)texturing; gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)fog; gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)dither; - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE); } drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode, immFirstSent_); immCount_ = 0; immFirstSent_ = true; - gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias; - gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading; - gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)prevCullEnable; - gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing; - gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)prevFog; - gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither; - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); + if (changed) { + DispatchFlush(); + gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias; + gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading; + gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)prevCullEnable; + gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing; + gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)prevFog; + gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither; + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE); + } } void GPUCommon::ExecuteOp(u32 op, u32 diff) { From c65880fa90f08f051b8a1f40d06ef9b76481cd06 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 06:40:15 -0700 Subject: [PATCH 069/326] headless: Add some new and passing tests. --- headless/Headless.cpp | 2 +- test.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 96a6820087..1deebd95cd 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -126,7 +126,7 @@ int printUsage(const char *progname, const char *reason) fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n"); fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n"); fprintf(stderr, " -l, --log full log output, not just emulated printfs\n"); - fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n"); + fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n"); fprintf(stderr, " --graphics=BACKEND use a different gpu backend\n"); fprintf(stderr, " options: gles, software, directx9, etc.\n"); diff --git a/test.py b/test.py index 72c6302eb2..a805fef82d 100755 --- a/test.py +++ b/test.py @@ -147,6 +147,7 @@ tests_good = [ "gpu/commands/blend", "gpu/commands/blend565", "gpu/commands/blocktransfer", + "gpu/commands/fog", "gpu/commands/material", "gpu/displaylist/alignment", "gpu/dither/dither", @@ -157,7 +158,10 @@ tests_good = [ "gpu/ge/enqueueparam", "gpu/ge/queue", "gpu/primitives/indices", + "gpu/primitives/trianglefan", + "gpu/primitives/trianglestrip", "gpu/primitives/triangles", + "gpu/rendertarget/copy", "gpu/rendertarget/depal", "gpu/signals/pause", "gpu/signals/pause2", @@ -269,6 +273,7 @@ tests_good = [ "threads/mutex/refer", "threads/mutex/try", "threads/mutex/unlock", + "threads/mutex/unlock2", "threads/semaphores/semaphores", "threads/semaphores/cancel", "threads/semaphores/create", @@ -394,21 +399,20 @@ tests_next = [ "gpu/ge/get", "gpu/primitives/bezier", "gpu/primitives/continue", + "gpu/primitives/immediate", "gpu/primitives/invalidprim", "gpu/primitives/lines", "gpu/primitives/linestrip", "gpu/primitives/points", "gpu/primitives/rectangles", "gpu/primitives/spline", - "gpu/primitives/trianglefan", - "gpu/primitives/trianglestrip", "gpu/reflection/reflection", - "gpu/rendertarget/copy", "gpu/rendertarget/rendertarget", "gpu/signals/continue", "gpu/signals/jumps", "gpu/signals/simple", "gpu/simple/simple", + "gpu/textures/size", "gpu/triangle/triangle", "gpu/vertices/colors", "gpu/vertices/texcoords", From ca248e1201df93d13ec11f1902caff2ed31f7fcd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 07:15:30 -0700 Subject: [PATCH 070/326] softgpu: Fix s8 primitives in throughmode. Also always cull no-position verts, hardware too. Matches tests. --- GPU/Common/VertexDecoderArm.cpp | 14 +++---------- GPU/Common/VertexDecoderArm64.cpp | 12 ++++------- GPU/Common/VertexDecoderCommon.cpp | 33 +++++++++++++++++------------- GPU/Common/VertexDecoderCommon.h | 1 + GPU/Common/VertexDecoderX86.cpp | 7 +------ GPU/Software/TransformUnit.cpp | 4 +--- test.py | 2 +- 7 files changed, 30 insertions(+), 43 deletions(-) diff --git a/GPU/Common/VertexDecoderArm.cpp b/GPU/Common/VertexDecoderArm.cpp index e3d5b11f6b..2b93b563f8 100644 --- a/GPU/Common/VertexDecoderArm.cpp +++ b/GPU/Common/VertexDecoderArm.cpp @@ -872,22 +872,14 @@ void VertexDecoderJitCache::Jit_NormalFloat() { STMIA(scratchReg, false, 3, tempReg1, tempReg2, tempReg3); } -// Through expands into floats, always. Might want to look at changing this. void VertexDecoderJitCache::Jit_PosS8Through() { - DEBUG_LOG_REPORT_ONCE(vertexS8Through, G3D, "Using S8 positions in throughmode"); _dbg_assert_msg_(fpScratchReg + 1 == fpScratchReg2, "VertexDecoder fpScratchRegs must be in order."); _dbg_assert_msg_(fpScratchReg2 + 1 == fpScratchReg3, "VertexDecoder fpScratchRegs must be in order."); - // TODO: SIMD - LDRSB(tempReg1, srcReg, dec_->posoff); - LDRSB(tempReg2, srcReg, dec_->posoff + 1); - LDRB(tempReg3, srcReg, dec_->posoff + 2); - static const ARMReg tr[3] = { tempReg1, tempReg2, tempReg3 }; - static const ARMReg fr[3] = { fpScratchReg, fpScratchReg2, fpScratchReg3 }; + // 8-bit positions in throughmode always decode to 0, depth included. + VEOR(neonScratchReg, neonScratchReg, neonScratchReg); + VEOR(neonScratchReg2, neonScratchReg, neonScratchReg); ADD(scratchReg, dstReg, dec_->decFmt.posoff); - VMOV(neonScratchReg, tempReg1, tempReg2); - VMOV(neonScratchReg2, tempReg3, tempReg3); - VCVT(F_32 | I_SIGNED, neonScratchRegQ, neonScratchRegQ); VST1(F_32, neonScratchReg, scratchReg, 2, ALIGN_NONE); } diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp index 5ff1d605f3..0ad04dbe5c 100644 --- a/GPU/Common/VertexDecoderArm64.cpp +++ b/GPU/Common/VertexDecoderArm64.cpp @@ -668,15 +668,11 @@ void VertexDecoderJitCache::Jit_PosFloat() { } void VertexDecoderJitCache::Jit_PosS8Through() { - LDRSB(INDEX_UNSIGNED, tempReg1, srcReg, dec_->posoff); - LDRSB(INDEX_UNSIGNED, tempReg2, srcReg, dec_->posoff + 1); - LDRB(INDEX_UNSIGNED, tempReg3, srcReg, dec_->posoff + 2); - fp.SCVTF(fpScratchReg, tempReg1); - fp.SCVTF(fpScratchReg2, tempReg2); - fp.SCVTF(fpScratchReg3, tempReg3); + // 8-bit positions in throughmode always decode to 0, depth included. + fp.EOR(fpScratchReg, fpScratchReg, fpScratchReg); STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff); - STR(INDEX_UNSIGNED, fpScratchReg2, dstReg, dec_->decFmt.posoff + 4); - STR(INDEX_UNSIGNED, fpScratchReg3, dstReg, dec_->decFmt.posoff + 8); + STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff + 4); + STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff + 8); } void VertexDecoderJitCache::Jit_PosS16Through() { diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index a9b4a1039a..62eb20ef84 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -773,14 +773,20 @@ void VertexDecoder::Step_PosFloatSkin() const Vec3ByMatrix43(pos, fn, skinMatrix); } -void VertexDecoder::Step_PosS8Through() const -{ +void VertexDecoder::Step_PosInvalid() const { + // Invalid positions are just culled. Simulate by forcing invalid values. float *v = (float *)(decoded_ + decFmt.posoff); - const s8 *sv = (const s8 *)(ptr_ + posoff); - const u8 *uv = (const u8 *)(ptr_ + posoff); - v[0] = sv[0]; - v[1] = sv[1]; - v[2] = uv[2]; + v[0] = std::numeric_limits::infinity(); + v[1] = std::numeric_limits::infinity(); + v[2] = std::numeric_limits::infinity(); +} + +void VertexDecoder::Step_PosS8Through() const { + // 8-bit positions in throughmode always decode to 0, depth included. + float *v = (float *)(decoded_ + decFmt.posoff); + v[0] = 0; + v[1] = 0; + v[2] = 0; } void VertexDecoder::Step_PosS16Through() const @@ -1023,35 +1029,35 @@ static const StepFunction nrmstep_morphskin[4] = { }; static const StepFunction posstep[4] = { - &VertexDecoder::Step_PosS8, + &VertexDecoder::Step_PosInvalid, &VertexDecoder::Step_PosS8, &VertexDecoder::Step_PosS16, &VertexDecoder::Step_PosFloat, }; static const StepFunction posstep_skin[4] = { - &VertexDecoder::Step_PosS8Skin, + &VertexDecoder::Step_PosInvalid, &VertexDecoder::Step_PosS8Skin, &VertexDecoder::Step_PosS16Skin, &VertexDecoder::Step_PosFloatSkin, }; static const StepFunction posstep_morph[4] = { - &VertexDecoder::Step_PosS8Morph, + &VertexDecoder::Step_PosInvalid, &VertexDecoder::Step_PosS8Morph, &VertexDecoder::Step_PosS16Morph, &VertexDecoder::Step_PosFloatMorph, }; static const StepFunction posstep_morph_skin[4] = { - &VertexDecoder::Step_PosS8MorphSkin, + &VertexDecoder::Step_PosInvalid, &VertexDecoder::Step_PosS8MorphSkin, &VertexDecoder::Step_PosS16MorphSkin, &VertexDecoder::Step_PosFloatMorphSkin, }; static const StepFunction posstep_through[4] = { - &VertexDecoder::Step_PosS8Through, + &VertexDecoder::Step_PosInvalid, &VertexDecoder::Step_PosS8Through, &VertexDecoder::Step_PosS16Through, &VertexDecoder::Step_PosFloatThrough, @@ -1224,9 +1230,8 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options, bool reportNoPos = false; if (!pos) { reportNoPos = true; - pos = 1; } - if (pos) { // there's always a position + if (pos >= 0) { // there's always a position size = align(size, posalign[pos]); posoff = size; size += possize[pos]; diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index 983f76f9b2..6a06093902 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -433,6 +433,7 @@ public: void Step_PosS16MorphSkin() const; void Step_PosFloatMorphSkin() const; + void Step_PosInvalid() const; void Step_PosS8Through() const; void Step_PosS16Through() const; void Step_PosFloatThrough() const; diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index d684078989..13aabe2df3 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -1345,14 +1345,9 @@ void VertexDecoderJitCache::Jit_NormalFloatSkin() { // Through expands into floats, always. Might want to look at changing this. void VertexDecoderJitCache::Jit_PosS8Through() { - DEBUG_LOG_REPORT_ONCE(vertexS8Through, G3D, "Using S8 positions in throughmode"); // SIMD doesn't really matter since this isn't useful on hardware. + XORPS(fpScratchReg, R(fpScratchReg)); for (int i = 0; i < 3; i++) { - if (i == 2) - MOVZX(32, 8, tempReg1, MDisp(srcReg, dec_->posoff + i)); - else - MOVSX(32, 8, tempReg1, MDisp(srcReg, dec_->posoff + i)); - CVTSI2SS(fpScratchReg, R(tempReg1)); MOVSS(MDisp(dstReg, dec_->decFmt.posoff + i * 4), fpScratchReg); } } diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 4822a5a215..c1471f7172 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -503,10 +503,8 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) { return; } - // Throughmode never draws 8-bit primitives, maybe because they can't fully specify the screen? - if ((vertex_type & GE_VTYPE_THROUGH_MASK) != 0 && (vertex_type & GE_VTYPE_POS_MASK) == GE_VTYPE_POS_8BIT) - return; // Vertices without position are just entirely culled. + // Note: Throughmode does draw 8-bit primitives, but positions are always zero - handled in decode. if ((vertex_type & GE_VTYPE_POS_MASK) == 0) return; diff --git a/test.py b/test.py index a805fef82d..4597804a51 100755 --- a/test.py +++ b/test.py @@ -158,6 +158,7 @@ tests_good = [ "gpu/ge/enqueueparam", "gpu/ge/queue", "gpu/primitives/indices", + "gpu/primitives/invalidprim", "gpu/primitives/trianglefan", "gpu/primitives/trianglestrip", "gpu/primitives/triangles", @@ -400,7 +401,6 @@ tests_next = [ "gpu/primitives/bezier", "gpu/primitives/continue", "gpu/primitives/immediate", - "gpu/primitives/invalidprim", "gpu/primitives/lines", "gpu/primitives/linestrip", "gpu/primitives/points", From 5b5529b390670b09030205a5b6a532ef7f320bb0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 09:52:15 -0700 Subject: [PATCH 071/326] GE Debugger: Show time spent stepping. --- GPU/Debugger/Debugger.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index fb88fdca2f..b505521631 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -18,6 +18,7 @@ #include #include "Common/Log.h" #include "Common/StringUtils.h" +#include "Common/TimeUtil.h" #include "GPU/GPU.h" #include "GPU/Debugger/Breakpoints.h" #include "GPU/Debugger/Debugger.h" @@ -35,6 +36,8 @@ static int primsLastFrame = 0; static int primsThisFrame = 0; static int thisFlipNum = 0; +static double lastStepTime = -1.0; + static std::vector> restrictPrimRanges; static std::string restrictPrimRule; @@ -56,6 +59,7 @@ void SetActive(bool flag) { breakNext = BreakNext::NONE; breakAtCount = -1; GPUStepping::ResumeFromStepping(); + lastStepTime = -1.0; } } @@ -79,6 +83,7 @@ void SetBreakNext(BreakNext next) { GPUBreakpoints::AddCmdBreakpoint(GE_CMD_SPLINE, true); } GPUStepping::ResumeFromStepping(); + lastStepTime = next == BreakNext::NONE ? -1.0 : time_now_d(); } void SetBreakCount(int c, bool relative) { @@ -130,7 +135,12 @@ bool NotifyCommand(u32 pc) { GPUBreakpoints::ClearTempBreakpoints(); auto info = gpuDebug->DissassembleOp(pc); - NOTICE_LOG(G3D, "Waiting at %08x, %s", pc, info.desc.c_str()); + if (lastStepTime >= 0.0) { + NOTICE_LOG(G3D, "Waiting at %08x, %s (%fms)", pc, info.desc.c_str(), (time_now_d() - lastStepTime) * 1000.0); + lastStepTime = -1.0; + } else { + NOTICE_LOG(G3D, "Waiting at %08x, %s", pc, info.desc.c_str()); + } GPUStepping::EnterStepping(); } @@ -141,7 +151,12 @@ void NotifyDraw() { if (!active) return; if (breakNext == BreakNext::DRAW && !GPUStepping::IsStepping()) { - NOTICE_LOG(G3D, "Waiting at a draw"); + if (lastStepTime >= 0.0) { + NOTICE_LOG(G3D, "Waiting at a draw (%fms)", (time_now_d() - lastStepTime) * 1000.0); + lastStepTime = -1.0; + } else { + NOTICE_LOG(G3D, "Waiting at a draw"); + } GPUStepping::EnterStepping(); } } From a0b44c52b80e600c8900b20f3cdaa80cfccf35e7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 09:52:38 -0700 Subject: [PATCH 072/326] GE Debugger: Allow search by state name. Since adding breakpoint as the first column, the default search behavior stopped working. This restores it, searching for any matching column. --- Windows/W32Util/Misc.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ Windows/W32Util/Misc.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index 657a0193d2..1849070afc 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -288,9 +288,49 @@ int GenericListControl::HandleNotify(LPARAM lParam) { return 0; } + if (mhdr->code == LVN_INCREMENTALSEARCH) { + NMLVFINDITEM *request = (NMLVFINDITEM *)lParam; + uint32_t supported = LVFI_WRAP | LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING; + if ((request->lvfi.flags & ~supported) == 0 && (request->lvfi.flags & LVFI_STRING) != 0) { + bool wrap = (request->lvfi.flags & LVFI_WRAP) != 0; + bool partial = (request->lvfi.flags & (LVFI_PARTIAL | LVFI_SUBSTRING)) != 0; + + // It seems like 0 is always sent for start, let's override. + int startRow = request->iStart; + if (startRow == 0) + startRow = GetSelectedIndex(); + int result = OnIncrementalSearch(startRow, request->lvfi.psz, wrap, partial); + if (result != -1) { + request->lvfi.flags = LVFI_PARAM; + request->lvfi.lParam = (LPARAM)result; + } + } + } + return 0; } +int GenericListControl::OnIncrementalSearch(int startRow, const wchar_t *str, bool wrap, bool partial) { + int size = GetRowCount(); + size_t searchlen = wcslen(str); + if (!wrap) + size -= startRow; + + // We start with the earliest column, preferring matches on the leftmost columns by default. + for (int c = 0; c < columnCount; ++c) { + for (int i = 0; i < size; ++i) { + int r = (startRow + i) % size; + stringBuffer[0] = 0; + GetColumnText(stringBuffer, r, c); + int difference = partial ? _wcsnicmp(str, stringBuffer, searchlen) : _wcsicmp(str, stringBuffer); + if (difference == 0) + return r; + } + } + + return -1; +} + void GenericListControl::Update() { if (!updateScheduled_) { SetTimer(handle, IDT_UPDATE, UPDATE_DELAY, nullptr); diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index 8a43c55b59..1b723cc241 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -68,6 +68,8 @@ protected: virtual bool OnRowPrePaint(int row, LPNMLVCUSTOMDRAW msg) { return false; } virtual bool OnColPrePaint(int row, int col, LPNMLVCUSTOMDRAW msg) { return false; } + virtual int OnIncrementalSearch(int startRow, const wchar_t *str, bool wrap, bool partial); + private: static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void ProcessUpdate(); From 7d4aa3eb2df0cb16c649dad51e4aef9513f617d6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 11:44:01 -0700 Subject: [PATCH 073/326] softgpu: Correct line early z checks. Was looking at completely wrong pixels, hadn't removed subpixels yet. --- GPU/Software/Rasterizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index fa7e0af3f6..4f140b206d 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -1365,7 +1365,7 @@ void DrawLine(const VertexData &v0, const VertexData &v1, const BinCoords &range maskOK = false; } - if (!CheckDepthTestPassed(pixelID.DepthTestFunc(), x, y, pixelID.cached.depthbufStride, z)) { + if (!CheckDepthTestPassed(pixelID.DepthTestFunc(), p.x, p.y, pixelID.cached.depthbufStride, z)) { maskOK = false; } } From c08c873462b63330d0846f48ecb1306ca37f8a79 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 13:16:59 -0700 Subject: [PATCH 074/326] GLES: Support more clip distances. Will be used later, for now just the enable/disable logic. --- Common/GPU/OpenGL/GLQueueRunner.cpp | 26 ++++++++++++++++---------- Common/GPU/OpenGL/GLRenderManager.h | 17 +++++++++++++---- Common/GPU/OpenGL/thin3d_gl.cpp | 3 ++- GPU/GLES/DepthBufferGLES.cpp | 3 ++- GPU/GLES/ShaderManagerGLES.cpp | 10 +++++++--- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index c680062b78..8f90122f39 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -811,7 +811,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last int logicOp = -1; bool logicEnabled = false; #endif - bool clipDistance0Enabled = false; + bool clipDistanceEnabled[8]{}; GLuint blendEqColor = (GLuint)-1; GLuint blendEqAlpha = (GLuint)-1; @@ -1119,14 +1119,18 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last { if (curProgram != c.program.program) { glUseProgram(c.program.program->program); - if (c.program.program->use_clip_distance0 != clipDistance0Enabled) { - if (c.program.program->use_clip_distance0) - glEnable(GL_CLIP_DISTANCE0); - else - glDisable(GL_CLIP_DISTANCE0); - clipDistance0Enabled = c.program.program->use_clip_distance0; - } curProgram = c.program.program; + + for (size_t i = 0; i < ARRAY_SIZE(clipDistanceEnabled); ++i) { + if (c.program.program->use_clip_distance[i] == clipDistanceEnabled[i]) + continue; + + if (c.program.program->use_clip_distance[i]) + glEnable(GL_CLIP_DISTANCE0 + (GLenum)i); + else + glDisable(GL_CLIP_DISTANCE0 + (GLenum)i); + clipDistanceEnabled[i] = c.program.program->use_clip_distance[i]; + } } CHECK_GL_ERROR_IF_DEBUG(); break; @@ -1366,8 +1370,10 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last glDisable(GL_COLOR_LOGIC_OP); } #endif - if (clipDistance0Enabled) - glDisable(GL_CLIP_DISTANCE0); + for (size_t i = 0; i < ARRAY_SIZE(clipDistanceEnabled); ++i) { + if (clipDistanceEnabled[i]) + glDisable(GL_CLIP_DISTANCE0 + (GLenum)i); + } if ((colorMask & 15) != 15) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); CHECK_GL_ERROR_IF_DEBUG(); diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 719603863e..c9889156b0 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -91,6 +91,13 @@ public: std::string error; }; +struct GLRProgramFlags { + bool supportDualSource : 1; + bool useClipDistance0 : 1; + bool useClipDistance1 : 1; + bool useClipDistance2 : 1; +}; + class GLRProgram { public: ~GLRProgram() { @@ -119,7 +126,7 @@ public: std::vector semantics_; std::vector queries_; std::vector initialize_; - bool use_clip_distance0 = false; + bool use_clip_distance[8]{}; struct UniformInfo { int loc_; @@ -427,15 +434,17 @@ public: // not be an active render pass. GLRProgram *CreateProgram( std::vector shaders, std::vector semantics, std::vector queries, - std::vector initializers, bool supportDualSource, bool useClipDistance0) { + std::vector initializers, const GLRProgramFlags &flags) { GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM }; _assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders)); step.create_program.program = new GLRProgram(); step.create_program.program->semantics_ = semantics; step.create_program.program->queries_ = queries; step.create_program.program->initialize_ = initializers; - step.create_program.program->use_clip_distance0 = useClipDistance0; - step.create_program.support_dual_source = supportDualSource; + step.create_program.program->use_clip_distance[0] = flags.useClipDistance0; + step.create_program.program->use_clip_distance[1] = flags.useClipDistance1; + step.create_program.program->use_clip_distance[2] = flags.useClipDistance2; + step.create_program.support_dual_source = flags.supportDualSource; _assert_msg_(shaders.size() > 0, "Can't create a program with zero shaders"); for (size_t i = 0; i < shaders.size(); i++) { step.create_program.shaders[i] = shaders[i]; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 047cc8b8fb..b2e6320455 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -1234,7 +1234,8 @@ bool OpenGLPipeline::LinkShaders() { } } - program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, false, false); + GLRProgramFlags flags{}; + program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, flags); return true; } diff --git a/GPU/GLES/DepthBufferGLES.cpp b/GPU/GLES/DepthBufferGLES.cpp index e5bcc20bf9..342ebdad60 100644 --- a/GPU/GLES/DepthBufferGLES.cpp +++ b/GPU/GLES/DepthBufferGLES.cpp @@ -118,7 +118,8 @@ void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int queries.push_back({ &u_depthDownloadTo8, "u_depthTo8" }); std::vector inits; inits.push_back({ &u_depthDownloadTex, 0, TEX_SLOT_PSP_TEXTURE }); - depthDownloadProgram_ = render->CreateProgram(shaders, semantics, queries, inits, false, false); + GLRProgramFlags flags{}; + depthDownloadProgram_ = render->CreateProgram(shaders, semantics, queries, inits, flags); for (auto iter : shaders) { render->DeleteShader(iter); } diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 4d37ae2ba6..de703cf0ba 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -192,9 +192,13 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, initialize.push_back({ &u_tess_weights_u, 0, TEX_SLOT_SPLINE_WEIGHTS_U }); initialize.push_back({ &u_tess_weights_v, 0, TEX_SLOT_SPLINE_WEIGHTS_V }); - bool useDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0; - bool useClip0 = VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); - program = render->CreateProgram(shaders, semantics, queries, initialize, useDualSource, useClip0); + GLRProgramFlags flags{}; + flags.supportDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0; + if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + flags.useClipDistance0 = true; + } + + program = render->CreateProgram(shaders, semantics, queries, initialize, flags); // The rest, use the "dirty" mechanism. dirtyUniforms = DIRTY_ALL_UNIFORMS; From a2a7e8258ba25621153a9b70823581bc9ac58421 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 13:18:02 -0700 Subject: [PATCH 075/326] GPU: Clip depth properly when also clamping. Helps situations like #11216, where only one side should be clamped. Keeps depth clamp (i.e. #7932) working. See #11399. --- GPU/Common/VertexShaderGenerator.cpp | 54 ++++++++++++++++++++++++---- GPU/GLES/ShaderManagerGLES.cpp | 7 +++- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index ccda9af86b..16cce87d0f 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -142,10 +142,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (gl_extensions.EXT_gpu_shader4) { gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable"); } - if (gl_extensions.EXT_clip_cull_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) { + bool useClamp = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !id.Bit(VS_BIT_IS_THROUGH); + if (gl_extensions.EXT_clip_cull_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) { gl_exts.push_back("#extension GL_EXT_clip_cull_distance : enable"); } - if (gl_extensions.APPLE_clip_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) { + if (gl_extensions.APPLE_clip_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) { gl_exts.push_back("#extension GL_APPLE_clip_distance : enable"); } if (gl_extensions.ARB_cull_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) { @@ -227,6 +228,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool texCoordInVec3 = false; bool vertexRangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING) && !isModeThrough; + bool clipClampedDepth = !isModeThrough && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); + const char *vertexRangeClipSuffix = "[0]"; + if (vertexRangeCulling && clipClampedDepth) + vertexRangeClipSuffix = "[2]"; if (compat.shaderLanguage == GLSL_VULKAN) { WRITE(p, "\n"); @@ -419,8 +424,15 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " vec4 gl_Position : POSITION;\n"); } else { WRITE(p, " vec4 gl_Position : SV_Position;\n"); - if (vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { - WRITE(p, " float gl_ClipDistance : SV_ClipDistance0;\n"); + bool clipRange = vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); + if (clipClampedDepth && clipRange) { + WRITE(p, " float3 gl_ClipDistance : SV_ClipDistance;\n"); + vertexRangeClipSuffix = ".z"; + } else if (clipClampedDepth) { + WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n"); + } else if (clipRange) { + WRITE(p, " float gl_ClipDistance : SV_ClipDistance;\n"); + vertexRangeClipSuffix = ""; } if (vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) { WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n"); @@ -1177,8 +1189,37 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix); } - if (vertexRangeCulling && !IsVRBuild()) { + if (clipClampedDepth || (vertexRangeCulling && !IsVRBuild())) { WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); + } + + if (clipClampedDepth) { + const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; + const char *clip1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; + WRITE(p, " mediump float integerZ = projPos.z * u_depthRange.x + u_depthRange.y;\n"); + + // This should clip against minz, but only when it's above zero. + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + // On OpenGL/GLES, these values account for the -1 -> 1 range. + WRITE(p, " if (u_depthRange.y - u_depthRange.x >= 1.0) {\n"); + } else { + // Everywhere else, it's 0 -> 1, simpler. + WRITE(p, " if (u_depthRange.y >= 1.0) {\n"); + } + WRITE(p, " %sgl_ClipDistance%s = integerZ;\n", compat.vsOutPrefix, clip0); + WRITE(p, " } else {\n"); + WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip0); + WRITE(p, " }\n"); + + // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here. + WRITE(p, " if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); + WRITE(p, " %sgl_ClipDistance%s = 65535.0 - integerZ;\n", compat.vsOutPrefix, clip1); + WRITE(p, " } else {\n"); + WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip1); + WRITE(p, " }\n"); + } + + if (vertexRangeCulling && !IsVRBuild()) { WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); // Vertex range culling doesn't happen when Z clips, note sign of w is important. WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); @@ -1194,12 +1235,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " }\n"); WRITE(p, " }\n"); - const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; const char *cull0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; const char *cull1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { // TODO: Not rectangles... - WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", compat.vsOutPrefix, clip0); + WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", compat.vsOutPrefix, vertexRangeClipSuffix); } if (gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) { // Cull any triangle fully outside in the same direction when depth clamp enabled. diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index de703cf0ba..c9ad82cd36 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -194,7 +194,12 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, GLRProgramFlags flags{}; flags.supportDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0; - if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + if (!VSID.Bit(VS_BIT_IS_THROUGH) && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP)) { + flags.useClipDistance0 = true; + flags.useClipDistance1 = true; + if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) + flags.useClipDistance2 = true; + } else if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { flags.useClipDistance0 = true; } From 9d4351b4845383465eee8de527e9a29218301ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Sep 2022 00:45:08 +0200 Subject: [PATCH 076/326] Fix the UI setting "Buffer graphics commands". Was off by 1. --- UI/GameSettingsScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 6c702bd7f0..0c1c0d009e 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -469,7 +469,7 @@ void GameSettingsScreen::CreateViews() { if (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::OPENGL) { static const char *bufferOptions[] = { "No buffer", "Up to 1", "Up to 2" }; - PopupMultiChoice *inflightChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iInflightFrames, gr->T("Buffer graphics commands (faster, input lag)"), bufferOptions, 0, ARRAY_SIZE(bufferOptions), gr->GetName(), screenManager())); + PopupMultiChoice *inflightChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iInflightFrames, gr->T("Buffer graphics commands (faster, input lag)"), bufferOptions, 1, ARRAY_SIZE(bufferOptions), gr->GetName(), screenManager())); inflightChoice->OnChoice.Handle(this, &GameSettingsScreen::OnInflightFramesChoice); } From f0afc97247a3dd69af86711686516ced891c0cce Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 18 Sep 2022 20:39:13 -0700 Subject: [PATCH 077/326] Gamedata: Cleanup install request args/validate. --- Core/Dialog/PSPGamedataInstallDialog.cpp | 24 ++++++++++++++++++++++-- Core/Dialog/PSPGamedataInstallDialog.h | 2 +- Core/HLE/sceUtility.cpp | 14 +++++++++----- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Core/Dialog/PSPGamedataInstallDialog.cpp b/Core/Dialog/PSPGamedataInstallDialog.cpp index 9fe30db3c8..3bbe9442f1 100644 --- a/Core/Dialog/PSPGamedataInstallDialog.cpp +++ b/Core/Dialog/PSPGamedataInstallDialog.cpp @@ -36,8 +36,9 @@ const static u32 GAMEDATA_BYTES_PER_READ = 32768; // If this is too high, some games (e.g. Senjou no Valkyria 3) will lag. const static u32 GAMEDATA_READS_PER_UPDATE = 20; -const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_WRITE_PROTECTED = 0x80111903; const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_REMOVED = 0x80111901; +const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_WRITE_PROTECTED = 0x80111903; +const u32 ERROR_UTILITY_GAMEDATA_INVALID_MODE = 0x80111908; static const std::string SFO_FILENAME = "PARAM.SFO"; @@ -88,9 +89,14 @@ int PSPGamedataInstallDialog::Init(u32 paramAddr) { } int size = Memory::Read_U32(paramAddr); + if (size != 1424 && size != 1432) { + ERROR_LOG_REPORT(SCEUTILITY, "sceGamedataInstallInitStart: invalid param size %d", size); + return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; + } + memset(&request, 0, sizeof(request)); // Only copy the right size to support different request format - Memory::Memcpy(&request, paramAddr, size); + Memory::Memcpy(&request, paramAddr, size, "sceGamedataInstallInitStart"); ChangeStatusInit(GAMEDATA_INIT_DELAY_US); return 0; @@ -100,6 +106,17 @@ int PSPGamedataInstallDialog::Update(int animSpeed) { if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) return SCE_ERROR_UTILITY_INVALID_STATUS; + if (param->mode >= 2) { + param->common.result = ERROR_UTILITY_GAMEDATA_INVALID_MODE; + param.NotifyWrite("DialogResult"); + ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0); + WARN_LOG_REPORT(SCEUTILITY, "sceUtilityGamedataInstallUpdate: invalid mode %d", param->mode); + return 0; + } + + // TODO: param->mode == 1 should show a prompt to confirm, then a progress bar. + // Any other mode (i.e. 0 or negative) should proceed and show no UI. + // TODO: This should return error codes in some cases, like write failure. // request.common.result must be updated for errors as well. @@ -222,6 +239,9 @@ void PSPGamedataInstallDialog::WriteSfoFile() { } int PSPGamedataInstallDialog::Abort() { + param->common.result = 1; + param.NotifyWrite("DialogResult"); + // TODO: Delete the files or anything? return PSPDialog::Shutdown(); } diff --git a/Core/Dialog/PSPGamedataInstallDialog.h b/Core/Dialog/PSPGamedataInstallDialog.h index e669a31873..a74fdb5254 100644 --- a/Core/Dialog/PSPGamedataInstallDialog.h +++ b/Core/Dialog/PSPGamedataInstallDialog.h @@ -22,7 +22,7 @@ struct SceUtilityGamedataInstallParam { pspUtilityDialogCommon common; - u32_le unknown1; + s32_le mode; char gameName[13]; char ignore1[3]; char dataName[20]; diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index e6a5124600..f1d7f73d0b 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -744,11 +744,14 @@ static int sceUtilityGamedataInstallInitStart(u32 paramsAddr) { } ActivateDialog(UtilityDialogType::GAMEDATAINSTALL); - return hleLogSuccessInfoX(SCEUTILITY, gamedataInstallDialog->Init(paramsAddr)); + int result = gamedataInstallDialog->Init(paramsAddr); + if (result < 0) + DeactivateDialog(); + return hleLogSuccessInfoX(SCEUTILITY, result); } static int sceUtilityGamedataInstallShutdownStart() { - if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { + if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); } @@ -757,7 +760,7 @@ static int sceUtilityGamedataInstallShutdownStart() { } static int sceUtilityGamedataInstallUpdate(int animSpeed) { - if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { + if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); } @@ -765,8 +768,9 @@ static int sceUtilityGamedataInstallUpdate(int animSpeed) { } static int sceUtilityGamedataInstallGetStatus() { - if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { + if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { // This is called incorrectly all the time by some games. So let's not bother warning. + hleEatCycles(200); return hleLogDebug(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); } @@ -776,7 +780,7 @@ static int sceUtilityGamedataInstallGetStatus() { } static int sceUtilityGamedataInstallAbort() { - if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { + if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) { return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); } From 789983ebaddded729f5847a7e591ca4b13f9a3fc Mon Sep 17 00:00:00 2001 From: Crow-bar Date: Mon, 19 Sep 2022 15:26:45 +0300 Subject: [PATCH 078/326] HLE: sceKernelAllocPartitionMemory volatile memory support (partition 5) --- Core/HLE/sceKernelMemory.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index ac853d5bb3..7c604710b4 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -46,6 +46,7 @@ const int TLSPL_NUM_INDEXES = 16; // STATE BEGIN BlockAllocator userMemory(256); BlockAllocator kernelMemory(256); +BlockAllocator volatileMemory(256); static int vplWaitTimer = -1; static int fplWaitTimer = -1; @@ -432,6 +433,7 @@ void __KernelMemoryInit() MemBlockInfoInit(); kernelMemory.Init(PSP_GetKernelMemoryBase(), PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase(), false); userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase(), false); + volatileMemory.Init(PSP_GetVolatileMemoryStart(), PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart(), false); ParallelMemset(&g_threadManager, Memory::GetPointerWrite(PSP_GetKernelMemoryBase()), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase()); NotifyMemInfo(MemBlockFlags::WRITE, PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase(), "MemInit"); INFO_LOG(SCEKERNEL, "Kernel and user memory pools initialized"); @@ -463,6 +465,7 @@ void __KernelMemoryDoState(PointerWrap &p) kernelMemory.DoState(p); userMemory.DoState(p); + volatileMemory.DoState(p); Do(p, vplWaitTimer); CoreTiming::RestoreRegisterEvent(vplWaitTimer, "VplTimeout", __KernelVplTimeout); @@ -481,6 +484,11 @@ void __KernelMemoryDoState(PointerWrap &p) void __KernelMemoryShutdown() { +#ifdef _DEBUG + INFO_LOG(SCEKERNEL, "Shutting down volatile memory pool: "); + volatileMemory.ListBlocks(); +#endif + volatileMemory.Shutdown(); #ifdef _DEBUG INFO_LOG(SCEKERNEL,"Shutting down user memory pool: "); userMemory.ListBlocks(); @@ -1021,7 +1029,7 @@ int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32 return SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE; } - PartitionMemoryBlock *block = new PartitionMemoryBlock(&userMemory, name, size, (MemblockType)type, addr); + PartitionMemoryBlock *block = new PartitionMemoryBlock((partition == 5) ? &volatileMemory : &userMemory, name, size, (MemblockType)type, addr); if (!block->IsValid()) { delete block; From 7b4cc3334b87f69b2853a98757fa405d0d5676fe Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 19 Sep 2022 08:30:12 -0700 Subject: [PATCH 079/326] GE Debugger: Save current clut in frame dumps. For example, #14465 shows a case where the frame relies on a previously loaded CLUT. --- GPU/Debugger/Record.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index cc63dccf72..d4a1838787 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -37,6 +37,7 @@ #include "Core/MemMap.h" #include "Core/System.h" #include "Core/ThreadPools.h" +#include "GPU/Common/GPUDebugInterface.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" #include "GPU/ge_constants.h" @@ -152,8 +153,19 @@ static void BeginRecording() { u32 sz = 512 * 4; pushbuf.resize(pushbuf.size() + sz); gstate.Save((u32_le *)(pushbuf.data() + ptr)); - commands.push_back({CommandType::INIT, sz, ptr}); + + // Also save the initial CLUT. + GPUDebugBuffer clut; + if (gpuDebug->GetCurrentClut(clut)) { + sz = clut.GetStride() * clut.PixelSize(); + _assert_msg_(sz == 1024, "CLUT should be 1024 bytes"); + ptr = (u32)pushbuf.size(); + pushbuf.resize(pushbuf.size() + sz); + memcpy(pushbuf.data() + ptr, clut.GetData(), sz); + commands.push_back({ CommandType::CLUT, sz, ptr }); + } + DirtyAllVRAM(DirtyVRAMFlag::DIRTY); } From e727570faea5f2940f5ee4e34eec0b0a719b434c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 19 Sep 2022 08:30:57 -0700 Subject: [PATCH 080/326] Windows: Normalize paths for OpenDirectory(). This was making GE frame dumps not automatically browse when run from MSVC debugging by default, because of the relative path. --- Windows/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Windows/main.cpp b/Windows/main.cpp index 4c0752bda8..e3888dfe4c 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -34,6 +34,7 @@ #include "Common/System/Display.h" #include "Common/System/NativeApp.h" #include "Common/System/System.h" +#include "Common/File/FileUtil.h" #include "Common/File/VFS/VFS.h" #include "Common/File/VFS/AssetReader.h" #include "Common/Data/Text/I18n.h" @@ -109,9 +110,12 @@ static std::thread inputBoxThread; static bool inputBoxRunning = false; void OpenDirectory(const char *path) { + // SHParseDisplayName can't handle relative paths, so normalize first. + std::string resolved = ReplaceAll(File::ResolvePath(path), "/", "\\"); + SFGAOF flags; PIDLIST_ABSOLUTE pidl = nullptr; - HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(ReplaceAll(path, "/", "\\")).c_str(), nullptr, &pidl, 0, &flags); + HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(resolved).c_str(), nullptr, &pidl, 0, &flags); if (pidl) { if (SUCCEEDED(hr)) From 259abf79ddeba96c95e90d7925d03eb09267b9bb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 19 Sep 2022 08:36:59 -0700 Subject: [PATCH 081/326] softgpu: Skip more triangles with equal verts. Even if X is not equal, if all Y are equal, we can still skip. This seems to happen more often than I expected with far away objects. Skipping here avoids filling up the queue. --- GPU/Software/BinManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 7fee77b64c..a1fb22bee6 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -326,7 +326,7 @@ void BinManager::AddTriangle(const VertexData &v0, const VertexData &v1, const V if (d01.x * d02.y - d01.y * d02.x < 0) return; // If all points have identical coords, we'll have 0 weights and not skip properly, so skip here. - if (d01.x == 0 && d01.y == 0 && d02.x == 0 && d02.y == 0) + if ((d01.x == 0 && d02.x == 0) || (d01.y == 0 && d02.y == 0)) return; // Was it fully outside the scissor? From e2929fb39541b529d442b6668c571f9c0b9cbe57 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 19 Sep 2022 08:38:11 -0700 Subject: [PATCH 082/326] softgpu: Skip flushing on no change. Minor tweak, we sometimes try to flush even without anything to flush, and were previously rechecking a lot of things. --- GPU/Software/BinManager.cpp | 3 +++ GPU/Software/SoftGpu.cpp | 15 ++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index a1fb22bee6..42eef12eda 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -474,6 +474,9 @@ void BinManager::Drain() { } void BinManager::Flush(const char *reason) { + if (queueRange_.x1 == 0x7FFFFFFF) + return; + double st; if (coreCollectDebugStats) st = time_now_d(); diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 01c0a46023..0f2d3bcda1 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1006,19 +1006,24 @@ void SoftGPU::Execute_LoadClut(u32 op, u32 diff) { void SoftGPU::Execute_FramebufPtr(u32 op, u32 diff) { // We assume fb.data won't change while we're drawing. - drawEngine_->transformUnit.Flush("framebuf"); - fb.data = Memory::GetPointerWrite(gstate.getFrameBufAddress()); + if (diff) { + drawEngine_->transformUnit.Flush("framebuf"); + fb.data = Memory::GetPointerWrite(gstate.getFrameBufAddress()); + } } void SoftGPU::Execute_FramebufFormat(u32 op, u32 diff) { // We should flush, because ranges within bins may change. - drawEngine_->transformUnit.Flush("framebuf"); + if (diff) + drawEngine_->transformUnit.Flush("framebuf"); } void SoftGPU::Execute_ZbufPtr(u32 op, u32 diff) { // We assume depthbuf.data won't change while we're drawing. - drawEngine_->transformUnit.Flush("depthbuf"); - depthbuf.data = Memory::GetPointerWrite(gstate.getDepthBufAddress()); + if (diff) { + drawEngine_->transformUnit.Flush("depthbuf"); + depthbuf.data = Memory::GetPointerWrite(gstate.getDepthBufAddress()); + } } void SoftGPU::Execute_VertexType(u32 op, u32 diff) { From d0016add37c68844f76135801f0dc791dd4c587c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Sep 2022 18:07:50 +0200 Subject: [PATCH 083/326] Start moving stuff out to VulkanFrameData.cpp --- Common/GPU/Vulkan/VulkanFrameData.cpp | 140 +++++++++++++++++++- Common/GPU/Vulkan/VulkanFrameData.h | 27 +++- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 4 +- Common/GPU/Vulkan/VulkanQueueRunner.h | 2 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 153 +++------------------- Common/GPU/Vulkan/VulkanRenderManager.h | 10 +- 6 files changed, 188 insertions(+), 148 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 58ce42deaf..897f2e72bf 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -1,8 +1,55 @@ #include "VulkanFrameData.h" +#include "Common/Log.h" -void FrameData::AcquireNextImage(VulkanContext *vulkan) { +void FrameData::Init(VulkanContext *vulkan) { + VkDevice device = vulkan->GetDevice(); + + VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + cmd_pool_info.queueFamilyIndex = vulkan->GetGraphicsQueueFamilyIndex(); + cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &cmdPoolInit); + _dbg_assert_(res == VK_SUCCESS); + res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &cmdPoolMain); + _dbg_assert_(res == VK_SUCCESS); + + VkCommandBufferAllocateInfo cmd_alloc = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + cmd_alloc.commandPool = cmdPoolInit; + cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cmd_alloc.commandBufferCount = 1; + res = vkAllocateCommandBuffers(device, &cmd_alloc, &initCmd); + _dbg_assert_(res == VK_SUCCESS); + cmd_alloc.commandPool = cmdPoolMain; + res = vkAllocateCommandBuffers(device, &cmd_alloc, &mainCmd); + res = vkAllocateCommandBuffers(device, &cmd_alloc, &presentCmd); + _dbg_assert_(res == VK_SUCCESS); + + // Creating the frame fence with true so they can be instantly waited on the first frame + fence = vulkan->CreateFence(true); + + // This fence one is used for synchronizing readbacks. Does not need preinitialization. + readbackFence = vulkan->CreateFence(false); + + VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO }; + query_ci.queryCount = MAX_TIMESTAMP_QUERIES; + query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP; + res = vkCreateQueryPool(device, &query_ci, nullptr, &profile.queryPool); +} + +void FrameData::Destroy(VulkanContext *vulkan) { + VkDevice device = vulkan->GetDevice(); + // TODO: I don't think free-ing command buffers is necessary before destroying a pool. + vkFreeCommandBuffers(device, cmdPoolInit, 1, &initCmd); + vkFreeCommandBuffers(device, cmdPoolMain, 1, &mainCmd); + vkDestroyCommandPool(device, cmdPoolInit, nullptr); + vkDestroyCommandPool(device, cmdPoolMain, nullptr); + vkDestroyFence(device, fence, nullptr); + vkDestroyFence(device, readbackFence, nullptr); + vkDestroyQueryPool(device, profile.queryPool, nullptr); +} + +void FrameData::AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared) { // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. - VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); + VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, shared.acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); if (res == VK_SUBOPTIMAL_KHR) { // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something. WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring"); @@ -13,3 +60,92 @@ void FrameData::AcquireNextImage(VulkanContext *vulkan) { _assert_msg_(res == VK_SUCCESS, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res)); } } + +void FrameData::SubmitInitCommands(VulkanContext *vulkan) { + if (!hasInitCommands) { + return; + } + + if (profilingEnabled_) { + // Pre-allocated query ID 1. + vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1); + } + + VkResult res = vkEndCommandBuffer(initCmd); + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); + + VkCommandBuffer cmdBufs[1]; + int numCmdBufs = 0; + + cmdBufs[numCmdBufs++] = initCmd; + // Send the init commands off separately, so they can be processed while we're building the rest of the list. + // (Likely the CPU will be more than a frame ahead anyway, but this will help when we try to work on latency). + VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit_info.commandBufferCount = (uint32_t)numCmdBufs; + submit_info.pCommandBuffers = cmdBufs; + res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE); + if (res == VK_ERROR_DEVICE_LOST) { + _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan"); + } else { + _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res)); + } + numCmdBufs = 0; + + hasInitCommands = false; +} + +void FrameData::SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, FrameDataShared &sharedData) { + VkCommandBuffer cmdBufs[2]; + int numCmdBufs = 0; + + cmdBufs[numCmdBufs++] = mainCmd; + if (hasPresentCommands) { + cmdBufs[numCmdBufs++] = presentCmd; + } + + VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; + VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + if (triggerFrameFence && !skipSwap) { + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &sharedData.acquireSemaphore; + submit_info.pWaitDstStageMask = waitStage; + } + submit_info.commandBufferCount = (uint32_t)numCmdBufs; + submit_info.pCommandBuffers = cmdBufs; + if (triggerFrameFence && !skipSwap) { + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &sharedData.renderingCompleteSemaphore; + } + VkResult res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, triggerFrameFence ? fence : readbackFence); + if (res == VK_ERROR_DEVICE_LOST) { + _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan"); + } else { + _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main)! result=%s", VulkanResultToString(res)); + } + + // When !triggerFence, we notify after syncing with Vulkan. + if (sharedData.useThread && triggerFrameFence) { + VERBOSE_LOG(G3D, "PULL: Frame %d.readyForFence = true", index); + std::unique_lock lock(push_mutex); + readyForFence = true; + push_condVar.notify_all(); + } + + hasInitCommands = false; + hasPresentCommands = false; +} + +void FrameDataShared::Init(VulkanContext *vulkan) { + VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + semaphoreCreateInfo.flags = 0; + VkResult res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore); + _dbg_assert_(res == VK_SUCCESS); + res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &renderingCompleteSemaphore); + _dbg_assert_(res == VK_SUCCESS); +} + +void FrameDataShared::Destroy(VulkanContext *vulkan) { + VkDevice device = vulkan->GetDevice(); + vkDestroySemaphore(device, acquireSemaphore, nullptr); + vkDestroySemaphore(device, renderingCompleteSemaphore, nullptr); +} diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index d7093c2f42..a57a14a340 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -9,6 +9,10 @@ struct VKRStep; +enum { + MAX_TIMESTAMP_QUERIES = 128, +}; + enum class VKRRunType { END, SYNC, @@ -22,6 +26,16 @@ struct QueueProfileContext { double cpuEndTime; }; +struct FrameDataShared { + // Permanent objects + VkSemaphore acquireSemaphore = VK_NULL_HANDLE; + VkSemaphore renderingCompleteSemaphore = VK_NULL_HANDLE; + bool useThread = true; + + void Init(VulkanContext *vulkan); + void Destroy(VulkanContext *vulkan); +}; + // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct FrameData { std::mutex push_mutex; @@ -56,11 +70,20 @@ struct FrameData { // Swapchain. bool hasBegun = false; uint32_t curSwapchainImage = -1; - VkSemaphore acquireSemaphore; // Not owned, shared between all FrameData. // Profiling. QueueProfileContext profile; bool profilingEnabled_; - void AcquireNextImage(VulkanContext *vulkan); + // Metadaata + int index; + + void Init(VulkanContext *vulkan); + void Destroy(VulkanContext *vulkan); + + void AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared); + + // This will only submit if we are actually recording init commands. + void SubmitInitCommands(VulkanContext *vulkan); + void SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, FrameDataShared &shared); }; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 5d9b1b1adf..dfe09ecaea 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -541,7 +541,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) { } } -void VulkanQueueRunner::RunSteps(FrameData &frameData) { +void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDataShared) { QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr; if (profile) @@ -564,7 +564,7 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData) { // When stepping in the GE debugger, we can end up here multiple times in a "frame". if (!frameData.hasAcquired) { frameData.hasAcquired = true; - frameData.AcquireNextImage(vulkan_); + frameData.AcquireNextImage(vulkan_, frameDataShared); SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); } VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index de3108c742..2c76262a6a 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -251,7 +251,7 @@ public: } void PreprocessSteps(std::vector &steps); - void RunSteps(FrameData &frameData); + void RunSteps(FrameData &frameData, FrameDataShared &frameDataShared); void LogSteps(const std::vector &steps, bool verbose); std::string StepToString(const VKRStep &step) const; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 1bf93caebc..9d4ac610e1 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -288,46 +288,12 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int } VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan), queueRunner_(vulkan) { - VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - semaphoreCreateInfo.flags = 0; - VkResult res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore_); - _dbg_assert_(res == VK_SUCCESS); - res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &renderingCompleteSemaphore_); - _dbg_assert_(res == VK_SUCCESS); - inflightFramesAtStart_ = vulkan_->GetInflightFrames(); + + frameDataShared_.Init(vulkan); + for (int i = 0; i < inflightFramesAtStart_; i++) { - VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; - cmd_pool_info.queueFamilyIndex = vulkan_->GetGraphicsQueueFamilyIndex(); - cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; - VkResult res = vkCreateCommandPool(vulkan_->GetDevice(), &cmd_pool_info, nullptr, &frameData_[i].cmdPoolInit); - _dbg_assert_(res == VK_SUCCESS); - res = vkCreateCommandPool(vulkan_->GetDevice(), &cmd_pool_info, nullptr, &frameData_[i].cmdPoolMain); - _dbg_assert_(res == VK_SUCCESS); - - VkCommandBufferAllocateInfo cmd_alloc = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; - cmd_alloc.commandPool = frameData_[i].cmdPoolInit; - cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - cmd_alloc.commandBufferCount = 1; - res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].initCmd); - _dbg_assert_(res == VK_SUCCESS); - cmd_alloc.commandPool = frameData_[i].cmdPoolMain; - res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].mainCmd); - res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].presentCmd); - _dbg_assert_(res == VK_SUCCESS); - - // Creating the frame fence with true so they can be instantly waited on the first frame - frameData_[i].fence = vulkan_->CreateFence(true); - - // This fence one is used for synchronizing readbacks. Does not need preinitialization. - frameData_[i].readbackFence = vulkan_->CreateFence(false); - - VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO }; - query_ci.queryCount = MAX_TIMESTAMP_QUERIES; - query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP; - res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].profile.queryPool); - - frameData_[i].acquireSemaphore = acquireSemaphore_; + frameData_[i].Init(vulkan); } queueRunner_.CreateDeviceObjects(); @@ -335,7 +301,7 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan // AMD hack for issue #10097 (older drivers only.) const auto &props = vulkan_->GetPhysicalDeviceProperties().properties; if (props.vendorID == VULKAN_VENDOR_AMD && props.apiVersion < VK_API_VERSION_1_1) { - useThread_ = false; + frameDataShared_.useThread = false; } } @@ -368,7 +334,7 @@ bool VulkanRenderManager::CreateBackbuffers() { outOfDateFrames_ = 0; // Start the thread. - if (useThread_ && HasBackbuffers()) { + if (frameDataShared_.useThread && HasBackbuffers()) { run_ = true; // Won't necessarily be 0. threadInitFrame_ = vulkan_->GetCurFrame(); @@ -381,7 +347,7 @@ bool VulkanRenderManager::CreateBackbuffers() { } void VulkanRenderManager::StopThread() { - if (useThread_ && run_) { + if (frameDataShared_.useThread && run_) { run_ = false; // Stop the thread. for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { @@ -449,16 +415,9 @@ VulkanRenderManager::~VulkanRenderManager() { DrainCompileQueue(); VkDevice device = vulkan_->GetDevice(); - vkDestroySemaphore(device, acquireSemaphore_, nullptr); - vkDestroySemaphore(device, renderingCompleteSemaphore_, nullptr); + frameDataShared_.Destroy(vulkan_); for (int i = 0; i < inflightFramesAtStart_; i++) { - vkFreeCommandBuffers(device, frameData_[i].cmdPoolInit, 1, &frameData_[i].initCmd); - vkFreeCommandBuffers(device, frameData_[i].cmdPoolMain, 1, &frameData_[i].mainCmd); - vkDestroyCommandPool(device, frameData_[i].cmdPoolInit, nullptr); - vkDestroyCommandPool(device, frameData_[i].cmdPoolMain, nullptr); - vkDestroyFence(device, frameData_[i].fence, nullptr); - vkDestroyFence(device, frameData_[i].readbackFence, nullptr); - vkDestroyQueryPool(device, frameData_[i].profile.queryPool, nullptr); + frameData_[i].Destroy(vulkan_); } queueRunner_.DestroyDeviceObjects(); } @@ -521,6 +480,7 @@ void VulkanRenderManager::ThreadFunc() { threadFrame = 0; } FrameData &frameData = frameData_[threadFrame]; + std::unique_lock lock(frameData.pull_mutex); while (!frameData.readyForRun && run_) { VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame); @@ -562,7 +522,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile FrameData &frameData = frameData_[curFrame]; // Make sure the very last command buffer from the frame before the previous has been fully executed. - if (useThread_) { + if (frameDataShared_.useThread) { std::unique_lock lock(frameData.push_mutex); while (!frameData.readyForFence) { VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); @@ -1234,7 +1194,7 @@ void VulkanRenderManager::Finish() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - if (!useThread_) { + if (!frameDataShared_.useThread) { frameData.steps = std::move(steps_); steps_.clear(); frameData.type = VKRRunType::END; @@ -1266,7 +1226,7 @@ void VulkanRenderManager::Wipe() { void VulkanRenderManager::BeginSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; - SubmitInitCommands(frame); + frameData.SubmitInitCommands(vulkan_); if (!frameData.hasBegun) { // Effectively resets both main and present command buffers, since they both live in this pool. @@ -1281,40 +1241,6 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { } } -void VulkanRenderManager::SubmitInitCommands(int frame) { - FrameData &frameData = frameData_[frame]; - if (!frameData.hasInitCommands) { - return; - } - - if (frameData.profilingEnabled_) { - // Pre-allocated query ID 1. - vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.profile.queryPool, 1); - } - - VkResult res = vkEndCommandBuffer(frameData.initCmd); - _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); - - VkCommandBuffer cmdBufs[1]; - int numCmdBufs = 0; - - cmdBufs[numCmdBufs++] = frameData.initCmd; - // Send the init commands off separately, so they can be processed while we're building the rest of the list. - // (Likely the CPU will be more than a frame ahead anyway, but this will help when we try to work on latency). - VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submit_info.commandBufferCount = (uint32_t)numCmdBufs; - submit_info.pCommandBuffers = cmdBufs; - res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE); - if (res == VK_ERROR_DEVICE_LOST) { - _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan"); - } else { - _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res)); - } - numCmdBufs = 0; - - frameData.hasInitCommands = false; -} - void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { FrameData &frameData = frameData_[frame]; @@ -1326,48 +1252,11 @@ void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res)); } - SubmitInitCommands(frame); + // If any init commands left unsubmitted (like by a frame sync etc), submit it first. + frameData.SubmitInitCommands(vulkan_); // Submit the main and final cmdbuf, ending by signalling the fence. - - VkCommandBuffer cmdBufs[2]; - int numCmdBufs = 0; - - cmdBufs[numCmdBufs++] = frameData.mainCmd; - if (frameData.hasPresentCommands) { - cmdBufs[numCmdBufs++] = frameData.presentCmd; - } - - VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - if (triggerFrameFence && !frameData.skipSwap) { - submit_info.waitSemaphoreCount = 1; - submit_info.pWaitSemaphores = &acquireSemaphore_; - submit_info.pWaitDstStageMask = waitStage; - } - submit_info.commandBufferCount = (uint32_t)numCmdBufs; - submit_info.pCommandBuffers = cmdBufs; - if (triggerFrameFence && !frameData.skipSwap) { - submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &renderingCompleteSemaphore_; - } - res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, triggerFrameFence ? frameData.fence : frameData.readbackFence); - if (res == VK_ERROR_DEVICE_LOST) { - _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan"); - } else { - _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main)! result=%s", VulkanResultToString(res)); - } - - // When !triggerFence, we notify after syncing with Vulkan. - if (useThread_ && triggerFrameFence) { - VLOG("PULL: Frame %d.readyForFence = true", frame); - std::unique_lock lock(frameData.push_mutex); - frameData.readyForFence = true; - frameData.push_condVar.notify_all(); - } - - frameData.hasInitCommands = false; - frameData.hasPresentCommands = false; + frameData.SubmitMainFinal(vulkan_, triggerFrameFence, frameDataShared_); } void VulkanRenderManager::EndSubmitFrame(int frame) { @@ -1382,7 +1271,7 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { present.swapchainCount = 1; present.pSwapchains = &swapchain; present.pImageIndices = &frameData.curSwapchainImage; - present.pWaitSemaphores = &renderingCompleteSemaphore_; + present.pWaitSemaphores = &frameDataShared_.renderingCompleteSemaphore; present.waitSemaphoreCount = 1; _dbg_assert_(frameData.hasAcquired); @@ -1414,7 +1303,7 @@ void VulkanRenderManager::Run(int frame) { FrameData &frameData = frameData_[frame]; queueRunner_.PreprocessSteps(frameData_[frame].steps); //queueRunner_.LogSteps(stepsOnThread, false); - queueRunner_.RunSteps(frameData); + queueRunner_.RunSteps(frameData, frameDataShared_); switch (frameData.type) { case VKRRunType::END: @@ -1456,7 +1345,7 @@ void VulkanRenderManager::EndSyncFrame(int frame) { VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); _assert_(res == VK_SUCCESS); - if (useThread_) { + if (frameDataShared_.useThread) { std::unique_lock lock(frameData.push_mutex); frameData.readyForFence = true; frameData.push_condVar.notify_all(); @@ -1468,7 +1357,7 @@ void VulkanRenderManager::FlushSync() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - if (!useThread_) { + if (!frameDataShared_.useThread) { frameData.steps = std::move(steps_); steps_.clear(); frameData.type = VKRRunType::SYNC; @@ -1484,7 +1373,7 @@ void VulkanRenderManager::FlushSync() { frameData.pull_condVar.notify_all(); } - if (useThread_) { + if (frameDataShared_.useThread) { std::unique_lock lock(frameData.push_mutex); // Wait for the flush to be hit, since we're syncing. while (!frameData.readyForFence) { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index a91669ce74..66d9da9902 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -65,10 +65,6 @@ private: std::string tag_; }; -enum { - MAX_TIMESTAMP_QUERIES = 128, -}; - struct BoundingRect { int x1; int y1; @@ -476,9 +472,7 @@ private: void StopThread(); - // Permanent objects - VkSemaphore acquireSemaphore_; - VkSemaphore renderingCompleteSemaphore_; + FrameDataShared frameDataShared_; FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; int newInflightFrames_ = -1; @@ -525,6 +519,4 @@ private: // pipelines to check and possibly create at the end of the current render pass. std::vector pipelinesToCheck_; - - bool useThread_ = true; }; From cfe4e3278d182ab57153258efd8908ea997e1d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Sep 2022 18:17:26 +0200 Subject: [PATCH 084/326] Vulkan: Remove the useThread bool entirely. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 2 +- Common/GPU/Vulkan/VulkanFrameData.h | 1 - Common/GPU/Vulkan/VulkanRenderManager.cpp | 146 ++++++++++------------ 3 files changed, 65 insertions(+), 84 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 897f2e72bf..97d86a8b8b 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -124,7 +124,7 @@ void FrameData::SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, F } // When !triggerFence, we notify after syncing with Vulkan. - if (sharedData.useThread && triggerFrameFence) { + if (triggerFrameFence) { VERBOSE_LOG(G3D, "PULL: Frame %d.readyForFence = true", index); std::unique_lock lock(push_mutex); readyForFence = true; diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index a57a14a340..98183c3603 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -30,7 +30,6 @@ struct FrameDataShared { // Permanent objects VkSemaphore acquireSemaphore = VK_NULL_HANDLE; VkSemaphore renderingCompleteSemaphore = VK_NULL_HANDLE; - bool useThread = true; void Init(VulkanContext *vulkan); void Destroy(VulkanContext *vulkan); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 9d4ac610e1..a1c2e67c89 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -297,12 +297,6 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan } queueRunner_.CreateDeviceObjects(); - - // AMD hack for issue #10097 (older drivers only.) - const auto &props = vulkan_->GetPhysicalDeviceProperties().properties; - if (props.vendorID == VULKAN_VENDOR_AMD && props.apiVersion < VK_API_VERSION_1_1) { - frameDataShared_.useThread = false; - } } bool VulkanRenderManager::CreateBackbuffers() { @@ -334,7 +328,7 @@ bool VulkanRenderManager::CreateBackbuffers() { outOfDateFrames_ = 0; // Start the thread. - if (frameDataShared_.useThread && HasBackbuffers()) { + if (HasBackbuffers()) { run_ = true; // Won't necessarily be 0. threadInitFrame_ = vulkan_->GetCurFrame(); @@ -347,57 +341,58 @@ bool VulkanRenderManager::CreateBackbuffers() { } void VulkanRenderManager::StopThread() { - if (frameDataShared_.useThread && run_) { - run_ = false; - // Stop the thread. - for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { - auto &frameData = frameData_[i]; - { - std::unique_lock lock(frameData.push_mutex); - frameData.push_condVar.notify_all(); - } - { - std::unique_lock lock(frameData.pull_mutex); - frameData.pull_condVar.notify_all(); - } - // Zero the queries so we don't try to pull them later. - frameData.profile.timestampDescriptions.clear(); - } - thread_.join(); - INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); - compileCond_.notify_all(); - compileThread_.join(); - INFO_LOG(G3D, "Vulkan compiler thread joined."); - - // Eat whatever has been queued up for this frame if anything. - Wipe(); - - // Wait for any fences to finish and be resignaled, so we don't have sync issues. - // Also clean out any queued data, which might refer to things that might not be valid - // when we restart... - for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { - auto &frameData = frameData_[i]; - _assert_(!frameData.readyForRun); - _assert_(frameData.steps.empty()); - if (frameData.hasInitCommands) { - // Clear 'em out. This can happen on restart sometimes. - vkEndCommandBuffer(frameData.initCmd); - frameData.hasInitCommands = false; - } - frameData.readyForRun = false; - for (size_t i = 0; i < frameData.steps.size(); i++) { - delete frameData.steps[i]; - } - frameData.steps.clear(); - - std::unique_lock lock(frameData.push_mutex); - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); - frameData.push_condVar.wait(lock); - } - } - } else { + if (!run_) { INFO_LOG(G3D, "Vulkan submission thread was already stopped."); + return; + } + + run_ = false; + // Stop the thread. + for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { + auto &frameData = frameData_[i]; + { + std::unique_lock lock(frameData.push_mutex); + frameData.push_condVar.notify_all(); + } + { + std::unique_lock lock(frameData.pull_mutex); + frameData.pull_condVar.notify_all(); + } + // Zero the queries so we don't try to pull them later. + frameData.profile.timestampDescriptions.clear(); + } + thread_.join(); + INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); + compileCond_.notify_all(); + compileThread_.join(); + INFO_LOG(G3D, "Vulkan compiler thread joined."); + + // Eat whatever has been queued up for this frame if anything. + Wipe(); + + // Wait for any fences to finish and be resignaled, so we don't have sync issues. + // Also clean out any queued data, which might refer to things that might not be valid + // when we restart... + for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { + auto &frameData = frameData_[i]; + _assert_(!frameData.readyForRun); + _assert_(frameData.steps.empty()); + if (frameData.hasInitCommands) { + // Clear 'em out. This can happen on restart sometimes. + vkEndCommandBuffer(frameData.initCmd); + frameData.hasInitCommands = false; + } + frameData.readyForRun = false; + for (size_t i = 0; i < frameData.steps.size(); i++) { + delete frameData.steps[i]; + } + frameData.steps.clear(); + + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); + frameData.push_condVar.wait(lock); + } } } @@ -522,14 +517,12 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile FrameData &frameData = frameData_[curFrame]; // Make sure the very last command buffer from the frame before the previous has been fully executed. - if (frameDataShared_.useThread) { - std::unique_lock lock(frameData.push_mutex); - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); - frameData.push_condVar.wait(lock); - } - frameData.readyForFence = false; + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); + frameData.push_condVar.wait(lock); } + frameData.readyForFence = false; VLOG("PUSH: Fencing %d", curFrame); @@ -1194,12 +1187,7 @@ void VulkanRenderManager::Finish() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - if (!frameDataShared_.useThread) { - frameData.steps = std::move(steps_); - steps_.clear(); - frameData.type = VKRRunType::END; - Run(curFrame); - } else { + { std::unique_lock lock(frameData.pull_mutex); VLOG("PUSH: Frame[%d].readyForRun = true", curFrame); frameData.steps = std::move(steps_); @@ -1345,11 +1333,9 @@ void VulkanRenderManager::EndSyncFrame(int frame) { VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); _assert_(res == VK_SUCCESS); - if (frameDataShared_.useThread) { - std::unique_lock lock(frameData.push_mutex); - frameData.readyForFence = true; - frameData.push_condVar.notify_all(); - } + std::unique_lock lock(frameData.push_mutex); + frameData.readyForFence = true; + frameData.push_condVar.notify_all(); } void VulkanRenderManager::FlushSync() { @@ -1357,12 +1343,8 @@ void VulkanRenderManager::FlushSync() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - if (!frameDataShared_.useThread) { - frameData.steps = std::move(steps_); - steps_.clear(); - frameData.type = VKRRunType::SYNC; - Run(curFrame); - } else { + + { std::unique_lock lock(frameData.pull_mutex); VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame); frameData.steps = std::move(steps_); @@ -1373,7 +1355,7 @@ void VulkanRenderManager::FlushSync() { frameData.pull_condVar.notify_all(); } - if (frameDataShared_.useThread) { + { std::unique_lock lock(frameData.push_mutex); // Wait for the flush to be hit, since we're syncing. while (!frameData.readyForFence) { From 2160882e95b16da474230d1f16786e9154625679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Sep 2022 19:27:50 +0200 Subject: [PATCH 085/326] Address feedback --- Common/GPU/Vulkan/VulkanFrameData.cpp | 3 ++- Common/GPU/Vulkan/VulkanFrameData.h | 4 ++-- Common/GPU/Vulkan/VulkanRenderManager.cpp | 14 ++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 97d86a8b8b..416c86999b 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -1,7 +1,8 @@ #include "VulkanFrameData.h" #include "Common/Log.h" -void FrameData::Init(VulkanContext *vulkan) { +void FrameData::Init(VulkanContext *vulkan, int index) { + this->index = index; VkDevice device = vulkan->GetDevice(); VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 98183c3603..d3c04ba8e4 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -74,10 +74,10 @@ struct FrameData { QueueProfileContext profile; bool profilingEnabled_; - // Metadaata + // Metadata for logging etc int index; - void Init(VulkanContext *vulkan); + void Init(VulkanContext *vulkan, int index); void Destroy(VulkanContext *vulkan); void AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index a1c2e67c89..d0b3777520 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -293,7 +293,7 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan frameDataShared_.Init(vulkan); for (int i = 0; i < inflightFramesAtStart_; i++) { - frameData_[i].Init(vulkan); + frameData_[i].Init(vulkan, i); } queueRunner_.CreateDeviceObjects(); @@ -517,12 +517,14 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile FrameData &frameData = frameData_[curFrame]; // Make sure the very last command buffer from the frame before the previous has been fully executed. - std::unique_lock lock(frameData.push_mutex); - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); - frameData.push_condVar.wait(lock); + { + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); + frameData.push_condVar.wait(lock); + } + frameData.readyForFence = false; } - frameData.readyForFence = false; VLOG("PUSH: Fencing %d", curFrame); From fddcbfc5fa5783e8dd12bce5bdc9682f0d1f35f7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 19 Sep 2022 12:01:34 -0700 Subject: [PATCH 086/326] GE Debugger: Include rendered CLUTs in frame dumps. Like with textures, this uses the VRAM address directly. --- GPU/Common/TextureCacheCommon.cpp | 8 +++- GPU/Debugger/Playback.cpp | 48 +++++++++++++++++---- GPU/Debugger/Record.cpp | 72 +++++++++++++++++++++---------- GPU/Debugger/RecordFormat.h | 1 + 4 files changed, 97 insertions(+), 32 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index cba174d416..952776c076 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -37,6 +37,7 @@ #include "GPU/Common/ShaderId.h" #include "GPU/Common/GPUStateUtils.h" #include "GPU/Debugger/Debugger.h" +#include "GPU/Debugger/Record.h" #include "GPU/GPUCommon.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" @@ -1267,14 +1268,17 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { // It's possible for a game to load CLUT outside valid memory without crashing, should result in zeroes. u32 bytes = Memory::ValidSize(clutAddr, loadBytes); - if (clutRenderAddress_ != 0xFFFFFFFF && PSP_CoreParameter().compat.flags().AllowDownloadCLUT) { + bool performDownload = PSP_CoreParameter().compat.flags().AllowDownloadCLUT; + if (GPURecord::IsActive()) + performDownload = true; + if (clutRenderAddress_ != 0xFFFFFFFF && performDownload) { framebufferManager_->DownloadFramebufferForClut(clutRenderAddress_, clutRenderOffset_ + bytes); Memory::MemcpyUnchecked(clutBufRaw_, clutAddr, bytes); if (bytes < loadBytes) { memset((u8 *)clutBufRaw_ + bytes, 0x00, loadBytes - bytes); } } else { - // Here we could check for clutRenderAddres_ != 0xFFFFFFFF and zero the CLUT or something, + // Here we could check for clutRenderAddress_ != 0xFFFFFFFF and zero the CLUT or something, // but choosing not to for now. Though the results of loading the CLUT from RAM here is // almost certainly going to be bogus. #ifdef _M_SSE diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index aa34ce3ccf..c47eba66b3 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -298,6 +298,7 @@ private: void Registers(u32 ptr, u32 sz); void Vertices(u32 ptr, u32 sz); void Indices(u32 ptr, u32 sz); + void ClutAddr(u32 ptr, u32 sz); void Clut(u32 ptr, u32 sz); void TransferSrc(u32 ptr, u32 sz); void Memset(u32 ptr, u32 sz); @@ -308,6 +309,8 @@ private: void Display(u32 ptr, u32 sz); u32 execMemcpyDest = 0; + u32 execClutAddr = 0; + u32 execClutFlags = 0; u32 execListBuf = 0; u32 execListPos = 0; u32 execListID = 0; @@ -472,15 +475,40 @@ void DumpExecute::Indices(u32 ptr, u32 sz) { execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF)); } -void DumpExecute::Clut(u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for clut"); - return; - } +void DumpExecute::ClutAddr(u32 ptr, u32 sz) { + struct ClutAddrData { + u32 addr; + u32 flags; + }; + const ClutAddrData *data = (const ClutAddrData *)(pushbuf_.data() + ptr); + execClutAddr = data->addr; + execClutFlags = data->flags; +} - execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); +void DumpExecute::Clut(u32 ptr, u32 sz) { + // This is always run when we have the actual address set. + if (execClutAddr != 0) { + const bool isTarget = (execClutFlags & 1) != 0; + const bool unchangedVRAM = (execClutFlags & 2) != 0; + + // TODO: Could use drawnVRAM flag, but it can be wrong. + // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior. + if (Memory::IsValidRange(execClutAddr, sz) && !unchangedVRAM && (!isTarget || !g_Config.bSoftwareRendering)) { + // Intentionally don't trigger an upload here. + Memory::MemcpyUnchecked(execClutAddr, pushbuf_.data() + ptr, sz); + } + + execClutAddr = 0; + } else { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for clut"); + return; + } + + execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); + } } void DumpExecute::TransferSrc(u32 ptr, u32 sz) { @@ -619,6 +647,10 @@ bool DumpExecute::Run() { Indices(cmd.ptr, cmd.sz); break; + case CommandType::CLUTADDR: + ClutAddr(cmd.ptr, cmd.sz); + break; + case CommandType::CLUT: Clut(cmd.ptr, cmd.sz); break; diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index d4a1838787..248ba1e48c 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -320,6 +320,34 @@ static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 alig return cmd; } +static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) { + const bool isTarget = lastRenderTargets.find(addr) != lastRenderTargets.end(); + + bool isDirtyVRAM = false; + bool isDrawnVRAM = false; + uint32_t start = (addr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK; + for (uint32_t i = 0; i < (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; ++i) { + DirtyVRAMFlag flag = dirtyVRAM[start + i]; + isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN; + isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN; + + // Mark the VRAM clean now that it's been copied to VRAM. + if (flag == DirtyVRAMFlag::DIRTY) + dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN; + } + + // The isTarget flag is mostly used for replay of dumps on a PSP. + u32 flags = isTarget ? 1 : 0; + // The unchangedVRAM flag tells us we can skip recopying. + if (!isDirtyVRAM) + flags |= 2; + // And the drawn flag tells us this data was potentially drawn to. + if (isDrawnVRAM) + flags |= 4; + + return flags; +} + static void EmitTextureData(int level, u32 texaddr) { GETextureFormat format = gstate.getTextureFormat(); int w = gstate.getTextureWidth(level); @@ -327,7 +355,6 @@ static void EmitTextureData(int level, u32 texaddr) { int bufw = GetTextureBufw(level, texaddr, format); int extraw = w > bufw ? w - bufw : 0; u32 sizeInRAM = (textureBitsPerPixel[format] * (bufw * h + extraw)) / 8; - const bool isTarget = lastRenderTargets.find(texaddr) != lastRenderTargets.end(); CommandType type = CommandType((int)CommandType::TEXTURE0 + level); const u8 *p = Memory::GetPointerUnchecked(texaddr); @@ -342,27 +369,7 @@ static void EmitTextureData(int level, u32 texaddr) { u32 pad; }; - bool isDirtyVRAM = false; - bool isDrawnVRAM = false; - uint32_t start = (texaddr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK; - for (uint32_t i = 0; i < (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; ++i) { - DirtyVRAMFlag flag = dirtyVRAM[start + i]; - isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN; - isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN; - - // Mark the VRAM clean now that it's been copied to VRAM. - if (flag == DirtyVRAMFlag::DIRTY) - dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN; - } - - // The isTarget flag is mostly used for replay of dumps on a PSP. - u32 flags = isTarget ? 1 : 0; - // The unchangedVRAM flag tells us we can skip recopying. - if (!isDirtyVRAM) - flags |= 2; - // And the drawn flag tells us this data was potentially drawn to. - if (isDrawnVRAM) - flags |= 4; + u32 flags = GetTargetFlags(texaddr, sizeInRAM); FramebufData framebuf{ texaddr, bufw, flags }; framebufData.resize(sizeof(framebuf) + bytes); memcpy(&framebufData[0], &framebuf, sizeof(framebuf)); @@ -468,12 +475,33 @@ static void EmitTransfer(u32 op) { static void EmitClut(u32 op) { u32 addr = gstate.getClutAddress(); + + // Hardware rendering may be using a framebuffer as CLUT. + // To get at this, we first run the command (normally we're called right before it has run.) + if (Memory::IsVRAMAddress(addr)) + gpuDebug->SetCmdValue(op); + // Actually should only be 0x3F, but we allow enhanced CLUTs. See #15727. u32 blocks = (op & 0x7F) == 0x40 ? 0x40 : (op & 0x3F); u32 bytes = blocks * 32; bytes = Memory::ValidSize(addr, bytes); if (bytes != 0) { + // Send the original address so VRAM can be reasoned about. + if (Memory::IsVRAMAddress(addr)) { + struct ClutAddrData { + u32 addr; + u32 flags; + }; + u32 flags = GetTargetFlags(addr, bytes); + ClutAddrData data{ addr, flags }; + + FlushRegisters(); + Command cmd{CommandType::CLUTADDR, sizeof(data), (u32)pushbuf.size()}; + pushbuf.resize(pushbuf.size() + sizeof(data)); + memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data)); + commands.push_back(cmd); + } EmitCommandWithRAM(CommandType::CLUT, Memory::GetPointerUnchecked(addr), bytes, 16); } diff --git a/GPU/Debugger/RecordFormat.h b/GPU/Debugger/RecordFormat.h index 8079136d0c..dc7abe3ec1 100644 --- a/GPU/Debugger/RecordFormat.h +++ b/GPU/Debugger/RecordFormat.h @@ -49,6 +49,7 @@ enum class CommandType : u8 { MEMCPYDEST = 7, MEMCPYDATA = 8, DISPLAY = 9, + CLUTADDR = 10, TEXTURE0 = 0x10, TEXTURE1 = 0x11, From 7580844ea5040be64caa7a85512dfaeaaf6e4fc9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 00:07:38 -0700 Subject: [PATCH 087/326] Kernel: Fix save states for volatile memory alloc. --- Core/HLE/sceKernelMemory.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 7c604710b4..f76658425f 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -459,13 +459,14 @@ void __KernelMemoryInit() void __KernelMemoryDoState(PointerWrap &p) { - auto s = p.Section("sceKernelMemory", 1, 2); + auto s = p.Section("sceKernelMemory", 1, 3); if (!s) return; kernelMemory.DoState(p); userMemory.DoState(p); - volatileMemory.DoState(p); + if (s >= 3) + volatileMemory.DoState(p); Do(p, vplWaitTimer); CoreTiming::RestoreRegisterEvent(vplWaitTimer, "VplTimeout", __KernelVplTimeout); From 8772c6e2f8a2148da241140529b61d287bd0b77d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 00:31:01 -0700 Subject: [PATCH 088/326] Kernel: Check sceKernelAllocPartitionMemory args. This is the order you get error codes back in if there are multiple errors. --- Core/HLE/sceKernelMemory.cpp | 79 ++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index f76658425f..4bbc7ac767 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -504,6 +504,31 @@ void __KernelMemoryShutdown() MemBlockInfoShutdown(); } +static BlockAllocator *BlockAllocatorFromID(int id) { + switch (id) { + case 1: + case 3: + case 4: + if (hleIsKernelMode()) + return &kernelMemory; + return nullptr; + + case 2: + case 6: + case 8: + case 10: + return &userMemory; + + case 5: + return &volatileMemory; + + default: + break; + } + + return nullptr; +} + enum SceKernelFplAttr { PSP_FPL_ATTR_FIFO = 0x0000, @@ -995,44 +1020,28 @@ static u32 sceKernelTotalFreeMemSize() return retVal; } -int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32 size, u32 addr) -{ - if (name == NULL) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid name", SCE_KERNEL_ERROR_ERROR); - return SCE_KERNEL_ERROR_ERROR; - } - if (size == 0) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid size %x", SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED, size); - return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED; +int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32 size, u32 addr) { + if (type < PSP_SMEM_Low || type > PSP_SMEM_HighAligned) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE, "invalid type %x", type); + // Alignment is only allowed for powers of 2. + if (type == PSP_SMEM_LowAligned || type == PSP_SMEM_HighAligned) { + if ((addr & (addr - 1)) != 0 || addr == 0) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, "invalid alignment %x", addr); } if (partition < 1 || partition > 9 || partition == 7) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid partition %x", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } - // We only support user right now. - if (partition != 2 && partition != 5 && partition != 6) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid partition %x", SCE_KERNEL_ERROR_ILLEGAL_PARTITION, partition); - return SCE_KERNEL_ERROR_ILLEGAL_PARTITION; - } - if (type < PSP_SMEM_Low || type > PSP_SMEM_HighAligned) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid type %x", SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE, type); - return SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE; - } - // Alignment is only allowed for powers of 2. - if ((type == PSP_SMEM_LowAligned || type == PSP_SMEM_HighAligned) && ((addr & (addr - 1)) != 0 || addr == 0)) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid alignment %x", SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, addr); - return SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %x", partition); - PartitionMemoryBlock *block = new PartitionMemoryBlock((partition == 5) ? &volatileMemory : &userMemory, name, size, (MemblockType)type, addr); - if (!block->IsValid()) - { + BlockAllocator *allocator = BlockAllocatorFromID(partition); + if (allocator == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PARTITION, "invalid partition %x", partition); + + if (name == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ERROR, "invalid name"); + if (size == 0) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED, "invalid size %x", size); + + PartitionMemoryBlock *block = new PartitionMemoryBlock(allocator, name, size, (MemblockType)type, addr); + if (!block->IsValid()) { delete block; ERROR_LOG(SCEKERNEL, "sceKernelAllocPartitionMemory(partition = %i, %s, type= %i, size= %i, addr= %08x): allocation failed", partition, name, type, size, addr); return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED; From ef5eecce560296719dc5dfecd113bc1a55dbfd2f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 00:37:21 -0700 Subject: [PATCH 089/326] Kernel: Load volatile memblocks from save states. Otherwise they'd reset to userMemory. --- Core/HLE/sceKernelMemory.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 4bbc7ac767..1a6e16c0fa 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -529,6 +529,16 @@ static BlockAllocator *BlockAllocatorFromID(int id) { return nullptr; } +static int BlockAllocatorToID(const BlockAllocator *alloc) { + if (alloc == &kernelMemory) + return 1; + if (alloc == &userMemory) + return 2; + if (alloc == &volatileMemory) + return 5; + return 0; +} + enum SceKernelFplAttr { PSP_FPL_ATTR_FIFO = 0x0000, @@ -989,18 +999,23 @@ public: alloc->Free(address); } bool IsValid() {return address != (u32)-1;} - BlockAllocator *alloc; void DoState(PointerWrap &p) override { - auto s = p.Section("PMB", 1); + auto s = p.Section("PMB", 1, 2); if (!s) return; Do(p, address); DoArray(p, name, sizeof(name)); + if (s >= 2) { + int allocType = BlockAllocatorToID(alloc); + Do(p, allocType); + alloc = BlockAllocatorFromID(allocType); + } } + BlockAllocator *alloc; u32 address; char name[32]; }; From 1ae7c0132cecc5d6d0d9e1b440f65ec72330ea1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 10:02:15 +0200 Subject: [PATCH 090/326] Start unifying setting of the GPU feature flags, now that thin3d has feature detection. --- Common/GPU/D3D11/thin3d_d3d11.cpp | 1 + Common/GPU/D3D9/thin3d_d3d9.cpp | 23 ++++++++++++++++++--- Common/GPU/OpenGL/thin3d_gl.cpp | 2 ++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 2 ++ Common/GPU/thin3d.h | 1 + GPU/D3D11/GPU_D3D11.cpp | 28 +++++-------------------- GPU/D3D11/GPU_D3D11.h | 2 +- GPU/Directx9/GPU_DX9.cpp | 32 +++++------------------------ GPU/Directx9/GPU_DX9.h | 2 +- GPU/GLES/GPU_GLES.cpp | 29 +++++--------------------- GPU/GLES/GPU_GLES.h | 2 +- GPU/GPUCommon.cpp | 27 ++++++++++++++++++++++++ GPU/GPUCommon.h | 2 +- GPU/Software/SoftGpu.h | 2 +- GPU/Vulkan/GPU_Vulkan.cpp | 29 ++++++-------------------- GPU/Vulkan/GPU_Vulkan.h | 2 +- 16 files changed, 80 insertions(+), 106 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index c17bb39919..a808386ce9 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -270,6 +270,7 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de caps_.anisoSupported = true; caps_.textureNPOTFullySupported = true; caps_.fragmentShaderDepthWriteSupported = true; + caps_.blendMinMaxSupported = true; D3D11_FEATURE_DATA_D3D11_OPTIONS options{}; HRESULT result = device_->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options)); diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index a84d42f64e..502d2c06db 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -665,14 +665,24 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID caps_.vendor = GPUVendor::VENDOR_UNKNOWN; } - if (!FAILED(device->GetDeviceCaps(&d3dCaps_))) { + D3DCAPS9 caps; + ZeroMemory(&caps, sizeof(caps)); + HRESULT result = 0; + if (deviceEx_) { + result = deviceEx_->GetDeviceCaps(&caps); + } else { + result = device_->GetDeviceCaps(&caps); + } + + if (SUCCEEDED(result)) { sprintf(shadeLangVersion_, "PS: %04x VS: %04x", d3dCaps_.PixelShaderVersion & 0xFFFF, d3dCaps_.VertexShaderVersion & 0xFFFF); } else { + WARN_LOG(G3D, "Direct3D9: Failed to get the device caps!"); strcpy(shadeLangVersion_, "N/A"); } + caps_.deviceID = identifier_.DeviceId; caps_.multiViewport = false; - caps_.anisoSupported = true; caps_.depthRangeMinusOneToOne = false; caps_.preferredDepthBufferFormat = DataFormat::D24_S8; caps_.dualSourceBlend = false; @@ -684,8 +694,15 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID caps_.framebufferDepthCopySupported = false; caps_.framebufferSeparateDepthCopySupported = false; caps_.texture3DSupported = true; - caps_.textureNPOTFullySupported = true; caps_.fragmentShaderDepthWriteSupported = true; + caps_.blendMinMaxSupported = true; + + if ((caps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && caps.MaxAnisotropy > 1) { + caps_.anisoSupported = true; + } + if ((caps.TextureCaps & (D3DPTEXTURECAPS_NONPOW2CONDITIONAL | D3DPTEXTURECAPS_POW2)) == 0) { + caps_.textureNPOTFullySupported = true; + } if (d3d) { D3DDISPLAYMODE displayMode; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index b2e6320455..dfe7f95c6f 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -552,6 +552,8 @@ OpenGLContext::OpenGLContext() { caps_.framebufferDepthBlitSupported = caps_.framebufferBlitSupported; caps_.framebufferStencilBlitSupported = caps_.framebufferBlitSupported; caps_.depthClampSupported = gl_extensions.ARB_depth_clamp; + caps_.blendMinMaxSupported = gl_extensions.EXT_blend_minmax; + if (gl_extensions.IsGLES) { caps_.clipDistanceSupported = gl_extensions.EXT_clip_cull_distance || gl_extensions.APPLE_clip_distance; caps_.cullDistanceSupported = gl_extensions.EXT_clip_cull_distance; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index d64fd7ae7d..34bbdd5efb 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -788,9 +788,11 @@ VKContext::VKContext(VulkanContext *vulkan) caps_.fragmentShaderInt32Supported = true; caps_.textureNPOTFullySupported = true; caps_.fragmentShaderDepthWriteSupported = true; + caps_.blendMinMaxSupported = true; caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.logicOp != 0; auto deviceProps = vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDeviceIndex()).properties; + switch (deviceProps.vendorID) { case VULKAN_VENDOR_AMD: caps_.vendor = GPUVendor::VENDOR_AMD; break; case VULKAN_VENDOR_ARM: caps_.vendor = GPUVendor::VENDOR_ARM; break; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index b4eca9d0bc..0fba6ee6f2 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -547,6 +547,7 @@ struct DeviceCaps { bool textureNPOTFullySupported; bool fragmentShaderDepthWriteSupported; bool textureDepthSupported; + bool blendMinMaxSupported; std::string deviceName; // The device name to use when creating the thin3d context, to get the same one. }; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index 3cfe0c4b37..bd4d131e98 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -82,7 +82,7 @@ GPU_D3D11::GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw) // No need to flush before the tex scale/offset commands if we are baking // the tex scale/offset into the vertices anyway. UpdateCmdInfo(); - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); BuildReportingInfo(); @@ -100,25 +100,15 @@ GPU_D3D11::~GPU_D3D11() { stockD3D11.Destroy(); } -void GPU_D3D11::CheckGPUFeatures() { - u32 features = 0; - - features |= GPU_SUPPORTS_BLEND_MINMAX; +u32 GPU_D3D11::CheckGPUFeatures() const { + u32 features = GPUCommon::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. // TODO: Breaks text in PaRappa for some reason? features |= GPU_SUPPORTS_ACCURATE_DEPTH; -#ifndef _M_ARM - // TODO: Do proper feature detection - features |= GPU_SUPPORTS_ANISOTROPY; -#endif - features |= GPU_SUPPORTS_DEPTH_TEXTURE; - features |= GPU_SUPPORTS_TEXTURE_NPOT; - if (draw_->GetDeviceCaps().dualSourceBlend) - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; if (draw_->GetDeviceCaps().depthClampSupported) features |= GPU_SUPPORTS_DEPTH_CLAMP; if (draw_->GetDeviceCaps().clipDistanceSupported) @@ -146,10 +136,6 @@ void GPU_D3D11::CheckGPUFeatures() { features |= GPU_SUPPORTS_16BIT_FORMATS; } - if (draw_->GetDeviceCaps().logicOpSupported) { - features |= GPU_SUPPORTS_LOGIC_OP; - } - if (!g_Config.bHighQualityDepth && (features & GPU_SUPPORTS_ACCURATE_DEPTH) != 0) { features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; } else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) { @@ -164,11 +150,7 @@ void GPU_D3D11::CheckGPUFeatures() { features |= GPU_USE_DEPTH_RANGE_HACK; } - if (PSP_CoreParameter().compat.flags().ClearToRAM) { - features |= GPU_USE_CLEAR_RAM_HACK; - } - - gstate_c.featureFlags = features; + return features; } // Needs to be called on GPU thread, not reporting thread. @@ -206,7 +188,7 @@ void GPU_D3D11::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); if (resized_) { - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); framebufferManager_->Resized(); drawEngine_.Resized(); textureCache_->NotifyConfigChanged(); diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index d1e5287782..e45c1ab79d 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -36,7 +36,7 @@ public: GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_D3D11(); - void CheckGPUFeatures() override; + u32 CheckGPUFeatures() const override; void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 3e5d0bac57..0ec02a3595 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -81,7 +81,7 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) // No need to flush before the tex scale/offset commands if we are baking // the tex scale/offset into the vertices anyway. UpdateCmdInfo(); - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); BuildReportingInfo(); @@ -155,10 +155,9 @@ static NVIDIAGeneration NVIDIAGetDeviceGeneration(int deviceID) { return NV_PRE_KEPLER; } -void GPU_DX9::CheckGPUFeatures() { - u32 features = 0; +u32 GPU_DX9::CheckGPUFeatures() const { + u32 features = GPUCommon::CheckGPUFeatures(); features |= GPU_SUPPORTS_16BIT_FORMATS; - features |= GPU_SUPPORTS_BLEND_MINMAX; features |= GPU_SUPPORTS_DEPTH_TEXTURE; features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; @@ -187,23 +186,6 @@ void GPU_DX9::CheckGPUFeatures() { } } - D3DCAPS9 caps; - ZeroMemory(&caps, sizeof(caps)); - HRESULT result = 0; - if (deviceEx_) { - result = deviceEx_->GetDeviceCaps(&caps); - } else { - result = device_->GetDeviceCaps(&caps); - } - if (FAILED(result)) { - WARN_LOG_REPORT(G3D, "Direct3D9: Failed to get the device caps!"); - } else { - if ((caps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && caps.MaxAnisotropy > 1) - features |= GPU_SUPPORTS_ANISOTROPY; - if ((caps.TextureCaps & (D3DPTEXTURECAPS_NONPOW2CONDITIONAL | D3DPTEXTURECAPS_POW2)) == 0) - features |= GPU_SUPPORTS_TEXTURE_NPOT; - } - if (!g_Config.bHighQualityDepth) { features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; } else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) { @@ -213,11 +195,7 @@ void GPU_DX9::CheckGPUFeatures() { features |= GPU_ROUND_DEPTH_TO_16BIT; } - if (PSP_CoreParameter().compat.flags().ClearToRAM) { - features |= GPU_USE_CLEAR_RAM_HACK; - } - - gstate_c.featureFlags = features; + return features; } GPU_DX9::~GPU_DX9() { @@ -261,7 +239,7 @@ void GPU_DX9::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); if (resized_) { - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); framebufferManager_->Resized(); drawEngine_.Resized(); shaderManagerDX9_->DirtyShader(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 0e313da397..7c6a88d180 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -35,7 +35,7 @@ public: GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~GPU_DX9(); - void CheckGPUFeatures() override; + u32 CheckGPUFeatures() const override; void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 75fd0080b5..7d9ce963a3 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -54,7 +54,7 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw) { UpdateVsyncInterval(true); - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); shaderManagerGL_ = new ShaderManagerGLES(draw); framebufferManagerGL_ = new FramebufferManagerGLES(draw); @@ -148,17 +148,11 @@ GPU_GLES::~GPU_GLES() { // 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. -void GPU_GLES::CheckGPUFeatures() { - u32 features = 0; +u32 GPU_GLES::CheckGPUFeatures() const { + u32 features = GPUCommon::CheckGPUFeatures(); features |= GPU_SUPPORTS_16BIT_FORMATS; - if (draw_->GetDeviceCaps().dualSourceBlend) { - if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) { - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; - } - } - if (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch) { // This has caused problems in the past. Let's only enable on GLES3. if (gl_extensions.GLES3) { @@ -169,15 +163,6 @@ void GPU_GLES::CheckGPUFeatures() { if ((gl_extensions.gpuVendor == GPU_VENDOR_NVIDIA) || (gl_extensions.gpuVendor == GPU_VENDOR_AMD)) features |= GPU_PREFER_REVERSE_COLOR_ORDER; - if (draw_->GetDeviceCaps().textureNPOTFullySupported) - features |= GPU_SUPPORTS_TEXTURE_NPOT; - - if (gl_extensions.EXT_blend_minmax) - features |= GPU_SUPPORTS_BLEND_MINMAX; - - if (draw_->GetDeviceCaps().logicOpSupported) - features |= GPU_SUPPORTS_LOGIC_OP; - if (gl_extensions.GLES3 || !gl_extensions.IsGLES) features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; @@ -245,11 +230,7 @@ void GPU_GLES::CheckGPUFeatures() { features |= GPU_USE_DEPTH_RANGE_HACK; } - if (PSP_CoreParameter().compat.flags().ClearToRAM) { - features |= GPU_USE_CLEAR_RAM_HACK; - } - - gstate_c.featureFlags = features; + return features; } bool GPU_GLES::IsReady() { @@ -321,7 +302,7 @@ void GPU_GLES::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); if (resized_) { - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); framebufferManager_->Resized(); drawEngine_.Resized(); shaderManagerGL_->DirtyShader(); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index cbe6bc00b0..98bb1d9362 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -38,7 +38,7 @@ public: ~GPU_GLES(); // This gets called on startup and when we get back from settings. - void CheckGPUFeatures() override; + u32 CheckGPUFeatures() const override; bool IsReady() override; void CancelReady() override; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 6d73d1d532..ea63b7a02b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3153,3 +3153,30 @@ size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) { vertexAverageCycles ); } + +u32 GPUCommon::CheckGPUFeatures() const { + u32 features = 0; + if (draw_->GetDeviceCaps().logicOpSupported) { + features |= GPU_SUPPORTS_LOGIC_OP; + } + if (draw_->GetDeviceCaps().anisoSupported) { + features |= GPU_SUPPORTS_ANISOTROPY; + } + if (draw_->GetDeviceCaps().textureNPOTFullySupported) { + features |= GPU_SUPPORTS_TEXTURE_NPOT; + } + if (draw_->GetDeviceCaps().dualSourceBlend) { + if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) { + features |= GPU_SUPPORTS_DUALSOURCE_BLEND; + } + } + if (draw_->GetDeviceCaps().blendMinMaxSupported) { + features |= GPU_SUPPORTS_BLEND_MINMAX; + } + + if (PSP_CoreParameter().compat.flags().ClearToRAM) { + features |= GPU_USE_CLEAR_RAM_HACK; + } + + return features; +} diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index cddb1ccdc1..1127e38d91 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -76,7 +76,7 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } - virtual void CheckGPUFeatures() = 0; + virtual u32 CheckGPUFeatures() const; void UpdateCmdInfo(); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 3ce69dcc18..e90f7c0fb8 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -127,7 +127,7 @@ public: SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw); ~SoftGPU(); - void CheckGPUFeatures() override {} + u32 CheckGPUFeatures() const override { return 0; } void InitClear() override {} void ExecuteOp(u32 op, u32 diff) override; void FinishDeferred() override; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index eed7c8b9d5..636f852666 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -52,7 +52,7 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw), drawEngine_(draw) { - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); VulkanContext *vulkan = (VulkanContext *)gfxCtx->GetAPIContext(); @@ -182,8 +182,8 @@ GPU_Vulkan::~GPU_Vulkan() { delete framebufferManagerVulkan_; } -void GPU_Vulkan::CheckGPUFeatures() { - uint32_t features = 0; +u32 GPU_Vulkan::CheckGPUFeatures() const { + uint32_t features = GPUCommon::CheckGPUFeatures(); VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT); switch (vulkan->GetPhysicalDeviceProperties().properties.vendorID) { @@ -222,8 +222,6 @@ void GPU_Vulkan::CheckGPUFeatures() { // Mandatory features on Vulkan, which may be checked in "centralized" code features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; - features |= GPU_SUPPORTS_BLEND_MINMAX; - features |= GPU_SUPPORTS_TEXTURE_NPOT; features |= GPU_SUPPORTS_INSTANCE_RENDERING; features |= GPU_SUPPORTS_VERTEX_TEXTURE_FETCH; features |= GPU_SUPPORTS_TEXTURE_FLOAT; @@ -254,17 +252,6 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_VS_RANGE_CULLING; } } - if (enabledFeatures.dualSrcBlend) { - if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) { - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; - } - } - if (draw_->GetDeviceCaps().logicOpSupported) { - features |= GPU_SUPPORTS_LOGIC_OP; - } - if (draw_->GetDeviceCaps().anisoSupported) { - features |= GPU_SUPPORTS_ANISOTROPY; - } // These are VULKAN_4444_FORMAT and friends. uint32_t fmt4444 = draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16); @@ -280,10 +267,6 @@ void GPU_Vulkan::CheckGPUFeatures() { INFO_LOG(G3D, "Deficient texture format support: 4444: %d 1555: %d 565: %d", fmt4444, fmt1555, fmt565); } - if (PSP_CoreParameter().compat.flags().ClearToRAM) { - features |= GPU_USE_CLEAR_RAM_HACK; - } - if (!g_Config.bHighQualityDepth && (features & GPU_SUPPORTS_ACCURATE_DEPTH) != 0) { features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; } @@ -295,7 +278,7 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_ROUND_DEPTH_TO_16BIT; } - gstate_c.featureFlags = features; + return features; } void GPU_Vulkan::BeginHostFrame() { @@ -303,7 +286,7 @@ void GPU_Vulkan::BeginHostFrame() { UpdateCmdInfo(); if (resized_) { - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); // In case the GPU changed. BuildReportingInfo(); framebufferManager_->Resized(); @@ -542,7 +525,7 @@ void GPU_Vulkan::DeviceRestore() { GPUCommon::DeviceRestore(); InitDeviceObjects(); - CheckGPUFeatures(); + gstate_c.featureFlags = CheckGPUFeatures(); BuildReportingInfo(); UpdateCmdInfo(); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 3c13d57621..3fbd329aae 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -38,7 +38,7 @@ public: ~GPU_Vulkan(); // This gets called on startup and when we get back from settings. - void CheckGPUFeatures() override; + u32 CheckGPUFeatures() const override; bool IsReady() override; void CancelReady() override; From 09bcf3ec13064608ff14feb06d404de4ab38e38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 10:15:04 +0200 Subject: [PATCH 091/326] Unify range culling detection --- Common/GPU/D3D9/thin3d_d3d9.cpp | 72 +++++++++++++++++++++++++++++++ GPU/D3D11/GPU_D3D11.cpp | 13 ------ GPU/Directx9/GPU_DX9.cpp | 75 --------------------------------- GPU/GLES/GPU_GLES.cpp | 16 ------- GPU/GPUCommon.cpp | 18 ++++++++ GPU/Vulkan/GPU_Vulkan.cpp | 16 ------- 6 files changed, 90 insertions(+), 120 deletions(-) diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 502d2c06db..bf33fd34ca 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -646,6 +646,63 @@ void D3D9Context::InvalidateCachedState() { curPipeline_ = nullptr; } +// TODO: Move this detection elsewhere when it's needed elsewhere, not before. It's ugly. +// Source: https://envytools.readthedocs.io/en/latest/hw/pciid.html#gf100 +enum NVIDIAGeneration { + NV_PRE_KEPLER, + NV_KEPLER, + NV_MAXWELL, + NV_PASCAL, + NV_VOLTA, + NV_TURING, // or later +}; + +static NVIDIAGeneration NVIDIAGetDeviceGeneration(int deviceID) { + if (deviceID >= 0x1180 && deviceID <= 0x11bf) + return NV_KEPLER; // GK104 + if (deviceID >= 0x11c0 && deviceID <= 0x11fa) + return NV_KEPLER; // GK106 + if (deviceID >= 0x0fc0 && deviceID <= 0x0fff) + return NV_KEPLER; // GK107 + if (deviceID >= 0x1003 && deviceID <= 0x1028) + return NV_KEPLER; // GK110(B) + if (deviceID >= 0x1280 && deviceID <= 0x12ba) + return NV_KEPLER; // GK208 + if (deviceID >= 0x1381 && deviceID <= 0x13b0) + return NV_MAXWELL; // GM107 + if (deviceID >= 0x1340 && deviceID <= 0x134d) + return NV_MAXWELL; // GM108 + if (deviceID >= 0x13c0 && deviceID <= 0x13d9) + return NV_MAXWELL; // GM204 + if (deviceID >= 0x1401 && deviceID <= 0x1427) + return NV_MAXWELL; // GM206 + if (deviceID >= 0x15f7 && deviceID <= 0x15f9) + return NV_PASCAL; // GP100 + if (deviceID >= 0x15f7 && deviceID <= 0x15f9) + return NV_PASCAL; // GP100 + if (deviceID >= 0x1b00 && deviceID <= 0x1b38) + return NV_PASCAL; // GP102 + if (deviceID >= 0x1b80 && deviceID <= 0x1be1) + return NV_PASCAL; // GP104 + if (deviceID >= 0x1c02 && deviceID <= 0x1c62) + return NV_PASCAL; // GP106 + if (deviceID >= 0x1c81 && deviceID <= 0x1c92) + return NV_PASCAL; // GP107 + if (deviceID >= 0x1d01 && deviceID <= 0x1d12) + return NV_PASCAL; // GP108 + if (deviceID >= 0x1d81 && deviceID <= 0x1dba) + return NV_VOLTA; // GV100 + if (deviceID >= 0x1e02 && deviceID <= 0x1e3c) + return NV_TURING; // TU102 + if (deviceID >= 0x1e82 && deviceID <= 0x1ed0) + return NV_TURING; // TU104 + if (deviceID >= 0x1f02 && deviceID <= 0x1f51) + return NV_TURING; // TU104 + if (deviceID >= 0x1e02) + return NV_TURING; // More TU models or later, probably. + return NV_PRE_KEPLER; +} + #define FB_DIV 1 #define FOURCC_INTZ ((D3DFORMAT)(MAKEFOURCC('I', 'N', 'T', 'Z'))) @@ -704,6 +761,21 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID caps_.textureNPOTFullySupported = true; } + // VS range culling (killing triangles in the vertex shader using NaN) causes problems on Intel. + // Also causes problems on old NVIDIA. + switch (caps_.vendor) { + case Draw::GPUVendor::VENDOR_INTEL: + bugs_.Infest(Bugs::BROKEN_NAN_IN_CONDITIONAL); + break; + case Draw::GPUVendor::VENDOR_NVIDIA: + // Older NVIDIAs don't seem to like NaNs in their DX9 vertex shaders. + // No idea if KEPLER is the right cutoff, but let's go with it. + if (NVIDIAGetDeviceGeneration(caps_.deviceID) < NV_KEPLER) { + bugs_.Infest(Bugs::BROKEN_NAN_IN_CONDITIONAL); + } + break; + } + if (d3d) { D3DDISPLAYMODE displayMode; d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode); diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index bd4d131e98..a5a35e3c12 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -111,19 +111,6 @@ u32 GPU_D3D11::CheckGPUFeatures() const { features |= GPU_SUPPORTS_DEPTH_TEXTURE; if (draw_->GetDeviceCaps().depthClampSupported) features |= GPU_SUPPORTS_DEPTH_CLAMP; - if (draw_->GetDeviceCaps().clipDistanceSupported) - features |= GPU_SUPPORTS_CLIP_DISTANCE; - if (draw_->GetDeviceCaps().cullDistanceSupported) - features |= GPU_SUPPORTS_CULL_DISTANCE; - if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - // Ignore the compat setting if clip and cull are both enabled. - // When supported, we can do the depth side of range culling more correctly. - const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; - const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; - if (supported || !disabled) { - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - } - } features |= GPU_SUPPORTS_TEXTURE_FLOAT; features |= GPU_SUPPORTS_INSTANCE_RENDERING; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 0ec02a3595..f11286a29d 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -98,63 +98,6 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) } } -// TODO: Move this detection elsewhere when it's needed elsewhere, not before. It's ugly. -// Source: https://envytools.readthedocs.io/en/latest/hw/pciid.html#gf100 -enum NVIDIAGeneration { - NV_PRE_KEPLER, - NV_KEPLER, - NV_MAXWELL, - NV_PASCAL, - NV_VOLTA, - NV_TURING, // or later -}; - -static NVIDIAGeneration NVIDIAGetDeviceGeneration(int deviceID) { - if (deviceID >= 0x1180 && deviceID <= 0x11bf) - return NV_KEPLER; // GK104 - if (deviceID >= 0x11c0 && deviceID <= 0x11fa) - return NV_KEPLER; // GK106 - if (deviceID >= 0x0fc0 && deviceID <= 0x0fff) - return NV_KEPLER; // GK107 - if (deviceID >= 0x1003 && deviceID <= 0x1028) - return NV_KEPLER; // GK110(B) - if (deviceID >= 0x1280 && deviceID <= 0x12ba) - return NV_KEPLER; // GK208 - if (deviceID >= 0x1381 && deviceID <= 0x13b0) - return NV_MAXWELL; // GM107 - if (deviceID >= 0x1340 && deviceID <= 0x134d) - return NV_MAXWELL; // GM108 - if (deviceID >= 0x13c0 && deviceID <= 0x13d9) - return NV_MAXWELL; // GM204 - if (deviceID >= 0x1401 && deviceID <= 0x1427) - return NV_MAXWELL; // GM206 - if (deviceID >= 0x15f7 && deviceID <= 0x15f9) - return NV_PASCAL; // GP100 - if (deviceID >= 0x15f7 && deviceID <= 0x15f9) - return NV_PASCAL; // GP100 - if (deviceID >= 0x1b00 && deviceID <= 0x1b38) - return NV_PASCAL; // GP102 - if (deviceID >= 0x1b80 && deviceID <= 0x1be1) - return NV_PASCAL; // GP104 - if (deviceID >= 0x1c02 && deviceID <= 0x1c62) - return NV_PASCAL; // GP106 - if (deviceID >= 0x1c81 && deviceID <= 0x1c92) - return NV_PASCAL; // GP107 - if (deviceID >= 0x1d01 && deviceID <= 0x1d12) - return NV_PASCAL; // GP108 - if (deviceID >= 0x1d81 && deviceID <= 0x1dba) - return NV_VOLTA; // GV100 - if (deviceID >= 0x1e02 && deviceID <= 0x1e3c) - return NV_TURING; // TU102 - if (deviceID >= 0x1e82 && deviceID <= 0x1ed0) - return NV_TURING; // TU104 - if (deviceID >= 0x1f02 && deviceID <= 0x1f51) - return NV_TURING; // TU104 - if (deviceID >= 0x1e02) - return NV_TURING; // More TU models or later, probably. - return NV_PRE_KEPLER; -} - u32 GPU_DX9::CheckGPUFeatures() const { u32 features = GPUCommon::CheckGPUFeatures(); features |= GPU_SUPPORTS_16BIT_FORMATS; @@ -167,24 +110,6 @@ u32 GPU_DX9::CheckGPUFeatures() const { features |= GPU_SUPPORTS_ACCURATE_DEPTH; auto vendor = draw_->GetDeviceCaps().vendor; - if (!PSP_CoreParameter().compat.flags().DisableRangeCulling) { - // VS range culling (killing triangles in the vertex shader using NaN) causes problems on Intel. - // Also causes problems on old NVIDIA. - switch (vendor) { - case Draw::GPUVendor::VENDOR_INTEL: - break; - case Draw::GPUVendor::VENDOR_NVIDIA: - // Older NVIDIAs don't seem to like NaNs in their DX9 vertex shaders. - // No idea if KEPLER is the right cutoff, but let's go with it. - if (NVIDIAGetDeviceGeneration(draw_->GetDeviceCaps().deviceID) >= NV_KEPLER) { - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - } - break; - default: - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - break; - } - } if (!g_Config.bHighQualityDepth) { features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 7d9ce963a3..d394407721 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -166,9 +166,6 @@ u32 GPU_GLES::CheckGPUFeatures() const { if (gl_extensions.GLES3 || !gl_extensions.IsGLES) features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; - if (draw_->GetDeviceCaps().anisoSupported) - features |= GPU_SUPPORTS_ANISOTROPY; - bool canUseInstanceID = gl_extensions.EXT_draw_instanced || gl_extensions.ARB_draw_instanced; bool canDefInstanceID = gl_extensions.IsGLES || gl_extensions.EXT_gpu_shader4 || gl_extensions.VersionGEThan(3, 1); bool instanceRendering = gl_extensions.GLES3 || (canUseInstanceID && canDefInstanceID); @@ -189,19 +186,6 @@ u32 GPU_GLES::CheckGPUFeatures() const { } if (draw_->GetDeviceCaps().textureDepthSupported) features |= GPU_SUPPORTS_DEPTH_TEXTURE; - if (draw_->GetDeviceCaps().clipDistanceSupported) - features |= GPU_SUPPORTS_CLIP_DISTANCE; - if (draw_->GetDeviceCaps().cullDistanceSupported) - features |= GPU_SUPPORTS_CULL_DISTANCE; - if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - // Ignore the compat setting if clip and cull are both enabled. - // When supported, we can do the depth side of range culling more correctly. - const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; - const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; - if (supported || !disabled) { - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - } - } // 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.cpp b/GPU/GPUCommon.cpp index ea63b7a02b..a3f1d5cd7d 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3174,6 +3174,24 @@ u32 GPUCommon::CheckGPUFeatures() const { features |= GPU_SUPPORTS_BLEND_MINMAX; } + if (draw_->GetDeviceCaps().clipDistanceSupported) { + features |= GPU_SUPPORTS_CLIP_DISTANCE; + } + + if (draw_->GetDeviceCaps().cullDistanceSupported) { + features |= GPU_SUPPORTS_CULL_DISTANCE; + } + + if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { + // Ignore the compat setting if clip and cull are both enabled. + // When supported, we can do the depth side of range culling more correctly. + const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; + const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; + if (supported || !disabled) { + features |= GPU_SUPPORTS_VS_RANGE_CULLING; + } + } + if (PSP_CoreParameter().compat.flags().ClearToRAM) { features |= GPU_USE_CLEAR_RAM_HACK; } diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 636f852666..1dca91ddcd 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -236,22 +236,6 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { if (enabledFeatures.depthClamp) { features |= GPU_SUPPORTS_DEPTH_CLAMP; } - if (enabledFeatures.shaderClipDistance) { - features |= GPU_SUPPORTS_CLIP_DISTANCE; - } - if (enabledFeatures.shaderCullDistance) { - // Must support at least 8 if feature supported, so we're fine. - features |= GPU_SUPPORTS_CULL_DISTANCE; - } - if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - // Ignore the compat setting if clip and cull are both enabled. - // When supported, we can do the depth side of range culling more correctly. - const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; - const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; - if (supported || !disabled) { - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - } - } // These are VULKAN_4444_FORMAT and friends. uint32_t fmt4444 = draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16); From aa19712fc3e718f843d7f91402dbb9db27ba5711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 10:47:49 +0200 Subject: [PATCH 092/326] Unify depth texture and framebuffer fetch checks --- Common/GPU/OpenGL/thin3d_gl.cpp | 4 +++- GPU/D3D11/GPU_D3D11.cpp | 1 - GPU/Directx9/GPU_DX9.cpp | 1 - GPU/GLES/GPU_GLES.cpp | 9 --------- GPU/GPUCommon.cpp | 8 ++++++++ GPU/Vulkan/GPU_Vulkan.cpp | 6 ------ 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index dfe7f95c6f..02decfe05c 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -713,8 +713,10 @@ OpenGLContext::OpenGLContext() { } } - if (gl_extensions.IsGLES) { + // NOTE: We only support framebuffer fetch on ES3 due to past issues.. + if (gl_extensions.IsGLES && gl_extensions.GLES3) { caps_.framebufferFetchSupported = (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch); + if (gl_extensions.EXT_shader_framebuffer_fetch) { shaderLanguageDesc_.framebufferFetchExtension = "#extension GL_EXT_shader_framebuffer_fetch : require"; shaderLanguageDesc_.lastFragData = gl_extensions.GLES3 ? "fragColor0" : "gl_LastFragData[0]"; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index a5a35e3c12..bfbd12f190 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -108,7 +108,6 @@ u32 GPU_D3D11::CheckGPUFeatures() const { // TODO: Breaks text in PaRappa for some reason? features |= GPU_SUPPORTS_ACCURATE_DEPTH; - features |= GPU_SUPPORTS_DEPTH_TEXTURE; if (draw_->GetDeviceCaps().depthClampSupported) features |= GPU_SUPPORTS_DEPTH_CLAMP; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index f11286a29d..075d89e460 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -101,7 +101,6 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) u32 GPU_DX9::CheckGPUFeatures() const { u32 features = GPUCommon::CheckGPUFeatures(); features |= GPU_SUPPORTS_16BIT_FORMATS; - features |= GPU_SUPPORTS_DEPTH_TEXTURE; features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; // Accurate depth is required because the Direct3D API does not support inverse Z. diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index d394407721..abae5134f9 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -153,13 +153,6 @@ u32 GPU_GLES::CheckGPUFeatures() const { features |= GPU_SUPPORTS_16BIT_FORMATS; - if (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch) { - // This has caused problems in the past. Let's only enable on GLES3. - if (gl_extensions.GLES3) { - features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; - } - } - if ((gl_extensions.gpuVendor == GPU_VENDOR_NVIDIA) || (gl_extensions.gpuVendor == GPU_VENDOR_AMD)) features |= GPU_PREFER_REVERSE_COLOR_ORDER; @@ -184,8 +177,6 @@ u32 GPU_GLES::CheckGPUFeatures() const { // Our implementation of depth texturing needs simple Z range, so can't // use the extension hacks (yet). } - if (draw_->GetDeviceCaps().textureDepthSupported) - features |= GPU_SUPPORTS_DEPTH_TEXTURE; // 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.cpp b/GPU/GPUCommon.cpp index a3f1d5cd7d..3d64bdb1ab 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3182,6 +3182,10 @@ u32 GPUCommon::CheckGPUFeatures() const { features |= GPU_SUPPORTS_CULL_DISTANCE; } + if (draw_->GetDeviceCaps().textureDepthSupported) { + features |= GPU_SUPPORTS_DEPTH_TEXTURE; + } + if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { // Ignore the compat setting if clip and cull are both enabled. // When supported, we can do the depth side of range culling more correctly. @@ -3192,6 +3196,10 @@ u32 GPUCommon::CheckGPUFeatures() const { } } + if (draw_->GetDeviceCaps().framebufferFetchSupported) { + features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; + } + if (PSP_CoreParameter().compat.flags().ClearToRAM) { features |= GPU_USE_CLEAR_RAM_HACK; } diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 1dca91ddcd..b38201c8c5 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -225,12 +225,6 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { features |= GPU_SUPPORTS_INSTANCE_RENDERING; features |= GPU_SUPPORTS_VERTEX_TEXTURE_FETCH; features |= GPU_SUPPORTS_TEXTURE_FLOAT; - features |= GPU_SUPPORTS_DEPTH_TEXTURE; - - // through input attachments, when not broken. - if (draw_->GetDeviceCaps().framebufferFetchSupported) { - features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; - } auto &enabledFeatures = vulkan->GetDeviceFeatures().enabled; if (enabledFeatures.depthClamp) { From c7322edf7b38be344758a3b6eba93d6389fae394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 14:49:39 +0200 Subject: [PATCH 093/326] Various refactoring --- Common/GPU/Vulkan/VulkanFrameData.cpp | 111 ++++++++++++++-------- Common/GPU/Vulkan/VulkanFrameData.h | 16 +++- Common/GPU/Vulkan/VulkanRenderManager.cpp | 64 ++++--------- Common/GPU/Vulkan/VulkanRenderManager.h | 2 +- 4 files changed, 104 insertions(+), 89 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 416c86999b..f5d26ef992 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -62,62 +62,100 @@ void FrameData::AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared) } } -void FrameData::SubmitInitCommands(VulkanContext *vulkan) { - if (!hasInitCommands) { - return; - } +VkResult FrameData::QueuePresent(VulkanContext *vulkan, FrameDataShared &shared) { + _dbg_assert_(hasAcquired); + hasAcquired = false; - if (profilingEnabled_) { - // Pre-allocated query ID 1. - vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1); - } + VkSwapchainKHR swapchain = vulkan->GetSwapchain(); + VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + present.swapchainCount = 1; + present.pSwapchains = &swapchain; + present.pImageIndices = &curSwapchainImage; + present.pWaitSemaphores = &shared.renderingCompleteSemaphore; + present.waitSemaphoreCount = 1; - VkResult res = vkEndCommandBuffer(initCmd); - _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); - - VkCommandBuffer cmdBufs[1]; - int numCmdBufs = 0; - - cmdBufs[numCmdBufs++] = initCmd; - // Send the init commands off separately, so they can be processed while we're building the rest of the list. - // (Likely the CPU will be more than a frame ahead anyway, but this will help when we try to work on latency). - VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submit_info.commandBufferCount = (uint32_t)numCmdBufs; - submit_info.pCommandBuffers = cmdBufs; - res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE); - if (res == VK_ERROR_DEVICE_LOST) { - _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan"); - } else { - _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res)); - } - numCmdBufs = 0; - - hasInitCommands = false; + return vkQueuePresentKHR(vulkan->GetGraphicsQueue(), &present); } -void FrameData::SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, FrameDataShared &sharedData) { +VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) { + if (!hasInitCommands) { + VkCommandBufferBeginInfo begin = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + vkResetCommandPool(vulkan->GetDevice(), cmdPoolInit, 0); + VkResult res = vkBeginCommandBuffer(initCmd, &begin); + if (res != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + hasInitCommands = true; + } + return initCmd; +} + +void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &sharedData) { VkCommandBuffer cmdBufs[2]; int numCmdBufs = 0; - cmdBufs[numCmdBufs++] = mainCmd; + VkFence fenceToTrigger = VK_NULL_HANDLE; + + if (hasInitCommands) { + if (profilingEnabled_) { + // Pre-allocated query ID 1 - end of init cmdbuf. + vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1); + } + + VkResult res = vkEndCommandBuffer(initCmd); + cmdBufs[numCmdBufs++] = initCmd; + + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res)); + hasInitCommands = false; + } + + if (hasMainCommands) { + VkResult res = vkEndCommandBuffer(mainCmd); + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res)); + + cmdBufs[numCmdBufs++] = mainCmd; + hasMainCommands = false; + + if (type == FrameSubmitType::Sync) { + fenceToTrigger = readbackFence; + } + } + if (hasPresentCommands) { + VkResult res = vkEndCommandBuffer(presentCmd); + _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res)); + cmdBufs[numCmdBufs++] = presentCmd; + hasPresentCommands = false; + + if (type == FrameSubmitType::Present) { + fenceToTrigger = fence; + } + } + + if (!numCmdBufs && fenceToTrigger == VK_NULL_HANDLE) { + // Nothing to do. + return; } VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - if (triggerFrameFence && !skipSwap) { + if (type == FrameSubmitType::Present && !skipSwap) { submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &sharedData.acquireSemaphore; submit_info.pWaitDstStageMask = waitStage; } submit_info.commandBufferCount = (uint32_t)numCmdBufs; submit_info.pCommandBuffers = cmdBufs; - if (triggerFrameFence && !skipSwap) { + if (type == FrameSubmitType::Present && !skipSwap) { submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &sharedData.renderingCompleteSemaphore; } - VkResult res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, triggerFrameFence ? fence : readbackFence); + VkResult res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); if (res == VK_ERROR_DEVICE_LOST) { _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan"); } else { @@ -125,15 +163,12 @@ void FrameData::SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, F } // When !triggerFence, we notify after syncing with Vulkan. - if (triggerFrameFence) { + if (type == FrameSubmitType::Present || type == FrameSubmitType::Sync) { VERBOSE_LOG(G3D, "PULL: Frame %d.readyForFence = true", index); std::unique_lock lock(push_mutex); readyForFence = true; push_condVar.notify_all(); } - - hasInitCommands = false; - hasPresentCommands = false; } void FrameDataShared::Init(VulkanContext *vulkan) { diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index d3c04ba8e4..764f681421 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -35,6 +35,12 @@ struct FrameDataShared { void Destroy(VulkanContext *vulkan); }; +enum class FrameSubmitType { + Pending, + Sync, + Present, +}; + // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct FrameData { std::mutex push_mutex; @@ -49,8 +55,7 @@ struct FrameData { VKRRunType type = VKRRunType::END; VkFence fence; - VkFence readbackFence; // Strictly speaking we might only need one of these. - bool readbackFenceUsed = false; + VkFence readbackFence; // Strictly speaking we might only need one global of these. // These are on different threads so need separate pools. VkCommandPool cmdPoolInit; // Written to from main thread @@ -61,7 +66,9 @@ struct FrameData { VkCommandBuffer presentCmd; bool hasInitCommands = false; + bool hasMainCommands = false; bool hasPresentCommands = false; + bool hasAcquired = false; std::vector steps; @@ -81,8 +88,9 @@ struct FrameData { void Destroy(VulkanContext *vulkan); void AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared); + VkResult QueuePresent(VulkanContext *vulkan, FrameDataShared &shared); + VkCommandBuffer GetInitCmd(VulkanContext *vulkan); // This will only submit if we are actually recording init commands. - void SubmitInitCommands(VulkanContext *vulkan); - void SubmitMainFinal(VulkanContext *vulkan, bool triggerFrameFence, FrameDataShared &shared); + void SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &shared); }; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index d0b3777520..3f1778bb7d 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -535,7 +535,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile // Can't set this until after the fence. frameData.profilingEnabled_ = enableProfiling; - frameData.readbackFenceUsed = false; uint64_t queryResults[MAX_TIMESTAMP_QUERIES]; @@ -600,21 +599,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile VkCommandBuffer VulkanRenderManager::GetInitCmd() { int curFrame = vulkan_->GetCurFrame(); - FrameData &frameData = frameData_[curFrame]; - if (!frameData.hasInitCommands) { - VkCommandBufferBeginInfo begin = { - VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - nullptr, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; - vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolInit, 0); - VkResult res = vkBeginCommandBuffer(frameData.initCmd, &begin); - if (res != VK_SUCCESS) { - return VK_NULL_HANDLE; - } - frameData.hasInitCommands = true; - } - return frameData_[curFrame].initCmd; + return frameData_[curFrame].GetInitCmd(vulkan_); } VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, uint32_t variantBitmask, const char *tag) { @@ -1210,13 +1195,18 @@ void VulkanRenderManager::Wipe() { steps_.clear(); } +// Called on the render thread. +// // Can be called multiple times with no bad side effects. This is so that we can either begin a frame the normal way, // or stop it in the middle for a synchronous readback, then start over again mostly normally but without repeating // the backbuffer image acquisition. void VulkanRenderManager::BeginSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; - frameData.SubmitInitCommands(vulkan_); + // Should only have at most the init command buffer pending here (that one came from the other thread). + _dbg_assert_(!frameData.hasMainCommands); + _dbg_assert_(!frameData.hasPresentCommands); + frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_); if (!frameData.hasBegun) { // Effectively resets both main and present command buffers, since they both live in this pool. @@ -1225,48 +1215,31 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); + frameData.hasMainCommands = true; _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); frameData.hasBegun = true; } } -void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) { +// Called on the render thread. +void VulkanRenderManager::Submit(int frame, FrameSubmitType submitType) { FrameData &frameData = frameData_[frame]; - VkResult res = vkEndCommandBuffer(frameData.mainCmd); - _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res)); - - if (frameData.hasPresentCommands) { - VkResult res = vkEndCommandBuffer(frameData.presentCmd); - _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res)); - } - - // If any init commands left unsubmitted (like by a frame sync etc), submit it first. - frameData.SubmitInitCommands(vulkan_); - // Submit the main and final cmdbuf, ending by signalling the fence. - frameData.SubmitMainFinal(vulkan_, triggerFrameFence, frameDataShared_); + // If any init commands left unsubmitted (like by a frame sync etc), they'll also tag along. + frameData.SubmitPending(vulkan_, submitType, frameDataShared_); } +// Called on the render thread. void VulkanRenderManager::EndSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; frameData.hasBegun = false; - Submit(frame, true); + Submit(frame, FrameSubmitType::Present); if (!frameData.skipSwap) { - VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); - VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; - present.swapchainCount = 1; - present.pSwapchains = &swapchain; - present.pImageIndices = &frameData.curSwapchainImage; - present.pWaitSemaphores = &frameDataShared_.renderingCompleteSemaphore; - present.waitSemaphoreCount = 1; - - _dbg_assert_(frameData.hasAcquired); - - VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); + VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_); if (res == VK_ERROR_OUT_OF_DATE_KHR) { // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above. // Do the increment. @@ -1279,7 +1252,6 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { // Success outOfDateFrames_ = 0; } - frameData.hasAcquired = false; } else { // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE. outOfDateFrames_++; @@ -1314,10 +1286,8 @@ void VulkanRenderManager::Run(int frame) { void VulkanRenderManager::EndSyncFrame(int frame) { FrameData &frameData = frameData_[frame]; - frameData.readbackFenceUsed = true; - // The submit will trigger the readbackFence. - Submit(frame, false); + Submit(frame, FrameSubmitType::Sync); // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback. vkWaitForFences(vulkan_->GetDevice(), 1, &frameData.readbackFence, true, UINT64_MAX); @@ -1331,8 +1301,10 @@ void VulkanRenderManager::EndSyncFrame(int frame) { nullptr, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; + _dbg_assert_(!frameData.hasPresentCommands); // Readbacks should happen before we try to submit any present commands. vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0); VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); + frameData.hasMainCommands = true; _assert_(res == VK_SUCCESS); std::unique_lock lock(frameData.push_mutex); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 66d9da9902..2fa7b95237 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -463,7 +463,7 @@ private: void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); - void Submit(int frame, bool triggerFence); + void Submit(int frame, FrameSubmitType submitType); void SubmitInitCommands(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). From b190c33cc7a5ecf2ceba81046803f45a0c47b69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 16:27:05 +0200 Subject: [PATCH 094/326] Rewrite some present logic for slightly more clarity. Fixes bad logic and a minor race condition. --- Common/GPU/OpenGL/GLRenderManager.h | 1 + Common/GPU/Vulkan/VulkanFrameData.cpp | 32 +++++++++-- Common/GPU/Vulkan/VulkanFrameData.h | 16 ++++-- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 1 - Common/GPU/Vulkan/VulkanRenderManager.cpp | 70 +++++++++++------------ 5 files changed, 71 insertions(+), 49 deletions(-) diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index c9889156b0..089c588aeb 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -1012,6 +1012,7 @@ private: bool readyForFence = true; bool readyForRun = false; bool readyForSubmit = false; + bool skipSwap = false; GLRRunType type = GLRRunType::END; diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index f5d26ef992..adac358f8f 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -49,22 +49,35 @@ void FrameData::Destroy(VulkanContext *vulkan) { } void FrameData::AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared) { + _dbg_assert_(!hasAcquired); + // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, shared.acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); - if (res == VK_SUBOPTIMAL_KHR) { + switch (res) { + case VK_SUCCESS: + hasAcquired = true; + break; + case VK_SUBOPTIMAL_KHR: + hasAcquired = true; // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something. WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring"); - } else if (res == VK_ERROR_OUT_OF_DATE_KHR) { - WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned - processing the frame, but not presenting"); + break; + case VK_ERROR_OUT_OF_DATE_KHR: + // We do not set hasAcquired here! + WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned from AcquireNextImage - processing the frame, but not presenting"); skipSwap = true; - } else { - _assert_msg_(res == VK_SUCCESS, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res)); + break; + default: + // Weird, shouldn't get any other values. Maybe lost device? + _assert_msg_(false, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res)); + break; } } VkResult FrameData::QueuePresent(VulkanContext *vulkan, FrameDataShared &shared) { _dbg_assert_(hasAcquired); hasAcquired = false; + _dbg_assert_(!skipSwap); VkSwapchainKHR swapchain = vulkan->GetSwapchain(); VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; @@ -145,6 +158,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; if (type == FrameSubmitType::Present && !skipSwap) { + _dbg_assert_(hasAcquired); submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &sharedData.acquireSemaphore; submit_info.pWaitDstStageMask = waitStage; @@ -162,11 +176,17 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main)! result=%s", VulkanResultToString(res)); } + if (type == FrameSubmitType::Sync) { + // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback. + vkWaitForFences(vulkan->GetDevice(), 1, &readbackFence, true, UINT64_MAX); + vkResetFences(vulkan->GetDevice(), 1, &readbackFence); + } + // When !triggerFence, we notify after syncing with Vulkan. if (type == FrameSubmitType::Present || type == FrameSubmitType::Sync) { VERBOSE_LOG(G3D, "PULL: Frame %d.readyForFence = true", index); std::unique_lock lock(push_mutex); - readyForFence = true; + readyForFence = true; // misnomer in sync mode! push_condVar.notify_all(); } } diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 764f681421..bc503bb18a 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -50,9 +50,8 @@ struct FrameData { std::condition_variable pull_condVar; bool readyForFence = true; - bool readyForRun = false; + bool readyForRun = false; // protected by pull_mutex bool skipSwap = false; - VKRRunType type = VKRRunType::END; VkFence fence; VkFence readbackFence; // Strictly speaking we might only need one global of these. @@ -81,9 +80,6 @@ struct FrameData { QueueProfileContext profile; bool profilingEnabled_; - // Metadata for logging etc - int index; - void Init(VulkanContext *vulkan, int index); void Destroy(VulkanContext *vulkan); @@ -93,4 +89,14 @@ struct FrameData { // This will only submit if we are actually recording init commands. void SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &shared); + + VKRRunType RunType() const { + return runType_; + } + + VKRRunType runType_ = VKRRunType::END; + +private: + // Metadata for logging etc + int index; }; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index dfe09ecaea..c3f78b810f 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -563,7 +563,6 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat if (!step.render.framebuffer) { // When stepping in the GE debugger, we can end up here multiple times in a "frame". if (!frameData.hasAcquired) { - frameData.hasAcquired = true; frameData.AcquireNextImage(vulkan_, frameDataShared); SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 3f1778bb7d..3e34c6f162 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -491,8 +491,7 @@ void VulkanRenderManager::ThreadFunc() { // but that created a race condition where frames could end up not finished properly on resize etc. // Only increment next time if we're done. - nextFrame = frameData.type == VKRRunType::END; - _dbg_assert_(frameData.type == VKRRunType::END || frameData.type == VKRRunType::SYNC); + nextFrame = frameData.RunType() == VKRRunType::END; } VLOG("PULL: Running frame %d", threadFrame); if (firstFrame) { @@ -658,6 +657,8 @@ void VulkanRenderManager::EndCurRenderStep() { curRenderStep_->render.colorStore, curRenderStep_->render.depthStore, curRenderStep_->render.stencilStore, }; RenderPassType rpType = RP_TYPE_COLOR_DEPTH; + // Save the accumulated pipeline flags so we can use that to configure the render pass. + // We'll often be able to avoid loading/saving the depth/stencil buffer. curRenderStep_->render.pipelineFlags = curPipelineFlags_; if (!curRenderStep_->render.framebuffer) { rpType = RP_TYPE_BACKBUFFER; @@ -665,12 +666,11 @@ void VulkanRenderManager::EndCurRenderStep() { // Not allowed on backbuffers. rpType = RP_TYPE_COLOR_DEPTH_INPUT; } + // TODO: Also add render pass types for depth/stencil-less. VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key); curRenderStep_->render.renderPassType = rpType; - // Save the accumulated pipeline flags so we can use that to configure the render pass. - // We'll often be able to avoid loading/saving the depth/stencil buffer. compileMutex_.lock(); bool needsCompile = false; for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_) { @@ -1162,6 +1162,9 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in } } +// Called on main thread. +// Sends the collected commands to the render thread. Submit-latency should be +// measured from here, probably. void VulkanRenderManager::Finish() { EndCurRenderStep(); @@ -1174,13 +1177,14 @@ void VulkanRenderManager::Finish() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; + { std::unique_lock lock(frameData.pull_mutex); VLOG("PUSH: Frame[%d].readyForRun = true", curFrame); frameData.steps = std::move(steps_); steps_.clear(); frameData.readyForRun = true; - frameData.type = VKRRunType::END; + frameData.runType_ = VKRRunType::END; frameData.pull_condVar.notify_all(); } vulkan_->EndFrame(); @@ -1204,7 +1208,6 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; // Should only have at most the init command buffer pending here (that one came from the other thread). - _dbg_assert_(!frameData.hasMainCommands); _dbg_assert_(!frameData.hasPresentCommands); frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_); @@ -1259,6 +1262,28 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { } } +void VulkanRenderManager::EndSyncFrame(int frame) { + FrameData &frameData = frameData_[frame]; + + // The submit will trigger the readbackFence, and wait. + Submit(frame, FrameSubmitType::Sync); + + // At this point we can resume filling the command buffers for the current frame since + // we know the device is idle - and thus all previously enqueued command buffers have been processed. + // No need to switch to the next frame number. + VkCommandBufferBeginInfo begin{ + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + _dbg_assert_(!frameData.hasPresentCommands); // Readbacks should happen before we try to submit any present commands. + frameData.hasBegun = false; // We're gonna record a new main command buffer. + + std::unique_lock lock(frameData.push_mutex); + frameData.readyForFence = true; + frameData.push_condVar.notify_all(); +} + void VulkanRenderManager::Run(int frame) { BeginSubmitFrame(frame); @@ -1267,7 +1292,7 @@ void VulkanRenderManager::Run(int frame) { //queueRunner_.LogSteps(stepsOnThread, false); queueRunner_.RunSteps(frameData, frameDataShared_); - switch (frameData.type) { + switch (frameData.runType_) { case VKRRunType::END: EndSubmitFrame(frame); break; @@ -1283,35 +1308,6 @@ void VulkanRenderManager::Run(int frame) { VLOG("PULL: Finished running frame %d", frame); } -void VulkanRenderManager::EndSyncFrame(int frame) { - FrameData &frameData = frameData_[frame]; - - // The submit will trigger the readbackFence. - Submit(frame, FrameSubmitType::Sync); - - // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback. - vkWaitForFences(vulkan_->GetDevice(), 1, &frameData.readbackFence, true, UINT64_MAX); - vkResetFences(vulkan_->GetDevice(), 1, &frameData.readbackFence); - - // At this point we can resume filling the command buffers for the current frame since - // we know the device is idle - and thus all previously enqueued command buffers have been processed. - // No need to switch to the next frame number. - VkCommandBufferBeginInfo begin{ - VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - nullptr, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; - _dbg_assert_(!frameData.hasPresentCommands); // Readbacks should happen before we try to submit any present commands. - vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0); - VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); - frameData.hasMainCommands = true; - _assert_(res == VK_SUCCESS); - - std::unique_lock lock(frameData.push_mutex); - frameData.readyForFence = true; - frameData.push_condVar.notify_all(); -} - void VulkanRenderManager::FlushSync() { renderStepOffset_ += (int)steps_.size(); @@ -1325,7 +1321,7 @@ void VulkanRenderManager::FlushSync() { steps_.clear(); frameData.readyForRun = true; _dbg_assert_(!frameData.readyForFence); - frameData.type = VKRRunType::SYNC; + frameData.runType_ = VKRRunType::SYNC; frameData.pull_condVar.notify_all(); } From c1d1c851165e90f78b4fd6b87ccce078ce0352d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 16:52:14 +0200 Subject: [PATCH 095/326] Minor code cleanups (hasBegun is now redundant, for example) --- Common/GPU/Vulkan/VulkanFrameData.h | 1 - Common/GPU/Vulkan/VulkanRenderManager.cpp | 31 +++++------------------ Common/GPU/Vulkan/VulkanRenderManager.h | 2 -- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index bc503bb18a..53de1e8f44 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -73,7 +73,6 @@ struct FrameData { std::vector steps; // Swapchain. - bool hasBegun = false; uint32_t curSwapchainImage = -1; // Profiling. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 3e34c6f162..3a0dde0312 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1211,8 +1211,9 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { _dbg_assert_(!frameData.hasPresentCommands); frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_); - if (!frameData.hasBegun) { + if (!frameData.hasMainCommands) { // Effectively resets both main and present command buffers, since they both live in this pool. + // We always record main commands first, so we don't need to reset the present command buffer separately. vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0); VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; @@ -1220,26 +1221,14 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); frameData.hasMainCommands = true; _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); - - frameData.hasBegun = true; } } -// Called on the render thread. -void VulkanRenderManager::Submit(int frame, FrameSubmitType submitType) { - FrameData &frameData = frameData_[frame]; - - // Submit the main and final cmdbuf, ending by signalling the fence. - // If any init commands left unsubmitted (like by a frame sync etc), they'll also tag along. - frameData.SubmitPending(vulkan_, submitType, frameDataShared_); -} - // Called on the render thread. void VulkanRenderManager::EndSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; - frameData.hasBegun = false; - Submit(frame, FrameSubmitType::Present); + frameData.SubmitPending(vulkan_, FrameSubmitType::Present, frameDataShared_); if (!frameData.skipSwap) { VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_); @@ -1265,20 +1254,12 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { void VulkanRenderManager::EndSyncFrame(int frame) { FrameData &frameData = frameData_[frame]; - // The submit will trigger the readbackFence, and wait. - Submit(frame, FrameSubmitType::Sync); + // The submit will trigger the readbackFence, and also do the wait for it. + frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_); // At this point we can resume filling the command buffers for the current frame since // we know the device is idle - and thus all previously enqueued command buffers have been processed. - // No need to switch to the next frame number. - VkCommandBufferBeginInfo begin{ - VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - nullptr, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; - _dbg_assert_(!frameData.hasPresentCommands); // Readbacks should happen before we try to submit any present commands. - frameData.hasBegun = false; // We're gonna record a new main command buffer. - + // No need to switch to the next frame number, would just be confusing. std::unique_lock lock(frameData.push_mutex); frameData.readyForFence = true; frameData.push_condVar.notify_all(); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 2fa7b95237..43cbbb7b86 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -463,8 +463,6 @@ private: void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); - void Submit(int frame, FrameSubmitType submitType); - void SubmitInitCommands(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void FlushSync(); From fca9b77bd225a919c144e3e474200cd0c1597ea9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 08:31:44 -0700 Subject: [PATCH 096/326] Kernel: Allow allocating FPLs in volatile memory. --- Core/HLE/sceKernel.cpp | 4 +-- Core/HLE/sceKernelMemory.cpp | 67 ++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index cb858c0e90..48a07d7c22 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -840,7 +840,7 @@ const HLEFunction ThreadManForUser[] = {0X1D371B8A, &WrapI_IU, "sceKernelCancelVpl", 'i', "ix" }, {0X39810265, &WrapI_IU, "sceKernelReferVplStatus", 'i', "ip" }, - {0XC07BB470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sxxxxx" }, + {0XC07BB470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sixxxp" }, {0XED1410E0, &WrapI_I, "sceKernelDeleteFpl", 'i', "i" }, {0XD979E9BF, &WrapI_IUU, "sceKernelAllocateFpl", 'i', "ixx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, {0XE7282CB6, &WrapI_IUU, "sceKernelAllocateFplCB", 'i', "ixx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, @@ -908,7 +908,7 @@ const HLEFunction ThreadManForKernel[] = {0x1fb15a32, &WrapU_IU, "sceKernelSetEventFlag", 'x', "ix", HLE_KERNEL_SYSCALL }, {0x812346e4, &WrapU_IU, "sceKernelClearEventFlag", 'x', "ix", HLE_KERNEL_SYSCALL }, {0x402fcf22, &WrapI_IUUUU, "sceKernelWaitEventFlag", 'i', "ixxpp", HLE_NOT_IN_INTERRUPT | HLE_KERNEL_SYSCALL}, - {0xc07bb470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sxxxxx" ,HLE_KERNEL_SYSCALL }, + {0xc07bb470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sixxxp" ,HLE_KERNEL_SYSCALL }, {0xed1410e0, &WrapI_I, "sceKernelDeleteFpl", 'i', "i" ,HLE_KERNEL_SYSCALL }, {0x623ae665, &WrapI_IU, "sceKernelTryAllocateFpl", 'i', "ix" ,HLE_KERNEL_SYSCALL }, {0x616403ba, &WrapI_I, "sceKernelTerminateThread", 'i', "i" ,HLE_KERNEL_SYSCALL }, diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 1a6e16c0fa..5575bb97d5 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -539,6 +539,17 @@ static int BlockAllocatorToID(const BlockAllocator *alloc) { return 0; } +static BlockAllocator *BlockAllocatorFromAddr(u32 addr) { + addr &= 0x3FFFFFFF; + if (Memory::IsKernelAndNotVolatileAddress(addr)) + return &kernelMemory; + if (Memory::IsKernelAddress(addr)) + return &volatileMemory; + if (Memory::IsRAMAddress(addr)) + return &userMemory; + return nullptr; +} + enum SceKernelFplAttr { PSP_FPL_ATTR_FIFO = 0x0000, @@ -624,29 +635,18 @@ static void __KernelSortFplThreads(FPL *fpl) std::stable_sort(fpl->waitingThreads.begin(), fpl->waitingThreads.end(), __FplThreadSortPriority); } -int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr) -{ +int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr) { if (!name) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name"); if (mpid < 1 || mpid > 9 || mpid == 7) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, mpid); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } - // We only support user right now. - if (mpid != 2 && mpid != 6) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, mpid); - return SCE_KERNEL_ERROR_ILLEGAL_PERM; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", mpid); + + BlockAllocator *allocator = BlockAllocatorFromID(mpid); + if (allocator == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", mpid); if (((attr & ~PSP_FPL_ATTR_KNOWN) & ~0xFF) != 0) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr); - return SCE_KERNEL_ERROR_ILLEGAL_ATTR; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr); + // There's probably a simpler way to get this same basic formula... // This is based on results from a PSP. bool illegalMemSize = blockSize == 0 || numBlocks == 0; @@ -655,25 +655,16 @@ int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 if (!illegalMemSize && (u64) numBlocks >= 0x100000000ULL / (((u64) blockSize + 3ULL) & ~3ULL)) illegalMemSize = true; if (illegalMemSize) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid blockSize/count", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE); - return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE; - } + return hleReportWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid blockSize/count"); int alignment = 4; - if (optPtr != 0) - { - u32 size = Memory::Read_U32(optPtr); - if (size > 8) - WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateFpl(): unsupported extra options, size = %d", size); + if (Memory::IsValidRange(optPtr, 4)) { + u32 size = Memory::ReadUnchecked_U32(optPtr); if (size >= 4) alignment = Memory::Read_U32(optPtr + 4); // Must be a power of 2 to be valid. if ((alignment & (alignment - 1)) != 0) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid alignment %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, alignment); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid alignment %d", alignment); } if (alignment < 4) @@ -682,9 +673,8 @@ int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 int alignedSize = ((int)blockSize + alignment - 1) & ~(alignment - 1); u32 totalSize = alignedSize * numBlocks; bool atEnd = (attr & PSP_FPL_ATTR_HIGHMEM) != 0; - u32 address = userMemory.Alloc(totalSize, atEnd, "FPL"); - if (address == (u32)-1) - { + u32 address = allocator->Alloc(totalSize, atEnd, "FPL"); + if (address == (u32)-1) { DEBUG_LOG(SCEKERNEL, "sceKernelCreateFpl(\"%s\", partition=%i, attr=%08x, bsize=%i, nb=%i) FAILED - out of ram", name, mpid, attr, blockSize, numBlocks); return SCE_KERNEL_ERROR_NO_MEMORY; @@ -726,7 +716,10 @@ int sceKernelDeleteFpl(SceUID uid) if (wokeThreads) hleReSchedule("fpl deleted"); - userMemory.Free(fpl->address); + BlockAllocator *alloc = BlockAllocatorFromAddr(fpl->address); + _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address"); + if (alloc) + alloc->Free(fpl->address); return kernelObjects.Destroy(uid); } else From fff080202f4420f7645337e47f4e0e87e5693bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 17:48:19 +0200 Subject: [PATCH 097/326] When taking screenshots, make sure that the readback command happens on the right cmdbuf. --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index c3f78b810f..d2442ae7c7 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -549,6 +549,8 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat bool emitLabels = vulkan_->Extensions().EXT_debug_utils; + VkCommandBuffer cmd = frameData.hasPresentCommands ? frameData.presentCmd : frameData.mainCmd; + for (size_t i = 0; i < frameData.steps.size(); i++) { const VKRStep &step = *frameData.steps[i]; @@ -562,42 +564,46 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat case VKRStepType::RENDER: if (!step.render.framebuffer) { // When stepping in the GE debugger, we can end up here multiple times in a "frame". + // So only acquire once. if (!frameData.hasAcquired) { frameData.AcquireNextImage(vulkan_, frameDataShared); SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); } + + // A RENDER step rendering to the backbuffer is normally the last step that happens in a frame, + // unless taking a screenshot, in which case there might be a READBACK_IMAGE after it. + // This is why we have to switch cmd to presentCmd, in this case. VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(frameData.presentCmd, &begin); frameData.hasPresentCommands = true; - PerformRenderPass(step, frameData.presentCmd); - } else { - PerformRenderPass(step, frameData.mainCmd); + cmd = frameData.presentCmd; } + PerformRenderPass(step, cmd); break; case VKRStepType::COPY: - PerformCopy(step, frameData.mainCmd); + PerformCopy(step, cmd); break; case VKRStepType::BLIT: - PerformBlit(step, frameData.mainCmd); + PerformBlit(step, cmd); break; case VKRStepType::READBACK: - PerformReadback(step, frameData.mainCmd); + PerformReadback(step, cmd); break; case VKRStepType::READBACK_IMAGE: - PerformReadbackImage(step, frameData.mainCmd); + PerformReadbackImage(step, cmd); break; case VKRStepType::RENDER_SKIP: break; } if (profile && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) { - vkCmdWriteTimestamp(frameData.mainCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); + vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size()); profile->timestampDescriptions.push_back(StepToString(step)); } if (emitLabels) { - vkCmdEndDebugUtilsLabelEXT(frameData.mainCmd); + vkCmdEndDebugUtilsLabelEXT(cmd); } } From f6cb6c76951b98ad1ab8911b32117387db1e401d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 17:52:35 +0200 Subject: [PATCH 098/326] Submit main command buffer before acquire. Fix sync bug. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 8 ++++---- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index adac358f8f..2c9c2487fc 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -126,16 +126,16 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame hasInitCommands = false; } + if ((hasMainCommands || hasPresentCommands) && type == FrameSubmitType::Sync) { + fenceToTrigger = readbackFence; + } + if (hasMainCommands) { VkResult res = vkEndCommandBuffer(mainCmd); _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res)); cmdBufs[numCmdBufs++] = mainCmd; hasMainCommands = false; - - if (type == FrameSubmitType::Sync) { - fenceToTrigger = readbackFence; - } } if (hasPresentCommands) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index d2442ae7c7..8127114148 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -563,6 +563,8 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat switch (step.stepType) { case VKRStepType::RENDER: if (!step.render.framebuffer) { + frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared); + // When stepping in the GE debugger, we can end up here multiple times in a "frame". // So only acquire once. if (!frameData.hasAcquired) { @@ -570,6 +572,7 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); } + _dbg_assert_(!frameData.hasPresentCommands); // A RENDER step rendering to the backbuffer is normally the last step that happens in a frame, // unless taking a screenshot, in which case there might be a READBACK_IMAGE after it. // This is why we have to switch cmd to presentCmd, in this case. From 3c80bd94a28d4ad0baa277e895d9213016479f20 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 08:57:24 -0700 Subject: [PATCH 099/326] Kernel: Allow kernel/volatile VPLs as well. --- Core/HLE/sceKernel.cpp | 4 +-- Core/HLE/sceKernelMemory.cpp | 51 ++++++++++++------------------------ 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 48a07d7c22..410b5c4377 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -831,7 +831,7 @@ const HLEFunction ThreadManForUser[] = {0X349B864D, &WrapI_IUU, "sceKernelCancelMsgPipe", 'i', "ixx" }, {0X33BE4024, &WrapI_IU, "sceKernelReferMsgPipeStatus", 'i', "ip" }, - {0X56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxx" }, + {0X56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxp" }, {0X89B3D48C, &WrapI_I, "sceKernelDeleteVpl", 'i', "i" }, {0XBED27435, &WrapI_IUUU, "sceKernelAllocateVpl", 'i', "ixxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, {0XEC0A693F, &WrapI_IUUU, "sceKernelAllocateVplCB", 'i', "ixxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, @@ -932,7 +932,7 @@ const HLEFunction ThreadManForKernel[] = {0x0D81716A, &WrapI_IU, "sceKernelPollMbx", 'i', "ix", HLE_KERNEL_SYSCALL }, {0x87D4DD36, &WrapI_IU, "sceKernelCancelReceiveMbx", 'i', "ix", HLE_KERNEL_SYSCALL }, {0xA8E8C846, &WrapI_IU, "sceKernelReferMbxStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, - {0x56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxx", HLE_KERNEL_SYSCALL }, + {0x56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxp", HLE_KERNEL_SYSCALL }, {0x89B3D48C, &WrapI_I, "sceKernelDeleteVpl", 'i', "i", HLE_KERNEL_SYSCALL }, {0xBED27435, &WrapI_IUUU, "sceKernelAllocateVpl", 'i', "ixxx", HLE_KERNEL_SYSCALL | HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, {0xEC0A693F, &WrapI_IUUU, "sceKernelAllocateVplCB", 'i', "ixxx", HLE_KERNEL_SYSCALL | HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 5575bb97d5..fee7100982 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -1477,40 +1477,23 @@ static void __KernelSortVplThreads(VPL *vpl) std::stable_sort(vpl->waitingThreads.begin(), vpl->waitingThreads.end(), __VplThreadSortPriority); } -SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr) -{ +SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr) { if (!name) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid name", SCE_KERNEL_ERROR_ERROR); - return SCE_KERNEL_ERROR_ERROR; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ERROR, "invalid name"); if (partition < 1 || partition > 9 || partition == 7) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } - // We only support user right now. - if (partition != 2 && partition != 6) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition); - return SCE_KERNEL_ERROR_ILLEGAL_PERM; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition); + + BlockAllocator *allocator = BlockAllocatorFromID(partition); + if (allocator == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", partition); + if (((attr & ~PSP_VPL_ATTR_KNOWN) & ~0xFF) != 0) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr); - return SCE_KERNEL_ERROR_ILLEGAL_ATTR; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr); if (vplSize == 0) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid size", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE); - return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid size"); // Block Allocator seems to A-OK this, let's stop it here. if (vplSize >= 0x80000000) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): way too big size", SCE_KERNEL_ERROR_NO_MEMORY); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "way too big size"); // Can't have that little space in a Vpl, sorry. if (vplSize <= 0x30) @@ -1519,12 +1502,9 @@ SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize // We ignore the upalign to 256 and do it ourselves by 8. u32 allocSize = vplSize; - u32 memBlockPtr = userMemory.Alloc(allocSize, (attr & PSP_VPL_ATTR_HIGHMEM) != 0, "VPL"); + u32 memBlockPtr = allocator->Alloc(allocSize, (attr & PSP_VPL_ATTR_HIGHMEM) != 0, "VPL"); if (memBlockPtr == (u32)-1) - { - ERROR_LOG(SCEKERNEL, "sceKernelCreateVpl(): Failed to allocate %i bytes of pool data", vplSize); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate %i bytes of pool data", vplSize); VPL *vpl = new VPL; SceUID id = kernelObjects.Create(vpl); @@ -1568,7 +1548,10 @@ int sceKernelDeleteVpl(SceUID uid) if (wokeThreads) hleReSchedule("vpl deleted"); - userMemory.Free(vpl->address); + BlockAllocator *alloc = BlockAllocatorFromAddr(vpl->address); + _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address"); + if (alloc) + alloc->Free(vpl->address); kernelObjects.Destroy(uid); return 0; } From 85d1f28997c1670ef55ee2ff26e9e15401166e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 17:54:46 +0200 Subject: [PATCH 100/326] Throw in assorted warning fixes --- GPU/Common/GPUStateUtils.cpp | 1 + GPU/Common/StencilCommon.cpp | 1 + GPU/Common/TextureCacheCommon.cpp | 3 +++ GPU/Software/Rasterizer.cpp | 1 + UI/DevScreens.h | 3 +-- 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 04f8240a61..2b8bb84dc1 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -231,6 +231,7 @@ StencilValueType ReplaceAlphaWithStencilType() { case GE_FORMAT_8888: case GE_FORMAT_INVALID: case GE_FORMAT_DEPTH16: + case GE_FORMAT_CLUT8: switch (gstate.getStencilOpZPass()) { case GE_STENCILOP_REPLACE: // TODO: Could detect zero here and force ZERO - less uniform updates? diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp index 81749ef678..561c6a2857 100644 --- a/GPU/Common/StencilCommon.cpp +++ b/GPU/Common/StencilCommon.cpp @@ -171,6 +171,7 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU break; case GE_FORMAT_INVALID: case GE_FORMAT_DEPTH16: + case GE_FORMAT_CLUT8: // Inconceivable. _assert_(false); break; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 952776c076..127d491010 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1997,6 +1997,9 @@ static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferForma return true; } break; + case GE_FORMAT_CLUT8: + // Shouldn't happen here. + return false; } WARN_LOG(G3D, "Invalid CLUT/framebuffer combination: %s vs %s", GeTextureFormatToString(texFormat), GeBufferFormatToString(bufferFormat)); return false; diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 4f140b206d..2336ff6bef 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -1235,6 +1235,7 @@ void ClearRectangle(const VertexData &v0, const VertexData &v1, const BinCoords case GE_FORMAT_INVALID: case GE_FORMAT_DEPTH16: + case GE_FORMAT_CLUT8: _dbg_assert_msg_(false, "Software: invalid framebuf format."); break; } diff --git a/UI/DevScreens.h b/UI/DevScreens.h index 92e7cc77f5..0df3d73ce3 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -100,8 +100,7 @@ public: const char *tag() const override { return "LogLevel"; } private: - virtual void OnCompleted(DialogResult result); - + void OnCompleted(DialogResult result) override; }; class SystemInfoScreen : public UIDialogScreenWithBackground { From 56f2d7cdac82c766985bc1d9bd36cd237076d5c4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 09:05:39 -0700 Subject: [PATCH 101/326] Kernel: Allow volatile for Tlspl objects. --- Core/HLE/sceKernel.cpp | 2 +- Core/HLE/sceKernelMemory.cpp | 76 ++++++++++++------------------------ 2 files changed, 26 insertions(+), 52 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 410b5c4377..64ceb0058c 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -864,7 +864,7 @@ const HLEFunction ThreadManForUser[] = {0XD8B299AE, &WrapU_IUUU, "sceKernelSetVTimerHandler", 'x', "ixxx" }, {0X53B00E9A, &WrapU_IU64UU, "sceKernelSetVTimerHandlerWide", 'x', "iXxx" }, - {0X8DAFF657, &WrapI_CUUUUU, "sceKernelCreateTlspl", 'i', "sxxxxx" }, + {0X8DAFF657, &WrapI_CUUUUU, "sceKernelCreateTlspl", 'i', "sixxxp" }, {0X32BF938E, &WrapI_I, "sceKernelDeleteTlspl", 'i', "i" }, {0X721067F3, &WrapI_IU, "sceKernelReferTlsplStatus", 'i', "xp" }, // Not completely certain about args. diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index fee7100982..c3fd130d02 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -2053,29 +2053,17 @@ void __KernelTlsplThreadEnd(SceUID threadID) tlsplThreadEndChecks.erase(locked.first, locked.second); } -SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 blockSize, u32 count, u32 optionsPtr) -{ +SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 blockSize, u32 count, u32 optionsPtr) { if (!name) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name"); if ((attr & ~PSP_TLSPL_ATTR_KNOWN) >= 0x100) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr); - return SCE_KERNEL_ERROR_ILLEGAL_ATTR; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr); if (partition < 1 || partition > 9 || partition == 7) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } - // We only support user right now. - if (partition != 2 && partition != 6) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition); - return SCE_KERNEL_ERROR_ILLEGAL_PERM; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition); + + BlockAllocator *allocator = BlockAllocatorFromID(partition); + if (allocator == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %x", partition); // There's probably a simpler way to get this same basic formula... // This is based on results from a PSP. @@ -2085,41 +2073,29 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block if (!illegalMemSize && (u64) count >= 0x100000000ULL / (((u64) blockSize + 3ULL) & ~3ULL)) illegalMemSize = true; if (illegalMemSize) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid blockSize/count", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE); - return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid blockSize/count"); int index = -1; - for (int i = 0; i < TLSPL_NUM_INDEXES; ++i) - if (tlsplUsedIndexes[i] == false) - { + for (int i = 0; i < TLSPL_NUM_INDEXES; ++i) { + if (tlsplUsedIndexes[i] == false) { index = i; break; } + } if (index == -1) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): ran out of indexes for TLS pools", PSP_ERROR_TOO_MANY_TLSPL); - return PSP_ERROR_TOO_MANY_TLSPL; - } + return hleLogWarning(SCEKERNEL, PSP_ERROR_TOO_MANY_TLSPL, "ran out of indexes for TLS pools"); // Unless otherwise specified, we align to 4 bytes (a mips word.) u32 alignment = 4; - if (optionsPtr != 0) - { - u32 size = Memory::Read_U32(optionsPtr); - if (size > 8) - WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateTlspl(%s) unsupported options parameter, size = %d", name, size); + if (Memory::IsValidRange(optionsPtr, 4)) { + u32 size = Memory::ReadUnchecked_U32(optionsPtr); if (size >= 8) alignment = Memory::Read_U32(optionsPtr + 4); // Note that 0 intentionally is allowed. if ((alignment & (alignment - 1)) != 0) - { - ERROR_LOG_REPORT(SCEKERNEL, "sceKernelCreateTlspl(%s): alignment is not a power of 2: %d", name, alignment); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } + return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "alignment is not a power of 2: %d", alignment); // This goes for 0, 1, and 2. Can't have less than 4 byte alignment. if (alignment < 4) alignment = 4; @@ -2129,16 +2105,13 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block u32 alignedSize = (blockSize + alignment - 1) & ~(alignment - 1); u32 totalSize = alignedSize * count; - u32 blockPtr = userMemory.Alloc(totalSize, (attr & PSP_TLSPL_ATTR_HIGHMEM) != 0, name); + u32 blockPtr = allocator->Alloc(totalSize, (attr & PSP_TLSPL_ATTR_HIGHMEM) != 0, name); #ifdef _DEBUG - userMemory.ListBlocks(); + allocator->ListBlocks(); #endif - if (blockPtr == (u32) -1) - { - ERROR_LOG(SCEKERNEL, "%08x=sceKernelCreateTlspl(%s, %d, %08x, %d, %d, %08x): failed to allocate memory", SCE_KERNEL_ERROR_NO_MEMORY, name, partition, attr, blockSize, count, optionsPtr); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + if (blockPtr == (u32)-1) + return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate memory"); TLSPL *tls = new TLSPL(); SceUID id = kernelObjects.Create(tls); @@ -2157,9 +2130,7 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block tls->alignment = alignment; tls->usage.resize(count, 0); - WARN_LOG(SCEKERNEL, "%08x=sceKernelCreateTlspl(%s, %d, %08x, %d, %d, %08x)", id, name, partition, attr, blockSize, count, optionsPtr); - - return id; + return hleLogSuccessInfoI(SCEKERNEL, id); } int sceKernelDeleteTlspl(SceUID uid) @@ -2187,7 +2158,10 @@ int sceKernelDeleteTlspl(SceUID uid) HLEKernel::ResumeFromWait(threadID, WAITTYPE_TLSPL, uid, 0); hleReSchedule("deleted tlspl"); - userMemory.Free(tls->address); + BlockAllocator *allocator = BlockAllocatorFromAddr(tls->address); + _assert_msg_(allocator != nullptr, "Should always have a valid allocator/address"); + if (allocator) + allocator->Free(tls->address); tlsplUsedIndexes[tls->ntls.index] = false; kernelObjects.Destroy(uid); } From 251cc73afd13d37719b99a3cff65d1554b89e4dc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 09:20:05 -0700 Subject: [PATCH 102/326] Kernel: Allow volatile for MsgPipe buffers. --- Core/HLE/sceKernel.cpp | 2 +- Core/HLE/sceKernelMemory.cpp | 6 ++--- Core/HLE/sceKernelMemory.h | 4 +++ Core/HLE/sceKernelMsgPipe.cpp | 50 +++++++++++++---------------------- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 64ceb0058c..b9d1028b48 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -820,7 +820,7 @@ const HLEFunction ThreadManForUser[] = {0X87D4DD36, &WrapI_IU, "sceKernelCancelReceiveMbx", 'i', "ix" }, {0XA8E8C846, &WrapI_IU, "sceKernelReferMbxStatus", 'i', "ip" }, - {0X7C0DC2A0, &WrapI_CIUUU, "sceKernelCreateMsgPipe", 'i', "sixxx" }, + {0X7C0DC2A0, &WrapI_CIUUU, "sceKernelCreateMsgPipe", 'i', "sixxp" }, {0XF0B7DA1C, &WrapI_I, "sceKernelDeleteMsgPipe", 'i', "i" }, {0X876DBFAD, &WrapI_IUUUUU, "sceKernelSendMsgPipe", 'i', "ixxxxx" }, {0X7C41F2C2, &WrapI_IUUUUU, "sceKernelSendMsgPipeCB", 'i', "ixxxxx" }, diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index c3fd130d02..7e54e45b96 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -504,7 +504,7 @@ void __KernelMemoryShutdown() MemBlockInfoShutdown(); } -static BlockAllocator *BlockAllocatorFromID(int id) { +BlockAllocator *BlockAllocatorFromID(int id) { switch (id) { case 1: case 3: @@ -529,7 +529,7 @@ static BlockAllocator *BlockAllocatorFromID(int id) { return nullptr; } -static int BlockAllocatorToID(const BlockAllocator *alloc) { +int BlockAllocatorToID(const BlockAllocator *alloc) { if (alloc == &kernelMemory) return 1; if (alloc == &userMemory) @@ -539,7 +539,7 @@ static int BlockAllocatorToID(const BlockAllocator *alloc) { return 0; } -static BlockAllocator *BlockAllocatorFromAddr(u32 addr) { +BlockAllocator *BlockAllocatorFromAddr(u32 addr) { addr &= 0x3FFFFFFF; if (Memory::IsKernelAndNotVolatileAddress(addr)) return &kernelMemory; diff --git a/Core/HLE/sceKernelMemory.h b/Core/HLE/sceKernelMemory.h index 29d7356d00..b217974170 100644 --- a/Core/HLE/sceKernelMemory.h +++ b/Core/HLE/sceKernelMemory.h @@ -40,6 +40,10 @@ KernelObject *__KernelMemoryVPLObject(); KernelObject *__KernelMemoryPMBObject(); KernelObject *__KernelTlsplObject(); +BlockAllocator *BlockAllocatorFromID(int id); +int BlockAllocatorToID(const BlockAllocator *alloc); +BlockAllocator *BlockAllocatorFromAddr(u32 addr); + SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr); int sceKernelDeleteVpl(SceUID uid); int sceKernelAllocateVpl(SceUID uid, u32 size, u32 addrPtr, u32 timeoutPtr); diff --git a/Core/HLE/sceKernelMsgPipe.cpp b/Core/HLE/sceKernelMsgPipe.cpp index b16e8060e2..0ff34c6290 100644 --- a/Core/HLE/sceKernelMsgPipe.cpp +++ b/Core/HLE/sceKernelMsgPipe.cpp @@ -140,10 +140,13 @@ struct MsgPipe : public KernelObject int GetIDType() const override { return SCE_KERNEL_TMID_Mpipe; } MsgPipe() : buffer(0) {} - ~MsgPipe() - { - if (buffer != 0) - userMemory.Free(buffer); + ~MsgPipe() { + if (buffer != 0) { + BlockAllocator *alloc = BlockAllocatorFromAddr(buffer); + _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address"); + if (alloc) + alloc->Free(buffer); + } } u32 GetUsedSize() @@ -667,41 +670,26 @@ void __KernelMsgPipeDoState(PointerWrap &p) CoreTiming::RestoreRegisterEvent(waitTimer, "MsgPipeTimeout", __KernelMsgPipeTimeout); } -int sceKernelCreateMsgPipe(const char *name, int partition, u32 attr, u32 size, u32 optionsPtr) -{ +int sceKernelCreateMsgPipe(const char *name, int partition, u32 attr, u32 size, u32 optionsPtr) { if (!name) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name"); if (partition < 1 || partition > 9 || partition == 7) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition); - return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; - } - // We only support user right now. - if (partition != 2 && partition != 6) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition); - return SCE_KERNEL_ERROR_ILLEGAL_PERM; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition); + + BlockAllocator *allocator = BlockAllocatorFromID(partition); + if (allocator == nullptr) + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", partition); + if ((attr & ~SCE_KERNEL_MPA_KNOWN) >= 0x100) - { - WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateEventFlag(%s): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, name, attr); - return SCE_KERNEL_ERROR_ILLEGAL_ATTR; - } + return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr); u32 memBlockPtr = 0; - if (size != 0) - { + if (size != 0) { // We ignore the upalign to 256. u32 allocSize = size; - memBlockPtr = userMemory.Alloc(allocSize, (attr & SCE_KERNEL_MPA_HIGHMEM) != 0, "MsgPipe"); + memBlockPtr = allocator->Alloc(allocSize, (attr & SCE_KERNEL_MPA_HIGHMEM) != 0, "MsgPipe"); if (memBlockPtr == (u32)-1) - { - ERROR_LOG(SCEKERNEL, "%08x=sceKernelCreateEventFlag(%s): Failed to allocate %i bytes for buffer", SCE_KERNEL_ERROR_NO_MEMORY, name, size); - return SCE_KERNEL_ERROR_NO_MEMORY; - } + return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate %i bytes for buffer", size); } MsgPipe *m = new MsgPipe(); From 39fae4cfcb08561fe69da841f09045c6ae213fcb Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 20 Sep 2022 18:57:58 +0200 Subject: [PATCH 103/326] OpenXR - Support for all headset's IPDs fixed --- Common/VR/VRRenderer.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index 79e6114a61..248b2b1487 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -9,6 +9,7 @@ #include #include +XrFovf fov; XrView* projections; XrPosef invViewTransform[2]; XrFrameState frameState = {}; @@ -293,7 +294,12 @@ bool VR_InitFrame( engine_t* engine ) { projections)); // + fov = {}; for (int eye = 0; eye < ovrMaxNumEyes; eye++) { + fov.angleLeft += projections[eye].fov.angleLeft / 2.0f; + fov.angleRight += projections[eye].fov.angleRight / 2.0f; + fov.angleUp += projections[eye].fov.angleUp / 2.0f; + fov.angleDown += projections[eye].fov.angleDown / 2.0f; invViewTransform[eye] = projections[eye].pose; } @@ -353,10 +359,7 @@ void VR_FinishFrame( engine_t* engine ) { for (int eye = 0; eye < ovrMaxNumEyes; eye++) { int imageLayer = engine->appState.Renderer.Multiview ? eye : 0; ovrFramebuffer* frameBuffer = &engine->appState.Renderer.FrameBuffer[0]; - XrFovf fov = projections[eye].fov; - if (vrMode == VR_MODE_MONO_6DOF) { - fov = projections[0].fov; - } else if (!engine->appState.Renderer.Multiview) { + if ((vrMode != VR_MODE_MONO_6DOF) && !engine->appState.Renderer.Multiview) { frameBuffer = &engine->appState.Renderer.FrameBuffer[eye]; } @@ -463,7 +466,6 @@ void VR_BindFramebuffer(engine_t *engine) { ovrMatrix4f VR_GetMatrix( VRMatrix matrix ) { ovrMatrix4f output; if ((matrix == VR_PROJECTION_MATRIX_LEFT_EYE) || (matrix == VR_PROJECTION_MATRIX_RIGHT_EYE)) { - XrFovf fov = matrix == VR_PROJECTION_MATRIX_LEFT_EYE ? projections[0].fov : projections[1].fov; float near = (float)vrConfig[VR_CONFIG_FOV_SCALE] / 200.0f; output = ovrMatrix4f_CreateProjectionFov(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown, near, 0.0f ); } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { From d665b2b6ca399adbc4fe40d8c6a9807eb3c4e459 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 10:46:07 -0700 Subject: [PATCH 104/326] Kernel: Disallow partitions 8/10 to match tests. I think these might be accessible only in kernel mode. --- Core/HLE/sceKernelMemory.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 7e54e45b96..935cbc8482 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -515,9 +515,13 @@ BlockAllocator *BlockAllocatorFromID(int id) { case 2: case 6: + return &userMemory; + case 8: case 10: - return &userMemory; + if (hleIsKernelMode()) + return &userMemory; + return nullptr; case 5: return &volatileMemory; From 67055ff2703da850fc88c3ae0d6e1c3d57622447 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 10:49:59 -0700 Subject: [PATCH 105/326] headless: Update tests. --- pspautotests | 2 +- test.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pspautotests b/pspautotests index d83d49800a..d7a55d5487 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit d83d49800a64bdb344211e4d1a8153ab99de8ee2 +Subproject commit d7a55d5487b1fa1a688178206abaad4aea5be18a diff --git a/test.py b/test.py index 4597804a51..47f5c36034 100755 --- a/test.py +++ b/test.py @@ -209,6 +209,7 @@ tests_good = [ "sysmem/freesize", "sysmem/memblock", "sysmem/sysmem", + "sysmem/volatile", "threads/alarm/alarm", "threads/alarm/cancel/cancel", "threads/alarm/refer/refer", From d19138f15789a8cd0004384c3f0fc0ce8ed20945 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 11:11:49 -0700 Subject: [PATCH 106/326] UI: Fix controller autoconf list result. --- UI/ControlMappingScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 7acdd36f05..f6255ad4e3 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -313,7 +313,7 @@ UI::EventReturn ControlMappingScreen::OnVisualizeMapping(UI::EventParams ¶ms } void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) { - if (result == DR_OK && dialog->tag() == "listpopup") { + if (result == DR_OK && std::string(dialog->tag()) == "listpopup") { ListPopupScreen *popup = (ListPopupScreen *)dialog; KeyMap::AutoConfForPad(popup->GetChoiceString()); } From 209b5b30799b491d506aa99137b60fd691888bb1 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 20 Sep 2022 21:53:32 +0200 Subject: [PATCH 107/326] OpenXR - Compile fix --- Common/VR/PPSSPPVR.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 520a43b0c8..322b485842 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -250,7 +250,7 @@ void PreGLCommand(const void* data) { } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { const auto* glrData = (const GLRRenderData*)data; if (glrData->cmd == GLRRenderCommand::BINDPROGRAM) { - vrCompat[VR_USE_CLIP] = glrData->program.program->use_clip_distance0; + vrCompat[VR_USE_CLIP] = glrData->program.program->use_clip_distance[0]; } else if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(glrData)) { glColorMask(false, false, false, false); } From 7483923d073cb32207353d122b3226f340b7c011 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 12:52:06 -0700 Subject: [PATCH 108/326] softgpu: Correct clear rect off by one issues. --- GPU/Software/Rasterizer.cpp | 15 +++++++++++---- test.py | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 2336ff6bef..b4d13fa6f6 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -1136,13 +1136,20 @@ void DrawPoint(const VertexData &v0, const BinCoords &range, const RasterizerSta } void ClearRectangle(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &state) { - DrawingCoords pprime = TransformUnit::ScreenToDrawing(range.x1, range.y1); - DrawingCoords pend = TransformUnit::ScreenToDrawing(range.x2, range.y2); + int entireX1 = std::min(v0.screenpos.x, v1.screenpos.x); + int entireY1 = std::min(v0.screenpos.y, v1.screenpos.y); + int entireX2 = std::max(v0.screenpos.x, v1.screenpos.x) - 1; + int entireY2 = std::max(v0.screenpos.y, v1.screenpos.y) - 1; + int minX = std::max(entireX1, range.x1) | (SCREEN_SCALE_FACTOR / 2 - 1); + int minY = std::max(entireY1, range.y1) | (SCREEN_SCALE_FACTOR / 2 - 1); + int maxX = std::min(entireX2, range.x2); + int maxY = std::min(entireY2, range.y2); + const DrawingCoords pprime = TransformUnit::ScreenToDrawing(minX, minY); + const DrawingCoords pend = TransformUnit::ScreenToDrawing(maxX, maxY); auto &pixelID = state.pixelID; auto &samplerID = state.samplerID; - // Min and max are in PSP fixed point screen coordinates, 16 here is for the 4 subpixel bits. - const int w = (range.x2 - range.x1 + 1) / SCREEN_SCALE_FACTOR; + const int w = pend.x - pprime.x + 1; if (w <= 0) return; diff --git a/test.py b/test.py index 47f5c36034..d8585b50dd 100755 --- a/test.py +++ b/test.py @@ -147,6 +147,7 @@ tests_good = [ "gpu/commands/blend", "gpu/commands/blend565", "gpu/commands/blocktransfer", + "gpu/commands/cull", "gpu/commands/fog", "gpu/commands/material", "gpu/displaylist/alignment", @@ -159,6 +160,7 @@ tests_good = [ "gpu/ge/queue", "gpu/primitives/indices", "gpu/primitives/invalidprim", + "gpu/primitives/points", "gpu/primitives/trianglefan", "gpu/primitives/trianglestrip", "gpu/primitives/triangles", @@ -181,6 +183,7 @@ tests_good = [ "gpu/texfunc/replace", "gpu/textures/mipmap", "gpu/textures/rotate", + "gpu/vertices/colors", "hash/hash", "hle/check_not_used_uids", "intr/intr", @@ -387,7 +390,6 @@ tests_next = [ "font/shadowglyphimageclip", "font/shadowinfo", "gpu/clipping/guardband", - "gpu/commands/cull", "gpu/commands/light", "gpu/complex/complex", "gpu/depth/precision", @@ -404,7 +406,6 @@ tests_next = [ "gpu/primitives/immediate", "gpu/primitives/lines", "gpu/primitives/linestrip", - "gpu/primitives/points", "gpu/primitives/rectangles", "gpu/primitives/spline", "gpu/reflection/reflection", @@ -415,7 +416,6 @@ tests_next = [ "gpu/simple/simple", "gpu/textures/size", "gpu/triangle/triangle", - "gpu/vertices/colors", "gpu/vertices/texcoords", "intr/registersub", "intr/releasesub", From 355c18512fa9354f12b73549c8e5fc4ccbb4a14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 20 Sep 2022 22:31:54 +0200 Subject: [PATCH 109/326] Fix BGRA issue on D3D with GPU CLUT textures --- GPU/Common/TextureCacheCommon.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 127d491010..97efd42cbe 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -418,10 +418,13 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { // Should probably revisit how this works.. gstate_c.SetNeedShaderTexclamp(false); gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE; - if (gstate_c.bgraTexture != isBgraBackend_) { + + bool isBgraTexture = isBgraBackend_ && !hasClutGPU; + + if (gstate_c.bgraTexture != isBgraTexture) { gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } - gstate_c.bgraTexture = isBgraBackend_; + gstate_c.bgraTexture = isBgraTexture; if (entryIter != cache_.end()) { entry = entryIter->second.get(); From 78a3925198bcefd9c6577cd33671240381fd03f3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 13:43:19 -0700 Subject: [PATCH 110/326] softgpu: Fix display framebuffer read. --- GPU/Software/SoftGpu.cpp | 21 ++++++++++++--------- GPU/Software/SoftGpu.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 0f2d3bcda1..ecef1ab647 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -490,14 +490,16 @@ void SoftGPU::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat for DSStretch g_DarkStalkerStretch; -void SoftGPU::ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, u8 *overrideData) { +void SoftGPU::ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, const uint16_t *overrideData) { // TODO: This should probably be converted in a shader instead.. fbTexBuffer_.resize(srcwidth * srcheight); - FormatBuffer displayBuffer; - displayBuffer.data = overrideData ? overrideData : Memory::GetPointerWrite(displayFramebuf_); + const uint16_t *displayBuffer = overrideData; + if (!displayBuffer) + displayBuffer = (const uint16_t *)Memory::GetPointer(displayFramebuf_); + for (int y = 0; y < srcheight; ++y) { u32 *buf_line = &fbTexBuffer_[y * srcwidth]; - const u16 *fb_line = &displayBuffer.as16[y * displayStride_]; + const u16 *fb_line = &displayBuffer[y * displayStride_]; switch (displayFormat_) { case GE_FORMAT_565: @@ -557,7 +559,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { bool hasPostShader = presentation_ && presentation_->HasPostShader(); if (PSP_CoreParameter().compat.flags().DarkStalkersPresentHack && displayFormat_ == GE_FORMAT_5551 && g_DarkStalkerStretch != DSStretch::Off) { - u8 *data = Memory::GetPointerWrite(0x04088000); + const u8 *data = Memory::GetPointerWrite(0x04088000); bool fillDesc = true; if (draw_->GetDataFormatSupport(Draw::DataFormat::A1B5G5R5_UNORM_PACK16) & Draw::FMT_TEXTURE) { // The perfect one. @@ -567,7 +569,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { desc.format = Draw::DataFormat::A1R5G5B5_UNORM_PACK16; outputFlags |= OutputFlags::RB_SWIZZLE; } else { - ConvertTextureDescFrom16(desc, srcwidth, srcheight, data); + ConvertTextureDescFrom16(desc, srcwidth, srcheight, (const uint16_t *)data); fillDesc = false; } if (fillDesc) { @@ -586,13 +588,13 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { hasImage = false; u1 = 1.0f; } else if (displayFormat_ == GE_FORMAT_8888) { - u8 *data = Memory::GetPointerWrite(displayFramebuf_); + const u8 *data = Memory::GetPointer(displayFramebuf_); desc.width = displayStride_ == 0 ? srcwidth : displayStride_; desc.height = srcheight; desc.initData.push_back(data); desc.format = Draw::DataFormat::R8G8B8A8_UNORM; } else if (displayFormat_ == GE_FORMAT_5551) { - const u8 *data = Memory::GetPointerWrite(displayFramebuf_); + const u8 *data = Memory::GetPointer(displayFramebuf_); bool fillDesc = true; if (draw_->GetDataFormatSupport(Draw::DataFormat::A1B5G5R5_UNORM_PACK16) & Draw::FMT_TEXTURE) { // The perfect one. @@ -1247,18 +1249,19 @@ bool SoftGPU::GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferT int stride = gstate.FrameBufStride(); DrawingCoords size = GetTargetSize(stride); GEBufferFormat fmt = gstate.FrameBufFormat(); + const u8 *src = fb.data; if (type == GPU_DBG_FRAMEBUF_DISPLAY) { size.x = 480; size.y = 272; stride = displayStride_; fmt = displayFormat_; + src = Memory::GetPointer(displayFramebuf_); } buffer.Allocate(size.x, size.y, fmt); const int depth = fmt == GE_FORMAT_8888 ? 4 : 2; - const u8 *src = fb.data; u8 *dst = buffer.GetData(); const int byteWidth = size.x * depth; for (int16_t y = 0; y < size.y; ++y) { diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index e90f7c0fb8..18fa23118d 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -194,7 +194,7 @@ public: protected: void FastRunLoop(DisplayList &list) override; void CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight); - void ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, u8 *overrideData = nullptr); + void ConvertTextureDescFrom16(Draw::TextureDesc &desc, int srcwidth, int srcheight, const uint16_t *overrideData = nullptr); private: void MarkDirty(uint32_t addr, uint32_t stride, uint32_t height, GEBufferFormat fmt, SoftGPUVRAMDirty value); From bf86f00df87c16aea79317fa036a4da399bb252f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 14:01:36 -0700 Subject: [PATCH 111/326] GPU: Correct display framebuffer reading. The displayFramebuf_ might not be set yet, but that doesn't mean we want an INVALID format and zero stride. We might also be rendering to a different target, but still want the display. --- GPU/Common/FramebufferManagerCommon.cpp | 8 ++++---- GPU/Common/FramebufferManagerCommon.h | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index ed43996676..dd01c3c499 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1021,7 +1021,7 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { // TODO: Could go through all FBOs, but probably not important? // TODO: Could also check for inner changes, but video is most important. // TODO: This shouldn't care if it's a display framebuf or not, should work exactly the same. - bool isDisplayBuf = addr == DisplayFramebufAddr() || addr == PrevDisplayFramebufAddr(); + bool isDisplayBuf = addr == CurrentDisplayFramebufAddr() || addr == PrevDisplayFramebufAddr(); // TODO: Deleting the FBO is a heavy hammer solution, so let's only do it if it'd help. if (!Memory::IsValidAddress(displayFramebufPtr_)) return; @@ -2182,7 +2182,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS // We may still do a partial block draw below if this doesn't pass. if (!useBufferedRendering_ && dstStride >= 480 && width >= 480 && height == 272) { bool isPrevDisplayBuffer = PrevDisplayFramebufAddr() == dstBasePtr; - bool isDisplayBuffer = DisplayFramebufAddr() == dstBasePtr; + bool isDisplayBuffer = CurrentDisplayFramebufAddr() == dstBasePtr; if (isPrevDisplayBuffer || isDisplayBuffer) { FlushBeforeCopy(); DrawFramebufferToOutput(Memory::GetPointerUnchecked(dstBasePtr), dstStride, displayFormat_); @@ -2357,8 +2357,8 @@ void FramebufferManagerCommon::ShowScreenResolution() { // * Save state screenshots(could probably be async but need to manage the stall.) bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxScaleFactor) { VirtualFramebuffer *vfb = currentRenderVfb_; - if (!vfb) { - vfb = GetVFBAt(fb_address); + if (!vfb || vfb->fb_address != fb_address) { + vfb = ResolveVFB(fb_address, fb_stride, format); } if (!vfb) { diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 5ef9159868..a38cc328d5 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -335,15 +335,18 @@ public: u32 PrevDisplayFramebufAddr() const { return prevDisplayFramebuf_ ? prevDisplayFramebuf_->fb_address : 0; } - u32 DisplayFramebufAddr() const { + u32 CurrentDisplayFramebufAddr() const { return displayFramebuf_ ? displayFramebuf_->fb_address : 0; } + u32 DisplayFramebufAddr() const { + return displayFramebufPtr_; + } u32 DisplayFramebufStride() const { - return displayFramebuf_ ? displayStride_ : 0; + return displayStride_; } GEBufferFormat DisplayFramebufFormat() const { - return displayFramebuf_ ? displayFormat_ : GE_FORMAT_INVALID; + return displayFormat_; } bool UseBufferedRendering() const { From 1dc1b2c35b2ad3dd5bee02013ad3b772ea13b794 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 14:05:50 -0700 Subject: [PATCH 112/326] headless: Use display buf for compare screenshot. This is what the test actually uses too. --- headless/StubHost.cpp | 2 +- test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index b3896df478..03a27ab1bf 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -46,7 +46,7 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) { const static u32 FRAME_HEIGHT = 272; GPUDebugBuffer buffer; - gpuDebug->GetCurrentFramebuffer(buffer, GPU_DBG_FRAMEBUF_RENDER); + gpuDebug->GetCurrentFramebuffer(buffer, GPU_DBG_FRAMEBUF_DISPLAY); const std::vector pixels = TranslateDebugBufferToCompare(&buffer, 512, 272); ScreenshotComparer comparer(pixels, FRAME_STRIDE, FRAME_WIDTH, FRAME_HEIGHT); diff --git a/test.py b/test.py index d8585b50dd..e2967f06a2 100755 --- a/test.py +++ b/test.py @@ -150,6 +150,7 @@ tests_good = [ "gpu/commands/cull", "gpu/commands/fog", "gpu/commands/material", + "gpu/complex/complex", "gpu/displaylist/alignment", "gpu/dither/dither", "gpu/filtering/mipmaplinear", @@ -391,7 +392,6 @@ tests_next = [ "font/shadowinfo", "gpu/clipping/guardband", "gpu/commands/light", - "gpu/complex/complex", "gpu/depth/precision", "gpu/displaylist/state", "gpu/filtering/linear", From cefef3b4f91b195a227449d12a1f04a0d5cdbd72 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 14:29:33 -0700 Subject: [PATCH 113/326] softgpu: Narrow blend check for fast path further. See #15756, frame was largely black because of a full screen blend rectangle intended to brighten the screen slighty (I assume.) --- GPU/Software/RasterizerRectangle.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index ff5b9c85d9..8c7b4b171f 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -93,7 +93,10 @@ static inline bool AlphaTestIsNeedless(const PixelFuncID &pixelID) { case GE_COMP_NOTEQUAL: case GE_COMP_GREATER: case GE_COMP_GEQUAL: - return pixelID.alphaBlend && pixelID.alphaTestRef == 0 && !pixelID.hasAlphaTestMask; + if (pixelID.alphaTestRef != 0 || pixelID.hasAlphaTestMask) + return false; + // DrawSinglePixel5551 assumes it can take the src color directly if full alpha. + return pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA; } return false; From 0a24004eac95272bf7d2bf647a20518d6a14b80d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 15:12:03 -0700 Subject: [PATCH 114/326] GPU: Account for w properly in lines, fixing width. See #15756. --- GPU/Common/SoftwareTransformCommon.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 98b5adc079..562769d660 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -790,13 +790,13 @@ void SoftwareTransform::ExpandLines(int vertexCount, int &maxIndex, u16 *&inds, float yoff = addWidth.y * dy; // bottom right - trans[0].CopyFromWithOffset(transVtx2, xoff, yoff); + trans[0].CopyFromWithOffset(transVtx2, xoff * transVtx2.pos_w, yoff * transVtx2.pos_w); // top right - trans[1].CopyFromWithOffset(transVtx1, xoff, yoff); + trans[1].CopyFromWithOffset(transVtx1, xoff * transVtx1.pos_w, yoff * transVtx1.pos_w); // top left - trans[2].CopyFromWithOffset(transVtx1, -xoff, -yoff); + trans[2].CopyFromWithOffset(transVtx1, -xoff * transVtx1.pos_w, -yoff * transVtx1.pos_w); // bottom left - trans[3].CopyFromWithOffset(transVtx2, -xoff, -yoff); + trans[3].CopyFromWithOffset(transVtx2, -xoff * transVtx2.pos_w, -yoff * transVtx2.pos_w); // Triangle: BR-TR-TL indsOut[0] = i * 2 + 0; @@ -835,17 +835,17 @@ void SoftwareTransform::ExpandLines(int vertexCount, int &maxIndex, u16 *&inds, // bottom right trans[0] = transVtxBL; - trans[0].x += addWidth.x * dx; - trans[0].y += addWidth.y * dy; - trans[0].u += addWidth.x * du; - trans[0].v += addWidth.y * dv; + trans[0].x += addWidth.x * dx * trans[0].pos_w; + trans[0].y += addWidth.y * dy * trans[0].pos_w; + trans[0].u += addWidth.x * du * trans[0].uv_w; + trans[0].v += addWidth.y * dv * trans[0].uv_w; // top right trans[1] = transVtxTL; - trans[1].x += addWidth.x * dx; - trans[1].y += addWidth.y * dy; - trans[1].u += addWidth.x * du; - trans[1].v += addWidth.y * dv; + trans[1].x += addWidth.x * dx * trans[1].pos_w; + trans[1].y += addWidth.y * dy * trans[1].pos_w; + trans[1].u += addWidth.x * du * trans[1].uv_w; + trans[1].v += addWidth.y * dv * trans[1].uv_w; // top left trans[2] = transVtxTL; From 2fc7f72d72c783e1737c18d9707ba0463d7340bc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 15:55:39 -0700 Subject: [PATCH 115/326] GPU: Clip clamped depth accounting for perspective. --- GPU/Common/VertexShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 16cce87d0f..adce008676 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1206,14 +1206,14 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // Everywhere else, it's 0 -> 1, simpler. WRITE(p, " if (u_depthRange.y >= 1.0) {\n"); } - WRITE(p, " %sgl_ClipDistance%s = integerZ;\n", compat.vsOutPrefix, clip0); + WRITE(p, " %sgl_ClipDistance%s = integerZ * outPos.w;\n", compat.vsOutPrefix, clip0); WRITE(p, " } else {\n"); WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip0); WRITE(p, " }\n"); // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here. WRITE(p, " if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); - WRITE(p, " %sgl_ClipDistance%s = 65535.0 - integerZ;\n", compat.vsOutPrefix, clip1); + WRITE(p, " %sgl_ClipDistance%s = (65535.0 - integerZ) * outPos.w;\n", compat.vsOutPrefix, clip1); WRITE(p, " } else {\n"); WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip1); WRITE(p, " }\n"); From f8d29fdc1a8b4b703ee9466e1127b9b571e4c64a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 16:21:17 -0700 Subject: [PATCH 116/326] GPU: Simplify depth clamped clip planes. There's no need to think about the scaled Z if we're using w anyway, just use the existing Z clipping. --- GPU/Common/VertexShaderGenerator.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index adce008676..6e3f3185b3 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1189,37 +1189,34 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix); } - if (clipClampedDepth || (vertexRangeCulling && !IsVRBuild())) { - WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); - } - if (clipClampedDepth) { const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; const char *clip1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; - WRITE(p, " mediump float integerZ = projPos.z * u_depthRange.x + u_depthRange.y;\n"); // This should clip against minz, but only when it's above zero. if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { // On OpenGL/GLES, these values account for the -1 -> 1 range. WRITE(p, " if (u_depthRange.y - u_depthRange.x >= 1.0) {\n"); + WRITE(p, " %sgl_ClipDistance%s = outPos.w + outPos.z;\n", compat.vsOutPrefix, clip0); } else { // Everywhere else, it's 0 -> 1, simpler. WRITE(p, " if (u_depthRange.y >= 1.0) {\n"); + WRITE(p, " %sgl_ClipDistance%s = outPos.z;\n", compat.vsOutPrefix, clip0); } - WRITE(p, " %sgl_ClipDistance%s = integerZ * outPos.w;\n", compat.vsOutPrefix, clip0); WRITE(p, " } else {\n"); WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip0); WRITE(p, " }\n"); // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here. WRITE(p, " if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); - WRITE(p, " %sgl_ClipDistance%s = (65535.0 - integerZ) * outPos.w;\n", compat.vsOutPrefix, clip1); + WRITE(p, " %sgl_ClipDistance%s = outPos.w - outPos.z;\n", compat.vsOutPrefix, clip1); WRITE(p, " } else {\n"); WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip1); WRITE(p, " }\n"); } if (vertexRangeCulling && !IsVRBuild()) { + WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); // Vertex range culling doesn't happen when Z clips, note sign of w is important. WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); From 79c5c93d351dd2eed359147f0e546e7ad0b1bb4d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 20 Sep 2022 19:25:32 -0700 Subject: [PATCH 117/326] Kernel: Match index lookup behavior for tls. It might be the uids it returns always follow this format. This makes the test almost pass, outside psplink using more memory (test should be adjusted to compensate.) --- Core/HLE/sceKernelMemory.cpp | 119 ++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 49 deletions(-) diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 935cbc8482..318cae7d0c 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -2174,68 +2174,89 @@ int sceKernelDeleteTlspl(SceUID uid) return error; } -int sceKernelGetTlsAddr(SceUID uid) -{ - // TODO: Allocate downward if PSP_TLSPL_ATTR_HIGHMEM? - DEBUG_LOG(SCEKERNEL, "sceKernelGetTlsAddr(%08x)", uid); +struct FindTLSByIndexArg { + int index; + TLSPL *result = nullptr; +}; +static bool FindTLSByIndex(TLSPL *possible, FindTLSByIndexArg *state) { + if (possible->ntls.index == state->index) { + state->result = possible; + return false; + } + return true; +} + +int sceKernelGetTlsAddr(SceUID uid) { if (!__KernelIsDispatchEnabled() || __IsInInterrupt()) - return 0; + return hleLogWarning(SCEKERNEL, 0, "dispatch disabled"); u32 error; TLSPL *tls = kernelObjects.Get(uid, error); - if (tls) - { - SceUID threadID = __KernelGetCurThread(); - int allocBlock = -1; - bool needsClear = false; + if (!tls) { + if (uid < 0) + return hleLogError(SCEKERNEL, 0, "tlspl not found"); - // If the thread already has one, return it. + // There's this weird behavior where it looks up by index. Maybe we shouldn't use uids... + if (!tlsplUsedIndexes[(uid >> 3) & 15]) + return hleLogError(SCEKERNEL, 0, "tlspl not found"); + + FindTLSByIndexArg state; + state.index = (uid >> 3) & 15; + kernelObjects.Iterate(&FindTLSByIndex, &state); + if (!state.result) + return hleLogError(SCEKERNEL, 0, "tlspl not found"); + + tls = state.result; + } + + SceUID threadID = __KernelGetCurThread(); + int allocBlock = -1; + bool needsClear = false; + + // If the thread already has one, return it. + for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i) + { + if (tls->usage[i] == threadID) + allocBlock = (int) i; + } + + if (allocBlock == -1) + { for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i) { - if (tls->usage[i] == threadID) - allocBlock = (int) i; + // The PSP doesn't give the same block out twice in a row, even if freed. + if (tls->usage[tls->next] == 0) + allocBlock = tls->next; + tls->next = (tls->next + 1) % tls->ntls.totalBlocks; } - if (allocBlock == -1) + if (allocBlock != -1) { - for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i) - { - // The PSP doesn't give the same block out twice in a row, even if freed. - if (tls->usage[tls->next] == 0) - allocBlock = tls->next; - tls->next = (tls->next + 1) % tls->ntls.totalBlocks; - } - - if (allocBlock != -1) - { - tls->usage[allocBlock] = threadID; - tlsplThreadEndChecks.insert(std::make_pair(threadID, uid)); - --tls->ntls.freeBlocks; - needsClear = true; - } + tls->usage[allocBlock] = threadID; + tlsplThreadEndChecks.insert(std::make_pair(threadID, uid)); + --tls->ntls.freeBlocks; + needsClear = true; } - - if (allocBlock == -1) - { - tls->waitingThreads.push_back(threadID); - __KernelWaitCurThread(WAITTYPE_TLSPL, uid, 1, 0, false, "allocate tls"); - return 0; - } - - u32 alignedSize = (tls->ntls.blockSize + tls->alignment - 1) & ~(tls->alignment - 1); - u32 allocAddress = tls->address + allocBlock * alignedSize; - NotifyMemInfo(MemBlockFlags::SUB_ALLOC, allocAddress, tls->ntls.blockSize, "TlsAddr"); - - // We clear the blocks upon first allocation (and also when they are freed, both are necessary.) - if (needsClear) { - Memory::Memset(allocAddress, 0, tls->ntls.blockSize, "TlsAddr"); - } - - return allocAddress; } - else - return 0; + + if (allocBlock == -1) + { + tls->waitingThreads.push_back(threadID); + __KernelWaitCurThread(WAITTYPE_TLSPL, uid, 1, 0, false, "allocate tls"); + return hleLogDebug(SCEKERNEL, 0, "waiting for tls alloc"); + } + + u32 alignedSize = (tls->ntls.blockSize + tls->alignment - 1) & ~(tls->alignment - 1); + u32 allocAddress = tls->address + allocBlock * alignedSize; + NotifyMemInfo(MemBlockFlags::SUB_ALLOC, allocAddress, tls->ntls.blockSize, "TlsAddr"); + + // We clear the blocks upon first allocation (and also when they are freed, both are necessary.) + if (needsClear) { + Memory::Memset(allocAddress, 0, tls->ntls.blockSize, "TlsAddr"); + } + + return hleLogDebug(SCEKERNEL, allocAddress); } // Parameters are an educated guess. From 30454f8dc747ea1fcf70deac6774cc18778cfadd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 21 Sep 2022 00:09:34 -0700 Subject: [PATCH 118/326] Vulkan: Avoid crash in headless on finish. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 3a0dde0312..4d1363e49c 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1269,7 +1269,10 @@ void VulkanRenderManager::Run(int frame) { BeginSubmitFrame(frame); FrameData &frameData = frameData_[frame]; - queueRunner_.PreprocessSteps(frameData_[frame].steps); + queueRunner_.PreprocessSteps(frameData.steps); + // Likely during shutdown, happens in headless. + if (frameData.steps.empty() && !frameData.hasAcquired) + frameData.skipSwap = true; //queueRunner_.LogSteps(stepsOnThread, false); queueRunner_.RunSteps(frameData, frameDataShared_); From bbc5a7cf4b5af932e9a75e5a3e1f8ff2aca456ce Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 21 Sep 2022 16:44:31 +0200 Subject: [PATCH 119/326] OpenXR - Stereo mirroring fixed, disable stereo when no world scale defined --- Common/VR/PPSSPPVR.cpp | 3 ++- Common/VR/VRRenderer.cpp | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 1e0931ebfe..13b424832c 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -230,7 +230,8 @@ bool StartVRRender() { // Decide if the scene is 3D or not if (g_Config.bEnableVR && !VR_GetConfig(VR_CONFIG_FORCE_2D) && (VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) > 15)) { - VR_SetConfig(VR_CONFIG_MODE, g_Config.bEnableStereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF); + bool stereo = VR_GetConfig(VR_CONFIG_6DOF_PRECISE) && g_Config.bEnableStereo; + VR_SetConfig(VR_CONFIG_MODE, stereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF); } else { VR_SetConfig(VR_CONFIG_MODE, VR_MODE_FLAT_SCREEN); } diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index 248b2b1487..2ad0014c10 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -359,13 +359,17 @@ void VR_FinishFrame( engine_t* engine ) { for (int eye = 0; eye < ovrMaxNumEyes; eye++) { int imageLayer = engine->appState.Renderer.Multiview ? eye : 0; ovrFramebuffer* frameBuffer = &engine->appState.Renderer.FrameBuffer[0]; - if ((vrMode != VR_MODE_MONO_6DOF) && !engine->appState.Renderer.Multiview) { - frameBuffer = &engine->appState.Renderer.FrameBuffer[eye]; + XrPosef pose = invViewTransform[0]; + if (vrMode != VR_MODE_MONO_6DOF) { + if (!engine->appState.Renderer.Multiview) { + frameBuffer = &engine->appState.Renderer.FrameBuffer[eye]; + } + pose = invViewTransform[eye]; } memset(&projection_layer_elements[eye], 0, sizeof(XrCompositionLayerProjectionView)); projection_layer_elements[eye].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; - projection_layer_elements[eye].pose = invViewTransform[eye]; + projection_layer_elements[eye].pose = pose; projection_layer_elements[eye].fov = fov; memset(&projection_layer_elements[eye].subImage, 0, sizeof(XrSwapchainSubImage)); @@ -502,9 +506,16 @@ ovrMatrix4f VR_GetMatrix( VRMatrix matrix ) { output.M[2][3] -= hmdposition.z * (vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f) * scale; } if (vrConfig[VR_CONFIG_6DOF_PRECISE] && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { - output.M[0][3] += (invViewTransform[1].position.x - invViewTransform[0].position.x) * scale; - output.M[1][3] += (invViewTransform[1].position.y - invViewTransform[0].position.y) * scale; - output.M[2][3] += (invViewTransform[1].position.z - invViewTransform[0].position.z) * scale; + float dx = fabs(invViewTransform[1].position.x - invViewTransform[0].position.x); + float dy = fabs(invViewTransform[1].position.y - invViewTransform[0].position.y); + float dz = fabs(invViewTransform[1].position.z - invViewTransform[0].position.z); + float ipd = sqrt(dx * dx + dy * dy + dz * dz); + XrVector3f separation = {ipd * scale, 0.0f, 0.0f}; + separation = XrQuaternionf_Rotate(invView.orientation, separation); + separation = XrVector3f_ScalarMultiply(separation, vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f); + output.M[0][3] -= separation.x; + output.M[1][3] -= separation.y; + output.M[2][3] -= separation.z; } } else { assert(false); From 94ae0fabfafbf28a6fd116a4f8d9a72deb380950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 18:33:15 +0200 Subject: [PATCH 120/326] CLUTs can be loaded from small rectangular textures. Need to linearize. Fixes #8406, although technically, we should wrap by bufw, not the texture width. --- GPU/Common/Draw2D.cpp | 24 ++++++++++++++++++++++++ GPU/Common/Draw2D.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 1 + GPU/Common/FramebufferManagerCommon.h | 1 + GPU/Common/TextureCacheCommon.cpp | 5 +++-- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index ff7aab3169..49aaa0bd5b 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -70,6 +70,23 @@ Draw2DPipelineInfo GenerateDraw2DCopyColorFs(ShaderWriter &writer) { }; } +Draw2DPipelineInfo GenerateDraw2DCopyColorRect2LinFs(ShaderWriter &writer) { + writer.DeclareSamplers(samplers); + writer.BeginFSMain(g_draw2Duniforms, varyings, FSFLAG_NONE); + writer.C(" vec2 tSize = texSize / scaleFactor;\n"); + writer.C(" vec2 pixels = v_texcoord * tSize;\n"); + writer.C(" float u = mod(floor(pixels.x), tSize.x);\n"); + writer.C(" float v = floor(pixels.x / tSize.x);\n"); + writer.C(" vec4 outColor = ").SampleTexture2D("tex", "vec2(u, v) / tSize").C(";\n"); + writer.EndFSMain("outColor", FSFLAG_NONE); + + return Draw2DPipelineInfo{ + "draw2d_copy_color_rect2lin", + RASTER_COLOR, + RASTER_COLOR, + }; +} + Draw2DPipelineInfo GenerateDraw2DCopyDepthFs(ShaderWriter &writer) { writer.DeclareSamplers(samplers); writer.BeginFSMain(Slice::empty(), varyings, FSFLAG_WRITEDEPTH); @@ -318,6 +335,13 @@ Draw2DPipeline *FramebufferManagerCommon::Get2DPipeline(Draw2DShader shader) { pipeline = draw2DPipelineColor_; break; + case DRAW2D_COPY_COLOR_RECT2LIN: + if (!draw2DPipelineColorRect2Lin_) { + draw2DPipelineColorRect2Lin_ = draw2D_.Create2DPipeline(&GenerateDraw2DCopyColorRect2LinFs); + } + pipeline = draw2DPipelineColorRect2Lin_; + break; + case DRAW2D_COPY_DEPTH: if (!draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) { // Can't do it diff --git a/GPU/Common/Draw2D.h b/GPU/Common/Draw2D.h index d44f7ff613..bf156e6d39 100644 --- a/GPU/Common/Draw2D.h +++ b/GPU/Common/Draw2D.h @@ -16,6 +16,7 @@ enum Draw2DShader { DRAW2D_COPY_DEPTH, DRAW2D_565_TO_DEPTH, DRAW2D_565_TO_DEPTH_DESWIZZLE, + DRAW2D_COPY_COLOR_RECT2LIN, }; inline RasterChannel Draw2DSourceChannel(Draw2DShader shader) { diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index dd01c3c499..8d17f2d9ff 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -2701,6 +2701,7 @@ void FramebufferManagerCommon::DeviceLost() { DoRelease(stencilUploadSampler_); DoRelease(stencilUploadPipeline_); DoRelease(draw2DPipelineColor_); + DoRelease(draw2DPipelineColorRect2Lin_); DoRelease(draw2DPipelineDepth_); DoRelease(draw2DPipeline565ToDepth_); DoRelease(draw2DPipeline565ToDepthDeswizzle_); diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index a38cc328d5..20fd15f87a 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -569,6 +569,7 @@ protected: // Draw2D pipelines Draw2DPipeline *draw2DPipelineColor_ = nullptr; + Draw2DPipeline *draw2DPipelineColorRect2Lin_ = nullptr; Draw2DPipeline *draw2DPipelineDepth_ = nullptr; Draw2DPipeline *draw2DPipeline565ToDepth_ = nullptr; Draw2DPipeline *draw2DPipeline565ToDepthDeswizzle_ = nullptr; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 97efd42cbe..8bc072114b 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1259,11 +1259,12 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { dynamicClutTemp_ = draw_->CreateFramebuffer(desc); } - // Download the pixels to our temp clut, scaling down if needed. + // Copy the pixels to our temp clut, scaling down if needed and wrapping. + // TODO: Take the clutRenderOffset_ into account here. framebufferManager_->BlitUsingRaster( chosenFramebuffer->fbo, 0.0f, 0.0f, 512.0f * chosenFramebuffer->renderScaleFactor, 1.0f, dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, - false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); + false, chosenFramebuffer->renderScaleFactor, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR_RECT2LIN), "copy_clut_to_temp"); clutRenderFormat_ = chosenFramebuffer->fb_format; } NotifyMemInfo(MemBlockFlags::ALLOC, clutAddr, loadBytes, "CLUT"); From 78ab0139144df5fb3bfc5f29460650b1159945bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 18:37:40 +0200 Subject: [PATCH 121/326] Shouldn't 'floor' there --- GPU/Common/Draw2D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index 49aaa0bd5b..83df3ee34d 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -75,7 +75,7 @@ Draw2DPipelineInfo GenerateDraw2DCopyColorRect2LinFs(ShaderWriter &writer) { writer.BeginFSMain(g_draw2Duniforms, varyings, FSFLAG_NONE); writer.C(" vec2 tSize = texSize / scaleFactor;\n"); writer.C(" vec2 pixels = v_texcoord * tSize;\n"); - writer.C(" float u = mod(floor(pixels.x), tSize.x);\n"); + writer.C(" float u = mod(pixels.x, tSize.x);\n"); writer.C(" float v = floor(pixels.x / tSize.x);\n"); writer.C(" vec4 outColor = ").SampleTexture2D("tex", "vec2(u, v) / tSize").C(";\n"); writer.EndFSMain("outColor", FSFLAG_NONE); From 8ed1694a2ff5bc80c075b42883e021f483a86df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 23:49:50 +0200 Subject: [PATCH 122/326] Don't try to replace or scale CLUT8-on-GPU textures. See #8509 --- GPU/Common/TextureCacheCommon.cpp | 41 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 8bc072114b..17624f1e02 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2634,14 +2634,32 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt } } - if (isPPGETexture) { - plan.replaced = &replacer_.FindNone(); - plan.replaceValid = false; + bool canReplace = !isPPGETexture; + if (entry->status & TexCacheEntry::TexStatus::STATUS_CLUT_GPU) { + _dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8); + plan.decodeToClut8 = true; + // We only support 1 mip level when doing CLUT on GPU for now. + // Supporting more would be possible, just not very interesting until we need it. + plan.levelsToCreate = 1; + plan.levelsToLoad = 1; + plan.maxPossibleLevels = 1; + plan.scaleFactor = 1; + plan.saveTexture = false; // Can't yet save these properly. + canReplace = false; } else { + plan.decodeToClut8 = false; + } + + if (canReplace) { plan.replaced = &FindReplacement(entry, plan.w, plan.h, plan.depth); plan.replaceValid = plan.replaced->Valid(); + } else { + plan.replaced = &replacer_.FindNone(); + plan.replaceValid = false; } + // NOTE! Last chance to change scale factor here! + plan.saveTexture = false; if (plan.replaceValid) { // We're replacing, so we won't scale. @@ -2652,7 +2670,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt // But, we still need to create the texture at a larger size. plan.replaced->GetSize(0, plan.createW, plan.createH); } else { - if (replacer_.Enabled() && !plan.replaceValid && plan.depth == 1) { + if (replacer_.Enabled() && !plan.replaceValid && plan.depth == 1 && canReplace) { ReplacedTextureDecodeInfo replacedInfo; // TODO: Do we handle the race where a replacement becomes valid AFTER this but before we save? replacedInfo.cachekey = entry->CacheKey(); @@ -2683,21 +2701,6 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt plan.maxPossibleLevels = log2i(std::min(plan.createW, plan.createH)) + 1; } - if (entry->status & TexCacheEntry::TexStatus::STATUS_CLUT_GPU) { - _dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8); - plan.decodeToClut8 = true; - // We only support 1 mip level when doing CLUT on GPU for now. - // Supporting more would be possible, just not very interesting until we need it. - plan.levelsToCreate = 1; - plan.levelsToLoad = 1; - plan.maxPossibleLevels = 1; - plan.scaleFactor = 1; - plan.saveTexture = false; // Can't yet save these properly. - // TODO: Also forcibly disable replacement, or check that the replacement is a 8-bit paletted texture. - } else { - plan.decodeToClut8 = false; - } - if (plan.levelsToCreate == 1) { entry->status |= TexCacheEntry::STATUS_NO_MIPS; } else { From a92aaf9311994bee1a79ed436226a69b2084035c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 23:57:19 +0200 Subject: [PATCH 123/326] Dirty more state after depal --- GPU/Common/TextureCacheCommon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 17624f1e02..cfb6d1640d 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2194,7 +2194,7 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer ApplySamplingParams(samplerKey); // Since we started/ended render passes, might need these. - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE | DIRTY_VERTEXSHADER_STATE); } // Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format. @@ -2286,7 +2286,7 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { ApplySamplingParams(samplerKey); // Since we started/ended render passes, might need these. - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE | DIRTY_VERTEXSHADER_STATE); } void TextureCacheCommon::Clear(bool delete_them) { From a681d149dba06c438ce3f06eb66de131edcb608a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 21 Sep 2022 21:15:58 -0700 Subject: [PATCH 124/326] GPU: Use hardware tess params for texgen. Fixes field in Test Drive Unlimited with hardware tessellation. --- GPU/Common/VertexShaderGenerator.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 6e3f3185b3..d6837422d4 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1128,7 +1128,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } } else { if (hasTexcoord) { - WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); + if (doBezier || doSpline) + WRITE(p, " %sv_texcoord = vec3(tess.tex.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); + else + WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); } else { WRITE(p, " %sv_texcoord = vec3(u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); } @@ -1140,26 +1143,36 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag std::string temp_tc; switch (uvProjMode) { case GE_PROJMAP_POSITION: // Use model space XYZ as source - temp_tc = "vec4(position, 1.0)"; + if (doBezier || doSpline) + temp_tc = "vec4(tess.pos, 1.0)"; + else + temp_tc = "vec4(position, 1.0)"; break; case GE_PROJMAP_UV: // Use unscaled UV as source { // prescale is false here. if (hasTexcoord) { - temp_tc = "vec4(texcoord.xy, 0.0, 1.0)"; + if (doBezier || doSpline) + temp_tc = "vec4(tess.tex.xy, 0.0, 1.0)"; + else + temp_tc = "vec4(texcoord.xy, 0.0, 1.0)"; } else { temp_tc = "vec4(0.0, 0.0, 0.0, 1.0)"; } } break; case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source - if (hasNormal) + if (hasNormalTess) + temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormal ? "-" : ""); + else if (hasNormal) temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : ""); else temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; break; case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source - if (hasNormal) + if (hasNormalTess) + temp_tc = flipNormal ? "vec4(-tess.nrm, 1.0)" : "vec4(tess.nrm, 1.0)"; + else if (hasNormal) temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)"; else temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; From 0e6c90bc28c30ef5dd5d25af431b5300dbfa21a6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 21 Sep 2022 21:16:56 -0700 Subject: [PATCH 125/326] GE Debugger: Default auto flush enabled. It seems like it's usually what one would want, you can disable for debugging state issues. --- Windows/GEDebugger/GEDebugger.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index c7d70dc3ee..beae98a04a 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -134,7 +134,7 @@ private: int textureLevel_ = 0; bool showClut_ = false; bool forceOpaque_ = false; - bool autoFlush_ = false; + bool autoFlush_ = true; // The most recent primary/framebuffer and texture buffers. const GPUDebugBuffer *primaryBuffer_ = nullptr; const GPUDebugBuffer *secondBuffer_ = nullptr; From 1e78f3aa573bc7d2781bee8ad1500b74f322ea55 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 21 Sep 2022 22:06:32 -0700 Subject: [PATCH 126/326] GPU: Correct neg normal for hwtess texgen. --- GPU/Common/VertexShaderGenerator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index d6837422d4..126d188500 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1162,16 +1162,16 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } break; case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source - if (hasNormalTess) - temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormal ? "-" : ""); + if ((doBezier || doSpline) && hasNormalTess) + temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormalTess ? "-" : ""); else if (hasNormal) temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : ""); else temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; break; case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source - if (hasNormalTess) - temp_tc = flipNormal ? "vec4(-tess.nrm, 1.0)" : "vec4(tess.nrm, 1.0)"; + if ((doBezier || doSpline) && hasNormalTess) + temp_tc = flipNormalTess ? "vec4(-tess.nrm, 1.0)" : "vec4(tess.nrm, 1.0)"; else if (hasNormal) temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)"; else From a8eced47736d427d229f06859ccac51a2023ef8c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 21 Sep 2022 23:30:00 -0700 Subject: [PATCH 127/326] GLES: Avoid resizing tessellation data textures. Just recreate when it needs to be larger. Fixes Test Drive Unlimited issues noted in #16069. --- GPU/GLES/DrawEngineGLES.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 4eb23cd4d4..47b10722ef 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -500,7 +500,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p prevSizeU = size_u; prevSizeV = size_v; if (!data_tex[0]) - data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D, size_u * 3, size_v, 1, 1); + renderManager_->DeleteTexture(data_tex[0]); + data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D, size_u * 3, size_v, 1, 1); renderManager_->TextureImage(data_tex[0], 0, size_u * 3, size_v, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[0], 0, false); } @@ -518,7 +519,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p if (prevSizeWU < weights.size_u) { prevSizeWU = weights.size_u; if (!data_tex[1]) - data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_u * 2, 1, 1, 1); + renderManager_->DeleteTexture(data_tex[1]); + data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_u * 2, 1, 1, 1); renderManager_->TextureImage(data_tex[1], 0, weights.size_u * 2, 1, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[1], 0, false); } @@ -529,7 +531,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p if (prevSizeWV < weights.size_v) { prevSizeWV = weights.size_v; if (!data_tex[2]) - data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_v * 2, 1, 1, 1); + renderManager_->DeleteTexture(data_tex[2]); + data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_v * 2, 1, 1, 1); renderManager_->TextureImage(data_tex[2], 0, weights.size_v * 2, 1, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[2], 0, false); } From fc39f042ae2bc7704c8fa59853e82d3407d0c6fb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 22 Sep 2022 00:08:38 -0700 Subject: [PATCH 128/326] softgpu: Avoid unnecessary flushing for curves. We don't need to flush all drawing between curves in softgpu, let them queue up. --- GPU/Common/DrawEngineCommon.h | 2 ++ GPU/Common/SplineCommon.cpp | 3 ++- GPU/GPUCommon.cpp | 6 ++++-- GPU/Software/TransformUnit.cpp | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 40c397649b..8fc0382a29 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -147,6 +147,8 @@ protected: bool useHWTransform_ = false; bool useHWTessellation_ = false; + // Used to prevent unnecessary flushing in softgpu. + bool flushOnParams_ = true; // Vertex collector buffers u8 *decoded = nullptr; diff --git a/GPU/Common/SplineCommon.cpp b/GPU/Common/SplineCommon.cpp index d3f864a4d4..b2358f401b 100644 --- a/GPU/Common/SplineCommon.cpp +++ b/GPU/Common/SplineCommon.cpp @@ -577,7 +577,8 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic if (output.count) DispatchSubmitPrim(output.vertices, output.indices, PatchPrimToPrim(surface.primType), output.count, vertTypeID, gstate.getCullMode(), &generatedBytesRead); - DispatchFlush(); + if (flushOnParams_) + DispatchFlush(); if (origVertType & GE_VTYPE_TC_MASK) { gstate_c.uv = prevUVScale; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 3d64bdb1ab..a5fd84e696 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1942,7 +1942,8 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) { } // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. - drawEngineCommon_->DispatchFlush(); + if (flushOnParams_) + drawEngineCommon_->DispatchFlush(); Spline::BezierSurface surface; surface.tess_u = gstate.getPatchDivisionU(); @@ -2014,7 +2015,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { } // Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already. - drawEngineCommon_->DispatchFlush(); + if (flushOnParams_) + drawEngineCommon_->DispatchFlush(); Spline::SplineSurface surface; surface.tess_u = gstate.getPatchDivisionU(); diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index c1471f7172..660e7a82e1 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -54,6 +54,7 @@ SoftwareDrawEngine::SoftwareDrawEngine() { // All this is a LOT of memory, need to see if we can cut down somehow. Used for splines. decoded = (u8 *)AllocateMemoryPages(DECODED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE); decIndex = (u16 *)AllocateMemoryPages(DECODED_INDEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE); + flushOnParams_ = false; } SoftwareDrawEngine::~SoftwareDrawEngine() { From 287e025978607666f17c9ffe9aac1159942c3b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 09:12:20 +0200 Subject: [PATCH 129/326] Minor cleanups around dirtying of render state --- GPU/Common/FramebufferManagerCommon.cpp | 12 ++++++------ GPU/Common/ShaderCommon.h | 4 ++++ GPU/Common/StencilCommon.cpp | 6 +----- GPU/Common/TextureCacheCommon.cpp | 9 ++++----- GPU/GPUState.h | 7 +++---- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 8d17f2d9ff..c4655cf02e 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -647,7 +647,7 @@ void FramebufferManagerCommon::CopyToDepthFromOverlappingFramebuffers(VirtualFra } } - gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_BLEND_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } // Can't easily dynamically create these strings, we just pass along the pointer. @@ -915,7 +915,7 @@ void FramebufferManagerCommon::BlitFramebufferDepth(VirtualFramebuffer *src, Vir // Some GPUs can copy depth but only if stencil gets to come along for the ride. We only want to use this if there is no blit functionality. if (useCopy) { - draw_->CopyFramebufferImage(src->fbo, 0, 0, 0, 0, dst->fbo, 0, 0, 0, 0, w, h, 1, Draw::FB_DEPTH_BIT, "BlitFramebufferDepth"); + draw_->CopyFramebufferImage(src->fbo, 0, 0, 0, 0, dst->fbo, 0, 0, 0, 0, w, h, 1, Draw::FB_DEPTH_BIT, "CopyFramebufferDepth"); RebindFramebuffer("After BlitFramebufferDepth"); } else if (useBlit) { // We'll accept whether we get a separate depth blit or not... @@ -1097,7 +1097,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int pixelsTex->Release(); draw_->InvalidateCachedState(); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } } @@ -2767,7 +2767,7 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline((flags & DRAWTEX_DEPTH) ? DRAW2D_COPY_DEPTH : DRAW2D_COPY_COLOR)); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag) { @@ -2871,7 +2871,7 @@ void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX draw_->InvalidateCachedState(); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } // The input is raw pixel coordinates, scale not taken into account. @@ -2907,7 +2907,7 @@ void FramebufferManagerCommon::BlitUsingRaster( draw2D_.Blit(pipeline, srcX1, srcY1, srcX2, srcY2, destX1, destY1, destX2, destY2, (float)srcW, (float)srcH, (float)destW, (float)destH, linearFilter, scaleFactor); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } VirtualFramebuffer *FramebufferManagerCommon::ResolveFramebufferColorToFormat(VirtualFramebuffer *src, GEBufferFormat newFormat) { diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index 1a69e5c387..a4adfb70ea 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -109,6 +109,10 @@ enum : uint64_t { DIRTY_VERTEXSHADER_STATE = 1ULL << 47, DIRTY_FRAGMENTSHADER_STATE = 1ULL << 48, + // Everything that's not uniforms. Use this after using thin3d. + // TODO: Should we also add DIRTY_FRAMEBUF here? It kinda generally takes care of itself. + DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS, + DIRTY_ALL = 0xFFFFFFFFFFFFFFFF }; diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp index 561c6a2857..86a5508b80 100644 --- a/GPU/Common/StencilCommon.cpp +++ b/GPU/Common/StencilCommon.cpp @@ -186,13 +186,9 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU // Otherwise, we can skip alpha in many cases, in which case we don't even use a shader. if (flags & StencilUpload::IGNORE_ALPHA) { - shaderManager_->DirtyLastShader(); - if (dstBuffer->fbo) { draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "PerformStencilUpload_Clear"); } - - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE); return true; } } @@ -333,6 +329,6 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU tex->Release(); draw_->InvalidateCachedState(); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); return true; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index cfb6d1640d..3b0f662002 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2095,7 +2095,6 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer mode = ShaderDepalMode::SMOOTHED; } - // Since we started/ended render passes, might need these. gstate_c.Dirty(DIRTY_DEPAL); gstate_c.SetUseShaderDepal(mode); gstate_c.depalFramebufferFormat = framebuffer->fb_format; @@ -2193,8 +2192,8 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer SamplerCacheKey samplerKey = GetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight); ApplySamplingParams(samplerKey); - // Since we started/ended render passes, might need these. - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE | DIRTY_VERTEXSHADER_STATE); + // Since we've drawn using thin3d, might need these. + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } // Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format. @@ -2285,8 +2284,8 @@ void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { SamplerCacheKey samplerKey = GetFramebufferSamplingParams(texWidth, texHeight); ApplySamplingParams(samplerKey); - // Since we started/ended render passes, might need these. - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE | DIRTY_VERTEXSHADER_STATE); + // Since we've drawn using thin3d, might need these. + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } void TextureCacheCommon::Clear(bool delete_them) { diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 191a670376..db4a04d3cd 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -469,12 +469,12 @@ struct UVScale { // Might want to move this mechanism into the backend later. enum { GPU_SUPPORTS_DUALSOURCE_BLEND = FLAG_BIT(0), - // Free bit: 1 - GPU_SUPPORTS_GLSL_330 = FLAG_BIT(2), + // Free bits: 1-2 GPU_SUPPORTS_VS_RANGE_CULLING = FLAG_BIT(3), GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4), GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5), GPU_USE_DEPTH_RANGE_HACK = FLAG_BIT(6), + // Free bit: 7 GPU_SUPPORTS_ANISOTROPY = FLAG_BIT(8), GPU_USE_CLEAR_RAM_HACK = FLAG_BIT(9), GPU_SUPPORTS_INSTANCE_RENDERING = FLAG_BIT(10), @@ -485,8 +485,7 @@ enum { // Free bit: 15 GPU_SUPPORTS_DEPTH_TEXTURE = FLAG_BIT(16), GPU_SUPPORTS_ACCURATE_DEPTH = FLAG_BIT(17), - GPU_SUPPORTS_FRAGMENT_SHADER_INTERLOCK = FLAG_BIT(18), - // Free bits: 19 + // Free bits: 18-19 GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20), GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT = FLAG_BIT(21), GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22), From 188ab67d6ab993184e9b0291b0451f11d156faaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 09:28:32 +0200 Subject: [PATCH 130/326] More lenient heuristic for uploading depth buffers. Still behind compat flag. See #11100 --- GPU/Common/FramebufferManagerCommon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 8d17f2d9ff..f421b3458a 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -553,10 +553,10 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); // Special compatibility trick for Burnout Dominator lens flares. Not sure how to best generalize this. See issue #11100 - if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && (currentRenderVfb_->usageFlags & FB_USAGE_CLUT) != 0) { + if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && currentRenderVfb_->z_address > 0x04110000) { // Set the flag, then upload memory contents to depth channel. // Sanity check the depth buffer pointer. - if (currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { + if (currentRenderVfb_->z_address > 0x04110000 && currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); From bd196f7a5053dea37bbeed70ebcba79ed4b50123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 09:57:53 +0200 Subject: [PATCH 131/326] Preserve depth buffer on framebuffer resize, if has been used. --- GPU/Common/FramebufferManagerCommon.cpp | 26 ++++++++++++++++--------- GPU/Common/TextureCacheCommon.cpp | 8 ++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index f421b3458a..cdfe1fd677 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -547,16 +547,19 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { return; } + // First time use of this framebuffer's depth buffer. + currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; + // If this first draw call is anything other than a clear, "resolve" the depth buffer, // by copying from any overlapping buffers with fresher content. - if (!isClearingDepth) { + if (!isClearingDepth && useBufferedRendering_) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); // Special compatibility trick for Burnout Dominator lens flares. Not sure how to best generalize this. See issue #11100 if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && currentRenderVfb_->z_address > 0x04110000) { // Set the flag, then upload memory contents to depth channel. // Sanity check the depth buffer pointer. - if (currentRenderVfb_->z_address > 0x04110000 && currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { + if (currentRenderVfb_->z_address > 0x04110000) { if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); @@ -565,9 +568,6 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { } } - // First time use of this framebuffer's depth buffer. - currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; - currentRenderVfb_->depthBindSeq = GetBindSeqCount(); } @@ -1540,7 +1540,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, if (creating) { WARN_LOG(FRAMEBUF, "Creating %s FBO at %08x/%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force); } else { - WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%d from %dx%d to %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force); + WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%d from %dx%d to %dx%d (force=%d, skipCopy=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force, (int)skipCopy); } // During hardware rendering, we always render at full color depth even if the game wouldn't on real hardware. @@ -1578,8 +1578,10 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, if (vfb->fbo) { draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "ResizeFramebufFBO"); if (!skipCopy) { - BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_COLOR, "Blit_ResizeFramebufFBO"); - // Depth copying is handled by deferred copies later. + BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_COLOR, "BlitColor_ResizeFramebufFBO"); + } + if (vfb->usageFlags & FB_USAGE_RENDER_DEPTH) { + BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_DEPTH, "BlitDepth_ResizeFramebufFBO"); } } fbosToDelete_.push_back(old.fbo); @@ -2214,8 +2216,9 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS int dstBpp = BufferFormatBytesPerPixel(dstRect.vfb->fb_format); float dstXFactor = (float)bpp / dstBpp; if (dstRect.w_bytes / bpp > dstRect.vfb->width || dstRect.h > dstRect.vfb->height) { - // The buffer isn't big enough, and we have a clear hint of size. Resize. + // The buffer isn't big enough, and we have a clear hint of size. Resize. // This happens in Valkyrie Profile when uploading video at the ending. + // Also happens to the CLUT framebuffer in the Burnout Dominator lens flare effect. See #16075 ResizeFramebufFBO(dstRect.vfb, dstRect.w_bytes / bpp, dstRect.h, false, true); // Make sure we don't flop back and forth. dstRect.vfb->newWidth = std::max(dstRect.w_bytes / bpp, (int)dstRect.vfb->width); @@ -2780,6 +2783,11 @@ void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX return; } + if (channel == RASTER_DEPTH && !draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) { + // Can't do anything :( + return; + } + // Perform a little bit of clipping first. // Block transfer coords are unsigned so I don't think we need to clip on the left side.. Although there are // other uses for BlitFramebuffer. diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index cfb6d1640d..686bb226ab 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1208,6 +1208,8 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { clutRenderOffset_ = MAX_CLUT_OFFSET; const std::vector &framebuffers = framebufferManager_->Framebuffers(); + u32 bestClutAddress = 0xFFFFFFFF; + VirtualFramebuffer *chosenFramebuffer = nullptr; for (VirtualFramebuffer *framebuffer : framebuffers) { const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF; @@ -1234,7 +1236,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); framebuffer->last_frame_clut = gpuStats.numFlips; framebuffer->usageFlags |= FB_USAGE_CLUT; - clutRenderAddress_ = framebuffer->fb_address; + bestClutAddress = framebuffer->fb_address; clutRenderOffset_ = (u32)offset; chosenFramebuffer = framebuffer; if (offset == 0) { @@ -1245,7 +1247,9 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { } } - if (chosenFramebuffer) { + if (chosenFramebuffer && chosenFramebuffer->fbo) { + clutRenderAddress_ = bestClutAddress; + if (!dynamicClutTemp_) { Draw::FramebufferDesc desc{}; desc.width = 512; From 8e30a7ccfcce8028a97b8e84615e1a5a30be3466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 13:12:58 +0200 Subject: [PATCH 132/326] Vulkan: Don't have renderpasses store/load depth buffers when we don't use them --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 56 +++++++++++++---------- Common/GPU/Vulkan/VulkanQueueRunner.h | 7 +-- Common/GPU/Vulkan/VulkanRenderManager.cpp | 29 +++++++----- GPU/Vulkan/PipelineManagerVulkan.cpp | 15 +++--- GPU/Vulkan/PipelineManagerVulkan.h | 1 - 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 8127114148..0db9618e76 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -326,29 +326,33 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) { // Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827 // Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies -VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) { - bool selfDependency = rpType == RP_TYPE_COLOR_DEPTH_INPUT; +VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) { + bool selfDependency = rpType == RP_TYPE_COLOR_INPUT || rpType == RP_TYPE_COLOR_DEPTH_INPUT; + bool isBackbuffer = rpType == RP_TYPE_BACKBUFFER; + bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT; VkAttachmentDescription attachments[2] = {}; - attachments[0].format = rpType == RP_TYPE_BACKBUFFER ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; + attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction); attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction); attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[0].initialLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[0].finalLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments[0].flags = 0; - attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat; - attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction); - attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction); - attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction); - attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); - attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - attachments[1].flags = 0; + if (hasDepth) { + attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat; + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction); + attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction); + attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction); + attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); + attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[1].flags = 0; + } VkAttachmentReference color_reference{}; color_reference.attachment = 0; @@ -371,7 +375,9 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_reference; subpass.pResolveAttachments = nullptr; - subpass.pDepthStencilAttachment = &depth_reference; + if (hasDepth) { + subpass.pDepthStencilAttachment = &depth_reference; + } subpass.preserveAttachmentCount = 0; subpass.pPreserveAttachments = nullptr; @@ -380,12 +386,12 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp size_t numDeps = 0; VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; - rp.attachmentCount = 2; + rp.attachmentCount = hasDepth ? 2 : 1; rp.pAttachments = attachments; rp.subpassCount = 1; rp.pSubpasses = &subpass; - if (rpType == RP_TYPE_BACKBUFFER) { + if (isBackbuffer) { deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL; deps[numDeps].dstSubpass = 0; deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; @@ -424,7 +430,7 @@ VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) { // practical later when referring to it. Could change to on-demand if it feels motivated // but I think the render pass objects are cheap. if (!pass[(int)rpType]) { - pass[(int)rpType] = CreateRP(vulkan, key_, (RenderPassType)rpType); + pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType); } return pass[(int)rpType]; } @@ -873,8 +879,10 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { const char *renderCmd; switch (step.render.renderPassType) { case RP_TYPE_BACKBUFFER: renderCmd = "BACKBUF"; break; - case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER"; break; - case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_INPUT"; break; + case RP_TYPE_COLOR: renderCmd = "RENDER"; break; + case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER_DEPTH"; break; + case RP_TYPE_COLOR_INPUT: renderCmd = "RENDER_INPUT"; break; + case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_DEPTH_INPUT"; break; default: renderCmd = "N/A"; } snprintf(buffer, sizeof(buffer), "%s %s (draws: %d, %dx%d/%dx%d, fb: %p, )", renderCmd, step.tag, step.render.numDraws, actual_w, actual_h, w, h, step.render.framebuffer); @@ -1153,7 +1161,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; break; default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected color layout %d", (int)colorLayout); + _dbg_assert_msg_(false, "TransitionToOptimal: Unexpected color layout %d", (int)colorLayout); break; } recordBarrier->TransitionImage( @@ -1189,7 +1197,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; break; default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected depth layout %d", (int)depthStencilLayout); + _dbg_assert_msg_(false, "TransitionToOptimal: Unexpected depth layout %d", (int)depthStencilLayout); break; } recordBarrier->TransitionImage( @@ -1236,7 +1244,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou // Nothing to do. break; default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected final color layout %d", (int)colorLayout); + _dbg_assert_msg_(false, "TransitionFromOptimal: Unexpected final color layout %d", (int)colorLayout); break; } barrier[0].oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; @@ -1275,7 +1283,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou // Nothing to do. break; default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected final depth layout %d", (int)depthStencilLayout); + _dbg_assert_msg_(false, "TransitionFromOptimal: Unexpected final depth layout %d", (int)depthStencilLayout); break; } barrier[barrierCount].oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 2c76262a6a..d6d07ec6ce 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -43,18 +43,19 @@ enum class VKRRenderCommand : uint8_t { enum class PipelineFlags { NONE = 0, - USES_LINES = (1 << 2), USES_BLEND_CONSTANT = (1 << 3), - USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer. + USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth or stencil buffers. USES_INPUT_ATTACHMENT = (1 << 5), }; ENUM_CLASS_BITOPS(PipelineFlags); // Pipelines need to be created for the right type of render pass. enum RenderPassType { - RP_TYPE_BACKBUFFER, + RP_TYPE_BACKBUFFER, // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible. RP_TYPE_COLOR_DEPTH, RP_TYPE_COLOR_DEPTH_INPUT, + RP_TYPE_COLOR, + RP_TYPE_COLOR_INPUT, // Later will add pure-color render passes. RP_TYPE_COUNT, }; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 4d1363e49c..045ebf7a8a 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -158,33 +158,37 @@ VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRe // We create the actual framebuffer objects on demand, because some combinations might not make sense. } -VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType renderPassType) { - if (framebuf[(int)renderPassType]) { - return framebuf[(int)renderPassType]; +VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) { + if (framebuf[(int)rpType]) { + return framebuf[(int)rpType]; } VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; VkImageView views[2]{}; - fbci.renderPass = compatibleRenderPass->Get(vulkan_, renderPassType); - fbci.attachmentCount = 2; - fbci.pAttachments = views; + bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT; + views[0] = color.imageView; - views[1] = depth.imageView; + if (hasDepth) { + views[1] = depth.imageView; + } + fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType); + fbci.attachmentCount = hasDepth ? 2 : 1; + fbci.pAttachments = views; fbci.width = width; fbci.height = height; fbci.layers = 1; - VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)renderPassType]); + VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]); _assert_(res == VK_SUCCESS); if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) { vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag_.c_str()).c_str()); vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag_.c_str()).c_str()); - vulkan_->SetDebugName(framebuf[(int)renderPassType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str()); + vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str()); } - return framebuf[(int)renderPassType]; + return framebuf[(int)rpType]; } VKRFramebuffer::~VKRFramebuffer() { @@ -656,15 +660,16 @@ void VulkanRenderManager::EndCurRenderStep() { curRenderStep_->render.colorLoad, curRenderStep_->render.depthLoad, curRenderStep_->render.stencilLoad, curRenderStep_->render.colorStore, curRenderStep_->render.depthStore, curRenderStep_->render.stencilStore, }; - RenderPassType rpType = RP_TYPE_COLOR_DEPTH; // Save the accumulated pipeline flags so we can use that to configure the render pass. // We'll often be able to avoid loading/saving the depth/stencil buffer. curRenderStep_->render.pipelineFlags = curPipelineFlags_; + bool depthStencil = (curPipelineFlags_ & PipelineFlags::USES_DEPTH_STENCIL) != 0; + RenderPassType rpType = depthStencil ? RP_TYPE_COLOR_DEPTH : RP_TYPE_COLOR; if (!curRenderStep_->render.framebuffer) { rpType = RP_TYPE_BACKBUFFER; } else if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) { // Not allowed on backbuffers. - rpType = RP_TYPE_COLOR_DEPTH_INPUT; + rpType = depthStencil ? RP_TYPE_COLOR_INPUT : RP_TYPE_COLOR_DEPTH_INPUT; } // TODO: Also add render pass types for depth/stencil-less. diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index e950dfc457..21ce7df61d 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -170,8 +170,8 @@ static std::string CutFromMain(std::string str) { } static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache, - VkPipelineLayout layout, PipelineFlags pipelineFlags, const VulkanPipelineRasterStateKey &key, - const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask) { + VkPipelineLayout layout, PipelineFlags pipelineFlags, const VulkanPipelineRasterStateKey &key, + const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask) { VulkanPipeline *vulkanPipeline = new VulkanPipeline(); VKRGraphicsPipelineDesc *desc = &vulkanPipeline->desc; desc->pipelineCache = pipelineCache; @@ -221,7 +221,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkDynamicState *dynamicStates = &desc->dynamicStates[0]; int numDyn = 0; if (key.blendEnable && - (UsesBlendConstant(key.srcAlpha) || UsesBlendConstant(key.srcColor) || UsesBlendConstant(key.destAlpha) || UsesBlendConstant(key.destColor))) { + (UsesBlendConstant(key.srcAlpha) || UsesBlendConstant(key.srcColor) || UsesBlendConstant(key.destAlpha) || UsesBlendConstant(key.destColor))) { dynamicStates[numDyn++] = VK_DYNAMIC_STATE_BLEND_CONSTANTS; useBlendConstant = true; } @@ -232,12 +232,12 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, dynamicStates[numDyn++] = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK; dynamicStates[numDyn++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; } - + VkPipelineDynamicStateCreateInfo &ds = desc->ds; ds.flags = 0; ds.pDynamicStates = dynamicStates; ds.dynamicStateCount = numDyn; - + VkPipelineRasterizationStateCreateInfo &rs = desc->rs; rs.flags = 0; rs.depthBiasEnable = false; @@ -299,10 +299,9 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, variantBitmask, "game"); vulkanPipeline->pipeline = pipeline; - if (useBlendConstant) + if (useBlendConstant) { pipelineFlags |= PipelineFlags::USES_BLEND_CONSTANT; - if (key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP) - pipelineFlags |= PipelineFlags::USES_LINES; + } if (dss.depthTestEnable || dss.stencilTestEnable) { pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL; } diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index 08907e3b3e..a7dac0c30f 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -58,7 +58,6 @@ struct VulkanPipeline { PipelineFlags pipelineFlags; // PipelineFlags enum above. bool UsesBlendConstant() const { return (pipelineFlags & PipelineFlags::USES_BLEND_CONSTANT) != 0; } - bool UsesLines() const { return (pipelineFlags & PipelineFlags::USES_LINES) != 0; } bool UsesDepthStencil() const { return (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) != 0; } bool UsesInputAttachment() const { return (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) != 0; } From 6d2f29e7eb6a9eb8a28058dd54ddf05342a64f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Sep 2022 13:24:39 +0200 Subject: [PATCH 133/326] If depth or stencil are cleared in a renderpass, set the pipeline flag. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 045ebf7a8a..d8c16e331a 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -719,9 +719,11 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } if (depth == VKRRenderPassLoadAction::CLEAR) { clearMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL; } if (stencil == VKRRenderPassLoadAction::CLEAR) { clearMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL; } // If we need a clear and the previous step has commands already, it's best to just add a clear and keep going. @@ -1002,6 +1004,10 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten curRenderStep_->render.depthLoad = (clearMask & VK_IMAGE_ASPECT_DEPTH_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP; curRenderStep_->render.stencilLoad = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP; + if (clearMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL; + } + // In case there were commands already. curRenderStep_->render.numDraws = 0; RemoveDrawCommands(&curRenderStep_->commands); From 11b807828fd4259102477270a425806a2b52938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 10:22:29 +0200 Subject: [PATCH 134/326] Fix for render pass merge --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 15 ++++++--------- Common/GPU/Vulkan/VulkanQueueRunner.h | 11 ++++++++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 0db9618e76..8ad509afbb 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -30,16 +30,14 @@ static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) { // We need to take the "max" of the features used in the two render passes. RenderPassType MergeRPTypes(RenderPassType a, RenderPassType b) { // Either both are backbuffer type, or neither are. - _dbg_assert_((a == RP_TYPE_BACKBUFFER) == (b == RP_TYPE_BACKBUFFER)); - if (a == b) { - // Trivial merging case. + // These can't merge with other renderpasses + if (a == RP_TYPE_BACKBUFFER || b == RP_TYPE_BACKBUFFER) { + _dbg_assert_(a == b); return a; - } else if (a == RP_TYPE_COLOR_DEPTH && b == RP_TYPE_COLOR_DEPTH_INPUT) { - return RP_TYPE_COLOR_DEPTH_INPUT; - } else if (a == RP_TYPE_COLOR_DEPTH_INPUT && b == RP_TYPE_COLOR_DEPTH) { - return RP_TYPE_COLOR_DEPTH_INPUT; } - return a; + + // The rest we can just OR together to get the maximum feature set. + return (RenderPassType)((u32)a | (u32)b); } void VulkanQueueRunner::CreateDeviceObjects() { @@ -399,7 +397,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas deps[numDeps].srcAccessMask = 0; deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; numDeps++; - rp.dependencyCount = 1; } if (selfDependency) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index d6d07ec6ce..9c1fee77d9 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -51,11 +51,16 @@ ENUM_CLASS_BITOPS(PipelineFlags); // Pipelines need to be created for the right type of render pass. enum RenderPassType { - RP_TYPE_BACKBUFFER, // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible. - RP_TYPE_COLOR_DEPTH, - RP_TYPE_COLOR_DEPTH_INPUT, + // These four are organized so that bit 0 is DEPTH and bit 1 is INPUT, so + // they can be OR-ed together in MergeRPTypes. RP_TYPE_COLOR, + RP_TYPE_COLOR_DEPTH, RP_TYPE_COLOR_INPUT, + RP_TYPE_COLOR_DEPTH_INPUT, + + // This is the odd one out, and gets special handling in MergeRPTypes. + RP_TYPE_BACKBUFFER, // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible. + // Later will add pure-color render passes. RP_TYPE_COUNT, }; From a31c5c8239e6a58abaf69db58e9d180212420c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 10:48:05 +0200 Subject: [PATCH 135/326] Cleanup logic --- GPU/Common/FramebufferManagerCommon.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index cdfe1fd677..140a0387bc 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -555,15 +555,12 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { if (!isClearingDepth && useBufferedRendering_) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); - // Special compatibility trick for Burnout Dominator lens flares. Not sure how to best generalize this. See issue #11100 + // Special compatibility trick for Burnout Dominator lens flares. See issue #11100 if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && currentRenderVfb_->z_address > 0x04110000) { - // Set the flag, then upload memory contents to depth channel. // Sanity check the depth buffer pointer. - if (currentRenderVfb_->z_address > 0x04110000) { - if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { - const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); - DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); - } + if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { + const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); + DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); } } } From 078fa9beb2ac8a22340e772d80799d40f6596721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 15:27:17 +0200 Subject: [PATCH 136/326] Fix corruption of Ridge Racer speedometers with AutoMaxQuality enabled. See #8509 --- GPU/Common/TextureCacheCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index e47f768b35..dddd2d2edd 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2698,7 +2698,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt plan.levelsToLoad = 1; } - if (plan.isVideo || plan.depth != 1) { + if (plan.isVideo || plan.depth != 1 || plan.decodeToClut8) { plan.maxPossibleLevels = 1; } else { plan.maxPossibleLevels = log2i(std::min(plan.createW, plan.createH)) + 1; From c76d7e844c36fd6ea9f65d6eb83a598933654ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 19:37:46 +0200 Subject: [PATCH 137/326] Fix Vulkan regression of #16075 due to silly typo. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 2 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index d8c16e331a..82c99e89ce 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -669,7 +669,7 @@ void VulkanRenderManager::EndCurRenderStep() { rpType = RP_TYPE_BACKBUFFER; } else if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) { // Not allowed on backbuffers. - rpType = depthStencil ? RP_TYPE_COLOR_INPUT : RP_TYPE_COLOR_DEPTH_INPUT; + rpType = depthStencil ? RP_TYPE_COLOR_DEPTH_INPUT : RP_TYPE_COLOR_INPUT; } // TODO: Also add render pass types for depth/stencil-less. diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 34bbdd5efb..6a5af31453 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1056,6 +1056,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char if (depth->info.depthTestEnable || depth->info.stencilTestEnable) { pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL; } + // TODO: We need code to set USES_BLEND_CONSTANT here too, if we're ever gonna use those in thin3d code. VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), pipelineFlags, tag); From 0ab5d4be7f2a08b8f3a63e43576ae346d4858e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 20:00:35 +0200 Subject: [PATCH 138/326] Use Unknown's idea from #16081 instead of the UploadDepthForCLUTTexture compat.ini flag. --- Core/Compatibility.cpp | 1 - Core/Compatibility.h | 1 - GPU/Common/FramebufferManagerCommon.cpp | 5 +++-- assets/compat.ini | 13 ------------- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 62ba3ec21b..75c0623ead 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -108,7 +108,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin); CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn); CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT); - CheckSetting(iniFile, gameID, "UploadDepthForCLUTTextures", &flags_.UploadDepthForCLUTTextures); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 3a84bfb88e..04f3ab22b7 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -89,7 +89,6 @@ struct CompatFlags { bool SplitFramebufferMargin; bool ForceLowerResolutionForEffectsOn; bool AllowDownloadCLUT; - bool UploadDepthForCLUTTextures; }; struct VRCompat { diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index af924216dd..99ceb7ec36 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -555,8 +555,9 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { if (!isClearingDepth && useBufferedRendering_) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); - // Special compatibility trick for Burnout Dominator lens flares. See issue #11100 - if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && currentRenderVfb_->z_address > 0x04110000) { + // Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081. + // Might make this more generic and upload the whole depth buffer if we find it's needed for something. + if (currentRenderVfb_->lastFrameNewSize == gpuStats.numFlips) { // Sanity check the depth buffer pointer. if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); diff --git a/assets/compat.ini b/assets/compat.ini index f153b93933..5ce6077091 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1278,16 +1278,3 @@ ULJM05738 = true [AllowDownloadCLUT] # Temporary compatibility option, while working on the GPU CLUT-from-framebuffer path. # Not required for any games now that it works, but might be useful for development. - -[UploadDepthForCLUTTextures] -# Burnout Dominator - lens flare effect (issue #11100) -# We need a preinitialized depth buffer -ULUS10236 = true -ULES00703 = true - -# Need for Speed - Shift (same as Burnout Dominator) -ULUS10462 = true -ULES01275 = true -ULJM05494 = true -NPJH50143 = true -ULJM05738 = true From a6d6e0a3cce3005ead5cafed2e306225cbb6436f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Sep 2022 22:11:16 +0200 Subject: [PATCH 139/326] Texture/Framebuffer match: Ignore stride if texHeight == 1. Fixes Ridge Racer lens flares. --- GPU/Common/FramebufferManagerCommon.cpp | 5 +++-- GPU/Common/TextureCacheCommon.cpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index af924216dd..99ceb7ec36 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -555,8 +555,9 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { if (!isClearingDepth && useBufferedRendering_) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); - // Special compatibility trick for Burnout Dominator lens flares. See issue #11100 - if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && currentRenderVfb_->z_address > 0x04110000) { + // Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081. + // Might make this more generic and upload the whole depth buffer if we find it's needed for something. + if (currentRenderVfb_->lastFrameNewSize == gpuStats.numFlips) { // Sanity check the depth buffer pointer. if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index dddd2d2edd..205dcbc602 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1018,7 +1018,8 @@ bool TextureCacheCommon::MatchFramebuffer( return false; } - if (fb_stride_in_bytes != tex_stride_in_bytes) { + // Note the check for texHeight - we really don't care about a stride mismatch if texHeight == 1. + if (fb_stride_in_bytes != tex_stride_in_bytes && texHeight > 1) { // Probably irrelevant. Although, as we shall see soon, there are exceptions. // Burnout Dominator lens flare trick special case. if (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8 && texWidth == 4 && texHeight == 1) { From 84a3f6de71a2e29c24af4f78b7d2dc7c35d4c45f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 22 Sep 2022 18:18:49 -0700 Subject: [PATCH 140/326] softgpu: Remove unnecessary state param. Oops, meant to remove this when refactoring imm prims. --- GPU/Software/BinManager.cpp | 2 +- GPU/Software/BinManager.h | 2 +- GPU/Software/TransformUnit.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 42eef12eda..f7005a25a5 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -161,7 +161,7 @@ BinManager::~BinManager() { } } -void BinManager::UpdateState(bool throughMode) { +void BinManager::UpdateState() { PROFILE_THIS_SCOPE("bin_state"); if (HasDirty(SoftDirty::PIXEL_ALL | SoftDirty::SAMPLER_ALL | SoftDirty::RAST_ALL)) { if (states_.Full()) diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index 79146eae1d..3d93446ca3 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -181,7 +181,7 @@ public: BinManager(); ~BinManager(); - void UpdateState(bool throughMode); + void UpdateState(); void UpdateClut(const void *src); const Rasterizer::RasterizerState &State() { diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 660e7a82e1..b6188abdce 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -530,7 +530,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // TODO: Do this in two passes - first process the vertices (before indexing/stripping), // then resolve the indices. This lets us avoid transforming shared vertices twice. - binner_->UpdateState(vreader.isThrough()); + binner_->UpdateState(); hasDraws_ = true; static TransformState transformState; From 067fac681702f7b18635bdf02a72e85cb9f04d0a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 22 Sep 2022 18:19:53 -0700 Subject: [PATCH 141/326] softgpu: Skip matrix multiply for fog factor calc. We can just use a dot product instead, and always skip viewpos. --- GPU/Software/TransformUnit.cpp | 83 ++++++++++++---------------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index b6188abdce..1792e9ca2c 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -224,19 +224,15 @@ ScreenCoords TransformUnit::DrawingToScreen(const DrawingCoords &coords, u16 z) } enum class MatrixMode { - NONE = 0, POS_TO_CLIP = 1, - POS_TO_VIEW = 2, - WORLD_TO_CLIP = 3, + WORLD_TO_CLIP = 2, }; struct TransformState { Lighting::State lightingState; - float fogEnd; - float fogSlope; - float matrix[16]; + Vec4f posToFog; Vec3f screenScale; Vec3f screenAdd; @@ -265,20 +261,7 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { state->uvGenMode = gstate.getUVGenMode(); if (state->enableTransform) { - if (state->enableFog) { - state->fogEnd = getFloat24(gstate.fog1); - state->fogSlope = getFloat24(gstate.fog2); - // Same fixup as in ShaderManagerGLES.cpp - if (my_isnanorinf(state->fogEnd)) { - state->fogEnd = std::signbit(state->fogEnd) ? -INFINITY : INFINITY; - } - if (my_isnanorinf(state->fogSlope)) { - state->fogSlope = std::signbit(state->fogSlope) ? -INFINITY : INFINITY; - } - } - bool canSkipWorldPos = true; - bool canSkipViewPos = !state->enableFog; if (state->enableLighting) { Lighting::ComputeState(&state->lightingState, vreader.hasColor0()); for (int i = 0; i < 4; ++i) { @@ -291,29 +274,35 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { float world[16]; float view[16]; - if (canSkipWorldPos && canSkipViewPos) { - state->matrixMode = (uint8_t)MatrixMode::POS_TO_CLIP; - + float worldview[16]; + ConvertMatrix4x3To4x4(view, gstate.viewMatrix); + if (state->enableFog || canSkipWorldPos) { ConvertMatrix4x3To4x4(world, gstate.worldMatrix); - ConvertMatrix4x3To4x4(view, gstate.viewMatrix); - - float worldview[16]; Matrix4ByMatrix4(worldview, world, view); + } + + if (canSkipWorldPos) { + state->matrixMode = (uint8_t)MatrixMode::POS_TO_CLIP; Matrix4ByMatrix4(state->matrix, worldview, gstate.projMatrix); - } else if (canSkipWorldPos) { - state->matrixMode = (uint8_t)MatrixMode::POS_TO_VIEW; - - ConvertMatrix4x3To4x4(world, gstate.worldMatrix); - ConvertMatrix4x3To4x4(view, gstate.viewMatrix); - - Matrix4ByMatrix4(state->matrix, world, view); - } else if (canSkipViewPos) { - state->matrixMode = (uint8_t)MatrixMode::WORLD_TO_CLIP; - - ConvertMatrix4x3To4x4(view, gstate.viewMatrix); - Matrix4ByMatrix4(state->matrix, view, gstate.projMatrix); } else { - state->matrixMode = (uint8_t)MatrixMode::NONE; + state->matrixMode = (uint8_t)MatrixMode::WORLD_TO_CLIP; + Matrix4ByMatrix4(state->matrix, view, gstate.projMatrix); + } + + if (state->enableFog) { + float fogEnd = getFloat24(gstate.fog1); + float fogSlope = getFloat24(gstate.fog2); + // Same fixup as in ShaderManagerGLES.cpp + if (my_isnanorinf(fogEnd)) { + fogEnd = std::signbit(fogEnd) ? -INFINITY : INFINITY; + } + if (my_isnanorinf(fogSlope)) { + fogSlope = std::signbit(fogSlope) ? -INFINITY : INFINITY; + } + + // We bake fog end and slope into the dot product. + state->posToFog = Vec4f(worldview[2], worldview[6], worldview[10], worldview[14] + fogEnd); + state->posToFog *= fogSlope; } state->screenScale = Vec3f(gstate.getViewportXScale(), gstate.getViewportYScale(), gstate.getViewportZScale()); @@ -379,28 +368,12 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState if (state.enableTransform) { WorldCoords worldpos; - ModelCoords viewpos; switch (MatrixMode(state.matrixMode)) { - case MatrixMode::NONE: - worldpos = TransformUnit::ModelToWorld(pos); - viewpos = TransformUnit::WorldToView(worldpos); - vertex.clippos = TransformUnit::ViewToClip(viewpos); - break; - case MatrixMode::POS_TO_CLIP: vertex.clippos = Vec3ByMatrix44(pos, state.matrix); break; - case MatrixMode::POS_TO_VIEW: -#ifdef _M_SSE - viewpos = Vec3ByMatrix44(pos, state.matrix).vec; -#else - viewpos = Vec3ByMatrix44(pos, state.matrix).rgb(); -#endif - vertex.clippos = TransformUnit::ViewToClip(viewpos); - break; - case MatrixMode::WORLD_TO_CLIP: worldpos = TransformUnit::ModelToWorld(pos); vertex.clippos = Vec3ByMatrix44(worldpos, state.matrix); @@ -424,7 +397,7 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState } if (state.enableFog) { - vertex.fogdepth = (viewpos.z + state.fogEnd) * state.fogSlope; + vertex.fogdepth = Dot(state.posToFog, Vec4f(pos, 1.0f)); } else { vertex.fogdepth = 1.0f; } From 88b3b26ed3d4647a643af6d645be7f87703aa909 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 22 Sep 2022 18:27:59 -0700 Subject: [PATCH 142/326] softgpu: Cache reused indexed verts. This happens a lot for spline/bezier, so can significantly speed up curve heavy scenes. Isn't necessarily that common otherwise, though. --- GPU/Software/TransformUnit.cpp | 126 ++++++++++++++++++++++----------- GPU/Software/TransformUnit.h | 3 + 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 1792e9ca2c..d6628044f7 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -465,10 +465,75 @@ SoftDirty TransformUnit::GetDirty() { return binner_->GetDirty(); } +class SoftwareVertexReader { +public: + SoftwareVertexReader(u8 *base, VertexDecoder &vdecoder, u32 vertex_type, int vertex_count, const void *vertices, const void *indices, const TransformState &transformState, TransformUnit &transform) + : vreader_(base, vdecoder.GetDecVtxFmt(), vertex_type), conv_(vertex_type, indices), transformState_(transformState), transform_(transform) { + useIndices_ = indices != nullptr; + lowerBound_ = 0; + upperBound_ = vertex_count == 0 ? 0 : vertex_count - 1; + + if (useIndices_) + GetIndexBounds(indices, vertex_count, vertex_type, &lowerBound_, &upperBound_); + if (vertex_count != 0) + vdecoder.DecodeVerts(base, vertices, lowerBound_, upperBound_); + + // If we're only using a subset of verts, it's better to decode with random access (usually.) + // However, if we're reusing a lot of verts, we should read and cache them. + useCache_ = useIndices_ && vertex_count > (upperBound_ - lowerBound_ + 1); + if (useCache_ && cached_.size() < upperBound_ - lowerBound_ + 1) + cached_.resize(std::max(128, upperBound_ - lowerBound_ + 1)); + } + + const VertexReader &GetVertexReader() const { + return vreader_; + } + + bool IsThrough() const { + return vreader_.isThrough(); + } + + void UpdateCache() { + if (!useCache_) + return; + + for (int i = 0; i < upperBound_ - lowerBound_ + 1; ++i) { + vreader_.Goto(i); + cached_[i] = transform_.ReadVertex(vreader_, transformState_); + } + } + + inline VertexData Read(int vtx) { + if (useIndices_) { + if (useCache_) { + return cached_[conv_(vtx) - lowerBound_]; + } + vreader_.Goto(conv_(vtx) - lowerBound_); + } else { + vreader_.Goto(vtx); + } + + return transform_.ReadVertex(vreader_, transformState_); + }; + +protected: + VertexReader vreader_; + const IndexConverter conv_; + const TransformState &transformState_; + TransformUnit &transform_; + uint16_t lowerBound_; + uint16_t upperBound_; + static std::vector cached_; + bool useIndices_ = false; + bool useCache_ = false; +}; + +// Static to reduce allocations mid-frame. +std::vector SoftwareVertexReader::cached_; + void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine) { VertexDecoder &vdecoder = *drawEngine->FindVertexDecoder(vertex_type); - const DecVtxFormat &vtxfmt = vdecoder.GetDecVtxFmt(); if (bytesRead) *bytesRead = vertex_count * vdecoder.VertexSize(); @@ -482,16 +547,8 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if ((vertex_type & GE_VTYPE_POS_MASK) == 0) return; - u16 index_lower_bound = 0; - u16 index_upper_bound = vertex_count == 0 ? 0 : vertex_count - 1; - IndexConverter ConvertIndex(vertex_type, indices); - - if (indices) - GetIndexBounds(indices, vertex_count, vertex_type, &index_lower_bound, &index_upper_bound); - if (vertex_count != 0) - vdecoder.DecodeVerts(decoded_, vertices, index_lower_bound, index_upper_bound); - - VertexReader vreader(decoded_, vtxfmt, vertex_type); + static TransformState transformState; + SoftwareVertexReader vreader(decoded_, vdecoder, vertex_type, vertex_count, vertices, indices, transformState, *this); if (prim_type != GE_PRIM_KEEP_PREVIOUS) { data_index_ = 0; @@ -500,32 +557,19 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G prim_type = prev_prim_; } - // TODO: Do this in two passes - first process the vertices (before indexing/stripping), - // then resolve the indices. This lets us avoid transforming shared vertices twice. - binner_->UpdateState(); hasDraws_ = true; - static TransformState transformState; if (binner_->HasDirty(SoftDirty::LIGHT_ALL | SoftDirty::TRANSFORM_ALL)) { - ComputeTransformState(&transformState, vreader); + ComputeTransformState(&transformState, vreader.GetVertexReader()); binner_->ClearDirty(SoftDirty::LIGHT_ALL | SoftDirty::TRANSFORM_ALL); } + vreader.UpdateCache(); bool skipCull = !gstate.isCullEnabled() || gstate.isModeClear(); const CullType cullType = skipCull ? CullType::OFF : (gstate.getCullMode() ? CullType::CCW : CullType::CW); - auto readVertexAt = [&](VertexReader &vreader, const TransformState &transformState, int vtx) { - if (indices) { - vreader.Goto(ConvertIndex(vtx) - index_lower_bound); - } else { - vreader.Goto(vtx); - } - - return ReadVertex(vreader, transformState); - }; - - if (vreader.isThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ == 0 && vertex_count >= 6 && ((vertex_count) % 6) == 0) { + if (vreader.IsThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ == 0 && vertex_count >= 6 && ((vertex_count) % 6) == 0) { // Some games send rectangles as a series of regular triangles. // We look for this, but only in throughmode. VertexData buf[6]; @@ -535,7 +579,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } for (int vtx = 0; vtx < vertex_count; ++vtx) { - buf[buf_index++] = readVertexAt(vreader, transformState, vtx); + buf[buf_index++] = vreader.Read(vtx); if (buf_index < 6) continue; @@ -576,7 +620,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G Clipper::ProcessPoint(data_[i], *binner_); data_index_ = 0; for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[0] = readVertexAt(vreader, transformState, vtx); + data_[0] = vreader.Read(vtx); Clipper::ProcessPoint(data_[0], *binner_); } break; @@ -586,7 +630,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G Clipper::ProcessLine(data_[i + 0], data_[i + 1], *binner_); data_index_ &= 1; for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[data_index_++] = readVertexAt(vreader, transformState, vtx); + data_[data_index_++] = vreader.Read(vtx); if (data_index_ == 2) { Clipper::ProcessLine(data_[0], data_[1], *binner_); data_index_ = 0; @@ -596,7 +640,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G case GE_PRIM_TRIANGLES: for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[data_index_++] = readVertexAt(vreader, transformState, vtx); + data_[data_index_++] = vreader.Read(vtx); if (data_index_ < 3) { // Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS. continue; @@ -615,9 +659,9 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G case GE_PRIM_RECTANGLES: for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[data_index_++] = readVertexAt(vreader, transformState, vtx); + data_[data_index_++] = vreader.Read(vtx); - if (data_index_ == 4 && vreader.isThrough() && cullType == CullType::OFF) { + if (data_index_ == 4 && vreader.IsThrough() && cullType == CullType::OFF) { if (Rasterizer::DetectRectangleThroughModeSlices(binner_->State(), data_)) { data_[1] = data_[3]; data_index_ = 2; @@ -643,7 +687,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // If data_index_ is 1 or 2, etc., it means we're continuing a line strip. int skip_count = data_index_ == 0 ? 1 : 0; for (int vtx = 0; vtx < vertex_count; ++vtx) { - data_[(data_index_++) & 1] = readVertexAt(vreader, transformState, vtx); + data_[(data_index_++) & 1] = vreader.Read(vtx); if (skip_count) { --skip_count; @@ -669,7 +713,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (data_index_ == 0 && vertex_count >= 4 && (vertex_count & 1) == 0 && cullType == CullType::OFF) { for (int base = 0; base < vertex_count - 2; base += 2) { for (int vtx = base == 0 ? 0 : 2; vtx < 4; ++vtx) { - data_[vtx] = readVertexAt(vreader, transformState, base + vtx); + data_[vtx] = vreader.Read(base + vtx); } // If a strip is effectively a rectangle, draw it as such! @@ -696,14 +740,14 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G for (int vtx = start_vtx; vtx < vertex_count && skip_count > 0; ++vtx) { int provoking_index = (data_index_++) % 3; - data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + data_[provoking_index] = vreader.Read(vtx); --skip_count; ++start_vtx; } for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { int provoking_index = (data_index_++) % 3; - data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + data_[provoking_index] = vreader.Read(vtx); int wind = (data_index_ - 1) % 2; CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); @@ -729,14 +773,14 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // Only read the central vertex if we're not continuing. if (data_index_ == 0 && vertex_count > 0) { - data_[0] = readVertexAt(vreader, transformState, 0); + data_[0] = vreader.Read(0); data_index_++; start_vtx = 1; } if (data_index_ == 1 && vertex_count == 4 && cullType == CullType::OFF) { for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { - data_[vtx] = readVertexAt(vreader, transformState, vtx); + data_[vtx] = vreader.Read(vtx); } int tl = -1, br = -1; @@ -748,14 +792,14 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G for (int vtx = start_vtx; vtx < vertex_count && skip_count > 0; ++vtx) { int provoking_index = 2 - ((data_index_++) % 2); - data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + data_[provoking_index] = vreader.Read(vtx); --skip_count; ++start_vtx; } for (int vtx = start_vtx; vtx < vertex_count; ++vtx) { int provoking_index = 2 - ((data_index_++) % 2); - data_[provoking_index] = readVertexAt(vreader, transformState, vtx); + data_[provoking_index] = vreader.Read(vtx); int wind = (data_index_ - 1) % 2; CullType altCullType = cullType == CullType::OFF ? cullType : CullType((int)cullType ^ wind); diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index 465f4bd8ab..644dd0079a 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -105,6 +105,7 @@ struct VertexData { class VertexReader; class SoftwareDrawEngine; +class SoftwareVertexReader; class TransformUnit { public: @@ -156,6 +157,8 @@ private: GEPrimitiveType prev_prim_ = GE_PRIM_POINTS; bool hasDraws_ = false; bool isImmDraw_ = false; + + friend SoftwareVertexReader; }; class SoftwareDrawEngine : public DrawEngineCommon { From 66b6dfd0a5618a4cd11f1c05dca26fbaecef9882 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 22 Sep 2022 20:21:44 -0700 Subject: [PATCH 143/326] softgpu: Fix self-render detect in Ridge Racer. When we flush we mark all pending writes zero, but we rely on this being set to detect self-render. TRANSFORM_ALL was wrong as well, sometimes clearing BINNER_RANGE. --- GPU/Software/BinManager.cpp | 23 +++++++++++++++++------ GPU/Software/BinManager.h | 1 + GPU/Software/SoftGpu.h | 10 +++++----- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 42eef12eda..fa7c78f21e 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -197,16 +197,16 @@ void BinManager::UpdateState(bool throughMode) { Flush("tex"); // Okay, now update what's pending. - constexpr uint32_t mirrorMask = 0x0FFFFFFF & ~0x00600000; - const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; - pendingWrites_[0].Expand(gstate.getFrameBufAddress() & mirrorMask, bpp, gstate.FrameBufStride(), scissorTL, scissorBR); - if (state.pixelID.depthWrite) - pendingWrites_[1].Expand(gstate.getDepthBufAddress() & mirrorMask, 2, gstate.DepthBufStride(), scissorTL, scissorBR); + MarkPendingWrites(state); ClearDirty(SoftDirty::BINNER_RANGE); } else if (pendingOverlap_) { - if (HasTextureWrite(state)) + if (HasTextureWrite(state)) { Flush("tex"); + + // We need the pending writes set, which flushing cleared. Set them again. + MarkPendingWrites(state); + } } if (HasDirty(SoftDirty::BINNER_OVERLAP)) { @@ -282,6 +282,17 @@ void BinManager::MarkPendingReads(const Rasterizer::RasterizerState &state) { } } +void BinManager::MarkPendingWrites(const Rasterizer::RasterizerState &state) { + DrawingCoords scissorTL(gstate.getScissorX1(), gstate.getScissorY1()); + DrawingCoords scissorBR(std::min(gstate.getScissorX2(), gstate.getRegionX2()), std::min(gstate.getScissorY2(), gstate.getRegionY2())); + + constexpr uint32_t mirrorMask = 0x0FFFFFFF & ~0x00600000; + const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; + pendingWrites_[0].Expand(gstate.getFrameBufAddress() & mirrorMask, bpp, gstate.FrameBufStride(), scissorTL, scissorBR); + if (state.pixelID.depthWrite) + pendingWrites_[1].Expand(gstate.getDepthBufAddress() & mirrorMask, 2, gstate.DepthBufStride(), scissorTL, scissorBR); +} + inline void BinDirtyRange::Expand(uint32_t newBase, uint32_t bpp, uint32_t stride, DrawingCoords &tl, DrawingCoords &br) { const uint32_t w = br.x - tl.x + 1; const uint32_t h = br.y - tl.y + 1; diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index 79146eae1d..e74f6faac2 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -267,6 +267,7 @@ private: int mostThreads_ = 0; void MarkPendingReads(const Rasterizer::RasterizerState &state); + void MarkPendingWrites(const Rasterizer::RasterizerState &state); bool HasTextureWrite(const Rasterizer::RasterizerState &state); BinCoords Scissor(BinCoords range); BinCoords Range(const VertexData &v0, const VertexData &v1, const VertexData &v2); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 18fa23118d..9944f2b789 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -64,17 +64,17 @@ enum class SoftDirty : uint64_t { PIXEL_DITHER = 1ULL << 3, PIXEL_WRITEMASK = 1ULL << 4, PIXEL_CACHED = 1ULL << 5, - PIXEL_ALL = 63ULL << 0, + PIXEL_ALL = 0b111111ULL << 0, SAMPLER_BASIC = 1ULL << 6, SAMPLER_TEXLIST = 1ULL << 7, SAMPLER_CLUT = 1ULL << 8, - SAMPLER_ALL = 7ULL << 6, + SAMPLER_ALL = 0b111ULL << 6, RAST_BASIC = 1ULL << 9, RAST_TEX = 1ULL << 10, RAST_OFFSET = 1ULL << 11, - RAST_ALL = 7ULL << 9, + RAST_ALL = 0b111ULL << 9, LIGHT_BASIC = 1ULL << 12, LIGHT_MATERIAL = 1ULL << 13, @@ -82,13 +82,13 @@ enum class SoftDirty : uint64_t { LIGHT_1 = 1ULL << 15, LIGHT_2 = 1ULL << 16, LIGHT_3 = 1ULL << 17, - LIGHT_ALL = 63ULL << 12, + LIGHT_ALL = 0b111111ULL << 12, TRANSFORM_BASIC = 1ULL << 18, TRANSFORM_MATRIX = 1ULL << 19, TRANSFORM_VIEWPORT = 1ULL << 20, TRANSFORM_FOG = 1ULL << 21, - TRANSFORM_ALL = 31ULL << 18, + TRANSFORM_ALL = 0b1111ULL << 18, BINNER_RANGE = 1ULL << 22, BINNER_OVERLAP = 1ULL << 23, From 93c909a88e7b2fa49ebd0c7b7d2dd9e26093e21f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 23 Sep 2022 00:04:14 -0700 Subject: [PATCH 144/326] GPU: Upload depth only on first usage. Fixes various glitches in Kingdom Hearts, etc. --- GPU/Common/FramebufferManagerCommon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 99ceb7ec36..330eda4796 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -548,6 +548,7 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { } // First time use of this framebuffer's depth buffer. + bool newlyUsingDepth = (currentRenderVfb_->usageFlags & FB_USAGE_RENDER_DEPTH) == 0; currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; // If this first draw call is anything other than a clear, "resolve" the depth buffer, @@ -557,7 +558,7 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081. // Might make this more generic and upload the whole depth buffer if we find it's needed for something. - if (currentRenderVfb_->lastFrameNewSize == gpuStats.numFlips) { + if (newlyUsingDepth) { // Sanity check the depth buffer pointer. if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); From d79828270ae6287a897bd475f2d29c98956d1a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 09:13:34 +0200 Subject: [PATCH 145/326] Add Burnout Dominator to list of games that require buffered rendering It does run without but looks really bad, don't want reports of this. --- assets/compat.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index 5ce6077091..8457a6a36f 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -520,6 +520,14 @@ ULES01086 = true # LEGO Batman: The Videogame ULUS10380 = true ULES01151 = true +# Burnout Dominator +ULUS10236 = true +ULES00750 = true +ULJM05242 = true +ULJM05371 = true +NPJH50304 = true +ULES00703 = true + # TODO: There are many more. [RequireBlockTransfer] From ac3eb876a8aea3d1b6dba2e1be29c3703f209421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 10:56:46 +0200 Subject: [PATCH 146/326] Fix memory leak (of Promise objects) in VK pipeline wrappers --- Common/GPU/Vulkan/VulkanRenderManager.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 43cbbb7b86..c68354f348 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -148,6 +148,12 @@ struct VKRComputePipelineDesc { // Wrapped pipeline. Doesn't own desc. struct VKRGraphicsPipeline { + ~VKRGraphicsPipeline() { + for (int i = 0; i < RP_TYPE_COUNT; i++) { + delete pipeline[i]; + } + } + bool Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType); // This deletes the whole VKRGraphicsPipeline, you must remove your last pointer to it when doing this. @@ -161,11 +167,12 @@ struct VKRGraphicsPipeline { }; struct VKRComputePipeline { - VKRComputePipeline() { - pipeline = VK_NULL_HANDLE; + ~VKRComputePipeline() { + delete pipeline; } + VKRComputePipelineDesc *desc = nullptr; - Promise *pipeline; + Promise *pipeline = nullptr; bool Create(VulkanContext *vulkan); bool Pending() const { From ac7ca963db1a3620bdd9d3ab3fcc0d61fbc050f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 12:24:43 +0200 Subject: [PATCH 147/326] Make valgrind happy --- Common/GPU/Vulkan/VulkanContext.cpp | 4 +-- Common/GPU/Vulkan/thin3d_vulkan.cpp | 2 +- Core/ELF/ElfReader.cpp | 3 +- Core/HLE/scePsmf.cpp | 46 ++++++++++++++--------------- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/Vulkan/DrawEngineVulkan.cpp | 2 +- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 0d29b518df..3b3b5124b9 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -1266,10 +1266,10 @@ bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, glslang::TProgram program; const char *shaderStrings[1]; - TBuiltInResource Resources; + TBuiltInResource Resources{}; init_resources(Resources); - int defaultVersion; + int defaultVersion = 0; EShMessages messages; EProfile profile; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 6a5af31453..fe508e59a3 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -976,7 +976,7 @@ void VKContext::WipeQueue() { } VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) { - DescriptorSetKey key; + DescriptorSetKey key{}; FrameData *frame = &frame_[vulkan_->GetCurFrame()]; diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index abfd881e87..980e427e83 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -583,7 +583,8 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) { if (!(sections[sectionToModify].sh_flags & SHF_ALLOC)) { - ERROR_LOG_REPORT(LOADER, "Trying to relocate non-loaded section %s, ignoring", GetSectionName(sectionToModify)); + // Generally stuff like debug info. We don't need it. + INFO_LOG(LOADER, "Skipping relocation of non-loaded section %s", GetSectionName(sectionToModify)); continue; } } diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index b2aefa9a1a..393469f029 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -175,35 +175,35 @@ public: int FindEPWithTimestamp(int pts) const; - u32 magic; - u32 version; - u32 streamOffset; - u32 streamSize; - u32 headerSize; - u32 headerOffset; - u32 streamType; - u32 streamChannel; + u32 magic = 0; + u32 version = 0; + u32 streamOffset = 0; + u32 streamSize = 0; + u32 headerSize = 0; + u32 headerOffset = 0; + u32 streamType = 0; + u32 streamChannel = 0; // 0x50 - u32 streamDataTotalSize; - u32 presentationStartTime; - u32 presentationEndTime; - u32 streamDataNextBlockSize; - u32 streamDataNextInnerBlockSize; + u32 streamDataTotalSize = 0; + u32 presentationStartTime = 0; + u32 presentationEndTime = 0; + u32 streamDataNextBlockSize = 0; + u32 streamDataNextInnerBlockSize = 0; - int numStreams; - int currentStreamNum; - int currentStreamType; - int currentStreamChannel; + int numStreams = 0; + int currentStreamNum = 0; + int currentStreamType = 0; + int currentStreamChannel = 0; // parameters gotten from streams // I guess this is the seek information? - u32 EPMapOffset; - u32 EPMapEntriesNum; + u32 EPMapOffset = 0; + u32 EPMapEntriesNum = 0; // These shouldn't be here, just here for convenience with old states. - int videoWidth; - int videoHeight; - int audioChannels; - int audioFrequency; + int videoWidth = 0; + int videoHeight = 0; + int audioChannels = 0; + int audioFrequency = 0; std::vector EPMap; PsmfStreamMap streamMap; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 205dcbc602..73718e72b7 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -152,7 +152,7 @@ static int TexLog2(float delta) { } SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCacheEntry *entry) { - SamplerCacheKey key; + SamplerCacheKey key{}; int minFilt = gstate.texfilter & 0x7; key.minFilt = minFilt & 1; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index f992df082f..d086b60921 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -381,7 +381,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView _dbg_assert_(light != VK_NULL_HANDLE); _dbg_assert_(bone != VK_NULL_HANDLE); - DescriptorSetKey key; + DescriptorSetKey key{}; key.imageView_ = imageView; key.sampler_ = sampler; key.secondaryImageView_ = boundSecondary_; From 7884e4ccb32992b879f68279069b067c4c6331ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 12:33:16 +0200 Subject: [PATCH 148/326] Another uninitialized variable (VAI minihash/hash) --- GPU/Vulkan/DrawEngineVulkan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 0de67940de..50df67b136 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -81,8 +81,8 @@ public: VAI_UNRELIABLE, // never cache }; - uint64_t hash; - u32 minihash; + uint64_t hash = 0; + u32 minihash = 0; // These will probably always be the same, but whatever. VkBuffer vb = VK_NULL_HANDLE; From 0acfa906b42d0357c292a11aaa5c0a0bc8024990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 13:31:32 +0200 Subject: [PATCH 149/326] Fix leak of shader modules from thin3d. --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index fe508e59a3..bc5d452e08 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -194,17 +194,19 @@ public: ~VKShaderModule() { if (module_) { DEBUG_LOG(G3D, "Queueing %s (shmodule %p) for release", tag_.c_str(), module_); - vulkan_->Delete().QueueDeleteShaderModule(module_); + VkShaderModule shaderModule = module_->BlockUntilReady(); + vulkan_->Delete().QueueDeleteShaderModule(shaderModule); + delete module_; } } - VkShaderModule Get() const { return module_; } + Promise *Get() const { return module_; } ShaderStage GetStage() const override { return stage_; } private: VulkanContext *vulkan_; - VkShaderModule module_ = VK_NULL_HANDLE; + Promise *module_ = nullptr; VkShaderStageFlagBits vkstage_; bool ok_ = false; ShaderStage stage_; @@ -232,7 +234,9 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con } #endif - if (vulkan->CreateShaderModule(spirv, &module_, vkstage_ == VK_SHADER_STAGE_VERTEX_BIT ? "thin3d_vs" : "thin3d_fs")) { + VkShaderModule shaderModule = VK_NULL_HANDLE; + if (vulkan->CreateShaderModule(spirv, &shaderModule, vkstage_ == VK_SHADER_STAGE_VERTEX_BIT ? "thin3d_vs" : "thin3d_fs")) { + module_ = Promise::AlreadyDone(shaderModule); ok_ = true; } else { WARN_LOG(G3D, "vkCreateShaderModule failed (%s)", tag_.c_str()); @@ -1070,9 +1074,9 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char vkshader->AddRef(); pipeline->deps.push_back(vkshader); if (vkshader->GetStage() == ShaderStage::Vertex) { - gDesc.vertexShader = Promise::AlreadyDone(vkshader->Get()); + gDesc.vertexShader = vkshader->Get(); } else if (vkshader->GetStage() == ShaderStage::Fragment) { - gDesc.fragmentShader = Promise::AlreadyDone(vkshader->Get()); + gDesc.fragmentShader = vkshader->Get(); } else { ERROR_LOG(G3D, "Bad stage"); return nullptr; From ac4ca592c33ad17ac42f814f5be88b1216131355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 13:47:11 +0200 Subject: [PATCH 150/326] Handle depth buffer write-after-write hazards in the queue runner --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 31 +++++++++++++++++++++++++ Common/GPU/Vulkan/VulkanQueueRunner.h | 1 + 2 files changed, 32 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 8ad509afbb..3ef8b5140b 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -1721,6 +1721,10 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) { if (dst->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { SetupTransitionToTransferDst(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_); _dbg_assert_(dst->depth.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + } else { + // Kingdom Hearts: Subsequent copies to the same depth buffer without any other use. + // Not super sure how that happens, but we need a barrier to pass sync validation. + SetupTransferDstWriteAfterWrite(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_); } } @@ -1916,6 +1920,33 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageAspec img.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; } +void VulkanQueueRunner::SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) { + VkImageAspectFlags imageAspect = aspect; + VkAccessFlags srcAccessMask = 0; + VkPipelineStageFlags srcStageMask = 0; + if (img.format == VK_FORMAT_D16_UNORM_S8_UINT || img.format == VK_FORMAT_D24_UNORM_S8_UINT || img.format == VK_FORMAT_D32_SFLOAT_S8_UINT) { + // Barrier must specify both for combined depth/stencil buffers. + imageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } else { + imageAspect = aspect; + } + _dbg_assert_(img.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + recordBarrier->TransitionImage( + img.image, + 0, + 1, + aspect, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT + ); +} + void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd) { ResizeReadbackBuffer(sizeof(uint32_t) * step.readback.srcRect.extent.width * step.readback.srcRect.extent.height); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 9c1fee77d9..0962df3eb7 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -336,6 +336,7 @@ private: static void SetupTransitionToTransferSrc(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); static void SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); + static void SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); static void SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier); From 7bef90297c680ea9821e712c0982c003a524a419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 13:54:14 +0200 Subject: [PATCH 151/326] Best practices layer warning: Don't pass in unused clear values. (probably zero impact, heh). --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 3ef8b5140b..8d739277b1 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -10,6 +10,16 @@ using namespace PPSSPP_VK; // Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState +static bool RenderPassTypeHasDepth(RenderPassType rpType) { + switch (rpType) { + case RP_TYPE_BACKBUFFER: + case RP_TYPE_COLOR_DEPTH: + case RP_TYPE_COLOR_DEPTH_INPUT: + return true; + } + return false; +} + static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) { if (dest->offset.x > src.offset.x) { dest->extent.width += (dest->offset.x - src.offset.x); @@ -1591,6 +1601,9 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR VkFramebuffer framebuf; int w; int h; + + bool hasDepth = RenderPassTypeHasDepth(step.render.renderPassType); + if (step.render.framebuffer) { _dbg_assert_(step.render.finalColorLayout != VK_IMAGE_LAYOUT_UNDEFINED); _dbg_assert_(step.render.finalDepthStencilLayout != VK_IMAGE_LAYOUT_UNDEFINED); @@ -1631,7 +1644,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); numClearVals = 1; } - if (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR) { + if (hasDepth && (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR)) { clearVal[1].depthStencil.depth = step.render.clearDepth; clearVal[1].depthStencil.stencil = step.render.clearStencil; numClearVals = 2; @@ -1650,7 +1663,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR h = vulkan_->GetBackbufferHeight(); Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); - numClearVals = 2; // We don't bother with a depth buffer here. + numClearVals = hasDepth ? 2 : 1; // We might do depth-less backbuffer in the future, though doubtful of the value. clearVal[1].depthStencil.depth = 0.0f; clearVal[1].depthStencil.stencil = 0; } From 5e63cdbcc4052b0baa196de58d162a3706185337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 15:24:26 +0200 Subject: [PATCH 152/326] Make functions private that should be private --- Common/GPU/Vulkan/VulkanRenderManager.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index c68354f348..899577def2 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -200,16 +200,10 @@ public: VulkanRenderManager(VulkanContext *vulkan); ~VulkanRenderManager(); - void ThreadFunc(); - void CompileThreadFunc(); - void DrainCompileQueue(); - // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. void BeginFrame(bool enableProfiling, bool enableLogProfiler); // Can run on a different thread! void Finish(); - void Run(int frame); - // Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown. void Wipe(); @@ -468,13 +462,17 @@ public: private: void EndCurRenderStep(); + void ThreadFunc(); + void CompileThreadFunc(); + void DrainCompileQueue(); + + void Run(int frame); void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); + void EndSyncFrame(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void FlushSync(); - void EndSyncFrame(int frame); - void StopThread(); FrameDataShared frameDataShared_; From f784112814d3b4897b04b4791d22fa06a90863a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 18:39:00 +0200 Subject: [PATCH 153/326] Flatten the Run function, to make it easier to reorganize. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 84 +++++++++-------------- Common/GPU/Vulkan/VulkanRenderManager.h | 2 - 2 files changed, 31 insertions(+), 55 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 82c99e89ce..9bdb1cdaee 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -531,6 +531,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile VLOG("PUSH: Fencing %d", curFrame); + // This must be the very first Vulkan call we do in a new frame. if (vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX) == VK_ERROR_DEVICE_LOST) { _assert_msg_(false, "Device lost in vkWaitForFences"); } @@ -1212,13 +1213,10 @@ void VulkanRenderManager::Wipe() { // Called on the render thread. // -// Can be called multiple times with no bad side effects. This is so that we can either begin a frame the normal way, -// or stop it in the middle for a synchronous readback, then start over again mostly normally but without repeating -// the backbuffer image acquisition. -void VulkanRenderManager::BeginSubmitFrame(int frame) { +// Can be called again after a VKRRunType::SYNC on the same frame. +void VulkanRenderManager::Run(int frame) { FrameData &frameData = frameData_[frame]; - // Should only have at most the init command buffer pending here (that one came from the other thread). _dbg_assert_(!frameData.hasPresentCommands); frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_); @@ -1233,53 +1231,7 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { frameData.hasMainCommands = true; _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); } -} -// Called on the render thread. -void VulkanRenderManager::EndSubmitFrame(int frame) { - FrameData &frameData = frameData_[frame]; - - frameData.SubmitPending(vulkan_, FrameSubmitType::Present, frameDataShared_); - - if (!frameData.skipSwap) { - VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_); - if (res == VK_ERROR_OUT_OF_DATE_KHR) { - // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above. - // Do the increment. - outOfDateFrames_++; - } else if (res == VK_SUBOPTIMAL_KHR) { - outOfDateFrames_++; - } else if (res != VK_SUCCESS) { - _assert_msg_(false, "vkQueuePresentKHR failed! result=%s", VulkanResultToString(res)); - } else { - // Success - outOfDateFrames_ = 0; - } - } else { - // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE. - outOfDateFrames_++; - frameData.skipSwap = false; - } -} - -void VulkanRenderManager::EndSyncFrame(int frame) { - FrameData &frameData = frameData_[frame]; - - // The submit will trigger the readbackFence, and also do the wait for it. - frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_); - - // At this point we can resume filling the command buffers for the current frame since - // we know the device is idle - and thus all previously enqueued command buffers have been processed. - // No need to switch to the next frame number, would just be confusing. - std::unique_lock lock(frameData.push_mutex); - frameData.readyForFence = true; - frameData.push_condVar.notify_all(); -} - -void VulkanRenderManager::Run(int frame) { - BeginSubmitFrame(frame); - - FrameData &frameData = frameData_[frame]; queueRunner_.PreprocessSteps(frameData.steps); // Likely during shutdown, happens in headless. if (frameData.steps.empty() && !frameData.hasAcquired) @@ -1289,11 +1241,36 @@ void VulkanRenderManager::Run(int frame) { switch (frameData.runType_) { case VKRRunType::END: - EndSubmitFrame(frame); + + frameData.SubmitPending(vulkan_, FrameSubmitType::Present, frameDataShared_); + + if (!frameData.skipSwap) { + VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_); + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above. + // Do the increment. + outOfDateFrames_++; + } else if (res == VK_SUBOPTIMAL_KHR) { + outOfDateFrames_++; + } else if (res != VK_SUCCESS) { + _assert_msg_(false, "vkQueuePresentKHR failed! result=%s", VulkanResultToString(res)); + } else { + // Success + outOfDateFrames_ = 0; + } + } else { + // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE. + outOfDateFrames_++; + frameData.skipSwap = false; + } break; case VKRRunType::SYNC: - EndSyncFrame(frame); + // The submit will trigger the readbackFence, and also do the wait for it. + frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_); + // At this point the GPU is idle, and we can resume filling the command buffers for the + // current frame since and thus all previously enqueued command buffers have been + // processed. No need to switch to the next frame number, would just be confusing. break; default: @@ -1303,6 +1280,7 @@ void VulkanRenderManager::Run(int frame) { VLOG("PULL: Finished running frame %d", frame); } +// Called from main thread. void VulkanRenderManager::FlushSync() { renderStepOffset_ += (int)steps_.size(); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 899577def2..26eccb94d7 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -468,8 +468,6 @@ private: void Run(int frame); void BeginSubmitFrame(int frame); - void EndSubmitFrame(int frame); - void EndSyncFrame(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void FlushSync(); From ef7c8844f88902bbc510841d188974b3cde1c04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 19:39:00 +0200 Subject: [PATCH 154/326] Greatly simplify the thread synchronization. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 11 +- Common/GPU/Vulkan/VulkanFrameData.h | 22 +-- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 10 +- Common/GPU/Vulkan/VulkanQueueRunner.h | 10 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 188 ++++++++++------------ Common/GPU/Vulkan/VulkanRenderManager.h | 17 +- 6 files changed, 116 insertions(+), 142 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 2c9c2487fc..fbd04d131f 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -146,8 +146,11 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame hasPresentCommands = false; if (type == FrameSubmitType::Present) { + NOTICE_LOG(G3D, "Has present commands, Triggering fence"); fenceToTrigger = fence; } + } else { + NOTICE_LOG(G3D, "No present commands, not triggering fence"); } if (!numCmdBufs && fenceToTrigger == VK_NULL_HANDLE) { @@ -181,14 +184,6 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame vkWaitForFences(vulkan->GetDevice(), 1, &readbackFence, true, UINT64_MAX); vkResetFences(vulkan->GetDevice(), 1, &readbackFence); } - - // When !triggerFence, we notify after syncing with Vulkan. - if (type == FrameSubmitType::Present || type == FrameSubmitType::Sync) { - VERBOSE_LOG(G3D, "PULL: Frame %d.readyForFence = true", index); - std::unique_lock lock(push_mutex); - readyForFence = true; // misnomer in sync mode! - push_condVar.notify_all(); - } } void FrameDataShared::Init(VulkanContext *vulkan) { diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 53de1e8f44..a80210e81c 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -7,15 +7,14 @@ #include "Common/GPU/Vulkan/VulkanContext.h" -struct VKRStep; - enum { MAX_TIMESTAMP_QUERIES = 128, }; enum class VKRRunType { - END, + PRESENT, SYNC, + EXIT, }; struct QueueProfileContext { @@ -43,14 +42,6 @@ enum class FrameSubmitType { // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct FrameData { - std::mutex push_mutex; - std::condition_variable push_condVar; - - std::mutex pull_mutex; - std::condition_variable pull_condVar; - - bool readyForFence = true; - bool readyForRun = false; // protected by pull_mutex bool skipSwap = false; VkFence fence; @@ -68,9 +59,10 @@ struct FrameData { bool hasMainCommands = false; bool hasPresentCommands = false; + bool hasFencePending = false; bool hasAcquired = false; - std::vector steps; + bool syncDone = false; // Swapchain. uint32_t curSwapchainImage = -1; @@ -89,12 +81,6 @@ struct FrameData { // This will only submit if we are actually recording init commands. void SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &shared); - VKRRunType RunType() const { - return runType_; - } - - VKRRunType runType_ = VKRRunType::END; - private: // Metadata for logging etc int index; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 8d739277b1..542ba7a1e2 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -554,7 +554,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) { } } -void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDataShared) { +void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared) { QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr; if (profile) @@ -564,8 +564,8 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat VkCommandBuffer cmd = frameData.hasPresentCommands ? frameData.presentCmd : frameData.mainCmd; - for (size_t i = 0; i < frameData.steps.size(); i++) { - const VKRStep &step = *frameData.steps[i]; + for (size_t i = 0; i < steps.size(); i++) { + const VKRStep &step = *steps[i]; if (emitLabels) { VkDebugUtilsLabelEXT labelInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; @@ -625,11 +625,11 @@ void VulkanQueueRunner::RunSteps(FrameData &frameData, FrameDataShared &frameDat // Deleting all in one go should be easier on the instruction cache than deleting // them as we go - and easier to debug because we can look backwards in the frame. - for (auto step : frameData.steps) { + for (auto step : steps) { delete step; } - frameData.steps.clear(); + steps.clear(); if (profile) profile->cpuEndTime = time_now_d(); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 0962df3eb7..c767e498fb 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -247,6 +247,14 @@ private: RPKey key_; }; +// These are enqueued from the main thread, +// and the render thread pops them off +struct VKRRenderThreadTask { + std::vector steps; + int frame; + VKRRunType runType; +}; + class VulkanQueueRunner { public: VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan), renderPasses_(16) {} @@ -257,7 +265,7 @@ public: } void PreprocessSteps(std::vector &steps); - void RunSteps(FrameData &frameData, FrameDataShared &frameDataShared); + void RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared); void LogSteps(const std::vector &steps, bool verbose); std::string StepToString(const VKRStep &step) const; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 9bdb1cdaee..4229ed5d86 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -14,7 +14,7 @@ #include "Common/Thread/ThreadUtil.h" #if 0 // def _DEBUG -#define VLOG(...) INFO_LOG(G3D, __VA_ARGS__) +#define VLOG(...) NOTICE_LOG(G3D, __VA_ARGS__) #else #define VLOG(...) #endif @@ -333,10 +333,9 @@ bool VulkanRenderManager::CreateBackbuffers() { // Start the thread. if (HasBackbuffers()) { - run_ = true; - // Won't necessarily be 0. - threadInitFrame_ = vulkan_->GetCurFrame(); - INFO_LOG(G3D, "Starting Vulkan submission thread (threadInitFrame_ = %d)", vulkan_->GetCurFrame()); + run_ = true; // For controlling the compiler thread's exit + + INFO_LOG(G3D, "Starting Vulkan submission thread"); thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this); INFO_LOG(G3D, "Starting Vulkan compiler thread"); compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this); @@ -344,29 +343,32 @@ bool VulkanRenderManager::CreateBackbuffers() { return true; } +// Called from main thread. void VulkanRenderManager::StopThread() { - if (!run_) { - INFO_LOG(G3D, "Vulkan submission thread was already stopped."); - return; + { + // Tell the render thread to quit when it's done. + std::unique_lock lock(pushMutex_); + VKRRenderThreadTask task; + task.frame = vulkan_->GetCurFrame(); + task.runType = VKRRunType::EXIT; + renderThreadQueue_.push(task); + pushCondVar_.notify_one(); } + // Compiler thread still relies on this. run_ = false; + // Stop the thread. + thread_.join(); + for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { auto &frameData = frameData_[i]; - { - std::unique_lock lock(frameData.push_mutex); - frameData.push_condVar.notify_all(); - } - { - std::unique_lock lock(frameData.pull_mutex); - frameData.pull_condVar.notify_all(); - } // Zero the queries so we don't try to pull them later. frameData.profile.timestampDescriptions.clear(); } - thread_.join(); + INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); + compileCond_.notify_all(); compileThread_.join(); INFO_LOG(G3D, "Vulkan compiler thread joined."); @@ -374,28 +376,24 @@ void VulkanRenderManager::StopThread() { // Eat whatever has been queued up for this frame if anything. Wipe(); - // Wait for any fences to finish and be resignaled, so we don't have sync issues. - // Also clean out any queued data, which might refer to things that might not be valid - // when we restart... + // Clean out any remaining queued data, which might refer to things that might not be valid + // when we restart the thread... + + // Not sure if this is still needed for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { auto &frameData = frameData_[i]; - _assert_(!frameData.readyForRun); - _assert_(frameData.steps.empty()); if (frameData.hasInitCommands) { // Clear 'em out. This can happen on restart sometimes. vkEndCommandBuffer(frameData.initCmd); frameData.hasInitCommands = false; } - frameData.readyForRun = false; - for (size_t i = 0; i < frameData.steps.size(); i++) { - delete frameData.steps[i]; + if (frameData.hasMainCommands) { + vkEndCommandBuffer(frameData.mainCmd); + frameData.hasMainCommands = false; } - frameData.steps.clear(); - - std::unique_lock lock(frameData.push_mutex); - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); - frameData.push_condVar.wait(lock); + if (frameData.hasPresentCommands) { + vkEndCommandBuffer(frameData.presentCmd); + frameData.hasPresentCommands = false; } } } @@ -468,45 +466,30 @@ void VulkanRenderManager::DrainCompileQueue() { void VulkanRenderManager::ThreadFunc() { SetCurrentThreadName("RenderMan"); - int threadFrame = threadInitFrame_; - bool nextFrame = false; - bool firstFrame = true; while (true) { + // Pop a task of the queue and execute it. + VKRRenderThreadTask task; { - if (nextFrame) { - threadFrame++; - if (threadFrame >= vulkan_->GetInflightFrames()) - threadFrame = 0; + std::unique_lock lock(pushMutex_); + while (renderThreadQueue_.empty()) { + pushCondVar_.wait(lock); } - FrameData &frameData = frameData_[threadFrame]; - - std::unique_lock lock(frameData.pull_mutex); - while (!frameData.readyForRun && run_) { - VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame); - frameData.pull_condVar.wait(lock); - } - if (!frameData.readyForRun && !run_) { - // This means we're out of frames to render and run_ is false, so bail. - break; - } - VLOG("PULL: frame[%d].readyForRun = false", threadFrame); - frameData.readyForRun = false; - // Previously we had a quick exit here that avoided calling Run() if run_ was suddenly false, - // but that created a race condition where frames could end up not finished properly on resize etc. - - // Only increment next time if we're done. - nextFrame = frameData.RunType() == VKRRunType::END; + task = renderThreadQueue_.front(); + renderThreadQueue_.pop(); } - VLOG("PULL: Running frame %d", threadFrame); - if (firstFrame) { - INFO_LOG(G3D, "Running first frame (%d)", threadFrame); - firstFrame = false; + + // Oh, we got a task! We can now have pushMutex_ unlocked, allowing the host to + // push more work when it feels like it, and just start working. + if (task.runType == VKRRunType::EXIT) { + // Oh, host wanted out. Let's leave. + break; } - Run(threadFrame); - VLOG("PULL: Finished frame %d", threadFrame); + + Run(task); } // Wait for the device to be done with everything, before tearing stuff down. + // TODO: Do we need this? vkDeviceWaitIdle(vulkan_->GetDevice()); VLOG("PULL: Quitting"); @@ -519,19 +502,10 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - // Make sure the very last command buffer from the frame before the previous has been fully executed. - { - std::unique_lock lock(frameData.push_mutex); - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); - frameData.push_condVar.wait(lock); - } - frameData.readyForFence = false; - } - VLOG("PUSH: Fencing %d", curFrame); // This must be the very first Vulkan call we do in a new frame. + // Makes sure the very last command buffer from the frame before the previous has been fully executed. if (vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX) == VK_ERROR_DEVICE_LOST) { _assert_msg_(false, "Device lost in vkWaitForFences"); } @@ -578,9 +552,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile // Must be after the fence - this performs deletes. VLOG("PUSH: BeginFrame %d", curFrame); - if (!run_) { - WARN_LOG(G3D, "BeginFrame while !run_!"); - } vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE); @@ -1085,7 +1056,6 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, if (dstPos.x != 0 || dstPos.y != 0 || !fillsDst) step->dependencies.insert(dst); - std::unique_lock lock(mutex_); steps_.push_back(step); } @@ -1131,7 +1101,6 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, if (!fillsDst) step->dependencies.insert(dst); - std::unique_lock lock(mutex_); steps_.push_back(step); } @@ -1191,16 +1160,18 @@ void VulkanRenderManager::Finish() { FrameData &frameData = frameData_[curFrame]; { - std::unique_lock lock(frameData.pull_mutex); - VLOG("PUSH: Frame[%d].readyForRun = true", curFrame); - frameData.steps = std::move(steps_); - steps_.clear(); - frameData.readyForRun = true; - frameData.runType_ = VKRRunType::END; - frameData.pull_condVar.notify_all(); + VLOG("PUSH: Frame[%d]", curFrame); + std::unique_lock lock(pushMutex_); + VKRRenderThreadTask task; + task.frame = curFrame; + task.runType = VKRRunType::PRESENT; + renderThreadQueue_.push(task); + renderThreadQueue_.back().steps = std::move(steps_); + pushCondVar_.notify_one(); } - vulkan_->EndFrame(); + steps_.clear(); + vulkan_->EndFrame(); insideFrame_ = false; } @@ -1214,8 +1185,8 @@ void VulkanRenderManager::Wipe() { // Called on the render thread. // // Can be called again after a VKRRunType::SYNC on the same frame. -void VulkanRenderManager::Run(int frame) { - FrameData &frameData = frameData_[frame]; +void VulkanRenderManager::Run(VKRRenderThreadTask &task) { + FrameData &frameData = frameData_[task.frame]; _dbg_assert_(!frameData.hasPresentCommands); frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_); @@ -1232,16 +1203,15 @@ void VulkanRenderManager::Run(int frame) { _assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res)); } - queueRunner_.PreprocessSteps(frameData.steps); + queueRunner_.PreprocessSteps(task.steps); // Likely during shutdown, happens in headless. - if (frameData.steps.empty() && !frameData.hasAcquired) + if (task.steps.empty() && !frameData.hasAcquired) frameData.skipSwap = true; //queueRunner_.LogSteps(stepsOnThread, false); - queueRunner_.RunSteps(frameData, frameDataShared_); - - switch (frameData.runType_) { - case VKRRunType::END: + queueRunner_.RunSteps(task.steps, frameData, frameDataShared_); + switch (task.runType) { + case VKRRunType::PRESENT: frameData.SubmitPending(vulkan_, FrameSubmitType::Present, frameDataShared_); if (!frameData.skipSwap) { @@ -1268,6 +1238,12 @@ void VulkanRenderManager::Run(int frame) { case VKRRunType::SYNC: // The submit will trigger the readbackFence, and also do the wait for it. frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_); + + { + std::unique_lock lock(syncMutex_); + syncCondVar_.notify_one(); + } + // At this point the GPU is idle, and we can resume filling the command buffers for the // current frame since and thus all previously enqueued command buffers have been // processed. No need to switch to the next frame number, would just be confusing. @@ -1277,7 +1253,7 @@ void VulkanRenderManager::Run(int frame) { _dbg_assert_(false); } - VLOG("PULL: Finished running frame %d", frame); + VLOG("PULL: Finished running frame %d", task.frame); } // Called from main thread. @@ -1288,23 +1264,23 @@ void VulkanRenderManager::FlushSync() { FrameData &frameData = frameData_[curFrame]; { - std::unique_lock lock(frameData.pull_mutex); - VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame); - frameData.steps = std::move(steps_); - steps_.clear(); - frameData.readyForRun = true; - _dbg_assert_(!frameData.readyForFence); - frameData.runType_ = VKRRunType::SYNC; - frameData.pull_condVar.notify_all(); + VLOG("PUSH: Frame[%d]", curFrame); + std::unique_lock lock(pushMutex_); + VKRRenderThreadTask task; + task.frame = curFrame; + task.runType = VKRRunType::SYNC; + renderThreadQueue_.push(task); + renderThreadQueue_.back().steps = std::move(steps_); + pushCondVar_.notify_one(); } { - std::unique_lock lock(frameData.push_mutex); + std::unique_lock lock(syncMutex_); // Wait for the flush to be hit, since we're syncing. - while (!frameData.readyForFence) { + while (!frameData.syncDone) { VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame); - frameData.push_condVar.wait(lock); + syncCondVar_.wait(lock); } - frameData.readyForFence = false; + frameData.syncDone = false; } } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 26eccb94d7..ed9f0d16e5 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -466,7 +466,7 @@ private: void CompileThreadFunc(); void DrainCompileQueue(); - void Run(int frame); + void Run(VKRRenderThreadTask &task); void BeginSubmitFrame(int frame); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). @@ -492,6 +492,8 @@ private: int curHeight_ = -1; bool insideFrame_ = false; + bool run_ = false; + // This is the offset within this frame, in case of a mid-frame sync. int renderStepOffset_ = 0; VKRStep *curRenderStep_ = nullptr; @@ -503,13 +505,20 @@ private: std::vector steps_; // Execution time state - bool run_ = true; VulkanContext *vulkan_; std::thread thread_; - std::mutex mutex_; - int threadInitFrame_ = 0; VulkanQueueRunner queueRunner_; + // For pushing data on the queue. + std::mutex pushMutex_; + std::condition_variable pushCondVar_; + + std::queue renderThreadQueue_; + + // For readbacks and other reasons we need to sync with the render thread. + std::mutex syncMutex_; + std::condition_variable syncCondVar_; + // Shader compilation thread to compile while emulating the rest of the frame. // Only one right now but we could use more. std::thread compileThread_; From f6c78584f61c066806b5adf74dcafe087710ee2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 19:45:50 +0200 Subject: [PATCH 155/326] Readback fix --- Common/GPU/Vulkan/VulkanFrameData.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index fbd04d131f..e8d6f67784 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -146,11 +146,8 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame hasPresentCommands = false; if (type == FrameSubmitType::Present) { - NOTICE_LOG(G3D, "Has present commands, Triggering fence"); fenceToTrigger = fence; } - } else { - NOTICE_LOG(G3D, "No present commands, not triggering fence"); } if (!numCmdBufs && fenceToTrigger == VK_NULL_HANDLE) { @@ -183,6 +180,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback. vkWaitForFences(vulkan->GetDevice(), 1, &readbackFence, true, UINT64_MAX); vkResetFences(vulkan->GetDevice(), 1, &readbackFence); + syncDone = true; } } From c671b3f41317fe6994a367397fde98fd9cf75edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 20:44:47 +0200 Subject: [PATCH 156/326] Can't seem to get around this condition variable... Oh well. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 16 +++++++++++++++- Common/GPU/Vulkan/VulkanFrameData.h | 4 ++++ Common/GPU/Vulkan/VulkanRenderManager.cpp | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index e8d6f67784..d5bc6f47aa 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -1,3 +1,5 @@ +#include + #include "VulkanFrameData.h" #include "Common/Log.h" @@ -26,6 +28,7 @@ void FrameData::Init(VulkanContext *vulkan, int index) { // Creating the frame fence with true so they can be instantly waited on the first frame fence = vulkan->CreateFence(true); + readyForFence = true; // This fence one is used for synchronizing readbacks. Does not need preinitialization. readbackFence = vulkan->CreateFence(false); @@ -169,7 +172,18 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &sharedData.renderingCompleteSemaphore; } - VkResult res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + + VkResult res; + if (fenceToTrigger == fence) { + // The fence is waited on by the main thread, they are not allowed to access it simultaneously. + std::lock_guard lock(fenceMutex); + res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + readyForFence = true; + fenceCondVar.notify_one(); + } else { + res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + } + if (res == VK_ERROR_DEVICE_LOST) { _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan"); } else { diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index a80210e81c..9fdd865ebf 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -44,6 +44,10 @@ enum class FrameSubmitType { struct FrameData { bool skipSwap = false; + std::mutex fenceMutex; + std::condition_variable fenceCondVar; + bool readyForFence = true; + VkFence fence; VkFence readbackFence; // Strictly speaking we might only need one global of these. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 4229ed5d86..c307314233 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -504,6 +504,16 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile VLOG("PUSH: Fencing %d", curFrame); + // Makes sure the submission from the previous time around has happened. Otherwise + // we are not allowed to wait from another thread here.. + { + std::unique_lock lock(frameData.fenceMutex); + while (!frameData.readyForFence) { + frameData.fenceCondVar.wait(lock); + } + frameData.readyForFence = false; + } + // This must be the very first Vulkan call we do in a new frame. // Makes sure the very last command buffer from the frame before the previous has been fully executed. if (vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX) == VK_ERROR_DEVICE_LOST) { From df1297bccf7ce523a0e91aa023702dddb663d527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 20:51:46 +0200 Subject: [PATCH 157/326] Small tweak --- Common/GPU/Vulkan/VulkanFrameData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index d5bc6f47aa..2aaebdbf8f 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -176,8 +176,8 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame VkResult res; if (fenceToTrigger == fence) { // The fence is waited on by the main thread, they are not allowed to access it simultaneously. - std::lock_guard lock(fenceMutex); res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); + std::lock_guard lock(fenceMutex); readyForFence = true; fenceCondVar.notify_one(); } else { From 1259283c2eb32b3926399654e7748581da8da6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 22:10:21 +0200 Subject: [PATCH 158/326] More tweaks, fix crash on exit (double-join thread) --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 10 ++++++---- GPU/Vulkan/StateMappingVulkan.cpp | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index c307314233..a666a830de 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -347,10 +347,10 @@ bool VulkanRenderManager::CreateBackbuffers() { void VulkanRenderManager::StopThread() { { // Tell the render thread to quit when it's done. - std::unique_lock lock(pushMutex_); VKRRenderThreadTask task; task.frame = vulkan_->GetCurFrame(); task.runType = VKRRunType::EXIT; + std::unique_lock lock(pushMutex_); renderThreadQueue_.push(task); pushCondVar_.notify_one(); } @@ -407,7 +407,9 @@ void VulkanRenderManager::DestroyBackbuffers() { VulkanRenderManager::~VulkanRenderManager() { INFO_LOG(G3D, "VulkanRenderManager destructor"); - StopThread(); + + _dbg_assert_(!run_); // StopThread should already have been called from DestroyBackbuffers. + vulkan_->WaitUntilQueueIdle(); DrainCompileQueue(); @@ -1171,10 +1173,10 @@ void VulkanRenderManager::Finish() { { VLOG("PUSH: Frame[%d]", curFrame); - std::unique_lock lock(pushMutex_); VKRRenderThreadTask task; task.frame = curFrame; task.runType = VKRRunType::PRESENT; + std::unique_lock lock(pushMutex_); renderThreadQueue_.push(task); renderThreadQueue_.back().steps = std::move(steps_); pushCondVar_.notify_one(); @@ -1275,10 +1277,10 @@ void VulkanRenderManager::FlushSync() { { VLOG("PUSH: Frame[%d]", curFrame); - std::unique_lock lock(pushMutex_); VKRRenderThreadTask task; task.frame = curFrame; task.runType = VKRRunType::SYNC; + std::unique_lock lock(pushMutex_); renderThreadQueue_.push(task); renderThreadQueue_.back().steps = std::move(steps_); pushCondVar_.notify_one(); diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index 290c7b010c..929482a88d 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -152,7 +152,7 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag GenericBlendState &blendState = pipelineState_.blendState; GenericLogicState &logicState = pipelineState_.logicState; - if (pipelineState_.FramebufferRead()) { + if (pipelineState_.FramebufferRead() && useBufferedRendering) { ApplyFramebufferRead(&fboTexBindState_); // The shader takes over the responsibility for blending, so recompute. // We might still end up using blend to write something to alpha. From 3393d47e92f50fca46cbe9bcd24e65fefd1840df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Sep 2022 23:14:36 +0200 Subject: [PATCH 159/326] Remove ill-formed asserts --- Common/Thread/Promise.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Common/Thread/Promise.h b/Common/Thread/Promise.h index 6fae6d7a2b..4145b4b6c1 100644 --- a/Common/Thread/Promise.h +++ b/Common/Thread/Promise.h @@ -79,7 +79,6 @@ public: // Returns T if the data is ready, nullptr if it's not. T Poll() { - _dbg_assert_(this != nullptr); std::lock_guard guard(readyMutex_); if (ready_) { return data_; @@ -96,7 +95,6 @@ public: } T BlockUntilReady() { - _dbg_assert_(this != nullptr); std::lock_guard guard(readyMutex_); if (ready_) { return data_; From 7b1ec28c30979ad6cdb97984f78602542ca74057 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 23 Sep 2022 18:45:31 -0700 Subject: [PATCH 160/326] Vulkan: Avoid crash on two backbuffer steps. Happens when resuming from stepping, sometimes: first EmuScreen_BackBuffer, which is done so UI is functional during stepping, and then the actual blit. Since this is an uncommon case, let's just allow it by reusing the presentCmd instead of submitting it early. Otherwise it ends up in a pending state. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 2 +- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 2aaebdbf8f..52fd21c893 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -141,7 +141,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame hasMainCommands = false; } - if (hasPresentCommands) { + if (hasPresentCommands && type != FrameSubmitType::Pending) { VkResult res = vkEndCommandBuffer(presentCmd); _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res)); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 542ba7a1e2..ccbf910f77 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -585,14 +585,15 @@ void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frame SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); } - _dbg_assert_(!frameData.hasPresentCommands); - // A RENDER step rendering to the backbuffer is normally the last step that happens in a frame, - // unless taking a screenshot, in which case there might be a READBACK_IMAGE after it. - // This is why we have to switch cmd to presentCmd, in this case. - VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - vkBeginCommandBuffer(frameData.presentCmd, &begin); - frameData.hasPresentCommands = true; + if (!frameData.hasPresentCommands) { + // A RENDER step rendering to the backbuffer is normally the last step that happens in a frame, + // unless taking a screenshot, in which case there might be a READBACK_IMAGE after it. + // This is why we have to switch cmd to presentCmd, in this case. + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(frameData.presentCmd, &begin); + frameData.hasPresentCommands = true; + } cmd = frameData.presentCmd; } PerformRenderPass(step, cmd); From cc447aa8683cad241e408adb45d25afc6ba44d5f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 23 Sep 2022 18:47:11 -0700 Subject: [PATCH 161/326] HLE: Fix bad logging without latestSyscall. --- Core/HLE/HLE.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 896bc3cac8..bd0e9d1cac 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -864,6 +864,8 @@ void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, funcName = latestSyscall->name; funcFlags = latestSyscall->flags; + } else { + strcpy(formatted_args, "?"); } const char *fmt; From c3c5450b8f6186cea12dd76b0c80202d358d67ad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 23 Sep 2022 21:18:39 -0700 Subject: [PATCH 162/326] GE Debugger: Fix small tex/clut recopying. If it's less than 256 bytes, we can't mark the entire VRAM area copied. This still helps frame dumps avoid excessively slow VRAM recopying situations, but fixes issues like missing trees in #12738. --- GPU/Debugger/Playback.cpp | 4 +--- GPU/Debugger/Record.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index c47eba66b3..ff35966ef9 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -489,11 +489,9 @@ void DumpExecute::Clut(u32 ptr, u32 sz) { // This is always run when we have the actual address set. if (execClutAddr != 0) { const bool isTarget = (execClutFlags & 1) != 0; - const bool unchangedVRAM = (execClutFlags & 2) != 0; - // TODO: Could use drawnVRAM flag, but it can be wrong. // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior. - if (Memory::IsValidRange(execClutAddr, sz) && !unchangedVRAM && (!isTarget || !g_Config.bSoftwareRendering)) { + if (Memory::IsValidRange(execClutAddr, sz) && (!isTarget || !g_Config.bSoftwareRendering)) { // Intentionally don't trigger an upload here. Memory::MemcpyUnchecked(execClutAddr, pushbuf_.data() + ptr, sz); } diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 248ba1e48c..cc73992c1b 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -326,14 +326,19 @@ static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) { bool isDirtyVRAM = false; bool isDrawnVRAM = false; uint32_t start = (addr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK; - for (uint32_t i = 0; i < (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; ++i) { + uint32_t blocks = (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; + bool startEven = (addr & DIRTY_VRAM_ROUND) == 0; + bool endEven = ((addr + sizeInRAM) & DIRTY_VRAM_ROUND) == 0; + for (uint32_t i = 0; i < blocks; ++i) { DirtyVRAMFlag flag = dirtyVRAM[start + i]; isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN; isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN; // Mark the VRAM clean now that it's been copied to VRAM. - if (flag == DirtyVRAMFlag::DIRTY) - dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN; + if (flag == DirtyVRAMFlag::DIRTY) { + if ((i > 0 || startEven) && (i < blocks || endEven)) + dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN; + } } // The isTarget flag is mostly used for replay of dumps on a PSP. From 7ff54349687ba449a7cda9cc2ded3778783717a8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 23 Sep 2022 21:20:14 -0700 Subject: [PATCH 163/326] GE Debugger: Tag frame dump replay VRAM writes. Just for debugging, it's helpful especially paired with softgpu tagging. --- GPU/Debugger/Playback.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index ff35966ef9..da55c9238d 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -28,6 +28,7 @@ #include "Core/Config.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/MemBlockInfo.h" #include "Core/ELF/ParamSFO.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/HLE/sceDisplay.h" @@ -494,6 +495,7 @@ void DumpExecute::Clut(u32 ptr, u32 sz) { if (Memory::IsValidRange(execClutAddr, sz) && (!isTarget || !g_Config.bSoftwareRendering)) { // Intentionally don't trigger an upload here. Memory::MemcpyUnchecked(execClutAddr, pushbuf_.data() + ptr, sz); + NotifyMemInfo(MemBlockFlags::WRITE, execClutAddr, sz, "ReplayClut"); } execClutAddr = 0; @@ -548,6 +550,7 @@ void DumpExecute::Memcpy(u32 ptr, u32 sz) { if (Memory::IsVRAMAddress(execMemcpyDest)) { SyncStall(); Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz); + NotifyMemInfo(MemBlockFlags::WRITE, execMemcpyDest, sz, "ReplayMemcpy"); gpu->PerformMemoryUpload(execMemcpyDest, sz); } } @@ -598,6 +601,7 @@ void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { if (Memory::IsValidRange(framebuf->addr, pspSize) && !unchangedVRAM && (!isTarget || !g_Config.bSoftwareRendering)) { // Intentionally don't trigger an upload here. Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize); + NotifyMemInfo(MemBlockFlags::WRITE, framebuf->addr, pspSize, "ReplayTex"); } } From 794a5c07ad6351975228749cd5a6db04be659c10 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 00:04:05 -0700 Subject: [PATCH 164/326] softgpu: Ignore a needless color test case. This happens in Ridge Racer, and we can entirely skip the color test. --- GPU/Software/FuncId.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GPU/Software/FuncId.cpp b/GPU/Software/FuncId.cpp index 0d194d0012..90a51778ae 100644 --- a/GPU/Software/FuncId.cpp +++ b/GPU/Software/FuncId.cpp @@ -168,6 +168,14 @@ void ComputePixelFuncID(PixelFuncID *id) { id->alphaBlendDst = (uint8_t)OptimizeAlphaFactor(gstate.getFixB()); } + if (id->colorTest && gstate.getColorTestFunction() == GE_COMP_NOTEQUAL && gstate.getColorTestRef() == 0 && gstate.getColorTestMask() == 0xFFFFFF) { + if (!id->depthWrite && !id->stencilTest && id->alphaBlend && id->AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) { + // Might be a pointless color test (seen in Ridge Racer, for example.) + if (id->AlphaBlendDst() == PixelBlendFactor::ONE) + id->colorTest = false; + } + } + id->applyLogicOp = gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY; id->applyFog = gstate.isFogEnabled() && !gstate.isModeThrough(); From a4c3718431ff09f925cf1f09df33be3219ba6df5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 00:04:48 -0700 Subject: [PATCH 165/326] softgpu: Optimize rectangle sampling/blending. Sometimes the vertex color or alpha can allow us to optimize away some multiplication. --- GPU/Software/Rasterizer.cpp | 66 ++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index b4d13fa6f6..c4eca9f93a 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -143,6 +143,68 @@ void ComputeRasterizerState(RasterizerState *state) { #endif } +RasterizerState OptimizeFlatRasterizerState(RasterizerState state, const VertexData &v1) { + uint8_t alpha = v1.color0 >> 24; + + bool changedPixelID = false; + bool changedSamplerID = false; + if (!state.pixelID.clearMode) { + auto &pixelID = state.pixelID; + auto &cached = pixelID.cached; + + bool useTextureAlpha = state.enableTextures && state.samplerID.useTextureAlpha; + if (pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && !useTextureAlpha) { + // Okay, we may be able to convert this to a fixed value. + if (alpha == 0) { + pixelID.alphaBlendSrc = (uint8_t)PixelBlendFactor::ZERO; + changedPixelID = true; + } else if (alpha == 0xFF) { + pixelID.alphaBlendSrc = (uint8_t)PixelBlendFactor::ONE; + changedPixelID = true; + } + } + if (pixelID.alphaBlend && pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && !useTextureAlpha) { + if (alpha == 0) { + pixelID.alphaBlendDst = (uint8_t)PixelBlendFactor::ONE; + changedPixelID = true; + } else if (alpha == 0xFF) { + pixelID.alphaBlendDst = (uint8_t)PixelBlendFactor::ZERO; + changedPixelID = true; + } + } + if (pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::ONE && pixelID.AlphaBlendDst() == PixelBlendFactor::ZERO) { + if (pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) { + pixelID.alphaBlend = false; + changedPixelID = true; + } + } + } + if (state.enableTextures) { + if (v1.color0 == 0xFFFFFFFF) { + // Modulate is common, sometimes even with a fixed color. Replace is cheaper. + if (state.samplerID.TexFunc() == GE_TEXFUNC_MODULATE) { + state.samplerID.texFunc = (uint8_t)GE_TEXFUNC_REPLACE; + changedSamplerID = true; + } + } + } + + if (changedPixelID) + state.drawPixel = Rasterizer::GetSingleFunc(state.pixelID); + if (changedSamplerID) { + state.linear = Sampler::GetLinearFunc(state.samplerID); + state.nearest = Sampler::GetNearestFunc(state.samplerID); + + // Since the definitions are the same, just force this setting using the func pointer. + if (g_Config.iTexFiltering == TEX_FILTER_FORCE_LINEAR) + state.nearest = state.linear; + else if (g_Config.iTexFiltering == TEX_FILTER_FORCE_NEAREST) + state.linear = state.nearest; + } + + return state; +} + static inline u8 ClampFogDepth(float fogdepth) { union FloatBits { float f; @@ -893,7 +955,7 @@ void DrawTriangle(const VertexData &v0, const VertexData &v1, const VertexData & drawSlice(v0, v1, v2, range.x1, range.y1, range.x2, range.y2, state); } -void DrawRectangle(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &state) { +void DrawRectangle(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &rastState) { int entireX1 = std::min(v0.screenpos.x, v1.screenpos.x); int entireY1 = std::min(v0.screenpos.y, v1.screenpos.y); int entireX2 = std::max(v0.screenpos.x, v1.screenpos.x) - 1; @@ -903,6 +965,8 @@ void DrawRectangle(const VertexData &v0, const VertexData &v1, const BinCoords & int maxX = std::min(entireX2, range.x2); int maxY = std::min(entireY2, range.y2); + RasterizerState state = OptimizeFlatRasterizerState(rastState, v1); + Vec2f rowST(0.0f, 0.0f); // Note: this is double the x or y movement. Vec2f stx(0.0f, 0.0f); From 6e6535c263c338c4d21bb95f058492c793241ebe Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 00:28:55 -0700 Subject: [PATCH 166/326] softjit: Skip reading dst pixel where blended out. Sometimes used by blends used purely to multiply the source color by something, usually prep for bloom. --- GPU/Software/DrawPixel.cpp | 6 ++++-- GPU/Software/DrawPixel.h | 1 + GPU/Software/DrawPixelX86.cpp | 12 ++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/GPU/Software/DrawPixel.cpp b/GPU/Software/DrawPixel.cpp index ee47c27b94..912af5c67a 100644 --- a/GPU/Software/DrawPixel.cpp +++ b/GPU/Software/DrawPixel.cpp @@ -673,11 +673,13 @@ void ComputePixelBlendState(PixelBlendState &state, const PixelFuncID &id) { state.srcColorAsFactor = true; break; + case PixelBlendFactor::ZERO: + state.readsDstPixel = state.dstColorAsFactor || state.usesDstAlpha; + break; + default: break; } - - state.dstColorAsFactor = state.dstColorAsFactor || state.usesDstAlpha; } } diff --git a/GPU/Software/DrawPixel.h b/GPU/Software/DrawPixel.h index f813bf61c1..65f6fa1ec5 100644 --- a/GPU/Software/DrawPixel.h +++ b/GPU/Software/DrawPixel.h @@ -51,6 +51,7 @@ struct PixelBlendState { bool dstFactorIsInverse = false; bool srcColorAsFactor = false; bool dstColorAsFactor = false; + bool readsDstPixel = true; }; void ComputePixelBlendState(PixelBlendState &state, const PixelFuncID &id); diff --git a/GPU/Software/DrawPixelX86.cpp b/GPU/Software/DrawPixelX86.cpp index dc53be1c62..ed70b9c466 100644 --- a/GPU/Software/DrawPixelX86.cpp +++ b/GPU/Software/DrawPixelX86.cpp @@ -1047,7 +1047,13 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) { // Step 1: Load and expand dest color. X64Reg dstReg = regCache_.Alloc(RegCache::VEC_TEMP0); - if (id.FBFormat() == GE_FORMAT_8888) { + if (!blendState.readsDstPixel) { + // Let's load colorOff just for registers to be consistent. + X64Reg colorOff = GetColorOff(id); + regCache_.Unlock(colorOff, RegCache::GEN_COLOR_OFF); + + PXOR(dstReg, R(dstReg)); + } else if (id.FBFormat() == GE_FORMAT_8888) { X64Reg colorOff = GetColorOff(id); Describe("AlphaBlend"); MOVD_xmm(dstReg, MatR(colorOff)); @@ -1073,7 +1079,6 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) { case GE_FORMAT_4444: success = success && Jit_ConvertFrom4444(id, dstGenReg, temp1Reg, temp2Reg, blendState.usesDstAlpha); - break; case GE_FORMAT_8888: @@ -1115,7 +1120,7 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) { // We also need to add a half bit later, so this gives us space. if (multiplySrc || blendState.srcColorAsFactor) PSLLW(argColorReg, 4); - if (multiplyDst || blendState.dstColorAsFactor) + if (multiplyDst || blendState.dstColorAsFactor || blendState.usesDstAlpha) PSLLW(dstReg, 4); // Okay, now grab our factors. Don't bother if they're known values. @@ -1219,7 +1224,6 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) { return success; } - bool PixelJitCache::Jit_BlendFactor(const PixelFuncID &id, RegCache::Reg factorReg, RegCache::Reg dstReg, PixelBlendFactor factor) { X64Reg idReg = INVALID_REG; X64Reg tempReg = INVALID_REG; From c76d31dfa86d251a9f773dba350c28f4f9a181b9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 01:24:53 -0700 Subject: [PATCH 167/326] GPU: Cleanup unused CheckAlpha() funcs. --- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/D3D11/TextureCacheD3D11.cpp | 14 -------------- GPU/D3D11/TextureCacheD3D11.h | 1 - GPU/Directx9/TextureCacheDX9.cpp | 14 -------------- GPU/Directx9/TextureCacheDX9.h | 1 - GPU/Vulkan/TextureCacheVulkan.cpp | 14 -------------- GPU/Vulkan/TextureCacheVulkan.h | 1 - 7 files changed, 1 insertion(+), 46 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 73718e72b7..f7c67bb2a1 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -2797,7 +2797,7 @@ CheckAlphaResult TextureCacheCommon::CheckCLUTAlpha(const uint8_t *pixelData, GE // Never has any alpha. return CHECKALPHA_FULL; default: - return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000); // note, the normal order here, unlike the 16-bit formats + return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000); } } diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 0930aec655..8d051c915c 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -453,20 +453,6 @@ DXGI_FORMAT TextureCacheD3D11::GetDestFormat(GETextureFormat format, GEPaletteFo } } -CheckAlphaResult TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int w) { - switch (dstFmt) { - case DXGI_FORMAT_B4G4R4A4_UNORM: - return CheckAlpha16((const u16 *)pixelData, w, 0xF000); - case DXGI_FORMAT_B5G5R5A1_UNORM: - return CheckAlpha16((const u16 *)pixelData, w, 0x8000); - case DXGI_FORMAT_B5G6R5_UNORM: - // Never has any alpha. - return CHECKALPHA_FULL; - default: - return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000); - } -} - bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { SetTexture(); if (!nextTexture_) { diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index e1be7d3aaf..8b9d7caa78 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -65,7 +65,6 @@ protected: private: DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - static CheckAlphaResult CheckAlpha(const u32 *pixelData, u32 dstFmt, int w); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void BuildTexture(TexCacheEntry *const entry) override; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 3a3feda7f3..9edc451a52 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -358,20 +358,6 @@ D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat } } -CheckAlphaResult TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int w) { - switch (dstFmt) { - case D3DFMT_A4R4G4B4: - return CheckAlpha16((const u16 *)pixelData, w, 0xF000); - case D3DFMT_A1R5G5B5: - return CheckAlpha16((const u16 *)pixelData, w, 0x8000); - case D3DFMT_R5G6B5: - // Never has any alpha. - return CHECKALPHA_FULL; - default: - return CheckAlpha32(pixelData, w, 0xFF000000); - } -} - bool TextureCacheDX9::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { SetTexture(); ApplyTexture(); diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index 58c8e17db6..6a52eb2007 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -56,7 +56,6 @@ private: void ApplySamplingParams(const SamplerCacheKey &key) override; D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - static CheckAlphaResult CheckAlpha(const u32 *pixelData, u32 dstFmt, int w); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void BuildTexture(TexCacheEntry *const entry) override; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 56345d4db5..75b067cc40 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -709,20 +709,6 @@ VkFormat TextureCacheVulkan::GetDestFormat(GETextureFormat format, GEPaletteForm } } -CheckAlphaResult TextureCacheVulkan::CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int w) { - switch (dstFmt) { - case VULKAN_4444_FORMAT: - return CheckAlpha16((const u16 *)pixelData, w, 0xF000); - case VULKAN_1555_FORMAT: - return CheckAlpha16((const u16 *)pixelData, w, 0x8000); - case VULKAN_565_FORMAT: - // Never has any alpha. - return CHECKALPHA_FULL; - default: - return CheckAlpha32(pixelData, w, 0xFF000000); - } -} - void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt) { int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 7dfb38fdc4..5deeb30ea9 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -102,7 +102,6 @@ protected: private: void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); VkFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - static CheckAlphaResult CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int w); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void BuildTexture(TexCacheEntry *const entry) override; From 84b748579883fb92d46d7b132746c15154f5dd28 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 11:14:20 +0200 Subject: [PATCH 168/326] OpenXR - Get rid of an ugly if condition --- Common/VR/PPSSPPVR.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 1711cc3e79..e2520d8002 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -232,15 +232,24 @@ void PreGLRenderPass(const void* step) { return; } const auto* glrStep = (const GLRStep*)step; - vrCompat[VR_COMPAT_GEOMETRY] = strcmp(glrStep->tag, "FramebufferSwitch") == 0; - // Clear the screen with fog color, only for passes containing geometry (and no lens flares) - if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY] && (glrStep->commands.size() > 50)) { - float color[4]; - Uint8x3ToFloat4(color, vrCompat[VR_COMPAT_FOG_COLOR]); - glClearColor(color[0], color[1], color[2], 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 1); + // Clear the screen with fog color, only for passes containing geometry + if (vrCompat[VR_COMPAT_SKYPLANE]) { + vrCompat[VR_COMPAT_GEOMETRY] = true; + for (auto& cmd : glrStep->commands) { + if (cmd.cmd == GLRRenderCommand::BIND_FB_TEXTURE) { + vrCompat[VR_COMPAT_GEOMETRY] = false; + break; + } + } + + if (vrCompat[VR_COMPAT_GEOMETRY]) { + float color[4]; + Uint8x3ToFloat4(color, vrCompat[VR_COMPAT_FOG_COLOR]); + glClearColor(color[0], color[1], color[2], 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glClearColor(0, 0, 0, 1); + } } } From 09bf5f507bac4dbed0c5f18566e29a866e115e0c Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 11:35:51 +0200 Subject: [PATCH 169/326] OpenXR - Simplify skyplane detection in GTA --- Common/VR/PPSSPPVR.cpp | 16 ++++------------ Common/VR/PPSSPPVR.h | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index e2520d8002..df2a813202 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -219,14 +219,6 @@ void UpdateVRScreenKey(const KeyInput &key) { ================================================================================ */ -bool IsSkyPlane(const GLRRenderData* data) { - if ((data->drawIndexed.count <= 60) && !vrCompat[VR_USE_CLIP]) { - //TODO:fix HUD items - return true; - } - return false; -} - void PreGLRenderPass(const void* step) { if (IsFlatVRScene()) { return; @@ -258,9 +250,9 @@ void PreGLCommand(const void* data) { return; } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { const auto* glrData = (const GLRRenderData*)data; - if (glrData->cmd == GLRRenderCommand::BINDPROGRAM) { - vrCompat[VR_USE_CLIP] = glrData->program.program->use_clip_distance[0]; - } else if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(glrData)) { + if (glrData->cmd == GLRRenderCommand::DEPTH) { + vrCompat[VR_COMPAT_DEPTH_ENABLED] = glrData->depth.enabled; + } else if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && !vrCompat[VR_COMPAT_DEPTH_ENABLED]) { glColorMask(false, false, false, false); } } @@ -271,7 +263,7 @@ void PostGLCommand(const void* data) { return; } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { const auto* glrData = (const GLRRenderData*)data; - if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && IsSkyPlane(glrData)) { + if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && !vrCompat[VR_COMPAT_DEPTH_ENABLED]) { glColorMask(true, true, true, true); } } diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 69f0a129e2..298a9a138e 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -8,7 +8,7 @@ enum VRCompatFlag { VR_COMPAT_SKYPLANE, //render state - VR_COMPAT_GEOMETRY, VR_USE_CLIP, + VR_COMPAT_GEOMETRY, VR_COMPAT_DEPTH_ENABLED, //uniforms VR_COMPAT_FOG_COLOR, From 5b334a7945a6b0246bb890ac234deb43f932232f Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 16:32:55 +0200 Subject: [PATCH 170/326] OpenXR - PreprocessStep function implemented --- Common/GPU/OpenGL/GLQueueRunner.cpp | 13 +++--- Common/VR/PPSSPPVR.cpp | 68 +++++++++++++---------------- Common/VR/PPSSPPVR.h | 10 ++--- 3 files changed, 40 insertions(+), 51 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 6157b2e1d1..c7f0bc93b9 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -701,7 +701,13 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal switch (step.stepType) { case GLRStepType::RENDER: renderCount++; - PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount); + if (IsVRBuild()) { + GLRStep vrStep = step; + PreprocessStepVR(&vrStep); + PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount); + } else { + PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount); + } break; case GLRStepType::COPY: PerformCopy(step); @@ -773,9 +779,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last PerformBindFramebufferAsRenderTarget(step); - const bool isVR = IsVRBuild(); - if (isVR) PreGLRenderPass(&step); - if (first) { glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); @@ -823,7 +826,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last CHECK_GL_ERROR_IF_DEBUG(); auto &commands = step.commands; for (const auto &c : commands) { - if (isVR) PreGLCommand(&c); switch (c.cmd) { case GLRRenderCommand::DEPTH: if (c.depth.enabled) { @@ -1335,7 +1337,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last Crash(); break; } - if (isVR) PostGLCommand(&c); } for (int i = 0; i < 7; i++) { diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index df2a813202..9d1db94576 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -219,54 +219,45 @@ void UpdateVRScreenKey(const KeyInput &key) { ================================================================================ */ -void PreGLRenderPass(const void* step) { +void PreprocessSkyplane(GLRStep* step) { + + // Do not do anything if the scene is not in VR. if (IsFlatVRScene()) { return; } - const auto* glrStep = (const GLRStep*)step; - // Clear the screen with fog color, only for passes containing geometry - if (vrCompat[VR_COMPAT_SKYPLANE]) { - vrCompat[VR_COMPAT_GEOMETRY] = true; - for (auto& cmd : glrStep->commands) { - if (cmd.cmd == GLRRenderCommand::BIND_FB_TEXTURE) { - vrCompat[VR_COMPAT_GEOMETRY] = false; - break; - } + // Check if it is the step we need to modify. + for (auto& cmd : step->commands) { + if (cmd.cmd == GLRRenderCommand::BIND_FB_TEXTURE) { + return; } + } - if (vrCompat[VR_COMPAT_GEOMETRY]) { - float color[4]; - Uint8x3ToFloat4(color, vrCompat[VR_COMPAT_FOG_COLOR]); - glClearColor(color[0], color[1], color[2], 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 1); + // Clear sky with the fog color. + if (!vrCompat[VR_COMPAT_FBO_CLEAR]) { + GLRRenderData skyClear {}; + skyClear.cmd = GLRRenderCommand::CLEAR; + skyClear.clear.colorMask = 0xF; + skyClear.clear.clearMask = GL_COLOR_BUFFER_BIT; + skyClear.clear.clearColor = vrCompat[VR_COMPAT_FOG_COLOR]; + step->commands.insert(step->commands.begin(), skyClear); + vrCompat[VR_COMPAT_FBO_CLEAR] = true; + } + + // Remove original sky plane. + bool depthEnabled = false; + for (auto& command : step->commands) { + if (command.cmd == GLRRenderCommand::DEPTH) { + depthEnabled = command.depth.enabled; + } else if ((command.cmd == GLRRenderCommand::DRAW_INDEXED) && !depthEnabled) { + command.drawIndexed.count = 0; } } } -void PreGLCommand(const void* data) { - if (IsFlatVRScene()) { - return; - } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { - const auto* glrData = (const GLRRenderData*)data; - if (glrData->cmd == GLRRenderCommand::DEPTH) { - vrCompat[VR_COMPAT_DEPTH_ENABLED] = glrData->depth.enabled; - } else if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && !vrCompat[VR_COMPAT_DEPTH_ENABLED]) { - glColorMask(false, false, false, false); - } - } -} - -void PostGLCommand(const void* data) { - if (IsFlatVRScene()) { - return; - } else if (vrCompat[VR_COMPAT_SKYPLANE] && vrCompat[VR_COMPAT_GEOMETRY]) { - const auto* glrData = (const GLRRenderData*)data; - if ((glrData->cmd == GLRRenderCommand::DRAW_INDEXED) && !vrCompat[VR_COMPAT_DEPTH_ENABLED]) { - glColorMask(true, true, true, true); - } - } +void PreprocessStepVR(void* step) { + auto* glrStep = (GLRStep*)step; + if (vrCompat[VR_COMPAT_SKYPLANE]) PreprocessSkyplane(glrStep); } void SetVRCompat(VRCompatFlag flag, long value) { @@ -320,6 +311,7 @@ void FinishVRRender() { void PreVRFrameRender(int fboIndex) { VR_BeginFrame(VR_GetEngine(), fboIndex); + vrCompat[VR_COMPAT_FBO_CLEAR] = false; } void PostVRFrameRender() { diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h index 298a9a138e..fe6f7c4e98 100644 --- a/Common/VR/PPSSPPVR.h +++ b/Common/VR/PPSSPPVR.h @@ -8,7 +8,7 @@ enum VRCompatFlag { VR_COMPAT_SKYPLANE, //render state - VR_COMPAT_GEOMETRY, VR_COMPAT_DEPTH_ENABLED, + VR_COMPAT_FBO_CLEAR, //uniforms VR_COMPAT_FOG_COLOR, @@ -28,9 +28,7 @@ void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(con void UpdateVRScreenKey(const KeyInput &key); // VR games compatibility -void PreGLRenderPass(const void* step); -void PreGLCommand(const void* data); -void PostGLCommand(const void* data); +void PreprocessStepVR(void* step); void SetVRCompat(VRCompatFlag flag, long value); // VR rendering integration @@ -57,9 +55,7 @@ inline void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTou inline void UpdateVRScreenKey(const KeyInput &key) {} // VR games compatibility -inline void PreGLRenderPass(const void* step) {} -inline void PreGLCommand(const void* data) {} -inline void PostGLCommand(const void* data) {} +inline void PreprocessStepVR(void* step) {} inline void SetVRCompat(VRCompatFlag flag, long value) {} // VR rendering integration From f3722faef48d2674cb1ce9b1fd3245c06e8b4936 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 09:59:41 -0700 Subject: [PATCH 171/326] GE Debugger: Add a direct copy for preview images. --- Windows/GEDebugger/GEDebugger.cpp | 62 +++++++++++++++++++++++++++++++ Windows/GEDebugger/GEDebugger.h | 1 + Windows/W32Util/Misc.cpp | 3 +- Windows/ppsspp.rc | 1 + Windows/resource.h | 3 +- 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 62026e6f07..be4c2d3ea3 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -291,6 +291,9 @@ void CGEDebugger::SetupPreviews() { case ID_GEDBG_EXPORT_IMAGE: PreviewExport(primaryBuffer_); break; + case ID_GEDBG_COPY_IMAGE: + PreviewToClipboard(primaryBuffer_); + break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 1; primaryWindow->Redraw(); @@ -321,6 +324,9 @@ void CGEDebugger::SetupPreviews() { case ID_GEDBG_EXPORT_IMAGE: PreviewExport(secondBuffer_); break; + case ID_GEDBG_COPY_IMAGE: + PreviewToClipboard(secondBuffer_); + break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 2; secondWindow->Redraw(); @@ -393,6 +399,62 @@ void CGEDebugger::PreviewExport(const GPUDebugBuffer *dbgBuffer) { } } +void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer) { + if (!OpenClipboard(GetDlgHandle())) { + return; + } + EmptyClipboard(); + + uint32_t byteStride = 3 * dbgBuffer->GetStride(); + while ((byteStride & 3) != 0) + ++byteStride; + + HANDLE memHandle = GlobalAlloc(GHND, sizeof(BITMAPV5HEADER) + byteStride * dbgBuffer->GetHeight()); + if (memHandle == NULL) { + CloseClipboard(); + return; + } + + BITMAPV5HEADER *header = (BITMAPV5HEADER *)GlobalLock(memHandle); + header->bV5Size = sizeof(BITMAPV5HEADER); + header->bV5Width = dbgBuffer->GetStride(); + // Bitmaps are flipped, but we can specify negative height to not be flipped. + header->bV5Height = -dbgBuffer->GetHeight(); + header->bV5Planes = 1; + header->bV5BitCount = 24; + header->bV5Compression = BI_RGB; + header->bV5SizeImage = byteStride * dbgBuffer->GetHeight(); + header->bV5CSType = LCS_sRGB; + header->bV5Intent = LCS_GM_GRAPHICS; + + uint8_t *flipbuffer = nullptr; + uint32_t w = (uint32_t)-1; + uint32_t h = (uint32_t)-1; + const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, false, flipbuffer, w, h); + if (buffer != nullptr) { + uint8_t *pixels = (uint8_t *)(header + 1); + for (uint32_t y = 0; y < dbgBuffer->GetHeight(); ++y) { + const uint8_t *src = buffer + y * 3 * w; + uint8_t *dst = pixels + y * byteStride; + for (uint32_t x = 0; x < w; ++x) { + // Have to swap B/R again for the bitmap, unfortunate. + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + src += 3; + dst += 3; + } + } + } + delete [] flipbuffer; + + GlobalUnlock(memHandle); + + // Takes ownership. + SetClipboardData(CF_DIBV5, memHandle); + CloseClipboard(); +} + void CGEDebugger::UpdatePreviews() { auto memLock = Memory::Lock(); if (!PSP_IsInited()) { diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index beae98a04a..eb4a7fe051 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -113,6 +113,7 @@ private: void PrimaryPreviewHover(int x, int y); void SecondPreviewHover(int x, int y); void PreviewExport(const GPUDebugBuffer *buffer); + void PreviewToClipboard(const GPUDebugBuffer *buffer); static void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); static void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void UpdateMenus(); diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index 1849070afc..f3d3b63690 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -59,7 +59,8 @@ namespace W32Util } BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext) { - OpenClipboard(hwnd); + if (!OpenClipboard(hwnd)) + return FALSE; EmptyClipboard(); HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) * sizeof(wchar_t)); if (hglbCopy == NULL) { diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 9ffc1747f7..31cdd235d5 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -784,6 +784,7 @@ BEGIN POPUP "gepreviewoptions" BEGIN MENUITEM "Export Image...", ID_GEDBG_EXPORT_IMAGE + MENUITEM "Copy Opaque Image", ID_GEDBG_COPY_IMAGE MENUITEM "Show Prim Preview", ID_GEDBG_ENABLE_PREVIEW END POPUP "matrixoptions" diff --git a/Windows/resource.h b/Windows/resource.h index 9ba6bc7946..0edc2cd6a5 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -333,6 +333,7 @@ #define ID_GEDBG_SHOWONTOPRIGHT 40221 #define IDC_GEDBG_STEPVSYNC 40222 #define ID_GEDBG_SETCOND 40223 +#define ID_GEDBG_COPY_IMAGE 40224 // Dummy option to let the buffered rendering hotkey cycle through all the options. @@ -346,7 +347,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40224 +#define _APS_NEXT_COMMAND_VALUE 40225 #define _APS_NEXT_CONTROL_VALUE 1202 #define _APS_NEXT_SYMED_VALUE 101 #endif From 3cc628beb4e6c4623e21d8de0193746a7dc3ce0e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 11:43:52 -0700 Subject: [PATCH 172/326] GE Debugger: Copy images optionally with alpha. Although, lots of apps don't support this. --- Core/Screenshot.cpp | 30 +++++++- Core/Screenshot.h | 3 + Windows/GEDebugger/GEDebugger.cpp | 123 ++++++++++++++++++++---------- Windows/GEDebugger/GEDebugger.h | 2 +- Windows/W32Util/Misc.cpp | 25 ++++++ Windows/W32Util/Misc.h | 16 ++++ Windows/ppsspp.rc | 1 + Windows/resource.h | 3 +- 8 files changed, 158 insertions(+), 45 deletions(-) diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index c049756798..00f7d0a305 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -406,8 +406,7 @@ bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 * } bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h) { - png_image png; - memset(&png, 0, sizeof(png)); + png_image png{}; png.version = PNG_IMAGE_VERSION; png.format = PNG_FORMAT_RGBA; png.width = w; @@ -421,3 +420,30 @@ bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h } return success; } + +bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h) { + png_image png{}; + png.version = PNG_IMAGE_VERSION; + png.format = PNG_FORMAT_RGBA; + png.width = w; + png.height = h; + + png_alloc_size_t allocSize = bufferPNG.size(); + int result = png_image_write_to_memory(&png, allocSize == 0 ? nullptr : bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr); + bool success = result != 0 && png.warning_or_error <= 1; + if (!success && allocSize != bufferPNG.size()) { + bufferPNG.resize(allocSize); + png.warning_or_error = 0; + result = png_image_write_to_memory(&png, bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr); + success = result != 0 && png.warning_or_error <= 1; + } + if (success) + bufferPNG.resize(allocSize); + png_image_free(&png); + + if (!success) { + ERROR_LOG(IO, "Buffering screenshot to PNG produced errors."); + bufferPNG.clear(); + } + return success; +} diff --git a/Core/Screenshot.h b/Core/Screenshot.h index 117cd5e625..ac124fef9a 100644 --- a/Core/Screenshot.h +++ b/Core/Screenshot.h @@ -39,5 +39,8 @@ const u8 *ConvertBufferToScreenshot(const GPUDebugBuffer &buf, bool alpha, u8 *& // Can only be used while in game. bool TakeGameScreenshot(const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int *width = nullptr, int *height = nullptr, int maxRes = -1); + bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h); bool Save8888RGBAScreenshot(const Path &filename, const u8 *bufferRGBA8888, int w, int h); +// Overallocate bufferPNG for better encoding speed. +bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h); diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index be4c2d3ea3..49b0d4a6cf 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -292,7 +292,10 @@ void CGEDebugger::SetupPreviews() { PreviewExport(primaryBuffer_); break; case ID_GEDBG_COPY_IMAGE: - PreviewToClipboard(primaryBuffer_); + PreviewToClipboard(primaryBuffer_, false); + break; + case ID_GEDBG_COPY_IMAGE_ALPHA: + PreviewToClipboard(primaryBuffer_, true); break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 1; @@ -325,7 +328,10 @@ void CGEDebugger::SetupPreviews() { PreviewExport(secondBuffer_); break; case ID_GEDBG_COPY_IMAGE: - PreviewToClipboard(secondBuffer_); + PreviewToClipboard(secondBuffer_, false); + break; + case ID_GEDBG_COPY_IMAGE_ALPHA: + PreviewToClipboard(secondBuffer_, true); break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 2; @@ -399,59 +405,94 @@ void CGEDebugger::PreviewExport(const GPUDebugBuffer *dbgBuffer) { } } -void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer) { +void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer, bool saveAlpha) { if (!OpenClipboard(GetDlgHandle())) { return; } EmptyClipboard(); - uint32_t byteStride = 3 * dbgBuffer->GetStride(); - while ((byteStride & 3) != 0) - ++byteStride; - - HANDLE memHandle = GlobalAlloc(GHND, sizeof(BITMAPV5HEADER) + byteStride * dbgBuffer->GetHeight()); - if (memHandle == NULL) { + uint8_t *flipbuffer = nullptr; + uint32_t w = (uint32_t)-1; + uint32_t h = (uint32_t)-1; + const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, saveAlpha, flipbuffer, w, h); + if (buffer == nullptr) { + delete [] flipbuffer; CloseClipboard(); return; } - BITMAPV5HEADER *header = (BITMAPV5HEADER *)GlobalLock(memHandle); - header->bV5Size = sizeof(BITMAPV5HEADER); - header->bV5Width = dbgBuffer->GetStride(); - // Bitmaps are flipped, but we can specify negative height to not be flipped. - header->bV5Height = -dbgBuffer->GetHeight(); - header->bV5Planes = 1; - header->bV5BitCount = 24; - header->bV5Compression = BI_RGB; - header->bV5SizeImage = byteStride * dbgBuffer->GetHeight(); - header->bV5CSType = LCS_sRGB; - header->bV5Intent = LCS_GM_GRAPHICS; + uint32_t pixelSize = saveAlpha ? 4 : 3; + uint32_t byteStride = pixelSize * w; + while ((byteStride & 3) != 0) + ++byteStride; - uint8_t *flipbuffer = nullptr; - uint32_t w = (uint32_t)-1; - uint32_t h = (uint32_t)-1; - const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, false, flipbuffer, w, h); - if (buffer != nullptr) { - uint8_t *pixels = (uint8_t *)(header + 1); - for (uint32_t y = 0; y < dbgBuffer->GetHeight(); ++y) { - const uint8_t *src = buffer + y * 3 * w; - uint8_t *dst = pixels + y * byteStride; - for (uint32_t x = 0; x < w; ++x) { - // Have to swap B/R again for the bitmap, unfortunate. - dst[0] = src[2]; - dst[1] = src[1]; - dst[2] = src[0]; - src += 3; - dst += 3; - } + // Various apps don't support alpha well, so also copy as PNG. + std::vector png; + if (saveAlpha) { + // Overallocate if we can. + png.resize(byteStride * h); + Save8888RGBAScreenshot(png, buffer, w, h); + + W32Util::ClipboardData png1("PNG", png.size()); + W32Util::ClipboardData png2("image/png", png.size()); + if (!png.empty() && png1 && png2) { + memcpy(png1.data, png.data(), png.size()); + memcpy(png2.data, png.data(), png.size()); + png1.Set(); + png2.Set(); } } + + W32Util::ClipboardData bitmap(CF_DIBV5, sizeof(BITMAPV5HEADER) + byteStride * h); + if (!bitmap) { + delete [] flipbuffer; + CloseClipboard(); + return; + } + + BITMAPV5HEADER *header = (BITMAPV5HEADER *)bitmap.data; + header->bV5Size = sizeof(BITMAPV5HEADER); + header->bV5Width = w; + header->bV5Height = h; + header->bV5Planes = 1; + header->bV5BitCount = saveAlpha ? 32 : 24; + header->bV5Compression = saveAlpha ? BI_BITFIELDS : BI_RGB; + header->bV5SizeImage = byteStride * h; + header->bV5CSType = LCS_WINDOWS_COLOR_SPACE; + header->bV5Intent = LCS_GM_GRAPHICS; + + if (saveAlpha) { + header->bV5RedMask = 0x000000FF; + header->bV5GreenMask = 0x0000FF00; + header->bV5BlueMask = 0x00FF0000; + // Only some applications respect the alpha mask... + header->bV5AlphaMask = 0xFF000000; + } + + uint8_t *pixels = (uint8_t *)(header + 1); + for (uint32_t y = 0; y < h; ++y) { + const uint8_t *src = buffer + y * pixelSize * w; + uint8_t *dst = pixels + (h - y - 1) * byteStride; + + if (saveAlpha) { + // No RB swap needed. + memcpy(dst, src, pixelSize * w); + continue; + } + + for (uint32_t x = 0; x < w; ++x) { + // Have to swap B/R again for the bitmap, unfortunate. + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + src += pixelSize; + dst += pixelSize; + } + } + delete [] flipbuffer; - GlobalUnlock(memHandle); - - // Takes ownership. - SetClipboardData(CF_DIBV5, memHandle); + bitmap.Set(); CloseClipboard(); } diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index eb4a7fe051..ba73f479a0 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -113,7 +113,7 @@ private: void PrimaryPreviewHover(int x, int y); void SecondPreviewHover(int x, int y); void PreviewExport(const GPUDebugBuffer *buffer); - void PreviewToClipboard(const GPUDebugBuffer *buffer); + void PreviewToClipboard(const GPUDebugBuffer *buffer, bool saveAlpha); static void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); static void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void UpdateMenus(); diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index f3d3b63690..4d9d101e41 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -157,6 +157,31 @@ namespace W32Util } ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW); } + + ClipboardData::ClipboardData(const char *format, size_t sz) { + format_ = RegisterClipboardFormatA(format); + handle_ = format_ != 0 ? GlobalAlloc(GHND, sz) : 0; + data = handle_ != 0 ? GlobalLock(handle_) : nullptr; + } + + ClipboardData::ClipboardData(UINT format, size_t sz) { + format_ = format; + handle_ = GlobalAlloc(GHND, sz); + data = handle_ != 0 ? GlobalLock(handle_) : nullptr; + } + + ClipboardData::~ClipboardData() { + if (handle_ != 0) { + GlobalUnlock(handle_); + GlobalFree(handle_); + } + } + + void ClipboardData::Set() { + if (format_ == 0 || handle_ == 0 || data == 0) + return; + SetClipboardData(format_, handle_); + } } static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042; diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index 1b723cc241..d20998fdc9 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -14,6 +14,22 @@ namespace W32Util void ExitAndRestart(bool overrideArgs = false, const std::string &args = ""); void SpawnNewInstance(bool overrideArgs = false, const std::string &args = ""); void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename); + + struct ClipboardData { + ClipboardData(const char *format, size_t sz); + ClipboardData(UINT format, size_t sz); + ~ClipboardData(); + + void Set(); + + operator bool() { + return data != nullptr; + } + + UINT format_; + HANDLE handle_; + void *data; + }; } struct GenericListViewColumn diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 31cdd235d5..304ffded6b 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -785,6 +785,7 @@ BEGIN BEGIN MENUITEM "Export Image...", ID_GEDBG_EXPORT_IMAGE MENUITEM "Copy Opaque Image", ID_GEDBG_COPY_IMAGE + MENUITEM "Copy Image With Alpha", ID_GEDBG_COPY_IMAGE_ALPHA MENUITEM "Show Prim Preview", ID_GEDBG_ENABLE_PREVIEW END POPUP "matrixoptions" diff --git a/Windows/resource.h b/Windows/resource.h index 0edc2cd6a5..822eb06bf9 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -334,6 +334,7 @@ #define IDC_GEDBG_STEPVSYNC 40222 #define ID_GEDBG_SETCOND 40223 #define ID_GEDBG_COPY_IMAGE 40224 +#define ID_GEDBG_COPY_IMAGE_ALPHA 40225 // Dummy option to let the buffered rendering hotkey cycle through all the options. @@ -347,7 +348,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40225 +#define _APS_NEXT_COMMAND_VALUE 40226 #define _APS_NEXT_CONTROL_VALUE 1202 #define _APS_NEXT_SYMED_VALUE 101 #endif From ca77240f3630ccce043022c45a6b28d1c4938897 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 21:16:57 +0200 Subject: [PATCH 173/326] OpenXR - Rename Quest build variant --- android/build.gradle | 10 +++++----- android/src/org/ppsspp/ppsspp/NativeActivity.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 14b7d5da92..cdb28b5d50 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -103,7 +103,7 @@ android { gold { res.srcDirs = ['gold/res'] } - quest { + vr_quest { manifest.srcFile 'QuestManifest.xml' } } @@ -145,7 +145,7 @@ android { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } - quest { + vr_quest { applicationId 'org.ppsspp.ppsspp' dimension "variant" externalNativeBuild { @@ -171,9 +171,9 @@ android { 'normalOptimized', // for testing 'normalRelease', // for Google Play releases 'goldRelease', // for Google Play releases - 'questDebug', // for VR debugging - 'questOptimized', // for VR testing - 'questRelease', // for VR releases + 'vr_questDebug', // for VR debugging on Quest + 'vr_questOptimized',// for VR testing on Quest + 'vr_questRelease', // for VR releases on Quest ] variant.setIgnore(!needed) } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 758d7eb6a8..625546b470 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1547,6 +1547,6 @@ public abstract class NativeActivity extends Activity { } private boolean isVRDevice() { - return BuildConfig.FLAVOR.compareTo("quest") == 0; + return BuildConfig.FLAVOR.startsWith("vr_") == 0; } } From ec22cee96a1843ab7e6ff8fe8409a360cdff6705 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 21:22:55 +0200 Subject: [PATCH 174/326] OpenXR - Add manifest for Pico --- android/PicoManifest.xml | 19 +++++++++++++++++++ android/build.gradle | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 android/PicoManifest.xml diff --git a/android/PicoManifest.xml b/android/PicoManifest.xml new file mode 100644 index 0000000000..081b497ec9 --- /dev/null +++ b/android/PicoManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index cdb28b5d50..f9e08f0fa1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -103,6 +103,9 @@ android { gold { res.srcDirs = ['gold/res'] } + vr_pico { + manifest.srcFile 'PicoManifest.xml' + } vr_quest { manifest.srcFile 'QuestManifest.xml' } @@ -145,6 +148,25 @@ android { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } + vr_pico { + applicationId 'org.ppsspp.ppsspp' + dimension "variant" + externalNativeBuild { + cmake { + // Available arguments listed at https://developer.android.com/ndk/guides/cmake.html + arguments '-DANDROID=true', + '-DANDROID_PLATFORM=android-16', + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_CPP_FEATURES=', + '-DANDROID_STL=c++_static', + '-DANDROID_ARM_NEON=TRUE', + '-DOPENXR=TRUE' + } + } + ndk { + abiFilters 'arm64-v8a' + } + } vr_quest { applicationId 'org.ppsspp.ppsspp' dimension "variant" @@ -171,6 +193,9 @@ android { 'normalOptimized', // for testing 'normalRelease', // for Google Play releases 'goldRelease', // for Google Play releases + 'vr_picoDebug', // for VR debugging on Pico + 'vr_picoOptimized', // for VR testing on Pico + 'vr_picoRelease', // for VR releases on Pico 'vr_questDebug', // for VR debugging on Quest 'vr_questOptimized',// for VR testing on Quest 'vr_questRelease', // for VR releases on Quest From 19970df4306176a8fac7144e8bb048f4789fa3b2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 12:23:02 -0700 Subject: [PATCH 175/326] GE Debugger: Add option to track pixel in preview. --- Windows/GEDebugger/GEDebugger.cpp | 44 +++++++++++++++++++++++++-- Windows/GEDebugger/GEDebugger.h | 5 +++ Windows/GEDebugger/SimpleGLWindow.cpp | 30 ++++++++++++------ Windows/GEDebugger/SimpleGLWindow.h | 5 +-- Windows/ppsspp.rc | 2 ++ Windows/resource.h | 4 ++- 6 files changed, 75 insertions(+), 15 deletions(-) diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 49b0d4a6cf..59e8fb018d 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -281,12 +281,13 @@ void CGEDebugger::SetupPreviews() { primaryWindow->SetHoverCallback([&] (int x, int y) { PrimaryPreviewHover(x, y); }); - primaryWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd) { + primaryWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd, int x, int y) { HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_PREVIEW); switch (cmd) { case 0: // Setup. CheckMenuItem(subMenu, ID_GEDBG_ENABLE_PREVIEW, MF_BYCOMMAND | ((previewsEnabled_ & 1) ? MF_CHECKED : MF_UNCHECKED)); + EnableMenuItem(subMenu, ID_GEDBG_TRACK_PIXEL_STOP, primaryTrackX_ == 0xFFFFFFFF ? MF_GRAYED : MF_ENABLED); break; case ID_GEDBG_EXPORT_IMAGE: PreviewExport(primaryBuffer_); @@ -297,6 +298,14 @@ void CGEDebugger::SetupPreviews() { case ID_GEDBG_COPY_IMAGE_ALPHA: PreviewToClipboard(primaryBuffer_, true); break; + case ID_GEDBG_TRACK_PIXEL: + primaryTrackX_ = x; + primaryTrackY_ = y; + break; + case ID_GEDBG_TRACK_PIXEL_STOP: + primaryTrackX_ = 0xFFFFFFFF; + primaryTrackX_ = 0xFFFFFFFF; + break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 1; primaryWindow->Redraw(); @@ -317,12 +326,13 @@ void CGEDebugger::SetupPreviews() { secondWindow->SetHoverCallback([&] (int x, int y) { SecondPreviewHover(x, y); }); - secondWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd) { + secondWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd, int x, int y) { HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_PREVIEW); switch (cmd) { case 0: // Setup. CheckMenuItem(subMenu, ID_GEDBG_ENABLE_PREVIEW, MF_BYCOMMAND | ((previewsEnabled_ & 2) ? MF_CHECKED : MF_UNCHECKED)); + EnableMenuItem(subMenu, ID_GEDBG_TRACK_PIXEL_STOP, secondTrackX_ == 0xFFFFFFFF ? MF_GRAYED : MF_ENABLED); break; case ID_GEDBG_EXPORT_IMAGE: PreviewExport(secondBuffer_); @@ -333,6 +343,14 @@ void CGEDebugger::SetupPreviews() { case ID_GEDBG_COPY_IMAGE_ALPHA: PreviewToClipboard(secondBuffer_, true); break; + case ID_GEDBG_TRACK_PIXEL: + secondTrackX_ = x; + secondTrackY_ = y; + break; + case ID_GEDBG_TRACK_PIXEL_STOP: + secondTrackX_ = 0xFFFFFFFF; + secondTrackX_ = 0xFFFFFFFF; + break; case ID_GEDBG_ENABLE_PREVIEW: previewsEnabled_ ^= 2; secondWindow->Redraw(); @@ -350,6 +368,12 @@ void CGEDebugger::SetupPreviews() { } void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, char desc[256]) { + if (primaryTrackX_ < primaryBuffer_->GetStride() && primaryTrackY_ < primaryBuffer_->GetHeight()) { + u32 pix = primaryBuffer_->GetRawPixel(primaryTrackX_, primaryTrackY_); + DescribePixel(pix, primaryBuffer_->GetFormat(), primaryTrackX_, primaryTrackY_, desc); + return; + } + if (showClut_) { // In this case, we're showing the texture here. snprintf(desc, 256, "Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_)); @@ -374,6 +398,22 @@ void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, char desc[256]) } void CGEDebugger::DescribeSecondPreview(const GPUgstate &state, char desc[256]) { + if (secondTrackX_ != 0xFFFFFFFF) { + uint32_t x = secondTrackX_; + uint32_t y = secondTrackY_; + if (showClut_) { + uint32_t clutWidth = secondBuffer_->GetStride() / 16; + x = y * clutWidth + x; + y = 0; + } + + if (x < secondBuffer_->GetStride() && y < secondBuffer_->GetHeight()) { + u32 pix = secondBuffer_->GetRawPixel(x, y); + DescribePixel(pix, secondBuffer_->GetFormat(), x, y, desc); + return; + } + } + if (showClut_) { snprintf(desc, 256, "CLUT: 0x%08x (%d)", state.getClutAddress(), state.getClutPaletteFormat()); } else { diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index ba73f479a0..9488c6ec21 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -140,6 +140,11 @@ private: const GPUDebugBuffer *primaryBuffer_ = nullptr; const GPUDebugBuffer *secondBuffer_ = nullptr; + uint32_t primaryTrackX_ = 0xFFFFFFFF; + uint32_t primaryTrackY_ = 0xFFFFFFFF; + uint32_t secondTrackX_ = 0xFFFFFFFF; + uint32_t secondTrackY_ = 0xFFFFFFFF; + bool updating_ = false; int previewsEnabled_ = 3; int minWidth_; diff --git a/Windows/GEDebugger/SimpleGLWindow.cpp b/Windows/GEDebugger/SimpleGLWindow.cpp index e04053ed3d..6f0464a87c 100644 --- a/Windows/GEDebugger/SimpleGLWindow.cpp +++ b/Windows/GEDebugger/SimpleGLWindow.cpp @@ -519,25 +519,34 @@ bool SimpleGLWindow::ToggleZoom() { return true; } -bool SimpleGLWindow::Hover(int mouseX, int mouseY) { - if (hoverCallback_ == nullptr) { - return false; - } - +POINT SimpleGLWindow::PosFromMouse(int mouseX, int mouseY) { float fw, fh; float x, y; GetContentSize(x, y, fw, fh); if (mouseX < x || mouseX >= x + fw || mouseY < y || mouseY >= y + fh) { // Outside of bounds. - hoverCallback_(-1, -1); - return true; + return POINT{ -1, -1 }; } float tx = (mouseX - x) * (tw_ / fw); float ty = (mouseY - y) * (th_ / fh); - hoverCallback_((int)tx, (int)ty); + return POINT{ (int)tx, (int)ty }; +} + +bool SimpleGLWindow::Hover(int mouseX, int mouseY) { + if (hoverCallback_ == nullptr) { + return false; + } + + POINT pos = PosFromMouse(mouseX, mouseY); + hoverCallback_(pos.x, pos.y); + + if (pos.x == -1 || pos.y == -1) { + // Outside of bounds, don't track. + return true; + } // Find out when they are done. TRACKMOUSEEVENT tracking = {0}; @@ -564,11 +573,12 @@ bool SimpleGLWindow::RightClick(int mouseX, int mouseY) { } POINT pt{mouseX, mouseY}; + POINT pos = PosFromMouse(mouseX, mouseY); - rightClickCallback_(0); + rightClickCallback_(0, pos.x, pos.y); int result = TriggerContextMenu(rightClickMenu_, hWnd_, ContextPoint::FromClient(pt)); if (result > 0) { - rightClickCallback_(result); + rightClickCallback_(result, pos.x, pos.y); } return true; diff --git a/Windows/GEDebugger/SimpleGLWindow.h b/Windows/GEDebugger/SimpleGLWindow.h index caf90b8b6d..605fb0c37f 100644 --- a/Windows/GEDebugger/SimpleGLWindow.h +++ b/Windows/GEDebugger/SimpleGLWindow.h @@ -117,7 +117,7 @@ struct SimpleGLWindow { } // Called first with 0 that it's opening, then the selected item. - void SetRightClickMenu(ContextMenuID menu, std::function callback) { + void SetRightClickMenu(ContextMenuID menu, std::function callback) { rightClickCallback_ = callback; rightClickMenu_ = menu; } @@ -136,6 +136,7 @@ protected: bool Leave(); bool RightClick(int mouseX, int mouseY); bool ToggleZoom(); + POINT PosFromMouse(int mouseX, int mouseY); const u8 *Reformat(const u8 *data, Format fmt, u32 numPixels); HWND hWnd_; @@ -173,6 +174,6 @@ protected: std::function redrawCallback_; std::function hoverCallback_; - std::function rightClickCallback_; + std::function rightClickCallback_; ContextMenuID rightClickMenu_; }; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 304ffded6b..c04801767e 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -786,6 +786,8 @@ BEGIN MENUITEM "Export Image...", ID_GEDBG_EXPORT_IMAGE MENUITEM "Copy Opaque Image", ID_GEDBG_COPY_IMAGE MENUITEM "Copy Image With Alpha", ID_GEDBG_COPY_IMAGE_ALPHA + MENUITEM "Track Pixel", ID_GEDBG_TRACK_PIXEL + MENUITEM "Stop Tracking Pixel", ID_GEDBG_TRACK_PIXEL_STOP MENUITEM "Show Prim Preview", ID_GEDBG_ENABLE_PREVIEW END POPUP "matrixoptions" diff --git a/Windows/resource.h b/Windows/resource.h index 822eb06bf9..ab68ee494f 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -335,6 +335,8 @@ #define ID_GEDBG_SETCOND 40223 #define ID_GEDBG_COPY_IMAGE 40224 #define ID_GEDBG_COPY_IMAGE_ALPHA 40225 +#define ID_GEDBG_TRACK_PIXEL 40226 +#define ID_GEDBG_TRACK_PIXEL_STOP 40227 // Dummy option to let the buffered rendering hotkey cycle through all the options. @@ -348,7 +350,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 256 -#define _APS_NEXT_COMMAND_VALUE 40226 +#define _APS_NEXT_COMMAND_VALUE 40228 #define _APS_NEXT_CONTROL_VALUE 1202 #define _APS_NEXT_SYMED_VALUE 101 #endif From 1fdbde8ee96cbba72b3044774c98019bd6776828 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 22:11:08 +0200 Subject: [PATCH 176/326] OpenXR - Support multiple loaders --- CMakeLists.txt | 12 +- android/build.gradle | 2 + .../src/org/ppsspp/ppsspp/NativeActivity.java | 2 +- .../libs/pico/arm64-v8a/libopenxr_loader.so | Bin 0 -> 1571392 bytes .../{ => quest}/arm64-v8a/libopenxr_loader.so | Bin ext/openxr/openxr.h | 1705 +---------------- ext/openxr/openxr_pico.h | 907 +++++++++ ext/openxr/openxr_platform.h | 177 +- ext/openxr/openxr_platform_defines.h | 2 +- ext/openxr/openxr_reflection.h | 1031 +--------- 10 files changed, 995 insertions(+), 2843 deletions(-) create mode 100644 ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so rename ext/openxr/libs/{ => quest}/arm64-v8a/libopenxr_loader.so (100%) mode change 100755 => 100644 ext/openxr/openxr.h create mode 100644 ext/openxr/openxr_pico.h mode change 100755 => 100644 ext/openxr/openxr_platform.h mode change 100755 => 100644 ext/openxr/openxr_platform_defines.h mode change 100755 => 100644 ext/openxr/openxr_reflection.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cc31004ebc..6a3bdff3ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,8 +120,16 @@ if(OPENXR) add_definitions(-DOPENXR) add_library(openxr SHARED IMPORTED) include_directories(ext/openxr) - set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/arm64-v8a/libopenxr_loader.so") - message("OpenXR enabled") + if(OPENXR_PLATFORM_PICO) + add_definitions(-DOPENXR_PLATFORM_PICO) + set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so") + message("OpenXR for Pico enabled") + endif() + if(OPENXR_PLATFORM_QUEST) + add_definitions(-DOPENXR_PLATFORM_QUEST) + set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/quest/arm64-v8a/libopenxr_loader.so") + message("OpenXR for Quest enabled") + endif() endif() if(GOLD) diff --git a/android/build.gradle b/android/build.gradle index f9e08f0fa1..47eef16fd7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -160,6 +160,7 @@ android { '-DANDROID_CPP_FEATURES=', '-DANDROID_STL=c++_static', '-DANDROID_ARM_NEON=TRUE', + '-DOPENXR_PLATFORM_PICO=TRUE', '-DOPENXR=TRUE' } } @@ -179,6 +180,7 @@ android { '-DANDROID_CPP_FEATURES=', '-DANDROID_STL=c++_static', '-DANDROID_ARM_NEON=TRUE', + '-DOPENXR_PLATFORM_QUEST=TRUE', '-DOPENXR=TRUE' } } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 625546b470..bed2de57e3 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1547,6 +1547,6 @@ public abstract class NativeActivity extends Activity { } private boolean isVRDevice() { - return BuildConfig.FLAVOR.startsWith("vr_") == 0; + return BuildConfig.FLAVOR.startsWith("vr_"); } } diff --git a/ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so b/ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so new file mode 100644 index 0000000000000000000000000000000000000000..784337e64e2ddcec00edf528d4c23f7b8e8a9048 GIT binary patch literal 1571392 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 z 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 &qYr?z#u(OoLd& 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 (L0Q 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≺`TUXc4~#8OJvFkT;<-^3K3`~+AA4@}=0TCi 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%0AP{*}Da@MCcK>>igKdL4aEbj!Gu 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 N@9YhPTk=Wp{|}gD> 48)& 0xffffULL) #define XR_VERSION_MINOR(version) (uint16_t)(((uint64_t)(version) >> 32) & 0xffffULL) @@ -181,33 +181,13 @@ typedef enum XrResult { XR_ERROR_LOCALIZED_NAME_DUPLICATED = -48, XR_ERROR_LOCALIZED_NAME_INVALID = -49, XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING = -50, - XR_ERROR_RUNTIME_UNAVAILABLE = -51, XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR = -1000003000, XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR = -1000003001, XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT = -1000039001, XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_MSFT = -1000053000, XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT = -1000055000, - XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT = -1000066000, - XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT = -1000097000, - XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT = -1000097001, - XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT = -1000097002, - XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT = -1000097003, - XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT = -1000097004, - XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT = -1000097005, XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB = -1000101000, XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB = -1000108000, - XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB = -1000118000, - XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB = -1000118001, - XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB = -1000118002, - XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB = -1000118003, - XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB = -1000118004, - XR_ERROR_UNKNOWN_PASSTHROUGH_FB = -1000118050, - XR_ERROR_RENDER_MODEL_KEY_INVALID_FB = -1000119000, - XR_RENDER_MODEL_UNAVAILABLE_FB = 1000119020, - XR_ERROR_MARKER_NOT_TRACKED_VARJO = -1000124000, - XR_ERROR_MARKER_ID_INVALID_VARJO = -1000124001, - XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT = -1000142001, - XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT = -1000142002, XR_RESULT_MAX_ENUM = 0x7FFFFFFF } XrResult; @@ -262,6 +242,17 @@ typedef enum XrStructureType { XR_TYPE_ACTIONS_SYNC_INFO = 61, XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO = 62, XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO = 63, + //pico add new type + XR_TYPE_EVENT_CONTROLLER_STATE_CHANGED = 1200006064, + XR_TYPE_EVENT_SEETHROUGH_STATE_CHANGED = 1200006065, + XR_TYPE_EVENT_HARDIPD_STATE_CHANGED = 1200006066, + XR_TYPE_EVENT_KEY_EVENT = 1200006067, + XR_TYPE_EVENT_FOVEATION_LEVEL_CHANGED = 1200006068, + XR_TYPE_EVENT_FRUSTUM_STATE_CHANGED = 1200006069, + XR_TYPE_EVENT_RENDER_TEXTURE_CHANGED = 1200006070, + XR_TYPE_EVENT_TARGET_FRAME_RATE_STATE_CHANGED = 1200006071, + XR_TYPE_EVENT_MRC_STATUS_CHANGED = 1200006072, + XR_TYPE_EVENT_REFRESH_RATE_CHANGE = 1200006073, XR_TYPE_COMPOSITION_LAYER_CUBE_KHR = 1000006000, XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR = 1000008000, XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR = 1000010000, @@ -300,8 +291,6 @@ typedef enum XrStructureType { XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR = 1000034000, XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT = 1000039000, XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT = 1000039001, - XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB = 1000040000, - XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB = 1000041001, XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT = 1000046000, XR_TYPE_GRAPHICS_BINDING_EGL_MNDX = 1000048004, XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT = 1000049000, @@ -328,90 +317,16 @@ typedef enum XrStructureType { XR_TYPE_CONTROLLER_MODEL_STATE_MSFT = 1000055004, XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC = 1000059000, XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT = 1000063000, - XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT = 1000066000, - XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT = 1000066001, XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB = 1000070000, - XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB = 1000072000, XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE = 1000079000, - XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT = 1000080000, XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR = 1000089000, XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR = 1000090000, XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR = 1000090001, XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR = 1000090003, XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR = 1000091000, - XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT = 1000097000, - XR_TYPE_SCENE_CREATE_INFO_MSFT = 1000097001, - XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT = 1000097002, - XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT = 1000097003, - XR_TYPE_SCENE_COMPONENTS_MSFT = 1000097004, - XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT = 1000097005, - XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT = 1000097006, - XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT = 1000097007, - XR_TYPE_SCENE_OBJECTS_MSFT = 1000097008, - XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT = 1000097009, - XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT = 1000097010, - XR_TYPE_SCENE_PLANES_MSFT = 1000097011, - XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT = 1000097012, - XR_TYPE_SCENE_MESHES_MSFT = 1000097013, - XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT = 1000097014, - XR_TYPE_SCENE_MESH_BUFFERS_MSFT = 1000097015, - XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT = 1000097016, - XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT = 1000097017, - XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT = 1000097018, - XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT = 1000098000, - XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT = 1000098001, XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB = 1000101000, - XR_TYPE_VIVE_TRACKER_PATHS_HTCX = 1000103000, - XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX = 1000103001, - XR_TYPE_SYSTEM_FACIAL_TRACKING_PROPERTIES_HTC = 1000104000, - XR_TYPE_FACIAL_TRACKER_CREATE_INFO_HTC = 1000104001, - XR_TYPE_FACIAL_EXPRESSIONS_HTC = 1000104002, XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB = 1000108000, - XR_TYPE_HAND_TRACKING_MESH_FB = 1000110001, - XR_TYPE_HAND_TRACKING_SCALE_FB = 1000110003, - XR_TYPE_HAND_TRACKING_AIM_STATE_FB = 1000111001, - XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB = 1000112000, - XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB = 1000114000, - XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB = 1000114001, - XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB = 1000114002, - XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB = 1000115000, - XR_TYPE_KEYBOARD_SPACE_CREATE_INFO_FB = 1000116009, - XR_TYPE_KEYBOARD_TRACKING_QUERY_FB = 1000116004, - XR_TYPE_SYSTEM_KEYBOARD_TRACKING_PROPERTIES_FB = 1000116002, - XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB = 1000117001, - XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB = 1000118000, - XR_TYPE_PASSTHROUGH_CREATE_INFO_FB = 1000118001, - XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB = 1000118002, - XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB = 1000118003, - XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB = 1000118004, - XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB = 1000118005, - XR_TYPE_PASSTHROUGH_STYLE_FB = 1000118020, - XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB = 1000118021, - XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB = 1000118022, - XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB = 1000118030, - XR_TYPE_RENDER_MODEL_PATH_INFO_FB = 1000119000, - XR_TYPE_RENDER_MODEL_PROPERTIES_FB = 1000119001, - XR_TYPE_RENDER_MODEL_BUFFER_FB = 1000119002, - XR_TYPE_RENDER_MODEL_LOAD_INFO_FB = 1000119003, - XR_TYPE_SYSTEM_RENDER_MODEL_PROPERTIES_FB = 1000119004, XR_TYPE_BINDING_MODIFICATIONS_KHR = 1000120000, - XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO = 1000121000, - XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO = 1000121001, - XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO = 1000121002, - XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO = 1000122000, - XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO = 1000124000, - XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO = 1000124001, - XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO = 1000124002, - XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT = 1000142000, - XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT = 1000142001, - XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB = 1000160000, - XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB = 1000161000, - XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB = 1000162000, - XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB = 1000163000, - XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB = 1000171000, - XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB = 1000171001, - XR_TYPE_DIGITAL_LENS_CONTROL_ALMALENCE = 1000196000, - XR_TYPE_PASSTHROUGH_KEYBOARD_HANDS_INTENSITY_FB = 1000203002, XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR, XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR, XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR, @@ -444,7 +359,6 @@ typedef enum XrReferenceSpaceType { XR_REFERENCE_SPACE_TYPE_LOCAL = 2, XR_REFERENCE_SPACE_TYPE_STAGE = 3, XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT = 1000038000, - XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO = 1000121000, XR_REFERENCE_SPACE_TYPE_MAX_ENUM = 0x7FFFFFFF } XrReferenceSpaceType; @@ -488,15 +402,6 @@ typedef enum XrObjectType { XR_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT = 1000019000, XR_OBJECT_TYPE_SPATIAL_ANCHOR_MSFT = 1000039000, XR_OBJECT_TYPE_HAND_TRACKER_EXT = 1000051000, - XR_OBJECT_TYPE_SCENE_OBSERVER_MSFT = 1000097000, - XR_OBJECT_TYPE_SCENE_MSFT = 1000097001, - XR_OBJECT_TYPE_FACIAL_TRACKER_HTC = 1000104000, - XR_OBJECT_TYPE_FOVEATION_PROFILE_FB = 1000114000, - XR_OBJECT_TYPE_TRIANGLE_MESH_FB = 1000117000, - XR_OBJECT_TYPE_PASSTHROUGH_FB = 1000118000, - XR_OBJECT_TYPE_PASSTHROUGH_LAYER_FB = 1000118002, - XR_OBJECT_TYPE_GEOMETRY_INSTANCE_FB = 1000118004, - XR_OBJECT_TYPE_SPATIAL_ANCHOR_STORE_CONNECTION_MSFT = 1000142000, XR_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF } XrObjectType; typedef XrFlags64 XrInstanceCreateFlags; @@ -538,7 +443,6 @@ static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT = 0x00000 static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_SAMPLED_BIT = 0x00000020; static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT = 0x00000040; static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_MND = 0x00000080; -static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_KHR = 0x00000080; // alias of XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_MND typedef XrFlags64 XrCompositionLayerFlags; @@ -651,7 +555,6 @@ typedef struct XrVector3f { float z; } XrVector3f; -// XrSpaceVelocity extends XrSpaceLocation typedef struct XrSpaceVelocity { XrStructureType type; void* XR_MAY_ALIAS next; @@ -1404,7 +1307,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrApplyHapticFeedback( XRAPI_ATTR XrResult XRAPI_CALL xrStopHapticFeedback( XrSession session, const XrHapticActionInfo* hapticActionInfo); -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_KHR_composition_layer_cube 1 @@ -1426,7 +1329,6 @@ typedef struct XrCompositionLayerCubeKHR { #define XR_KHR_composition_layer_depth 1 #define XR_KHR_composition_layer_depth_SPEC_VERSION 5 #define XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME "XR_KHR_composition_layer_depth" -// XrCompositionLayerDepthInfoKHR extends XrCompositionLayerProjectionView typedef struct XrCompositionLayerDepthInfoKHR { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -1507,21 +1409,18 @@ typedef struct XrEventDataVisibilityMaskChangedKHR { typedef XrResult (XRAPI_PTR *PFN_xrGetVisibilityMaskKHR)(XrSession session, XrViewConfigurationType viewConfigurationType, uint32_t viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, XrVisibilityMaskKHR* visibilityMask); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrGetVisibilityMaskKHR( XrSession session, XrViewConfigurationType viewConfigurationType, uint32_t viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, XrVisibilityMaskKHR* visibilityMask); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_KHR_composition_layer_color_scale_bias 1 #define XR_KHR_composition_layer_color_scale_bias_SPEC_VERSION 5 #define XR_KHR_COMPOSITION_LAYER_COLOR_SCALE_BIAS_EXTENSION_NAME "XR_KHR_composition_layer_color_scale_bias" -// XrCompositionLayerColorScaleBiasKHR extends XrCompositionLayerBaseHeader typedef struct XrCompositionLayerColorScaleBiasKHR { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -1542,11 +1441,9 @@ typedef struct XR_MAY_ALIAS XrLoaderInitInfoBaseHeaderKHR { typedef XrResult (XRAPI_PTR *PFN_xrInitializeLoaderKHR)(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrInitializeLoaderKHR( const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_KHR_composition_layer_equirect2 1 @@ -1571,12 +1468,11 @@ typedef struct XrCompositionLayerEquirect2KHR { #define XR_KHR_binding_modification 1 #define XR_KHR_binding_modification_SPEC_VERSION 1 #define XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME "XR_KHR_binding_modification" -typedef struct XR_MAY_ALIAS XrBindingModificationBaseHeaderKHR { +typedef struct XrBindingModificationBaseHeaderKHR { XrStructureType type; const void* XR_MAY_ALIAS next; } XrBindingModificationBaseHeaderKHR; -// XrBindingModificationsKHR extends XrInteractionProfileSuggestedBinding typedef struct XrBindingModificationsKHR { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -1586,13 +1482,8 @@ typedef struct XrBindingModificationsKHR { -#define XR_KHR_swapchain_usage_input_attachment_bit 1 -#define XR_KHR_swapchain_usage_input_attachment_bit_SPEC_VERSION 3 -#define XR_KHR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_EXTENSION_NAME "XR_KHR_swapchain_usage_input_attachment_bit" - - #define XR_EXT_performance_settings 1 -#define XR_EXT_performance_settings_SPEC_VERSION 3 +#define XR_EXT_performance_settings_SPEC_VERSION 1 #define XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME "XR_EXT_performance_settings" typedef enum XrPerfSettingsDomainEXT { @@ -1634,35 +1525,31 @@ typedef struct XrEventDataPerfSettingsEXT { typedef XrResult (XRAPI_PTR *PFN_xrPerfSettingsSetPerformanceLevelEXT)(XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsLevelEXT level); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrPerfSettingsSetPerformanceLevelEXT( XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsLevelEXT level); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_EXT_thermal_query 1 -#define XR_EXT_thermal_query_SPEC_VERSION 2 +#define XR_EXT_thermal_query_SPEC_VERSION 1 #define XR_EXT_THERMAL_QUERY_EXTENSION_NAME "XR_EXT_thermal_query" typedef XrResult (XRAPI_PTR *PFN_xrThermalGetTemperatureTrendEXT)(XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsNotificationLevelEXT* notificationLevel, float* tempHeadroom, float* tempSlope); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrThermalGetTemperatureTrendEXT( XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsNotificationLevelEXT* notificationLevel, float* tempHeadroom, float* tempSlope); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_EXT_debug_utils 1 XR_DEFINE_HANDLE(XrDebugUtilsMessengerEXT) -#define XR_EXT_debug_utils_SPEC_VERSION 4 +#define XR_EXT_debug_utils_SPEC_VERSION 3 #define XR_EXT_DEBUG_UTILS_EXTENSION_NAME "XR_EXT_debug_utils" typedef XrFlags64 XrDebugUtilsMessageSeverityFlagsEXT; @@ -1713,7 +1600,6 @@ typedef XrBool32 (XRAPI_PTR *PFN_xrDebugUtilsMessengerCallbackEXT)( void* userData); -// XrDebugUtilsMessengerCreateInfoEXT extends XrInstanceCreateInfo typedef struct XrDebugUtilsMessengerCreateInfoEXT { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -1732,7 +1618,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrSessionEndDebugUtilsLabelRegionEXT)(XrSession typedef XrResult (XRAPI_PTR *PFN_xrSessionInsertDebugUtilsLabelEXT)(XrSession session, const XrDebugUtilsLabelEXT* labelInfo); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrSetDebugUtilsObjectNameEXT( XrInstance instance, const XrDebugUtilsObjectNameInfoEXT* nameInfo); @@ -1761,21 +1646,18 @@ XRAPI_ATTR XrResult XRAPI_CALL xrSessionEndDebugUtilsLabelRegionEXT( XRAPI_ATTR XrResult XRAPI_CALL xrSessionInsertDebugUtilsLabelEXT( XrSession session, const XrDebugUtilsLabelEXT* labelInfo); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_EXT_eye_gaze_interaction 1 #define XR_EXT_eye_gaze_interaction_SPEC_VERSION 1 #define XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME "XR_EXT_eye_gaze_interaction" -// XrSystemEyeGazeInteractionPropertiesEXT extends XrSystemProperties typedef struct XrSystemEyeGazeInteractionPropertiesEXT { XrStructureType type; void* XR_MAY_ALIAS next; XrBool32 supportsEyeGazeInteraction; } XrSystemEyeGazeInteractionPropertiesEXT; -// XrEyeGazeSampleTimeEXT extends XrSpaceLocation typedef struct XrEyeGazeSampleTimeEXT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -1785,18 +1667,18 @@ typedef struct XrEyeGazeSampleTimeEXT { #define XR_EXTX_overlay 1 -#define XR_EXTX_overlay_SPEC_VERSION 5 +#define XR_EXTX_overlay_SPEC_VERSION 4 #define XR_EXTX_OVERLAY_EXTENSION_NAME "XR_EXTX_overlay" typedef XrFlags64 XrOverlaySessionCreateFlagsEXTX; // Flag bits for XrOverlaySessionCreateFlagsEXTX +static const XrOverlaySessionCreateFlagsEXTX XR_OVERLAY_SESSION_CREATE_RELAXED_DISPLAY_TIME_BIT_EXTX = 0x00000001; typedef XrFlags64 XrOverlayMainSessionFlagsEXTX; // Flag bits for XrOverlayMainSessionFlagsEXTX static const XrOverlayMainSessionFlagsEXTX XR_OVERLAY_MAIN_SESSION_ENABLED_COMPOSITION_LAYER_INFO_DEPTH_BIT_EXTX = 0x00000001; -// XrSessionCreateInfoOverlayEXTX extends XrSessionCreateInfo typedef struct XrSessionCreateInfoOverlayEXTX { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -1825,7 +1707,7 @@ typedef struct XrEventDataMainSessionVisibilityChangedEXTX { #define XR_MSFT_spatial_anchor 1 XR_DEFINE_HANDLE(XrSpatialAnchorMSFT) -#define XR_MSFT_spatial_anchor_SPEC_VERSION 2 +#define XR_MSFT_spatial_anchor_SPEC_VERSION 1 #define XR_MSFT_SPATIAL_ANCHOR_EXTENSION_NAME "XR_MSFT_spatial_anchor" typedef struct XrSpatialAnchorCreateInfoMSFT { XrStructureType type; @@ -1847,7 +1729,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorSpaceMSFT)(XrSession sessi typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAnchorMSFT)(XrSpatialAnchorMSFT anchor); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorMSFT( XrSession session, const XrSpatialAnchorCreateInfoMSFT* createInfo, @@ -1860,50 +1741,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorSpaceMSFT( XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAnchorMSFT( XrSpatialAnchorMSFT anchor); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_composition_layer_image_layout 1 -#define XR_FB_composition_layer_image_layout_SPEC_VERSION 1 -#define XR_FB_COMPOSITION_LAYER_IMAGE_LAYOUT_EXTENSION_NAME "XR_FB_composition_layer_image_layout" -typedef XrFlags64 XrCompositionLayerImageLayoutFlagsFB; - -// Flag bits for XrCompositionLayerImageLayoutFlagsFB -static const XrCompositionLayerImageLayoutFlagsFB XR_COMPOSITION_LAYER_IMAGE_LAYOUT_VERTICAL_FLIP_BIT_FB = 0x00000001; - -// XrCompositionLayerImageLayoutFB extends XrCompositionLayerBaseHeader -typedef struct XrCompositionLayerImageLayoutFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrCompositionLayerImageLayoutFlagsFB flags; -} XrCompositionLayerImageLayoutFB; - - - -#define XR_FB_composition_layer_alpha_blend 1 -#define XR_FB_composition_layer_alpha_blend_SPEC_VERSION 2 -#define XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME "XR_FB_composition_layer_alpha_blend" - -typedef enum XrBlendFactorFB { - XR_BLEND_FACTOR_ZERO_FB = 0, - XR_BLEND_FACTOR_ONE_FB = 1, - XR_BLEND_FACTOR_SRC_ALPHA_FB = 2, - XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB = 3, - XR_BLEND_FACTOR_DST_ALPHA_FB = 4, - XR_BLEND_FACTOR_ONE_MINUS_DST_ALPHA_FB = 5, - XR_BLEND_FACTOR_MAX_ENUM_FB = 0x7FFFFFFF -} XrBlendFactorFB; -// XrCompositionLayerAlphaBlendFB extends XrCompositionLayerBaseHeader -typedef struct XrCompositionLayerAlphaBlendFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBlendFactorFB srcFactorColor; - XrBlendFactorFB dstFactorColor; - XrBlendFactorFB srcFactorAlpha; - XrBlendFactorFB dstFactorAlpha; -} XrCompositionLayerAlphaBlendFB; - +#endif #define XR_MND_headless 1 @@ -1919,7 +1757,6 @@ typedef struct XrCompositionLayerAlphaBlendFB { #define XR_EXT_view_configuration_depth_range 1 #define XR_EXT_view_configuration_depth_range_SPEC_VERSION 1 #define XR_EXT_VIEW_CONFIGURATION_DEPTH_RANGE_EXTENSION_NAME "XR_EXT_view_configuration_depth_range" -// XrViewConfigurationDepthRangeEXT extends XrViewConfigurationView typedef struct XrViewConfigurationDepthRangeEXT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -1932,7 +1769,7 @@ typedef struct XrViewConfigurationDepthRangeEXT { #define XR_EXT_conformance_automation 1 -#define XR_EXT_conformance_automation_SPEC_VERSION 3 +#define XR_EXT_conformance_automation_SPEC_VERSION 1 #define XR_EXT_CONFORMANCE_AUTOMATION_EXTENSION_NAME "XR_EXT_conformance_automation" typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceActiveEXT)(XrSession session, XrPath interactionProfile, XrPath topLevelPath, XrBool32 isActive); typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceStateBoolEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, XrBool32 state); @@ -1941,7 +1778,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceStateVector2fEXT)(XrSession ses typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceLocationEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, XrSpace space, XrPosef pose); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceActiveEXT( XrSession session, XrPath interactionProfile, @@ -1972,8 +1808,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceLocationEXT( XrPath inputSourcePath, XrSpace space, XrPosef pose); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_MSFT_spatial_graph_bridge 1 @@ -1996,13 +1831,11 @@ typedef struct XrSpatialGraphNodeSpaceCreateInfoMSFT { typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialGraphNodeSpaceMSFT)(XrSession session, const XrSpatialGraphNodeSpaceCreateInfoMSFT* createInfo, XrSpace* space); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialGraphNodeSpaceMSFT( XrSession session, const XrSpatialGraphNodeSpaceCreateInfoMSFT* createInfo, XrSpace* space); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_MSFT_hand_interaction 1 @@ -2015,7 +1848,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialGraphNodeSpaceMSFT( #define XR_HAND_JOINT_COUNT_EXT 26 XR_DEFINE_HANDLE(XrHandTrackerEXT) -#define XR_EXT_hand_tracking_SPEC_VERSION 4 +#define XR_EXT_hand_tracking_SPEC_VERSION 2 #define XR_EXT_HAND_TRACKING_EXTENSION_NAME "XR_EXT_hand_tracking" typedef enum XrHandEXT { @@ -2058,7 +1891,6 @@ typedef enum XrHandJointSetEXT { XR_HAND_JOINT_SET_DEFAULT_EXT = 0, XR_HAND_JOINT_SET_MAX_ENUM_EXT = 0x7FFFFFFF } XrHandJointSetEXT; -// XrSystemHandTrackingPropertiesEXT extends XrSystemProperties typedef struct XrSystemHandTrackingPropertiesEXT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -2099,7 +1931,6 @@ typedef struct XrHandJointLocationsEXT { XrHandJointLocationEXT* jointLocations; } XrHandJointLocationsEXT; -// XrHandJointVelocitiesEXT extends XrHandJointLocationsEXT typedef struct XrHandJointVelocitiesEXT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -2112,7 +1943,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrDestroyHandTrackerEXT)(XrHandTrackerEXT handT typedef XrResult (XRAPI_PTR *PFN_xrLocateHandJointsEXT)(XrHandTrackerEXT handTracker, const XrHandJointsLocateInfoEXT* locateInfo, XrHandJointLocationsEXT* locations); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrCreateHandTrackerEXT( XrSession session, const XrHandTrackerCreateInfoEXT* createInfo, @@ -2125,12 +1955,11 @@ XRAPI_ATTR XrResult XRAPI_CALL xrLocateHandJointsEXT( XrHandTrackerEXT handTracker, const XrHandJointsLocateInfoEXT* locateInfo, XrHandJointLocationsEXT* locations); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_MSFT_hand_tracking_mesh 1 -#define XR_MSFT_hand_tracking_mesh_SPEC_VERSION 4 +#define XR_MSFT_hand_tracking_mesh_SPEC_VERSION 2 #define XR_MSFT_HAND_TRACKING_MESH_EXTENSION_NAME "XR_MSFT_hand_tracking_mesh" typedef enum XrHandPoseTypeMSFT { @@ -2138,7 +1967,6 @@ typedef enum XrHandPoseTypeMSFT { XR_HAND_POSE_TYPE_REFERENCE_OPEN_PALM_MSFT = 1, XR_HAND_POSE_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF } XrHandPoseTypeMSFT; -// XrSystemHandTrackingMeshPropertiesMSFT extends XrSystemProperties typedef struct XrSystemHandTrackingMeshPropertiesMSFT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -2190,7 +2018,6 @@ typedef struct XrHandMeshMSFT { XrHandMeshVertexBufferMSFT vertexBuffer; } XrHandMeshMSFT; -// XrHandPoseTypeInfoMSFT extends XrHandTrackerCreateInfoEXT typedef struct XrHandPoseTypeInfoMSFT { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -2201,7 +2028,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrCreateHandMeshSpaceMSFT)(XrHandTrackerEXT han typedef XrResult (XRAPI_PTR *PFN_xrUpdateHandMeshMSFT)(XrHandTrackerEXT handTracker, const XrHandMeshUpdateInfoMSFT* updateInfo, XrHandMeshMSFT* handMesh); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrCreateHandMeshSpaceMSFT( XrHandTrackerEXT handTracker, const XrHandMeshSpaceCreateInfoMSFT* createInfo, @@ -2211,14 +2037,12 @@ XRAPI_ATTR XrResult XRAPI_CALL xrUpdateHandMeshMSFT( XrHandTrackerEXT handTracker, const XrHandMeshUpdateInfoMSFT* updateInfo, XrHandMeshMSFT* handMesh); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_MSFT_secondary_view_configuration 1 #define XR_MSFT_secondary_view_configuration_SPEC_VERSION 1 #define XR_MSFT_SECONDARY_VIEW_CONFIGURATION_EXTENSION_NAME "XR_MSFT_secondary_view_configuration" -// XrSecondaryViewConfigurationSessionBeginInfoMSFT extends XrSessionBeginInfo typedef struct XrSecondaryViewConfigurationSessionBeginInfoMSFT { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -2233,7 +2057,6 @@ typedef struct XrSecondaryViewConfigurationStateMSFT { XrBool32 active; } XrSecondaryViewConfigurationStateMSFT; -// XrSecondaryViewConfigurationFrameStateMSFT extends XrFrameState typedef struct XrSecondaryViewConfigurationFrameStateMSFT { XrStructureType type; void* XR_MAY_ALIAS next; @@ -2250,7 +2073,6 @@ typedef struct XrSecondaryViewConfigurationLayerInfoMSFT { const XrCompositionLayerBaseHeader* const* layers; } XrSecondaryViewConfigurationLayerInfoMSFT; -// XrSecondaryViewConfigurationFrameEndInfoMSFT extends XrFrameEndInfo typedef struct XrSecondaryViewConfigurationFrameEndInfoMSFT { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -2258,7 +2080,6 @@ typedef struct XrSecondaryViewConfigurationFrameEndInfoMSFT { const XrSecondaryViewConfigurationLayerInfoMSFT* viewConfigurationLayersInfo; } XrSecondaryViewConfigurationFrameEndInfoMSFT; -// XrSecondaryViewConfigurationSwapchainCreateInfoMSFT extends XrSwapchainCreateInfo typedef struct XrSecondaryViewConfigurationSwapchainCreateInfoMSFT { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -2321,7 +2142,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrGetControllerModelPropertiesMSFT)(XrSession s typedef XrResult (XRAPI_PTR *PFN_xrGetControllerModelStateMSFT)(XrSession session, XrControllerModelKeyMSFT modelKey, XrControllerModelStateMSFT* state); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelKeyMSFT( XrSession session, XrPath topLevelUserPath, @@ -2343,8 +2163,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelStateMSFT( XrSession session, XrControllerModelKeyMSFT modelKey, XrControllerModelStateMSFT* state); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_EXT_win32_appcontainer_compatible 1 @@ -2355,7 +2174,6 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelStateMSFT( #define XR_EPIC_view_configuration_fov 1 #define XR_EPIC_view_configuration_fov_SPEC_VERSION 2 #define XR_EPIC_VIEW_CONFIGURATION_FOV_EXTENSION_NAME "XR_EPIC_view_configuration_fov" -// XrViewConfigurationViewFovEPIC extends XrViewConfigurationView typedef struct XrViewConfigurationViewFovEPIC { XrStructureType type; const void* XR_MAY_ALIAS next; @@ -2365,97 +2183,13 @@ typedef struct XrViewConfigurationViewFovEPIC { -#define XR_MSFT_composition_layer_reprojection 1 -#define XR_MSFT_composition_layer_reprojection_SPEC_VERSION 1 -#define XR_MSFT_COMPOSITION_LAYER_REPROJECTION_EXTENSION_NAME "XR_MSFT_composition_layer_reprojection" - -typedef enum XrReprojectionModeMSFT { - XR_REPROJECTION_MODE_DEPTH_MSFT = 1, - XR_REPROJECTION_MODE_PLANAR_FROM_DEPTH_MSFT = 2, - XR_REPROJECTION_MODE_PLANAR_MANUAL_MSFT = 3, - XR_REPROJECTION_MODE_ORIENTATION_ONLY_MSFT = 4, - XR_REPROJECTION_MODE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrReprojectionModeMSFT; -// XrCompositionLayerReprojectionInfoMSFT extends XrCompositionLayerProjection -typedef struct XrCompositionLayerReprojectionInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrReprojectionModeMSFT reprojectionMode; -} XrCompositionLayerReprojectionInfoMSFT; - -// XrCompositionLayerReprojectionPlaneOverrideMSFT extends XrCompositionLayerProjection -typedef struct XrCompositionLayerReprojectionPlaneOverrideMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrVector3f position; - XrVector3f normal; - XrVector3f velocity; -} XrCompositionLayerReprojectionPlaneOverrideMSFT; - -typedef XrResult (XRAPI_PTR *PFN_xrEnumerateReprojectionModesMSFT)(XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType, uint32_t modeCapacityInput, uint32_t* modeCountOutput, XrReprojectionModeMSFT* modes); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateReprojectionModesMSFT( - XrInstance instance, - XrSystemId systemId, - XrViewConfigurationType viewConfigurationType, - uint32_t modeCapacityInput, - uint32_t* modeCountOutput, - XrReprojectionModeMSFT* modes); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - #define XR_HUAWEI_controller_interaction 1 #define XR_HUAWEI_controller_interaction_SPEC_VERSION 1 #define XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HUAWEI_controller_interaction" -#define XR_FB_swapchain_update_state 1 -#define XR_FB_swapchain_update_state_SPEC_VERSION 3 -#define XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME "XR_FB_swapchain_update_state" -typedef struct XR_MAY_ALIAS XrSwapchainStateBaseHeaderFB { - XrStructureType type; - void* XR_MAY_ALIAS next; -} XrSwapchainStateBaseHeaderFB; - -typedef XrResult (XRAPI_PTR *PFN_xrUpdateSwapchainFB)(XrSwapchain swapchain, const XrSwapchainStateBaseHeaderFB* state); -typedef XrResult (XRAPI_PTR *PFN_xrGetSwapchainStateFB)(XrSwapchain swapchain, XrSwapchainStateBaseHeaderFB* state); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSwapchainFB( - XrSwapchain swapchain, - const XrSwapchainStateBaseHeaderFB* state); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetSwapchainStateFB( - XrSwapchain swapchain, - XrSwapchainStateBaseHeaderFB* state); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_composition_layer_secure_content 1 -#define XR_FB_composition_layer_secure_content_SPEC_VERSION 1 -#define XR_FB_COMPOSITION_LAYER_SECURE_CONTENT_EXTENSION_NAME "XR_FB_composition_layer_secure_content" -typedef XrFlags64 XrCompositionLayerSecureContentFlagsFB; - -// Flag bits for XrCompositionLayerSecureContentFlagsFB -static const XrCompositionLayerSecureContentFlagsFB XR_COMPOSITION_LAYER_SECURE_CONTENT_EXCLUDE_LAYER_BIT_FB = 0x00000001; -static const XrCompositionLayerSecureContentFlagsFB XR_COMPOSITION_LAYER_SECURE_CONTENT_REPLACE_LAYER_BIT_FB = 0x00000002; - -// XrCompositionLayerSecureContentFB extends XrCompositionLayerBaseHeader -typedef struct XrCompositionLayerSecureContentFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrCompositionLayerSecureContentFlagsFB flags; -} XrCompositionLayerSecureContentFB; - - - #define XR_VALVE_analog_threshold 1 -#define XR_VALVE_analog_threshold_SPEC_VERSION 2 +#define XR_VALVE_analog_threshold_SPEC_VERSION 1 #define XR_VALVE_ANALOG_THRESHOLD_EXTENSION_NAME "XR_VALVE_analog_threshold" typedef struct XrInteractionProfileAnalogThresholdVALVE { XrStructureType type; @@ -2470,24 +2204,6 @@ typedef struct XrInteractionProfileAnalogThresholdVALVE { -#define XR_EXT_hand_joints_motion_range 1 -#define XR_EXT_hand_joints_motion_range_SPEC_VERSION 1 -#define XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME "XR_EXT_hand_joints_motion_range" - -typedef enum XrHandJointsMotionRangeEXT { - XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT = 1, - XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT = 2, - XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT = 0x7FFFFFFF -} XrHandJointsMotionRangeEXT; -// XrHandJointsMotionRangeInfoEXT extends XrHandJointsLocateInfoEXT -typedef struct XrHandJointsMotionRangeInfoEXT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrHandJointsMotionRangeEXT handJointsMotionRange; -} XrHandJointsMotionRangeInfoEXT; - - - #define XR_EXT_samsung_odyssey_controller 1 #define XR_EXT_samsung_odyssey_controller_SPEC_VERSION 1 #define XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME "XR_EXT_samsung_odyssey_controller" @@ -2503,374 +2219,6 @@ typedef struct XrHandJointsMotionRangeInfoEXT { #define XR_MND_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_EXTENSION_NAME "XR_MND_swapchain_usage_input_attachment_bit" -#define XR_MSFT_scene_understanding 1 - - XR_DEFINE_HANDLE(XrSceneObserverMSFT) - - - XR_DEFINE_HANDLE(XrSceneMSFT) - -#define XR_MSFT_scene_understanding_SPEC_VERSION 1 -#define XR_MSFT_SCENE_UNDERSTANDING_EXTENSION_NAME "XR_MSFT_scene_understanding" - -typedef enum XrSceneComputeFeatureMSFT { - XR_SCENE_COMPUTE_FEATURE_PLANE_MSFT = 1, - XR_SCENE_COMPUTE_FEATURE_PLANE_MESH_MSFT = 2, - XR_SCENE_COMPUTE_FEATURE_VISUAL_MESH_MSFT = 3, - XR_SCENE_COMPUTE_FEATURE_COLLIDER_MESH_MSFT = 4, - XR_SCENE_COMPUTE_FEATURE_SERIALIZE_SCENE_MSFT = 1000098000, - XR_SCENE_COMPUTE_FEATURE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrSceneComputeFeatureMSFT; - -typedef enum XrSceneComputeConsistencyMSFT { - XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_COMPLETE_MSFT = 1, - XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_INCOMPLETE_FAST_MSFT = 2, - XR_SCENE_COMPUTE_CONSISTENCY_OCCLUSION_OPTIMIZED_MSFT = 3, - XR_SCENE_COMPUTE_CONSISTENCY_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrSceneComputeConsistencyMSFT; - -typedef enum XrMeshComputeLodMSFT { - XR_MESH_COMPUTE_LOD_COARSE_MSFT = 1, - XR_MESH_COMPUTE_LOD_MEDIUM_MSFT = 2, - XR_MESH_COMPUTE_LOD_FINE_MSFT = 3, - XR_MESH_COMPUTE_LOD_UNLIMITED_MSFT = 4, - XR_MESH_COMPUTE_LOD_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrMeshComputeLodMSFT; - -typedef enum XrSceneComponentTypeMSFT { - XR_SCENE_COMPONENT_TYPE_INVALID_MSFT = -1, - XR_SCENE_COMPONENT_TYPE_OBJECT_MSFT = 1, - XR_SCENE_COMPONENT_TYPE_PLANE_MSFT = 2, - XR_SCENE_COMPONENT_TYPE_VISUAL_MESH_MSFT = 3, - XR_SCENE_COMPONENT_TYPE_COLLIDER_MESH_MSFT = 4, - XR_SCENE_COMPONENT_TYPE_SERIALIZED_SCENE_FRAGMENT_MSFT = 1000098000, - XR_SCENE_COMPONENT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrSceneComponentTypeMSFT; - -typedef enum XrSceneObjectTypeMSFT { - XR_SCENE_OBJECT_TYPE_UNCATEGORIZED_MSFT = -1, - XR_SCENE_OBJECT_TYPE_BACKGROUND_MSFT = 1, - XR_SCENE_OBJECT_TYPE_WALL_MSFT = 2, - XR_SCENE_OBJECT_TYPE_FLOOR_MSFT = 3, - XR_SCENE_OBJECT_TYPE_CEILING_MSFT = 4, - XR_SCENE_OBJECT_TYPE_PLATFORM_MSFT = 5, - XR_SCENE_OBJECT_TYPE_INFERRED_MSFT = 6, - XR_SCENE_OBJECT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrSceneObjectTypeMSFT; - -typedef enum XrScenePlaneAlignmentTypeMSFT { - XR_SCENE_PLANE_ALIGNMENT_TYPE_NON_ORTHOGONAL_MSFT = 0, - XR_SCENE_PLANE_ALIGNMENT_TYPE_HORIZONTAL_MSFT = 1, - XR_SCENE_PLANE_ALIGNMENT_TYPE_VERTICAL_MSFT = 2, - XR_SCENE_PLANE_ALIGNMENT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrScenePlaneAlignmentTypeMSFT; - -typedef enum XrSceneComputeStateMSFT { - XR_SCENE_COMPUTE_STATE_NONE_MSFT = 0, - XR_SCENE_COMPUTE_STATE_UPDATING_MSFT = 1, - XR_SCENE_COMPUTE_STATE_COMPLETED_MSFT = 2, - XR_SCENE_COMPUTE_STATE_COMPLETED_WITH_ERROR_MSFT = 3, - XR_SCENE_COMPUTE_STATE_MAX_ENUM_MSFT = 0x7FFFFFFF -} XrSceneComputeStateMSFT; -typedef struct XrUuidMSFT { - uint8_t bytes[16]; -} XrUuidMSFT; - -typedef struct XrSceneObserverCreateInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; -} XrSceneObserverCreateInfoMSFT; - -typedef struct XrSceneCreateInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; -} XrSceneCreateInfoMSFT; - -typedef struct XrSceneSphereBoundMSFT { - XrVector3f center; - float radius; -} XrSceneSphereBoundMSFT; - -typedef struct XrSceneOrientedBoxBoundMSFT { - XrPosef pose; - XrVector3f extents; -} XrSceneOrientedBoxBoundMSFT; - -typedef struct XrSceneFrustumBoundMSFT { - XrPosef pose; - XrFovf fov; - float farDistance; -} XrSceneFrustumBoundMSFT; - -typedef struct XrSceneBoundsMSFT { - XrSpace space; - XrTime time; - uint32_t sphereCount; - const XrSceneSphereBoundMSFT* spheres; - uint32_t boxCount; - const XrSceneOrientedBoxBoundMSFT* boxes; - uint32_t frustumCount; - const XrSceneFrustumBoundMSFT* frustums; -} XrSceneBoundsMSFT; - -typedef struct XrNewSceneComputeInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint32_t requestedFeatureCount; - const XrSceneComputeFeatureMSFT* requestedFeatures; - XrSceneComputeConsistencyMSFT consistency; - XrSceneBoundsMSFT bounds; -} XrNewSceneComputeInfoMSFT; - -// XrVisualMeshComputeLodInfoMSFT extends XrNewSceneComputeInfoMSFT -typedef struct XrVisualMeshComputeLodInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrMeshComputeLodMSFT lod; -} XrVisualMeshComputeLodInfoMSFT; - -typedef struct XrSceneComponentMSFT { - XrSceneComponentTypeMSFT componentType; - XrUuidMSFT id; - XrUuidMSFT parentId; - XrTime updateTime; -} XrSceneComponentMSFT; - -typedef struct XrSceneComponentsMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t componentCapacityInput; - uint32_t componentCountOutput; - XrSceneComponentMSFT* components; -} XrSceneComponentsMSFT; - -typedef struct XrSceneComponentsGetInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrSceneComponentTypeMSFT componentType; -} XrSceneComponentsGetInfoMSFT; - -typedef struct XrSceneComponentLocationMSFT { - XrSpaceLocationFlags flags; - XrPosef pose; -} XrSceneComponentLocationMSFT; - -typedef struct XrSceneComponentLocationsMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t locationCount; - XrSceneComponentLocationMSFT* locations; -} XrSceneComponentLocationsMSFT; - -typedef struct XrSceneComponentsLocateInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrSpace baseSpace; - XrTime time; - uint32_t componentIdCount; - const XrUuidMSFT* componentIds; -} XrSceneComponentsLocateInfoMSFT; - -typedef struct XrSceneObjectMSFT { - XrSceneObjectTypeMSFT objectType; -} XrSceneObjectMSFT; - -// XrSceneObjectsMSFT extends XrSceneComponentsMSFT -typedef struct XrSceneObjectsMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t sceneObjectCount; - XrSceneObjectMSFT* sceneObjects; -} XrSceneObjectsMSFT; - -// XrSceneComponentParentFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT -typedef struct XrSceneComponentParentFilterInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrUuidMSFT parentId; -} XrSceneComponentParentFilterInfoMSFT; - -// XrSceneObjectTypesFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT -typedef struct XrSceneObjectTypesFilterInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint32_t objectTypeCount; - const XrSceneObjectTypeMSFT* objectTypes; -} XrSceneObjectTypesFilterInfoMSFT; - -typedef struct XrScenePlaneMSFT { - XrScenePlaneAlignmentTypeMSFT alignment; - XrExtent2Df size; - uint64_t meshBufferId; - XrBool32 supportsIndicesUint16; -} XrScenePlaneMSFT; - -// XrScenePlanesMSFT extends XrSceneComponentsMSFT -typedef struct XrScenePlanesMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t scenePlaneCount; - XrScenePlaneMSFT* scenePlanes; -} XrScenePlanesMSFT; - -// XrScenePlaneAlignmentFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT -typedef struct XrScenePlaneAlignmentFilterInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint32_t alignmentCount; - const XrScenePlaneAlignmentTypeMSFT* alignments; -} XrScenePlaneAlignmentFilterInfoMSFT; - -typedef struct XrSceneMeshMSFT { - uint64_t meshBufferId; - XrBool32 supportsIndicesUint16; -} XrSceneMeshMSFT; - -// XrSceneMeshesMSFT extends XrSceneComponentsMSFT -typedef struct XrSceneMeshesMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t sceneMeshCount; - XrSceneMeshMSFT* sceneMeshes; -} XrSceneMeshesMSFT; - -typedef struct XrSceneMeshBuffersGetInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint64_t meshBufferId; -} XrSceneMeshBuffersGetInfoMSFT; - -typedef struct XrSceneMeshBuffersMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; -} XrSceneMeshBuffersMSFT; - -typedef struct XrSceneMeshVertexBufferMSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t vertexCapacityInput; - uint32_t vertexCountOutput; - XrVector3f* vertices; -} XrSceneMeshVertexBufferMSFT; - -typedef struct XrSceneMeshIndicesUint32MSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t indexCapacityInput; - uint32_t indexCountOutput; - uint32_t* indices; -} XrSceneMeshIndicesUint32MSFT; - -typedef struct XrSceneMeshIndicesUint16MSFT { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t indexCapacityInput; - uint32_t indexCountOutput; - uint16_t* indices; -} XrSceneMeshIndicesUint16MSFT; - -typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSceneComputeFeaturesMSFT)(XrInstance instance, XrSystemId systemId, uint32_t featureCapacityInput, uint32_t* featureCountOutput, XrSceneComputeFeatureMSFT* features); -typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneObserverMSFT)(XrSession session, const XrSceneObserverCreateInfoMSFT* createInfo, XrSceneObserverMSFT* sceneObserver); -typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneObserverMSFT)(XrSceneObserverMSFT sceneObserver); -typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrSceneCreateInfoMSFT* createInfo, XrSceneMSFT* scene); -typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneMSFT)(XrSceneMSFT scene); -typedef XrResult (XRAPI_PTR *PFN_xrComputeNewSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrNewSceneComputeInfoMSFT* computeInfo); -typedef XrResult (XRAPI_PTR *PFN_xrGetSceneComputeStateMSFT)(XrSceneObserverMSFT sceneObserver, XrSceneComputeStateMSFT* state); -typedef XrResult (XRAPI_PTR *PFN_xrGetSceneComponentsMSFT)(XrSceneMSFT scene, const XrSceneComponentsGetInfoMSFT* getInfo, XrSceneComponentsMSFT* components); -typedef XrResult (XRAPI_PTR *PFN_xrLocateSceneComponentsMSFT)(XrSceneMSFT scene, const XrSceneComponentsLocateInfoMSFT* locateInfo, XrSceneComponentLocationsMSFT* locations); -typedef XrResult (XRAPI_PTR *PFN_xrGetSceneMeshBuffersMSFT)(XrSceneMSFT scene, const XrSceneMeshBuffersGetInfoMSFT* getInfo, XrSceneMeshBuffersMSFT* buffers); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSceneComputeFeaturesMSFT( - XrInstance instance, - XrSystemId systemId, - uint32_t featureCapacityInput, - uint32_t* featureCountOutput, - XrSceneComputeFeatureMSFT* features); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneObserverMSFT( - XrSession session, - const XrSceneObserverCreateInfoMSFT* createInfo, - XrSceneObserverMSFT* sceneObserver); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneObserverMSFT( - XrSceneObserverMSFT sceneObserver); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneMSFT( - XrSceneObserverMSFT sceneObserver, - const XrSceneCreateInfoMSFT* createInfo, - XrSceneMSFT* scene); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneMSFT( - XrSceneMSFT scene); - -XRAPI_ATTR XrResult XRAPI_CALL xrComputeNewSceneMSFT( - XrSceneObserverMSFT sceneObserver, - const XrNewSceneComputeInfoMSFT* computeInfo); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneComputeStateMSFT( - XrSceneObserverMSFT sceneObserver, - XrSceneComputeStateMSFT* state); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneComponentsMSFT( - XrSceneMSFT scene, - const XrSceneComponentsGetInfoMSFT* getInfo, - XrSceneComponentsMSFT* components); - -XRAPI_ATTR XrResult XRAPI_CALL xrLocateSceneComponentsMSFT( - XrSceneMSFT scene, - const XrSceneComponentsLocateInfoMSFT* locateInfo, - XrSceneComponentLocationsMSFT* locations); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneMeshBuffersMSFT( - XrSceneMSFT scene, - const XrSceneMeshBuffersGetInfoMSFT* getInfo, - XrSceneMeshBuffersMSFT* buffers); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_MSFT_scene_understanding_serialization 1 -#define XR_MSFT_scene_understanding_serialization_SPEC_VERSION 1 -#define XR_MSFT_SCENE_UNDERSTANDING_SERIALIZATION_EXTENSION_NAME "XR_MSFT_scene_understanding_serialization" -typedef struct XrSerializedSceneFragmentDataGetInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrUuidMSFT sceneFragmentId; -} XrSerializedSceneFragmentDataGetInfoMSFT; - -typedef struct XrDeserializeSceneFragmentMSFT { - uint32_t bufferSize; - const uint8_t* buffer; -} XrDeserializeSceneFragmentMSFT; - -typedef struct XrSceneDeserializeInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint32_t fragmentCount; - const XrDeserializeSceneFragmentMSFT* fragments; -} XrSceneDeserializeInfoMSFT; - -typedef XrResult (XRAPI_PTR *PFN_xrDeserializeSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrSceneDeserializeInfoMSFT* deserializeInfo); -typedef XrResult (XRAPI_PTR *PFN_xrGetSerializedSceneFragmentDataMSFT)(XrSceneMSFT scene, const XrSerializedSceneFragmentDataGetInfoMSFT* getInfo, uint32_t countInput, uint32_t* readOutput, uint8_t* buffer); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrDeserializeSceneMSFT( - XrSceneObserverMSFT sceneObserver, - const XrSceneDeserializeInfoMSFT* deserializeInfo); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetSerializedSceneFragmentDataMSFT( - XrSceneMSFT scene, - const XrSerializedSceneFragmentDataGetInfoMSFT* getInfo, - uint32_t countInput, - uint32_t* readOutput, - uint8_t* buffer); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - #define XR_FB_display_refresh_rate 1 #define XR_FB_display_refresh_rate_SPEC_VERSION 1 #define XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME "XR_FB_display_refresh_rate" @@ -2886,7 +2234,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrGetDisplayRefreshRateFB)(XrSession session, f typedef XrResult (XRAPI_PTR *PFN_xrRequestDisplayRefreshRateFB)(XrSession session, float displayRefreshRate); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateDisplayRefreshRatesFB( XrSession session, uint32_t displayRefreshRateCapacityInput, @@ -2900,8 +2247,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetDisplayRefreshRateFB( XRAPI_ATTR XrResult XRAPI_CALL xrRequestDisplayRefreshRateFB( XrSession session, float displayRefreshRate); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ +#endif #define XR_HTC_vive_cosmos_controller_interaction 1 @@ -2909,161 +2255,8 @@ XRAPI_ATTR XrResult XRAPI_CALL xrRequestDisplayRefreshRateFB( #define XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HTC_vive_cosmos_controller_interaction" -#define XR_HTCX_vive_tracker_interaction 1 -#define XR_HTCX_vive_tracker_interaction_SPEC_VERSION 1 -#define XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME "XR_HTCX_vive_tracker_interaction" -typedef struct XrViveTrackerPathsHTCX { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrPath persistentPath; - XrPath rolePath; -} XrViveTrackerPathsHTCX; - -typedef struct XrEventDataViveTrackerConnectedHTCX { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrViveTrackerPathsHTCX* paths; -} XrEventDataViveTrackerConnectedHTCX; - -typedef XrResult (XRAPI_PTR *PFN_xrEnumerateViveTrackerPathsHTCX)(XrInstance instance, uint32_t pathCapacityInput, uint32_t* pathCountOutput, XrViveTrackerPathsHTCX* paths); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateViveTrackerPathsHTCX( - XrInstance instance, - uint32_t pathCapacityInput, - uint32_t* pathCountOutput, - XrViveTrackerPathsHTCX* paths); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_HTC_facial_tracking 1 - -#define XR_FACIAL_EXPRESSION_EYE_COUNT_HTC 14 - - -#define XR_FACIAL_EXPRESSION_LIP_COUNT_HTC 37 - -XR_DEFINE_HANDLE(XrFacialTrackerHTC) -#define XR_HTC_facial_tracking_SPEC_VERSION 1 -#define XR_HTC_FACIAL_TRACKING_EXTENSION_NAME "XR_HTC_facial_tracking" - -typedef enum XrEyeExpressionHTC { - XR_EYE_EXPRESSION_LEFT_BLINK_HTC = 0, - XR_EYE_EXPRESSION_LEFT_WIDE_HTC = 1, - XR_EYE_EXPRESSION_RIGHT_BLINK_HTC = 2, - XR_EYE_EXPRESSION_RIGHT_WIDE_HTC = 3, - XR_EYE_EXPRESSION_LEFT_SQUEEZE_HTC = 4, - XR_EYE_EXPRESSION_RIGHT_SQUEEZE_HTC = 5, - XR_EYE_EXPRESSION_LEFT_DOWN_HTC = 6, - XR_EYE_EXPRESSION_RIGHT_DOWN_HTC = 7, - XR_EYE_EXPRESSION_LEFT_OUT_HTC = 8, - XR_EYE_EXPRESSION_RIGHT_IN_HTC = 9, - XR_EYE_EXPRESSION_LEFT_IN_HTC = 10, - XR_EYE_EXPRESSION_RIGHT_OUT_HTC = 11, - XR_EYE_EXPRESSION_LEFT_UP_HTC = 12, - XR_EYE_EXPRESSION_RIGHT_UP_HTC = 13, - XR_EYE_EXPRESSION_MAX_ENUM_HTC = 0x7FFFFFFF -} XrEyeExpressionHTC; - -typedef enum XrLipExpressionHTC { - XR_LIP_EXPRESSION_JAW_RIGHT_HTC = 0, - XR_LIP_EXPRESSION_JAW_LEFT_HTC = 1, - XR_LIP_EXPRESSION_JAW_FORWARD_HTC = 2, - XR_LIP_EXPRESSION_JAW_OPEN_HTC = 3, - XR_LIP_EXPRESSION_MOUTH_APE_SHAPE_HTC = 4, - XR_LIP_EXPRESSION_MOUTH_UPPER_RIGHT_HTC = 5, - XR_LIP_EXPRESSION_MOUTH_UPPER_LEFT_HTC = 6, - XR_LIP_EXPRESSION_MOUTH_LOWER_RIGHT_HTC = 7, - XR_LIP_EXPRESSION_MOUTH_LOWER_LEFT_HTC = 8, - XR_LIP_EXPRESSION_MOUTH_UPPER_OVERTURN_HTC = 9, - XR_LIP_EXPRESSION_MOUTH_LOWER_OVERTURN_HTC = 10, - XR_LIP_EXPRESSION_MOUTH_POUT_HTC = 11, - XR_LIP_EXPRESSION_MOUTH_SMILE_RIGHT_HTC = 12, - XR_LIP_EXPRESSION_MOUTH_SMILE_LEFT_HTC = 13, - XR_LIP_EXPRESSION_MOUTH_SAD_RIGHT_HTC = 14, - XR_LIP_EXPRESSION_MOUTH_SAD_LEFT_HTC = 15, - XR_LIP_EXPRESSION_CHEEK_PUFF_RIGHT_HTC = 16, - XR_LIP_EXPRESSION_CHEEK_PUFF_LEFT_HTC = 17, - XR_LIP_EXPRESSION_CHEEK_SUCK_HTC = 18, - XR_LIP_EXPRESSION_MOUTH_UPPER_UPRIGHT_HTC = 19, - XR_LIP_EXPRESSION_MOUTH_UPPER_UPLEFT_HTC = 20, - XR_LIP_EXPRESSION_MOUTH_LOWER_DOWNRIGHT_HTC = 21, - XR_LIP_EXPRESSION_MOUTH_LOWER_DOWNLEFT_HTC = 22, - XR_LIP_EXPRESSION_MOUTH_UPPER_INSIDE_HTC = 23, - XR_LIP_EXPRESSION_MOUTH_LOWER_INSIDE_HTC = 24, - XR_LIP_EXPRESSION_MOUTH_LOWER_OVERLAY_HTC = 25, - XR_LIP_EXPRESSION_TONGUE_LONGSTEP1_HTC = 26, - XR_LIP_EXPRESSION_TONGUE_LEFT_HTC = 27, - XR_LIP_EXPRESSION_TONGUE_RIGHT_HTC = 28, - XR_LIP_EXPRESSION_TONGUE_UP_HTC = 29, - XR_LIP_EXPRESSION_TONGUE_DOWN_HTC = 30, - XR_LIP_EXPRESSION_TONGUE_ROLL_HTC = 31, - XR_LIP_EXPRESSION_TONGUE_LONGSTEP2_HTC = 32, - XR_LIP_EXPRESSION_TONGUE_UPRIGHT_MORPH_HTC = 33, - XR_LIP_EXPRESSION_TONGUE_UPLEFT_MORPH_HTC = 34, - XR_LIP_EXPRESSION_TONGUE_DOWNRIGHT_MORPH_HTC = 35, - XR_LIP_EXPRESSION_TONGUE_DOWNLEFT_MORPH_HTC = 36, - XR_LIP_EXPRESSION_MAX_ENUM_HTC = 0x7FFFFFFF -} XrLipExpressionHTC; - -typedef enum XrFacialTrackingTypeHTC { - XR_FACIAL_TRACKING_TYPE_EYE_DEFAULT_HTC = 1, - XR_FACIAL_TRACKING_TYPE_LIP_DEFAULT_HTC = 2, - XR_FACIAL_TRACKING_TYPE_MAX_ENUM_HTC = 0x7FFFFFFF -} XrFacialTrackingTypeHTC; -// XrSystemFacialTrackingPropertiesHTC extends XrSystemProperties -typedef struct XrSystemFacialTrackingPropertiesHTC { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 supportEyeFacialTracking; - XrBool32 supportLipFacialTracking; -} XrSystemFacialTrackingPropertiesHTC; - -typedef struct XrFacialExpressionsHTC { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrBool32 isActive; - XrTime sampleTime; - uint32_t expressionCount; - float* expressionWeightings; -} XrFacialExpressionsHTC; - -typedef struct XrFacialTrackerCreateInfoHTC { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrFacialTrackingTypeHTC facialTrackingType; -} XrFacialTrackerCreateInfoHTC; - -typedef XrResult (XRAPI_PTR *PFN_xrCreateFacialTrackerHTC)(XrSession session, const XrFacialTrackerCreateInfoHTC* createInfo, XrFacialTrackerHTC* facialTracker); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyFacialTrackerHTC)(XrFacialTrackerHTC facialTracker); -typedef XrResult (XRAPI_PTR *PFN_xrGetFacialExpressionsHTC)(XrFacialTrackerHTC facialTracker, XrFacialExpressionsHTC* facialExpressions); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrCreateFacialTrackerHTC( - XrSession session, - const XrFacialTrackerCreateInfoHTC* createInfo, - XrFacialTrackerHTC* facialTracker); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFacialTrackerHTC( - XrFacialTrackerHTC facialTracker); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetFacialExpressionsHTC( - XrFacialTrackerHTC facialTracker, - XrFacialExpressionsHTC* facialExpressions); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_HTC_vive_focus3_controller_interaction 1 -#define XR_HTC_vive_focus3_controller_interaction_SPEC_VERSION 1 -#define XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HTC_vive_focus3_controller_interaction" - - #define XR_FB_color_space 1 -#define XR_FB_color_space_SPEC_VERSION 2 +#define XR_FB_color_space_SPEC_VERSION 1 #define XR_FB_COLOR_SPACE_EXTENSION_NAME "XR_FB_color_space" typedef enum XrColorSpaceFB { @@ -3077,7 +2270,6 @@ typedef enum XrColorSpaceFB { XR_COLOR_SPACE_ADOBE_RGB_FB = 7, XR_COLOR_SPACE_MAX_ENUM_FB = 0x7FFFFFFF } XrColorSpaceFB; -// XrSystemColorSpacePropertiesFB extends XrSystemProperties typedef struct XrSystemColorSpacePropertiesFB { XrStructureType type; void* XR_MAY_ALIAS next; @@ -3088,7 +2280,6 @@ typedef XrResult (XRAPI_PTR *PFN_xrEnumerateColorSpacesFB)(XrSession session, ui typedef XrResult (XRAPI_PTR *PFN_xrSetColorSpaceFB)(XrSession session, const XrColorSpaceFB colorspace); #ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateColorSpacesFB( XrSession session, uint32_t colorSpaceCapacityInput, @@ -3098,825 +2289,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateColorSpacesFB( XRAPI_ATTR XrResult XRAPI_CALL xrSetColorSpaceFB( XrSession session, const XrColorSpaceFB colorspace); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_hand_tracking_mesh 1 -#define XR_FB_hand_tracking_mesh_SPEC_VERSION 1 -#define XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME "XR_FB_hand_tracking_mesh" -typedef struct XrVector4sFB { - int16_t x; - int16_t y; - int16_t z; - int16_t w; -} XrVector4sFB; - -typedef struct XrHandTrackingMeshFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t jointCapacityInput; - uint32_t jointCountOutput; - XrPosef* jointBindPoses; - float* jointRadii; - XrHandJointEXT* jointParents; - uint32_t vertexCapacityInput; - uint32_t vertexCountOutput; - XrVector3f* vertexPositions; - XrVector3f* vertexNormals; - XrVector2f* vertexUVs; - XrVector4sFB* vertexBlendIndices; - XrVector4f* vertexBlendWeights; - uint32_t indexCapacityInput; - uint32_t indexCountOutput; - int16_t* indices; -} XrHandTrackingMeshFB; - -// XrHandTrackingScaleFB extends XrHandJointsLocateInfoEXT -typedef struct XrHandTrackingScaleFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - float sensorOutput; - float currentOutput; - XrBool32 overrideHandScale; - float overrideValueInput; -} XrHandTrackingScaleFB; - -typedef XrResult (XRAPI_PTR *PFN_xrGetHandMeshFB)(XrHandTrackerEXT handTracker, XrHandTrackingMeshFB* mesh); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrGetHandMeshFB( - XrHandTrackerEXT handTracker, - XrHandTrackingMeshFB* mesh); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_hand_tracking_aim 1 -#define XR_FB_hand_tracking_aim_SPEC_VERSION 1 -#define XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME "XR_FB_hand_tracking_aim" -typedef XrFlags64 XrHandTrackingAimFlagsFB; - -// Flag bits for XrHandTrackingAimFlagsFB -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_COMPUTED_BIT_FB = 0x00000001; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_VALID_BIT_FB = 0x00000002; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB = 0x00000004; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB = 0x00000008; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB = 0x00000010; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB = 0x00000020; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_SYSTEM_GESTURE_BIT_FB = 0x00000040; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_DOMINANT_HAND_BIT_FB = 0x00000080; -static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB = 0x00000100; - -// XrHandTrackingAimStateFB extends XrHandJointsLocateInfoEXT -typedef struct XrHandTrackingAimStateFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrHandTrackingAimFlagsFB status; - XrPosef aimPose; - float pinchStrengthIndex; - float pinchStrengthMiddle; - float pinchStrengthRing; - float pinchStrengthLittle; -} XrHandTrackingAimStateFB; - - - -#define XR_FB_hand_tracking_capsules 1 -#define XR_HAND_TRACKING_CAPSULE_POINT_COUNT_FB 2 -#define XR_FB_HAND_TRACKING_CAPSULE_POINT_COUNT XR_HAND_TRACKING_CAPSULE_POINT_COUNT_FB -#define XR_HAND_TRACKING_CAPSULE_COUNT_FB 19 -#define XR_FB_HAND_TRACKING_CAPSULE_COUNT XR_HAND_TRACKING_CAPSULE_COUNT_FB -#define XR_FB_hand_tracking_capsules_SPEC_VERSION 2 -#define XR_FB_HAND_TRACKING_CAPSULES_EXTENSION_NAME "XR_FB_hand_tracking_capsules" -typedef struct XrHandCapsuleFB { - XrVector3f points[XR_FB_HAND_TRACKING_CAPSULE_POINT_COUNT]; - float radius; - XrHandJointEXT joint; -} XrHandCapsuleFB; - -// XrHandTrackingCapsulesStateFB extends XrHandJointsLocateInfoEXT -typedef struct XrHandTrackingCapsulesStateFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrHandCapsuleFB capsules[XR_FB_HAND_TRACKING_CAPSULE_COUNT]; -} XrHandTrackingCapsulesStateFB; - - - -#define XR_FB_foveation 1 -XR_DEFINE_HANDLE(XrFoveationProfileFB) -#define XR_FB_foveation_SPEC_VERSION 1 -#define XR_FB_FOVEATION_EXTENSION_NAME "XR_FB_foveation" -typedef XrFlags64 XrSwapchainCreateFoveationFlagsFB; - -// Flag bits for XrSwapchainCreateFoveationFlagsFB -static const XrSwapchainCreateFoveationFlagsFB XR_SWAPCHAIN_CREATE_FOVEATION_SCALED_BIN_BIT_FB = 0x00000001; -static const XrSwapchainCreateFoveationFlagsFB XR_SWAPCHAIN_CREATE_FOVEATION_FRAGMENT_DENSITY_MAP_BIT_FB = 0x00000002; - -typedef XrFlags64 XrSwapchainStateFoveationFlagsFB; - -// Flag bits for XrSwapchainStateFoveationFlagsFB - -typedef struct XrFoveationProfileCreateInfoFB { - XrStructureType type; - void* XR_MAY_ALIAS next; -} XrFoveationProfileCreateInfoFB; - -// XrSwapchainCreateInfoFoveationFB extends XrSwapchainCreateInfo -typedef struct XrSwapchainCreateInfoFoveationFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrSwapchainCreateFoveationFlagsFB flags; -} XrSwapchainCreateInfoFoveationFB; - -typedef struct XrSwapchainStateFoveationFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrSwapchainStateFoveationFlagsFB flags; - XrFoveationProfileFB profile; -} XrSwapchainStateFoveationFB; - -typedef XrResult (XRAPI_PTR *PFN_xrCreateFoveationProfileFB)(XrSession session, const XrFoveationProfileCreateInfoFB* createInfo, XrFoveationProfileFB* profile); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyFoveationProfileFB)(XrFoveationProfileFB profile); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrCreateFoveationProfileFB( - XrSession session, - const XrFoveationProfileCreateInfoFB* createInfo, - XrFoveationProfileFB* profile); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFoveationProfileFB( - XrFoveationProfileFB profile); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_foveation_configuration 1 -#define XR_FB_foveation_configuration_SPEC_VERSION 1 -#define XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME "XR_FB_foveation_configuration" - -typedef enum XrFoveationLevelFB { - XR_FOVEATION_LEVEL_NONE_FB = 0, - XR_FOVEATION_LEVEL_LOW_FB = 1, - XR_FOVEATION_LEVEL_MEDIUM_FB = 2, - XR_FOVEATION_LEVEL_HIGH_FB = 3, - XR_FOVEATION_LEVEL_MAX_ENUM_FB = 0x7FFFFFFF -} XrFoveationLevelFB; - -typedef enum XrFoveationDynamicFB { - XR_FOVEATION_DYNAMIC_DISABLED_FB = 0, - XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB = 1, - XR_FOVEATION_DYNAMIC_MAX_ENUM_FB = 0x7FFFFFFF -} XrFoveationDynamicFB; -// XrFoveationLevelProfileCreateInfoFB extends XrFoveationProfileCreateInfoFB -typedef struct XrFoveationLevelProfileCreateInfoFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrFoveationLevelFB level; - float verticalOffset; - XrFoveationDynamicFB dynamic; -} XrFoveationLevelProfileCreateInfoFB; - - - -#define XR_FB_keyboard_tracking 1 -#define XR_FB_keyboard_tracking_SPEC_VERSION 1 -#define XR_FB_KEYBOARD_TRACKING_EXTENSION_NAME "XR_FB_keyboard_tracking" -#define XR_MAX_KEYBOARD_TRACKING_NAME_SIZE_FB 128 -typedef XrFlags64 XrKeyboardTrackingFlagsFB; - -// Flag bits for XrKeyboardTrackingFlagsFB -static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_EXISTS_BIT_FB = 0x00000001; -static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_LOCAL_BIT_FB = 0x00000002; -static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_REMOTE_BIT_FB = 0x00000004; -static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_CONNECTED_BIT_FB = 0x00000008; - -typedef XrFlags64 XrKeyboardTrackingQueryFlagsFB; - -// Flag bits for XrKeyboardTrackingQueryFlagsFB -static const XrKeyboardTrackingQueryFlagsFB XR_KEYBOARD_TRACKING_QUERY_LOCAL_BIT_FB = 0x00000002; -static const XrKeyboardTrackingQueryFlagsFB XR_KEYBOARD_TRACKING_QUERY_REMOTE_BIT_FB = 0x00000004; - -// XrSystemKeyboardTrackingPropertiesFB extends XrSystemProperties -typedef struct XrSystemKeyboardTrackingPropertiesFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 supportsKeyboardTracking; -} XrSystemKeyboardTrackingPropertiesFB; - -typedef struct XrKeyboardTrackingDescriptionFB { - uint64_t trackedKeyboardId; - XrVector3f size; - XrKeyboardTrackingFlagsFB flags; - char name[XR_MAX_KEYBOARD_TRACKING_NAME_SIZE_FB]; -} XrKeyboardTrackingDescriptionFB; - -typedef struct XrKeyboardSpaceCreateInfoFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint64_t trackedKeyboardId; -} XrKeyboardSpaceCreateInfoFB; - -typedef struct XrKeyboardTrackingQueryFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrKeyboardTrackingQueryFlagsFB flags; -} XrKeyboardTrackingQueryFB; - -typedef XrResult (XRAPI_PTR *PFN_xrQuerySystemTrackedKeyboardFB)(XrSession session, const XrKeyboardTrackingQueryFB* queryInfo, XrKeyboardTrackingDescriptionFB* keyboard); -typedef XrResult (XRAPI_PTR *PFN_xrCreateKeyboardSpaceFB)(XrSession session, const XrKeyboardSpaceCreateInfoFB* createInfo, XrSpace* keyboardSpace); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrQuerySystemTrackedKeyboardFB( - XrSession session, - const XrKeyboardTrackingQueryFB* queryInfo, - XrKeyboardTrackingDescriptionFB* keyboard); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateKeyboardSpaceFB( - XrSession session, - const XrKeyboardSpaceCreateInfoFB* createInfo, - XrSpace* keyboardSpace); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_triangle_mesh 1 -XR_DEFINE_HANDLE(XrTriangleMeshFB) -#define XR_FB_triangle_mesh_SPEC_VERSION 1 -#define XR_FB_TRIANGLE_MESH_EXTENSION_NAME "XR_FB_triangle_mesh" - -typedef enum XrWindingOrderFB { - XR_WINDING_ORDER_UNKNOWN_FB = 0, - XR_WINDING_ORDER_CW_FB = 1, - XR_WINDING_ORDER_CCW_FB = 2, - XR_WINDING_ORDER_MAX_ENUM_FB = 0x7FFFFFFF -} XrWindingOrderFB; -typedef XrFlags64 XrTriangleMeshFlagsFB; - -// Flag bits for XrTriangleMeshFlagsFB -static const XrTriangleMeshFlagsFB XR_TRIANGLE_MESH_MUTABLE_BIT_FB = 0x00000001; - -typedef struct XrTriangleMeshCreateInfoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrTriangleMeshFlagsFB flags; - XrWindingOrderFB windingOrder; - uint32_t vertexCount; - const XrVector3f* vertexBuffer; - uint32_t triangleCount; - const uint32_t* indexBuffer; -} XrTriangleMeshCreateInfoFB; - -typedef XrResult (XRAPI_PTR *PFN_xrCreateTriangleMeshFB)(XrSession session, const XrTriangleMeshCreateInfoFB* createInfo, XrTriangleMeshFB* outTriangleMesh); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyTriangleMeshFB)(XrTriangleMeshFB mesh); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshGetVertexBufferFB)(XrTriangleMeshFB mesh, XrVector3f** outVertexBuffer); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshGetIndexBufferFB)(XrTriangleMeshFB mesh, uint32_t** outIndexBuffer); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshBeginUpdateFB)(XrTriangleMeshFB mesh); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshEndUpdateFB)(XrTriangleMeshFB mesh, uint32_t vertexCount, uint32_t triangleCount); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshBeginVertexBufferUpdateFB)(XrTriangleMeshFB mesh, uint32_t* outVertexCount); -typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshEndVertexBufferUpdateFB)(XrTriangleMeshFB mesh); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrCreateTriangleMeshFB( - XrSession session, - const XrTriangleMeshCreateInfoFB* createInfo, - XrTriangleMeshFB* outTriangleMesh); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyTriangleMeshFB( - XrTriangleMeshFB mesh); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshGetVertexBufferFB( - XrTriangleMeshFB mesh, - XrVector3f** outVertexBuffer); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshGetIndexBufferFB( - XrTriangleMeshFB mesh, - uint32_t** outIndexBuffer); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshBeginUpdateFB( - XrTriangleMeshFB mesh); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshEndUpdateFB( - XrTriangleMeshFB mesh, - uint32_t vertexCount, - uint32_t triangleCount); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshBeginVertexBufferUpdateFB( - XrTriangleMeshFB mesh, - uint32_t* outVertexCount); - -XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshEndVertexBufferUpdateFB( - XrTriangleMeshFB mesh); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_passthrough 1 -XR_DEFINE_HANDLE(XrPassthroughFB) -XR_DEFINE_HANDLE(XrPassthroughLayerFB) -XR_DEFINE_HANDLE(XrGeometryInstanceFB) -#define XR_FB_passthrough_SPEC_VERSION 1 -#define XR_FB_PASSTHROUGH_EXTENSION_NAME "XR_FB_passthrough" -#define XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB 256 - -typedef enum XrPassthroughLayerPurposeFB { - XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB = 0, - XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB = 1, - XR_PASSTHROUGH_LAYER_PURPOSE_TRACKED_KEYBOARD_HANDS_FB = 1000203001, - XR_PASSTHROUGH_LAYER_PURPOSE_MAX_ENUM_FB = 0x7FFFFFFF -} XrPassthroughLayerPurposeFB; -typedef XrFlags64 XrPassthroughFlagsFB; - -// Flag bits for XrPassthroughFlagsFB -static const XrPassthroughFlagsFB XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB = 0x00000001; - -typedef XrFlags64 XrPassthroughStateChangedFlagsFB; - -// Flag bits for XrPassthroughStateChangedFlagsFB -static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_REINIT_REQUIRED_BIT_FB = 0x00000001; -static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_NON_RECOVERABLE_ERROR_BIT_FB = 0x00000002; -static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_RECOVERABLE_ERROR_BIT_FB = 0x00000004; -static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_RESTORED_ERROR_BIT_FB = 0x00000008; - -// XrSystemPassthroughPropertiesFB extends XrSystemProperties -typedef struct XrSystemPassthroughPropertiesFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrBool32 supportsPassthrough; -} XrSystemPassthroughPropertiesFB; - -typedef struct XrPassthroughCreateInfoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrPassthroughFlagsFB flags; -} XrPassthroughCreateInfoFB; - -typedef struct XrPassthroughLayerCreateInfoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrPassthroughFB passthrough; - XrPassthroughFlagsFB flags; - XrPassthroughLayerPurposeFB purpose; -} XrPassthroughLayerCreateInfoFB; - -// XrCompositionLayerPassthroughFB extends XrCompositionLayerBaseHeader -typedef struct XrCompositionLayerPassthroughFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrCompositionLayerFlags flags; - XrSpace space; - XrPassthroughLayerFB layerHandle; -} XrCompositionLayerPassthroughFB; - -typedef struct XrGeometryInstanceCreateInfoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrPassthroughLayerFB layer; - XrTriangleMeshFB mesh; - XrSpace baseSpace; - XrPosef pose; - XrVector3f scale; -} XrGeometryInstanceCreateInfoFB; - -typedef struct XrGeometryInstanceTransformFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrSpace baseSpace; - XrTime time; - XrPosef pose; - XrVector3f scale; -} XrGeometryInstanceTransformFB; - -typedef struct XrPassthroughStyleFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - float textureOpacityFactor; - XrColor4f edgeColor; -} XrPassthroughStyleFB; - -typedef struct XrPassthroughColorMapMonoToRgbaFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrColor4f textureColorMap[XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB]; -} XrPassthroughColorMapMonoToRgbaFB; - -typedef struct XrPassthroughColorMapMonoToMonoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint8_t textureColorMap[XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB]; -} XrPassthroughColorMapMonoToMonoFB; - -typedef struct XrEventDataPassthroughStateChangedFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrPassthroughStateChangedFlagsFB flags; -} XrEventDataPassthroughStateChangedFB; - -typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughFB)(XrSession session, const XrPassthroughCreateInfoFB* createInfo, XrPassthroughFB* outPassthrough); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughFB)(XrPassthroughFB passthrough); -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughStartFB)(XrPassthroughFB passthrough); -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughPauseFB)(XrPassthroughFB passthrough); -typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughLayerFB)(XrSession session, const XrPassthroughLayerCreateInfoFB* createInfo, XrPassthroughLayerFB* outLayer); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughLayerFB)(XrPassthroughLayerFB layer); -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerPauseFB)(XrPassthroughLayerFB layer); -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerResumeFB)(XrPassthroughLayerFB layer); -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerSetStyleFB)(XrPassthroughLayerFB layer, const XrPassthroughStyleFB* style); -typedef XrResult (XRAPI_PTR *PFN_xrCreateGeometryInstanceFB)(XrSession session, const XrGeometryInstanceCreateInfoFB* createInfo, XrGeometryInstanceFB* outGeometryInstance); -typedef XrResult (XRAPI_PTR *PFN_xrDestroyGeometryInstanceFB)(XrGeometryInstanceFB instance); -typedef XrResult (XRAPI_PTR *PFN_xrGeometryInstanceSetTransformFB)(XrGeometryInstanceFB instance, const XrGeometryInstanceTransformFB* transformation); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughFB( - XrSession session, - const XrPassthroughCreateInfoFB* createInfo, - XrPassthroughFB* outPassthrough); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughFB( - XrPassthroughFB passthrough); - -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughStartFB( - XrPassthroughFB passthrough); - -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughPauseFB( - XrPassthroughFB passthrough); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughLayerFB( - XrSession session, - const XrPassthroughLayerCreateInfoFB* createInfo, - XrPassthroughLayerFB* outLayer); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughLayerFB( - XrPassthroughLayerFB layer); - -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerPauseFB( - XrPassthroughLayerFB layer); - -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerResumeFB( - XrPassthroughLayerFB layer); - -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerSetStyleFB( - XrPassthroughLayerFB layer, - const XrPassthroughStyleFB* style); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateGeometryInstanceFB( - XrSession session, - const XrGeometryInstanceCreateInfoFB* createInfo, - XrGeometryInstanceFB* outGeometryInstance); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroyGeometryInstanceFB( - XrGeometryInstanceFB instance); - -XRAPI_ATTR XrResult XRAPI_CALL xrGeometryInstanceSetTransformFB( - XrGeometryInstanceFB instance, - const XrGeometryInstanceTransformFB* transformation); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_render_model 1 - -#define XR_NULL_RENDER_MODEL_KEY_FB 0 - -XR_DEFINE_ATOM(XrRenderModelKeyFB) -#define XR_FB_render_model_SPEC_VERSION 1 -#define XR_FB_RENDER_MODEL_EXTENSION_NAME "XR_FB_render_model" -#define XR_MAX_RENDER_MODEL_NAME_SIZE_FB 64 -typedef XrFlags64 XrRenderModelFlagsFB; - -// Flag bits for XrRenderModelFlagsFB - -typedef struct XrRenderModelPathInfoFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrPath path; -} XrRenderModelPathInfoFB; - -typedef struct XrRenderModelPropertiesFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t vendorId; - char modelName[XR_MAX_RENDER_MODEL_NAME_SIZE_FB]; - XrRenderModelKeyFB modelKey; - uint32_t modelVersion; - XrRenderModelFlagsFB flags; -} XrRenderModelPropertiesFB; - -typedef struct XrRenderModelBufferFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t bufferCapacityInput; - uint32_t bufferCountOutput; - uint8_t* buffer; -} XrRenderModelBufferFB; - -typedef struct XrRenderModelLoadInfoFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrRenderModelKeyFB modelKey; -} XrRenderModelLoadInfoFB; - -// XrSystemRenderModelPropertiesFB extends XrSystemProperties -typedef struct XrSystemRenderModelPropertiesFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 supportsRenderModelLoading; -} XrSystemRenderModelPropertiesFB; - -typedef XrResult (XRAPI_PTR *PFN_xrEnumerateRenderModelPathsFB)(XrSession session, uint32_t pathCapacityInput, uint32_t* pathCountOutput, XrRenderModelPathInfoFB* paths); -typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelPropertiesFB)(XrSession session, XrPath path, XrRenderModelPropertiesFB* properties); -typedef XrResult (XRAPI_PTR *PFN_xrLoadRenderModelFB)(XrSession session, const XrRenderModelLoadInfoFB* info, XrRenderModelBufferFB* buffer); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateRenderModelPathsFB( - XrSession session, - uint32_t pathCapacityInput, - uint32_t* pathCountOutput, - XrRenderModelPathInfoFB* paths); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelPropertiesFB( - XrSession session, - XrPath path, - XrRenderModelPropertiesFB* properties); - -XRAPI_ATTR XrResult XRAPI_CALL xrLoadRenderModelFB( - XrSession session, - const XrRenderModelLoadInfoFB* info, - XrRenderModelBufferFB* buffer); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_VARJO_foveated_rendering 1 -#define XR_VARJO_foveated_rendering_SPEC_VERSION 2 -#define XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME "XR_VARJO_foveated_rendering" -// XrViewLocateFoveatedRenderingVARJO extends XrViewLocateInfo -typedef struct XrViewLocateFoveatedRenderingVARJO { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrBool32 foveatedRenderingActive; -} XrViewLocateFoveatedRenderingVARJO; - -// XrFoveatedViewConfigurationViewVARJO extends XrViewConfigurationView -typedef struct XrFoveatedViewConfigurationViewVARJO { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 foveatedRenderingActive; -} XrFoveatedViewConfigurationViewVARJO; - -// XrSystemFoveatedRenderingPropertiesVARJO extends XrSystemProperties -typedef struct XrSystemFoveatedRenderingPropertiesVARJO { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 supportsFoveatedRendering; -} XrSystemFoveatedRenderingPropertiesVARJO; - - - -#define XR_VARJO_composition_layer_depth_test 1 -#define XR_VARJO_composition_layer_depth_test_SPEC_VERSION 2 -#define XR_VARJO_COMPOSITION_LAYER_DEPTH_TEST_EXTENSION_NAME "XR_VARJO_composition_layer_depth_test" -// XrCompositionLayerDepthTestVARJO extends XrCompositionLayerProjection -typedef struct XrCompositionLayerDepthTestVARJO { - XrStructureType type; - const void* XR_MAY_ALIAS next; - float depthTestRangeNearZ; - float depthTestRangeFarZ; -} XrCompositionLayerDepthTestVARJO; - - - -#define XR_VARJO_environment_depth_estimation 1 -#define XR_VARJO_environment_depth_estimation_SPEC_VERSION 1 -#define XR_VARJO_ENVIRONMENT_DEPTH_ESTIMATION_EXTENSION_NAME "XR_VARJO_environment_depth_estimation" -typedef XrResult (XRAPI_PTR *PFN_xrSetEnvironmentDepthEstimationVARJO)(XrSession session, XrBool32 enabled); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrSetEnvironmentDepthEstimationVARJO( - XrSession session, - XrBool32 enabled); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_VARJO_marker_tracking 1 -#define XR_VARJO_marker_tracking_SPEC_VERSION 1 -#define XR_VARJO_MARKER_TRACKING_EXTENSION_NAME "XR_VARJO_marker_tracking" -// XrSystemMarkerTrackingPropertiesVARJO extends XrSystemProperties -typedef struct XrSystemMarkerTrackingPropertiesVARJO { - XrStructureType type; - void* XR_MAY_ALIAS next; - XrBool32 supportsMarkerTracking; -} XrSystemMarkerTrackingPropertiesVARJO; - -typedef struct XrEventDataMarkerTrackingUpdateVARJO { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint64_t markerId; - XrBool32 isActive; - XrBool32 isPredicted; - XrTime time; -} XrEventDataMarkerTrackingUpdateVARJO; - -typedef struct XrMarkerSpaceCreateInfoVARJO { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint64_t markerId; - XrPosef poseInMarkerSpace; -} XrMarkerSpaceCreateInfoVARJO; - -typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingVARJO)(XrSession session, XrBool32 enabled); -typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingTimeoutVARJO)(XrSession session, uint64_t markerId, XrDuration timeout); -typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingPredictionVARJO)(XrSession session, uint64_t markerId, XrBool32 enabled); -typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerSizeVARJO)(XrSession session, uint64_t markerId, XrExtent2Df* size); -typedef XrResult (XRAPI_PTR *PFN_xrCreateMarkerSpaceVARJO)(XrSession session, const XrMarkerSpaceCreateInfoVARJO* createInfo, XrSpace* space); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingVARJO( - XrSession session, - XrBool32 enabled); - -XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingTimeoutVARJO( - XrSession session, - uint64_t markerId, - XrDuration timeout); - -XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingPredictionVARJO( - XrSession session, - uint64_t markerId, - XrBool32 enabled); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerSizeVARJO( - XrSession session, - uint64_t markerId, - XrExtent2Df* size); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateMarkerSpaceVARJO( - XrSession session, - const XrMarkerSpaceCreateInfoVARJO* createInfo, - XrSpace* space); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_MSFT_spatial_anchor_persistence 1 -XR_DEFINE_HANDLE(XrSpatialAnchorStoreConnectionMSFT) -#define XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_MSFT 256 -#define XR_MSFT_spatial_anchor_persistence_SPEC_VERSION 2 -#define XR_MSFT_SPATIAL_ANCHOR_PERSISTENCE_EXTENSION_NAME "XR_MSFT_spatial_anchor_persistence" -typedef struct XrSpatialAnchorPersistenceNameMSFT { - char name[XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_MSFT]; -} XrSpatialAnchorPersistenceNameMSFT; - -typedef struct XrSpatialAnchorPersistenceInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrSpatialAnchorPersistenceNameMSFT spatialAnchorPersistenceName; - XrSpatialAnchorMSFT spatialAnchor; -} XrSpatialAnchorPersistenceInfoMSFT; - -typedef struct XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore; - XrSpatialAnchorPersistenceNameMSFT spatialAnchorPersistenceName; -} XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT; - -typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorStoreConnectionMSFT)(XrSession session, XrSpatialAnchorStoreConnectionMSFT* spatialAnchorStore); -typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAnchorStoreConnectionMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); -typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialAnchorMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, const XrSpatialAnchorPersistenceInfoMSFT* spatialAnchorPersistenceInfo); -typedef XrResult (XRAPI_PTR *PFN_xrEnumeratePersistedSpatialAnchorNamesMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, uint32_t spatialAnchorNamesCapacityInput, uint32_t* spatialAnchorNamesCountOutput, XrSpatialAnchorPersistenceNameMSFT* persistedAnchorNames); -typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorFromPersistedNameMSFT)(XrSession session, const XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT* spatialAnchorCreateInfo, XrSpatialAnchorMSFT* spatialAnchor); -typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialAnchorMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, const XrSpatialAnchorPersistenceNameMSFT* spatialAnchorPersistenceName); -typedef XrResult (XRAPI_PTR *PFN_xrClearSpatialAnchorStoreMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorStoreConnectionMSFT( - XrSession session, - XrSpatialAnchorStoreConnectionMSFT* spatialAnchorStore); - -XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAnchorStoreConnectionMSFT( - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); - -XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialAnchorMSFT( - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, - const XrSpatialAnchorPersistenceInfoMSFT* spatialAnchorPersistenceInfo); - -XRAPI_ATTR XrResult XRAPI_CALL xrEnumeratePersistedSpatialAnchorNamesMSFT( - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, - uint32_t spatialAnchorNamesCapacityInput, - uint32_t* spatialAnchorNamesCountOutput, - XrSpatialAnchorPersistenceNameMSFT* persistedAnchorNames); - -XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorFromPersistedNameMSFT( - XrSession session, - const XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT* spatialAnchorCreateInfo, - XrSpatialAnchorMSFT* spatialAnchor); - -XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialAnchorMSFT( - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, - const XrSpatialAnchorPersistenceNameMSFT* spatialAnchorPersistenceName); - -XRAPI_ATTR XrResult XRAPI_CALL xrClearSpatialAnchorStoreMSFT( - XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_space_warp 1 -#define XR_FB_space_warp_SPEC_VERSION 1 -#define XR_FB_SPACE_WARP_EXTENSION_NAME "XR_FB_space_warp" -typedef XrFlags64 XrCompositionLayerSpaceWarpInfoFlagsFB; - -// Flag bits for XrCompositionLayerSpaceWarpInfoFlagsFB - -// XrCompositionLayerSpaceWarpInfoFB extends XrCompositionLayerProjectionView -typedef struct XrCompositionLayerSpaceWarpInfoFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrCompositionLayerSpaceWarpInfoFlagsFB layerFlags; - XrSwapchainSubImage motionVectorSubImage; - XrPosef appSpaceDeltaPose; - XrSwapchainSubImage depthSubImage; - float minDepth; - float maxDepth; - float nearZ; - float farZ; -} XrCompositionLayerSpaceWarpInfoFB; - -// XrSystemSpaceWarpPropertiesFB extends XrSystemProperties -typedef struct XrSystemSpaceWarpPropertiesFB { - XrStructureType type; - void* XR_MAY_ALIAS next; - uint32_t recommendedMotionVectorImageRectWidth; - uint32_t recommendedMotionVectorImageRectHeight; -} XrSystemSpaceWarpPropertiesFB; - - - -#define XR_ALMALENCE_digital_lens_control 1 -#define XR_ALMALENCE_digital_lens_control_SPEC_VERSION 1 -#define XR_ALMALENCE_DIGITAL_LENS_CONTROL_EXTENSION_NAME "XR_ALMALENCE_digital_lens_control" -typedef XrFlags64 XrDigitalLensControlFlagsALMALENCE; - -// Flag bits for XrDigitalLensControlFlagsALMALENCE -static const XrDigitalLensControlFlagsALMALENCE XR_DIGITAL_LENS_CONTROL_PROCESSING_DISABLE_BIT_ALMALENCE = 0x00000001; - -typedef struct XrDigitalLensControlALMALENCE { - XrStructureType type; - const void* XR_MAY_ALIAS next; - XrDigitalLensControlFlagsALMALENCE flags; -} XrDigitalLensControlALMALENCE; - -typedef XrResult (XRAPI_PTR *PFN_xrSetDigitalLensControlALMALENCE)(XrSession session, const XrDigitalLensControlALMALENCE* digitalLensControl); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrSetDigitalLensControlALMALENCE( - XrSession session, - const XrDigitalLensControlALMALENCE* digitalLensControl); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_FB_passthrough_keyboard_hands 1 -#define XR_FB_passthrough_keyboard_hands_SPEC_VERSION 1 -#define XR_FB_PASSTHROUGH_KEYBOARD_HANDS_EXTENSION_NAME "XR_FB_passthrough_keyboard_hands" -typedef struct XrPassthroughKeyboardHandsIntensityFB { - XrStructureType type; - const void* XR_MAY_ALIAS next; - float leftHandIntensity; - float rightHandIntensity; -} XrPassthroughKeyboardHandsIntensityFB; - -typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerSetKeyboardHandsIntensityFB)(XrPassthroughLayerFB layer, const XrPassthroughKeyboardHandsIntensityFB* intensity); - -#ifndef XR_NO_PROTOTYPES -#ifdef XR_EXTENSION_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerSetKeyboardHandsIntensityFB( - XrPassthroughLayerFB layer, - const XrPassthroughKeyboardHandsIntensityFB* intensity); -#endif /* XR_EXTENSION_PROTOTYPES */ -#endif /* !XR_NO_PROTOTYPES */ - - -#define XR_EXT_uuid 1 -#define XR_EXT_uuid_SPEC_VERSION 1 -#define XR_EXT_UUID_EXTENSION_NAME "XR_EXT_uuid" -#define XR_UUID_SIZE_EXT 16 -typedef struct XrUuidEXT { - uint8_t data[XR_UUID_SIZE_EXT]; -} XrUuidEXT; - +#endif #ifdef __cplusplus } diff --git a/ext/openxr/openxr_pico.h b/ext/openxr/openxr_pico.h new file mode 100644 index 0000000000..eb44317017 --- /dev/null +++ b/ext/openxr/openxr_pico.h @@ -0,0 +1,907 @@ +#ifndef OPENXR_PICO_H_ +#define OPENXR_PICO_H_ 1 + + +#include "openxr.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct { + char software_ver[6]; + char hardware_ver[3]; + char sn[18]; + char addr[6]; + char ndi_version[5]; +} XrControllerInfo; + +typedef enum +{ + XR_CONTROLLER_KEY_HOME = 0, + XR_CONTROLLER_KEY_AX = 1, + XR_CONTROLLER_KEY_BY= 2, + XR_CONTROLLER_KEY_BACK = 3, + XR_CONTROLLER_KEY_TRIGGER = 4, + XR_CONTROLLER_KEY_VOL_UP = 5, + XR_CONTROLLER_KEY_VOL_DOWN = 6, + XR_CONTROLLER_KEY_ROCKER = 7, + XR_CONTROLLER_KEY_GRIP = 8, + XR_CONTROLLER_KEY_TOUCHPAD = 9, + XR_CONTROLLER_KEY_LASTONE = 127, + + XR_CONTROLLER_TOUCH_AX = 128, + XR_CONTROLLER_TOUCH_BY = 129, + XR_CONTROLLER_TOUCH_ROCKER = 130, + XR_CONTROLLER_TOUCH_TRIGGER = 131, + XR_CONTROLLER_TOUCH_THUMB = 132, + XR_CONTROLLER_TOUCH_LASTONE = 255 +} XrControllerKeyMap; + +enum xrt_device_eventtype +{ + XRT_DEVICE_CONNECTCHANGED = 0, + XRT_DEVICE_MAIN_CHANGED = 1, + XRT_DEVICE_VERSION = 2, + XRT_DEVICE_SN = 3, + XRT_DEVICE_BIND_STATUS = 4, + XRT_STATION_STATUS = 5, + XRT_DEVICE_IOBUSY = 6, + XRT_DEVICE_OTASTAUS = 7, + XRT_DEVICE_ID = 8, +// XRT_DEVICE_OTASATAION_PROGRESS = 9, +// XRT_DEVICE_OTASATAION_CODE = 10, +// XRT_DEVICE_OTACONTROLLER_PROGRESS = 11, +// XRT_DEVICE_OTACONTROLLER_CODE = 12, +// XRT_DEVICE_OTA_SUCCESS = 13, +// XRT_DEVICE_BLEMAC = 14, + XRT_DEVICE_HANDNESS_CHANGED = 15, + XRT_DEVICE_CHANNEL = 16, + XRT_DEVICE_LOSSRATE = 17, + XRT_DEVICE_THREAD_STARTED = 18 +}; + +typedef struct XrRigidBodyPosef_ { + XrPosef Pose; + XrVector3f AngularVelocity; + XrVector3f LinearVelocity; + XrVector3f AngularAcceleration; + XrVector3f LinearAcceleration; + int64_t TimeInSeconds; //< Absolute time of this pose. + int64_t PredictionInSeconds; //< Seconds this pose was predicted ahead. +} XrRigidBodyPosef; + +typedef struct XrTracking_ { + unsigned int Status; + XrRigidBodyPosef ControllerLocalPose; + XrRigidBodyPosef ControllerGlobalPose; +} XrTracking; + +//pico add event:Seethrough +typedef struct XrEventDataSeethroughStateChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int state; +} XrEventDataSeethroughStateChanged; + +typedef struct XrEventDataKeyEvent{ + XrStructureType type; + const void* XR_MAY_ALIAS next; + int32_t repeat; + int32_t keyCode; + int8_t keyAction; + }XrEventDataKeyEvent; +//pico add new device interface +typedef struct XrControllerEventChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + enum xrt_device_eventtype eventtype; + uint8_t controller; + uint8_t status; + uint8_t varying[400]; + uint16_t length; +} XrControllerEventChanged; + +typedef struct XrEventDataHardIPDStateChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float ipd; +} XrEventDataHardIPDStateChanged; + +typedef struct XrEventDataFoveationLevelChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int level; +} XrEventDataFoveationLevelChanged; + +typedef struct XrEventDataFrustumChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEventDataFrustumChanged; + +typedef struct XrEventDataRenderTextureChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int width; + int height; +} XrEventDataRenderTextureChanged; + +typedef struct XrEventDataTargetFrameRateChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int frameRate; +} XrEventDataTargetFrameRateChanged; + +typedef struct XrEventDataMrcStatusChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int mrcStatus; +} XrEventDataMrcStatusChanged; + +//XR_TYPE_EVENT_REFRESH_RATE_CHANGE +typedef struct XrEventDataRefreshRateChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float refreshRate; +} XrEventDataRefreshRateChanged; + +//pico add end + + +typedef enum XrTrackingMode +{ + XR_TRACKING_MODE_ROTATION = 0x1, + XR_TRACKING_MODE_POSITION = 0x2, + XR_TRACKING_MODE_EYE = 0x4 +}XrTrackingMode; + +typedef struct XrEyeTrackingData +{ + int32_t leftEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating left eye pose status + int32_t rightEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating right eye pose status + int32_t combinedEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating combined eye pose status + + float leftEyeGazePoint[3]; //!< Left Eye Gaze Point + float rightEyeGazePoint[3]; //!< Right Eye Gaze Point + float combinedEyeGazePoint[3]; //!< Combined Eye Gaze Point (HMD center-eye point) + + float leftEyeGazeVector[3]; //!< Left Eye Gaze Point + float rightEyeGazeVector[3]; //!< Right Eye Gaze Point + float combinedEyeGazeVector[3]; //!< Comnbined Eye Gaze Vector (HMD center-eye point) + + float leftEyeOpenness; //!< Left eye value between 0.0 and 1.0 where 1.0 means fully open and 0.0 closed. + float rightEyeOpenness; //!< Right eye value between 0.0 and 1.0 where 1.0 means fully open and 0.0 closed. + + float leftEyePupilDilation; //!< Left eye value in millimeters indicating the pupil dilation + float rightEyePupilDilation; //!< Right eye value in millimeters indicating the pupil dilation + + float leftEyePositionGuide[3]; //!< Position of the inner corner of the left eye in meters from the HMD center-eye coordinate system's origin. + float rightEyePositionGuide[3]; //!< Position of the inner corner of the right eye in meters from the HMD center-eye coordinate system's origin. + float foveatedGazeDirection[3]; //!< Position of the gaze direction in meters from the HMD center-eye coordinate system's origin. + int32_t foveatedGazeTrackingState; //!< The current state of the foveatedGazeDirection signal. + +}XrEyeTrackingData; + +typedef XrResult (XRAPI_PTR *PFN_xrSetTrackingModePICO)(XrSession session, uint32_t trackingMode); +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackingModePICO)(XrSession session, uint32_t *trackingMode); + +typedef XrResult (XRAPI_PTR *PFN_xrGetEyeTrackingDataPICO)(XrSession session, XrEyeTrackingData *eyeTrackingData); + +#ifndef XR_NO_PROTOTYPES + +XRAPI_ATTR XrResult XRAPI_CALL xrSetTrackingModePICO( + XrSession session, + uint32_t trackingMode); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackingModePICO( + XrSession session, + uint32_t * trackingMode); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetEyeTrackingDataPICO( + XrSession session, + XrEyeTrackingData * eyeTrackingData); + +#endif + +#define XR_PICO_singlepass_enable 1 +#define XR_PICO_singlepass_enable_SPEC_VERSION 1 +#define XR_PICO_SINGLEPASS_ENABLE_EXTENSION_NAME "XR_PICO_singlepass_enable" + +#ifdef XR_USE_PLATFORM_ANDROID + +//kevin extend for pass user created textures to runtime begin@{ +#define XR_PICO_android_swapchain_ext_enable 1 +#define XR_PICO_android_swapchain_ext_enable_SPEC_VERSION 1 +#define XR_PICO_ANDROID_SWAPCHAIN_EXT_ENABLE_EXTENSION_NAME "XR_PICO_android_swapchain_ext_enable" +typedef struct XrSwapchainCreateInfoAndroidEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t imageNumExt; + uint32_t* imagesExt; +} XrSwapchainCreateInfoAndroidEXT; +//kevin extend end@} + +//peter extend for pass user ffr to runtime begin@{ +typedef enum XrFoveationLevel{ + XR_FOVEATION_LEVEL_NONE = -1, + XR_FOVEATION_LEVEL_LOW = 0, + XR_FOVEATION_LEVEL_MID = 1, + XR_FOVEATION_LEVEL_HIGH = 2, + XR_FOVEATION_LEVEL_TOP_HIGH = 3 +}XrFoveationLevel; + +typedef enum XrFoveationType{ + XR_FOVEATION_LEVEL = 0, + XR_FOVEATION_PARAMETERS = 1 +}XrFoveationType; +typedef struct XrFoveationParametersEXT{ + XrStructureType type; + const void* XR_MAY_ALIAS next; + int textureIdCount; + int* textureId; + int* previousId; + float focalPointX; + float focalPointY; + XrFoveationType ffrType; + float foveationGainX; + float foveationGainY; + float foveationArea; + float foveationMinimum; + XrFoveationLevel level; + int frameOffsetCount; + float* frameOffset; +}XrFoveationParametersEXT; +//peter extend end@} + +//peter extend for IPD in runtime begin@{ +#define XR_PICO_ipd 1 +#define XR_PICO_ipd_SPEC_VERSION 1 +#define XR_PICO_IPD_EXTENSION_NAME "XR_PICO_ipd" + +typedef XrResult (XRAPI_PTR *PFN_xrSetIPDPICO)(XrSession session, float distance); +typedef XrResult (XRAPI_PTR *PFN_xrGetIPDPICO)(XrSession session, float* ipd); +typedef XrResult (XRAPI_PTR *PFN_xrSetTrackingIPDEnabledPICO)(XrSession session, bool enable); +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackingIPDEnabledPICO)(XrSession session, bool* enable); +typedef XrResult (XRAPI_PTR *PFN_xrGetEyeTrackingAutoIPDPICO)(XrSession session, float* autoIPD); + +#ifndef XR_NO_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetIPDPICO(XrSession session, float distance); +XRAPI_ATTR XrResult XRAPI_CALL xrGetIPDPICO(XrSession session, float* ipd); +XRAPI_ATTR XrResult XRAPI_CALL xrSetTrackingIPDEnabledPICO(XrSession session, bool enable); +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackingIPDEnabledPICO(XrSession session, bool* enable); +XRAPI_ATTR XrResult XRAPI_CALL xrGetEyeTrackingAutoIPDPICO(XrSession session, float* autoIPD); +#endif +//peter extend for IPD end@} + +//peter extend for stencilmesh in runtime begin @ { +#define XR_PICO_stencilmesh 1 +#define XR_PICO_stencilmesh_SPEC_VERSION 1 +#define XR_PICO_STENCILMESH_EXTENSION_NAME "XR_PICO_stencilmesh" + +typedef XrResult (XRAPI_PTR *PFN_xrGetStencilmeshPICO)( + XrSession session, + int eye, + int *vertsCount, + int *indexCount, + float **localVerts, + unsigned int **localIndex +); + +#ifndef XR_NO_PROTOTYPES + +XRAPI_ATTR XrResult XRAPI_CALL xrGetStencilmeshPICO( + XrSession session, + int eye, + int *vertsCount, + int *indexCount, + float **localVerts, + unsigned int **localIndex +); +#endif +//peter extend for stencilmesh in runtime end @ } + +// peter extend for frustum in runtime before @ { +#define XR_PICO_view_frustum_ext 1 +#define XR_PICO_view_frustum_ext_SPEC_VERSION 1 +#define XR_PICO_VIEW_FRUSTUM_EXT_EXTENSION_NAME "XR_PICO_view_frustum_ext" + +struct XrViewFrustum +{ + float left;//! Date: Sat, 24 Sep 2022 22:13:19 +0200 Subject: [PATCH 177/326] OpenXR - Disable performance extension on Pico --- Common/VR/VRBase.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Common/VR/VRBase.h b/Common/VR/VRBase.h index fffd234868..a94e3d3fa9 100644 --- a/Common/VR/VRBase.h +++ b/Common/VR/VRBase.h @@ -73,7 +73,9 @@ static void OXR_CheckErrors(XrInstance instance, XrResult result, const char* fu #define OXR(func) func; #endif +#ifdef OPENXR_PLATFORM_QUEST #define OPENXR_HAS_PERFORMANCE_EXTENSION +#endif enum { ovrMaxLayerCount = 1 }; enum { ovrMaxNumEyes = 2 }; From 60871eeac5ef5aa892fcc0cf50aa6019d5eb8e64 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Sep 2022 22:39:07 +0200 Subject: [PATCH 178/326] OpenXR - Initialize Pico subsystems --- Common/VR/VRBase.cpp | 102 +++++ ext/openxr/openxr_pico.h | 907 --------------------------------------- 2 files changed, 102 insertions(+), 907 deletions(-) delete mode 100644 ext/openxr/openxr_pico.h diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index 9932d09553..673b1bcc97 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -5,14 +5,103 @@ #include #include +#ifdef OPENXR_PLATFORM_PICO +#define XR_PICO_ANDROID_CONTROLLER_FUNCTION_EXT_ENABLE_EXTENSION_NAME "XR_PICO_android_controller_function_ext_enable" +#define XR_PICO_VIEW_STATE_EXT_ENABLE_EXTENSION_NAME "XR_PICO_view_state_ext_enable" +#define XR_PICO_FRAME_END_INFO_EXT_EXTENSION_NAME "XR_PICO_frame_end_info_ext" +#define XR_PICO_CONFIGS_EXT_EXTENSION_NAME "XR_PICO_configs_ext" +#define XR_PICO_RESET_SENSOR_EXTENSION_NAME "XR_PICO_reset_sensor" + +enum ConfigsSetEXT +{ + UNREAL_VERSION = 0, + TRACKING_ORIGIN, + OPENGL_NOERROR, + ENABLE_SIX_DOF, + PRESENTATION_FLAG, + ENABLE_CPT, + PLATFORM, + FOVEATION_LEVEL, + SET_DISPLAY_RATE = 8, + MRC_TEXTURE_ID = 9, +}; + +typedef enum +{ + PXR_HMD_3DOF = 0, + PXR_HMD_6DOF +} PxrHmdDof; + +typedef enum +{ + PXR_CONTROLLER_3DOF = 0, + PXR_CONTROLLER_6DOF +} PxrControllerDof; + +typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) ( +XrSession session, +enum ConfigsSetEXT configIndex, +char * configData); +PFN_xrSetConfigPICO pfnXrSetConfigPICO; + +//cmc ext function ,not use from 2021/07 +typedef XrResult (XRAPI_PTR *PFN_xrSetEngineVersionPico)(XrInstance instance,const char* version); +typedef XrResult (XRAPI_PTR *PFN_xrStartCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState); +typedef XrResult (XRAPI_PTR *PFN_xrStopCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState); + +XrInstance mControllerInstance; +PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = NULL; +PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = NULL; +PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = NULL; + +inline void InitializeGraphicDeivce(XrInstance mInstance) { + mControllerInstance = mInstance; + xrGetInstanceProcAddr(mInstance, "xrSetEngineVersionPico", (PFN_xrVoidFunction*)(&pfnXrSetEngineVersionPico)); + xrGetInstanceProcAddr(mInstance, "xrStartCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStartCVControllerThreadPico)); + xrGetInstanceProcAddr(mInstance, "xrStopCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStopCVControllerThreadPico)); +} + +inline int Pxr_SetEngineVersion(const char *version) { + if (pfnXrSetEngineVersionPico != NULL) { + return pfnXrSetEngineVersionPico(mControllerInstance,version); + } else { + return -1; + } +} + +inline int Pxr_StartCVControllerThread(int headSensorState, int handSensorState) { + if (pfnXrStartCVControllerThreadPico != NULL) { + return pfnXrStartCVControllerThreadPico(mControllerInstance,headSensorState, handSensorState); + } else { + return -1; + } +} + +inline int Pxr_StopCVControllerThread(int headSensorState, int handSensorState) { + if (pfnXrStopCVControllerThreadPico != NULL) { + return pfnXrStopCVControllerThreadPico(mControllerInstance,headSensorState, handSensorState); + } else { + return -1; + } +} +#endif + static engine_t vr_engine; int vr_initialized = 0; const char* const requiredExtensionNames[] = { + XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, #ifdef OPENXR_HAS_PERFORMANCE_EXTENSION XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, +#endif +#ifdef OPENXR_PLATFORM_PICO + XR_PICO_ANDROID_CONTROLLER_FUNCTION_EXT_ENABLE_EXTENSION_NAME, + XR_PICO_VIEW_STATE_EXT_ENABLE_EXTENSION_NAME, + XR_PICO_FRAME_END_INFO_EXT_EXTENSION_NAME, + XR_PICO_CONFIGS_EXT_EXTENSION_NAME, + XR_PICO_RESET_SENSOR_EXTENSION_NAME, #endif XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME}; const uint32_t numRequiredExtensions = @@ -64,6 +153,13 @@ void VR_Init( ovrJava java ) { exit(1); } +#ifdef OPENXR_PLATFORM_PICO + InitializeGraphicDeivce(vr_engine.appState.Instance); + xrGetInstanceProcAddr(vr_engine.appState.Instance,"xrSetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrSetConfigPICO)); + Pxr_SetEngineVersion("2.8.0.1"); + Pxr_StartCVControllerThread(PXR_HMD_6DOF, PXR_CONTROLLER_6DOF); +#endif + XrInstanceProperties instanceInfo; instanceInfo.type = XR_TYPE_INSTANCE_PROPERTIES; instanceInfo.next = NULL; @@ -108,6 +204,9 @@ void VR_Init( ovrJava java ) { void VR_Destroy( engine_t* engine ) { if (engine == &vr_engine) { +#ifdef OPENXR_PLATFORM_PICO + Pxr_StopCVControllerThread(PXR_HMD_6DOF, PXR_CONTROLLER_6DOF); +#endif xrDestroyInstance(engine->appState.Instance); ovrApp_Destroy(&engine->appState); } @@ -141,6 +240,9 @@ void VR_EnterVR( engine_t* engine ) { ALOGE("Failed to create XR session: %d.", initResult); exit(1); } +#ifdef OPENXR_PLATFORM_PICO + pfnXrSetConfigPICO(engine->appState.Session, TRACKING_ORIGIN, "1"); +#endif // Create a space to the first path XrReferenceSpaceCreateInfo spaceCreateInfo = {}; diff --git a/ext/openxr/openxr_pico.h b/ext/openxr/openxr_pico.h deleted file mode 100644 index eb44317017..0000000000 --- a/ext/openxr/openxr_pico.h +++ /dev/null @@ -1,907 +0,0 @@ -#ifndef OPENXR_PICO_H_ -#define OPENXR_PICO_H_ 1 - - -#include "openxr.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -typedef struct { - char software_ver[6]; - char hardware_ver[3]; - char sn[18]; - char addr[6]; - char ndi_version[5]; -} XrControllerInfo; - -typedef enum -{ - XR_CONTROLLER_KEY_HOME = 0, - XR_CONTROLLER_KEY_AX = 1, - XR_CONTROLLER_KEY_BY= 2, - XR_CONTROLLER_KEY_BACK = 3, - XR_CONTROLLER_KEY_TRIGGER = 4, - XR_CONTROLLER_KEY_VOL_UP = 5, - XR_CONTROLLER_KEY_VOL_DOWN = 6, - XR_CONTROLLER_KEY_ROCKER = 7, - XR_CONTROLLER_KEY_GRIP = 8, - XR_CONTROLLER_KEY_TOUCHPAD = 9, - XR_CONTROLLER_KEY_LASTONE = 127, - - XR_CONTROLLER_TOUCH_AX = 128, - XR_CONTROLLER_TOUCH_BY = 129, - XR_CONTROLLER_TOUCH_ROCKER = 130, - XR_CONTROLLER_TOUCH_TRIGGER = 131, - XR_CONTROLLER_TOUCH_THUMB = 132, - XR_CONTROLLER_TOUCH_LASTONE = 255 -} XrControllerKeyMap; - -enum xrt_device_eventtype -{ - XRT_DEVICE_CONNECTCHANGED = 0, - XRT_DEVICE_MAIN_CHANGED = 1, - XRT_DEVICE_VERSION = 2, - XRT_DEVICE_SN = 3, - XRT_DEVICE_BIND_STATUS = 4, - XRT_STATION_STATUS = 5, - XRT_DEVICE_IOBUSY = 6, - XRT_DEVICE_OTASTAUS = 7, - XRT_DEVICE_ID = 8, -// XRT_DEVICE_OTASATAION_PROGRESS = 9, -// XRT_DEVICE_OTASATAION_CODE = 10, -// XRT_DEVICE_OTACONTROLLER_PROGRESS = 11, -// XRT_DEVICE_OTACONTROLLER_CODE = 12, -// XRT_DEVICE_OTA_SUCCESS = 13, -// XRT_DEVICE_BLEMAC = 14, - XRT_DEVICE_HANDNESS_CHANGED = 15, - XRT_DEVICE_CHANNEL = 16, - XRT_DEVICE_LOSSRATE = 17, - XRT_DEVICE_THREAD_STARTED = 18 -}; - -typedef struct XrRigidBodyPosef_ { - XrPosef Pose; - XrVector3f AngularVelocity; - XrVector3f LinearVelocity; - XrVector3f AngularAcceleration; - XrVector3f LinearAcceleration; - int64_t TimeInSeconds; //< Absolute time of this pose. - int64_t PredictionInSeconds; //< Seconds this pose was predicted ahead. -} XrRigidBodyPosef; - -typedef struct XrTracking_ { - unsigned int Status; - XrRigidBodyPosef ControllerLocalPose; - XrRigidBodyPosef ControllerGlobalPose; -} XrTracking; - -//pico add event:Seethrough -typedef struct XrEventDataSeethroughStateChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - int state; -} XrEventDataSeethroughStateChanged; - -typedef struct XrEventDataKeyEvent{ - XrStructureType type; - const void* XR_MAY_ALIAS next; - int32_t repeat; - int32_t keyCode; - int8_t keyAction; - }XrEventDataKeyEvent; -//pico add new device interface -typedef struct XrControllerEventChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - enum xrt_device_eventtype eventtype; - uint8_t controller; - uint8_t status; - uint8_t varying[400]; - uint16_t length; -} XrControllerEventChanged; - -typedef struct XrEventDataHardIPDStateChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - float ipd; -} XrEventDataHardIPDStateChanged; - -typedef struct XrEventDataFoveationLevelChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - int level; -} XrEventDataFoveationLevelChanged; - -typedef struct XrEventDataFrustumChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; -} XrEventDataFrustumChanged; - -typedef struct XrEventDataRenderTextureChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - int width; - int height; -} XrEventDataRenderTextureChanged; - -typedef struct XrEventDataTargetFrameRateChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - int frameRate; -} XrEventDataTargetFrameRateChanged; - -typedef struct XrEventDataMrcStatusChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - int mrcStatus; -} XrEventDataMrcStatusChanged; - -//XR_TYPE_EVENT_REFRESH_RATE_CHANGE -typedef struct XrEventDataRefreshRateChanged { - XrStructureType type; - const void* XR_MAY_ALIAS next; - float refreshRate; -} XrEventDataRefreshRateChanged; - -//pico add end - - -typedef enum XrTrackingMode -{ - XR_TRACKING_MODE_ROTATION = 0x1, - XR_TRACKING_MODE_POSITION = 0x2, - XR_TRACKING_MODE_EYE = 0x4 -}XrTrackingMode; - -typedef struct XrEyeTrackingData -{ - int32_t leftEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating left eye pose status - int32_t rightEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating right eye pose status - int32_t combinedEyePoseStatus; //!< Bit field (pvrEyePoseStatus) indicating combined eye pose status - - float leftEyeGazePoint[3]; //!< Left Eye Gaze Point - float rightEyeGazePoint[3]; //!< Right Eye Gaze Point - float combinedEyeGazePoint[3]; //!< Combined Eye Gaze Point (HMD center-eye point) - - float leftEyeGazeVector[3]; //!< Left Eye Gaze Point - float rightEyeGazeVector[3]; //!< Right Eye Gaze Point - float combinedEyeGazeVector[3]; //!< Comnbined Eye Gaze Vector (HMD center-eye point) - - float leftEyeOpenness; //!< Left eye value between 0.0 and 1.0 where 1.0 means fully open and 0.0 closed. - float rightEyeOpenness; //!< Right eye value between 0.0 and 1.0 where 1.0 means fully open and 0.0 closed. - - float leftEyePupilDilation; //!< Left eye value in millimeters indicating the pupil dilation - float rightEyePupilDilation; //!< Right eye value in millimeters indicating the pupil dilation - - float leftEyePositionGuide[3]; //!< Position of the inner corner of the left eye in meters from the HMD center-eye coordinate system's origin. - float rightEyePositionGuide[3]; //!< Position of the inner corner of the right eye in meters from the HMD center-eye coordinate system's origin. - float foveatedGazeDirection[3]; //!< Position of the gaze direction in meters from the HMD center-eye coordinate system's origin. - int32_t foveatedGazeTrackingState; //!< The current state of the foveatedGazeDirection signal. - -}XrEyeTrackingData; - -typedef XrResult (XRAPI_PTR *PFN_xrSetTrackingModePICO)(XrSession session, uint32_t trackingMode); -typedef XrResult (XRAPI_PTR *PFN_xrGetTrackingModePICO)(XrSession session, uint32_t *trackingMode); - -typedef XrResult (XRAPI_PTR *PFN_xrGetEyeTrackingDataPICO)(XrSession session, XrEyeTrackingData *eyeTrackingData); - -#ifndef XR_NO_PROTOTYPES - -XRAPI_ATTR XrResult XRAPI_CALL xrSetTrackingModePICO( - XrSession session, - uint32_t trackingMode); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackingModePICO( - XrSession session, - uint32_t * trackingMode); - -XRAPI_ATTR XrResult XRAPI_CALL xrGetEyeTrackingDataPICO( - XrSession session, - XrEyeTrackingData * eyeTrackingData); - -#endif - -#define XR_PICO_singlepass_enable 1 -#define XR_PICO_singlepass_enable_SPEC_VERSION 1 -#define XR_PICO_SINGLEPASS_ENABLE_EXTENSION_NAME "XR_PICO_singlepass_enable" - -#ifdef XR_USE_PLATFORM_ANDROID - -//kevin extend for pass user created textures to runtime begin@{ -#define XR_PICO_android_swapchain_ext_enable 1 -#define XR_PICO_android_swapchain_ext_enable_SPEC_VERSION 1 -#define XR_PICO_ANDROID_SWAPCHAIN_EXT_ENABLE_EXTENSION_NAME "XR_PICO_android_swapchain_ext_enable" -typedef struct XrSwapchainCreateInfoAndroidEXT { - XrStructureType type; - const void* XR_MAY_ALIAS next; - uint32_t imageNumExt; - uint32_t* imagesExt; -} XrSwapchainCreateInfoAndroidEXT; -//kevin extend end@} - -//peter extend for pass user ffr to runtime begin@{ -typedef enum XrFoveationLevel{ - XR_FOVEATION_LEVEL_NONE = -1, - XR_FOVEATION_LEVEL_LOW = 0, - XR_FOVEATION_LEVEL_MID = 1, - XR_FOVEATION_LEVEL_HIGH = 2, - XR_FOVEATION_LEVEL_TOP_HIGH = 3 -}XrFoveationLevel; - -typedef enum XrFoveationType{ - XR_FOVEATION_LEVEL = 0, - XR_FOVEATION_PARAMETERS = 1 -}XrFoveationType; -typedef struct XrFoveationParametersEXT{ - XrStructureType type; - const void* XR_MAY_ALIAS next; - int textureIdCount; - int* textureId; - int* previousId; - float focalPointX; - float focalPointY; - XrFoveationType ffrType; - float foveationGainX; - float foveationGainY; - float foveationArea; - float foveationMinimum; - XrFoveationLevel level; - int frameOffsetCount; - float* frameOffset; -}XrFoveationParametersEXT; -//peter extend end@} - -//peter extend for IPD in runtime begin@{ -#define XR_PICO_ipd 1 -#define XR_PICO_ipd_SPEC_VERSION 1 -#define XR_PICO_IPD_EXTENSION_NAME "XR_PICO_ipd" - -typedef XrResult (XRAPI_PTR *PFN_xrSetIPDPICO)(XrSession session, float distance); -typedef XrResult (XRAPI_PTR *PFN_xrGetIPDPICO)(XrSession session, float* ipd); -typedef XrResult (XRAPI_PTR *PFN_xrSetTrackingIPDEnabledPICO)(XrSession session, bool enable); -typedef XrResult (XRAPI_PTR *PFN_xrGetTrackingIPDEnabledPICO)(XrSession session, bool* enable); -typedef XrResult (XRAPI_PTR *PFN_xrGetEyeTrackingAutoIPDPICO)(XrSession session, float* autoIPD); - -#ifndef XR_NO_PROTOTYPES -XRAPI_ATTR XrResult XRAPI_CALL xrSetIPDPICO(XrSession session, float distance); -XRAPI_ATTR XrResult XRAPI_CALL xrGetIPDPICO(XrSession session, float* ipd); -XRAPI_ATTR XrResult XRAPI_CALL xrSetTrackingIPDEnabledPICO(XrSession session, bool enable); -XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackingIPDEnabledPICO(XrSession session, bool* enable); -XRAPI_ATTR XrResult XRAPI_CALL xrGetEyeTrackingAutoIPDPICO(XrSession session, float* autoIPD); -#endif -//peter extend for IPD end@} - -//peter extend for stencilmesh in runtime begin @ { -#define XR_PICO_stencilmesh 1 -#define XR_PICO_stencilmesh_SPEC_VERSION 1 -#define XR_PICO_STENCILMESH_EXTENSION_NAME "XR_PICO_stencilmesh" - -typedef XrResult (XRAPI_PTR *PFN_xrGetStencilmeshPICO)( - XrSession session, - int eye, - int *vertsCount, - int *indexCount, - float **localVerts, - unsigned int **localIndex -); - -#ifndef XR_NO_PROTOTYPES - -XRAPI_ATTR XrResult XRAPI_CALL xrGetStencilmeshPICO( - XrSession session, - int eye, - int *vertsCount, - int *indexCount, - float **localVerts, - unsigned int **localIndex -); -#endif -//peter extend for stencilmesh in runtime end @ } - -// peter extend for frustum in runtime before @ { -#define XR_PICO_view_frustum_ext 1 -#define XR_PICO_view_frustum_ext_SPEC_VERSION 1 -#define XR_PICO_VIEW_FRUSTUM_EXT_EXTENSION_NAME "XR_PICO_view_frustum_ext" - -struct XrViewFrustum -{ - float left;//! Date: Sat, 24 Sep 2022 22:39:22 +0200 Subject: [PATCH 179/326] Vulkan: Don't compile pipeline variants that don't make sense given their flags. Ran into this with cache files from previous version of my change. Also bumping the shader cache ID again to avoid this in other ways, but good to be robust here. --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 10 ---------- Common/GPU/Vulkan/VulkanQueueRunner.h | 14 +++++++++++--- Common/GPU/Vulkan/VulkanRenderManager.cpp | 17 ++++++++++++++++- Common/GPU/Vulkan/VulkanRenderManager.h | 2 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 2 +- GPU/Vulkan/PipelineManagerVulkan.cpp | 2 +- 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index ccbf910f77..abcbb90c90 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -10,16 +10,6 @@ using namespace PPSSPP_VK; // Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState -static bool RenderPassTypeHasDepth(RenderPassType rpType) { - switch (rpType) { - case RP_TYPE_BACKBUFFER: - case RP_TYPE_COLOR_DEPTH: - case RP_TYPE_COLOR_DEPTH_INPUT: - return true; - } - return false; -} - static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) { if (dest->offset.x > src.offset.x) { dest->extent.width += (dest->offset.x - src.offset.x); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index c767e498fb..65654d01f5 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -43,9 +43,9 @@ enum class VKRRenderCommand : uint8_t { enum class PipelineFlags { NONE = 0, - USES_BLEND_CONSTANT = (1 << 3), - USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth or stencil buffers. - USES_INPUT_ATTACHMENT = (1 << 5), + USES_BLEND_CONSTANT = (1 << 1), + USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers. + USES_INPUT_ATTACHMENT = (1 << 3), }; ENUM_CLASS_BITOPS(PipelineFlags); @@ -65,6 +65,14 @@ enum RenderPassType { RP_TYPE_COUNT, }; +inline bool RenderPassTypeHasDepth(RenderPassType type) { + return type == RP_TYPE_BACKBUFFER || type == RP_TYPE_COLOR_DEPTH || type == RP_TYPE_COLOR_DEPTH_INPUT; +} + +inline bool RenderPassTypeHasInput(RenderPassType type) { + return type == RP_TYPE_COLOR_INPUT || type == RP_TYPE_COLOR_DEPTH_INPUT; +} + struct VkRenderData { VKRRenderCommand cmd; union { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index a666a830de..138740a471 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -589,7 +589,7 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { return frameData_[curFrame].GetInitCmd(vulkan_); } -VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, uint32_t variantBitmask, const char *tag) { +VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag) { VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline(); _dbg_assert_(desc->vertexShader); _dbg_assert_(desc->fragmentShader); @@ -615,6 +615,21 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe if (!(variantBitmask & (1 << i))) continue; RenderPassType rpType = (RenderPassType)i; + + // Sanity check - don't compile incompatible types (could be caused by corrupt caches, changes in data structures, etc). + if (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) { + if (!RenderPassTypeHasDepth(rpType)) { + WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type"); + continue; + } + } + if (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) { + if (!RenderPassTypeHasInput(rpType)) { + WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type"); + continue; + } + } + pipeline->pipeline[rpType] = Promise::CreateEmpty(); compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType), rpType)); needsCompile = true; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index ed9f0d16e5..3c27c8da98 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -241,7 +241,7 @@ public: // We delay creating pipelines until the end of the current render pass, so we can create the right type immediately. // Unless a variantBitmask is passed in, in which case we can just go ahead. // WARNING: desc must stick around during the lifetime of the pipeline! It's not enough to build it on the stack and drop it. - VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, uint32_t variantBitmask, const char *tag); + VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag); VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc); void NudgeCompilerThread() { diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index bc5d452e08..aaf27d2590 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1134,7 +1134,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; raster->ToVulkan(&gDesc.rs); - pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, 1 << RP_TYPE_BACKBUFFER, tag ? tag : "thin3d"); + pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << RP_TYPE_BACKBUFFER, tag ? tag : "thin3d"); if (desc.uniformDesc) { pipeline->dynamicUniformSize = (int)desc.uniformDesc->uniformBufferSize; diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 21ce7df61d..c965130113 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -296,7 +296,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, desc->pipelineLayout = layout; - VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, variantBitmask, "game"); + VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc, pipelineFlags, variantBitmask, "game"); vulkanPipeline->pipeline = pipeline; if (useBlendConstant) { From 08d2cb4486b1d9b41ade156dfa9faa0f5ea3ed0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 24 Sep 2022 22:40:42 +0200 Subject: [PATCH 180/326] Bump the shader cache version --- GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 132b4fba20..a9504977d1 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -377,7 +377,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 26 +#define CACHE_VERSION 27 struct VulkanCacheHeader { uint32_t magic; uint32_t version; From 753fd13494b27ddcd1f0644df18020cd3d86ed9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 24 Sep 2022 23:21:48 +0200 Subject: [PATCH 181/326] Vulkan: Avoid allocating depth images for stuff like temp copies, depal buffers etc. Pretty small impact, but good to do. An interesting extension of this would upconvert framebuffers in the FramebufferManager to have a depth buffer the first time depth is used on them, but this doesn't do that yet. --- Common/GPU/Vulkan/VulkanBarrier.h | 4 ++++ Common/GPU/Vulkan/VulkanQueueRunner.cpp | 4 ++-- Common/GPU/Vulkan/VulkanRenderManager.cpp | 14 ++++++++++---- Common/GPU/Vulkan/VulkanRenderManager.h | 6 +++--- Common/GPU/Vulkan/thin3d_vulkan.cpp | 4 +--- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanBarrier.h b/Common/GPU/Vulkan/VulkanBarrier.h index 0d5754b3f4..19edc548ba 100644 --- a/Common/GPU/Vulkan/VulkanBarrier.h +++ b/Common/GPU/Vulkan/VulkanBarrier.h @@ -19,6 +19,8 @@ public: VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask ) { + _dbg_assert_(image != VK_NULL_HANDLE); + srcStageMask_ |= srcStageMask; dstStageMask_ |= dstStageMask; dependencyFlags_ |= VK_DEPENDENCY_BY_REGION_BIT; @@ -46,6 +48,8 @@ public: void TransitionImageAuto( VkImage image, int baseMip, int numMipLevels, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout ) { + _dbg_assert_(image != VK_NULL_HANDLE); + VkAccessFlags srcAccessMask = 0; VkAccessFlags dstAccessMask = 0; switch (oldImageLayout) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index ccbf910f77..6b8d6548b3 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -1323,7 +1323,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c iter.targetLayout ); iter.fb->color.layout = iter.targetLayout; - } else if ((iter.aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && iter.fb->depth.layout != iter.targetLayout) { + } else if (iter.fb->depth.image != VK_NULL_HANDLE && (iter.aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && iter.fb->depth.layout != iter.targetLayout) { recordBarrier_.TransitionImageAuto( iter.fb->depth.image, 0, @@ -1367,7 +1367,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ); } - if (step.render.framebuffer->depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + if (step.render.framebuffer->depth.image != VK_NULL_HANDLE && step.render.framebuffer->depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { recordBarrier_.TransitionImage( step.render.framebuffer->depth.image, 0, diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index a666a830de..d769b40a15 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -146,16 +146,23 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) { return success; } -VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag) : vulkan_(vk), tag_(tag) { +VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag) : vulkan_(vk), tag_(tag) { width = _width; height = _height; _dbg_assert_(tag); CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); - CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); + vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag).c_str()); + if (createDepthStencilBuffer) { + CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); + vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag).c_str()); + } else { + NOTICE_LOG(G3D, "creating target without depth: %dx%d", _width, _height); + } // We create the actual framebuffer objects on demand, because some combinations might not make sense. + // Framebuffer objects are just pointers to a set of images, so no biggie. } VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) { @@ -170,6 +177,7 @@ VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPas views[0] = color.imageView; if (hasDepth) { + _dbg_assert_(depth.imageView != VK_NULL_HANDLE); views[1] = depth.imageView; } fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType); @@ -183,8 +191,6 @@ VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPas _assert_(res == VK_SUCCESS); if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) { - vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag_.c_str()).c_str()); - vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag_.c_str()).c_str()); vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str()); } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index ed9f0d16e5..bb98a8be10 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -43,15 +43,15 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int class VKRFramebuffer { public: - VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag); + VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag); ~VKRFramebuffer(); VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType); int width = 0; int height = 0; - VKRImage color{}; - VKRImage depth{}; + VKRImage color{}; // color.image is always there. + VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE. const char *Tag() const { return tag_.c_str(); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index bc5d452e08..d11d5fc0fb 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1496,9 +1496,7 @@ private: Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) { VkCommandBuffer cmd = renderManager_.GetInitCmd(); - // TODO: We always create with depth here, even when it's not needed (such as color temp FBOs). - // Should optimize those away. - VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.tag); + VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.z_stencil, desc.tag); return new VKFramebuffer(vkrfb); } From 81e8336985afce0df1e55f9cbf678394b3288c04 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 15:14:08 -0700 Subject: [PATCH 182/326] D3D9: Allow INTZ depth buffers more correctly. The FBO check was wrong and just always failed. --- Common/GPU/D3D9/thin3d_d3d9.cpp | 6 ++++-- GPU/Directx9/FramebufferManagerDX9.cpp | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index bf33fd34ca..0bc8003f1e 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -23,6 +23,7 @@ #include "Common/Math/lin/matrix4x4.h" #include "Common/GPU/thin3d.h" #include "Common/GPU/D3D9/D3D9StateCache.h" +#include "Common/OSVersion.h" #include "Common/StringUtils.h" #include "Common/Log.h" @@ -782,8 +783,9 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID // To be safe, make sure both the display format and the FBO format support INTZ. HRESULT displayINTZ = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, displayMode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ); - HRESULT fboINTZ = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ); - supportsINTZ = SUCCEEDED(displayINTZ) && SUCCEEDED(fboINTZ); + HRESULT displayINTY = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, displayMode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, ((D3DFORMAT)(MAKEFOURCC('I', 'N', 'T', 'Y')))); + // Try to prevent INTZ on older Intel drivers that claim support. + supportsINTZ = SUCCEEDED(displayINTZ) && !SUCCEEDED(displayINTY) && IsWin7OrHigher(); } caps_.textureDepthSupported = supportsINTZ; diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index f828336edd..c3d9489b93 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -365,8 +365,11 @@ } if (!vfb) { + if (!Memory::IsValidAddress(fb_address)) + return false; // If there's no vfb and we're drawing there, must be memory? - buffer = GPUDebugBuffer(Memory::GetPointerWrite(vfb->z_address), vfb->z_stride, 512, GPU_DBG_FORMAT_16BIT); + // TODO: Actually get the stencil. + buffer = GPUDebugBuffer(Memory::GetPointerWrite(fb_address), fb_stride, 512, GPU_DBG_FORMAT_8888); return true; } From f30b1d048d98d35daa91e0b234fcea57f38ad2d9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 17:53:09 -0700 Subject: [PATCH 183/326] softgpu: Avoid fast path in another wrong case. Seen in Kurohyo. Missed that the alpha blend check essentially means only standard blending can work. --- GPU/Software/RasterizerRectangle.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 8c7b4b171f..35a665d5a0 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -35,6 +35,7 @@ namespace Rasterizer { // Through mode, with the specific Darkstalker settings. inline void DrawSinglePixel5551(u16 *pixel, const u32 color_in, const PixelFuncID &pixelID) { u32 new_color; + // Because of this check, we only support src.a / 1-src.a blending. if ((color_in >> 24) == 255) { new_color = color_in & 0xFFFFFF; } else { @@ -78,7 +79,7 @@ static inline Vec4IntResult SOFTRAST_CALL ModulateRGBA(Vec4IntArg prim_in, Vec4I return ToVec4IntResult(out); } -// Check if we can safely ignore the alpha test. +// Check if we can safely ignore the alpha test, assuming standard alpha blending. static inline bool AlphaTestIsNeedless(const PixelFuncID &pixelID) { switch (pixelID.AlphaTestFunc()) { case GE_COMP_NEVER: @@ -95,8 +96,7 @@ static inline bool AlphaTestIsNeedless(const PixelFuncID &pixelID) { case GE_COMP_GEQUAL: if (pixelID.alphaTestRef != 0 || pixelID.hasAlphaTestMask) return false; - // DrawSinglePixel5551 assumes it can take the src color directly if full alpha. - return pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA; + return true; } return false; @@ -161,6 +161,9 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran !pixelID.colorTest && !pixelID.dithering && pixelID.alphaBlend && + pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD && + pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && + pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && AlphaTestIsNeedless(pixelID) && samplerID.useTextureAlpha && samplerID.TexFunc() == GE_TEXFUNC_MODULATE && @@ -249,6 +252,9 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran !pixelID.colorTest && !pixelID.dithering && pixelID.alphaBlend && + pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD && + pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && + pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && AlphaTestIsNeedless(pixelID) && !pixelID.applyColorWriteMask && pixelID.FBFormat() == GE_FORMAT_5551) { From 1eeb4f0bcfa3b52cd7e86e4f70c6607150971aa1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 18:04:23 -0700 Subject: [PATCH 184/326] softpu: Refactor out 5551 fast path checks. They were duplicated, and better to organize them according to state. --- GPU/Software/RasterizerRectangle.cpp | 92 +++++++++++++--------------- 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 35a665d5a0..0322ae185f 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -49,6 +49,47 @@ inline void DrawSinglePixel5551(u16 *pixel, const u32 color_in, const PixelFuncI *pixel = RGBA8888ToRGBA5551(new_color); } +// Check if we can safely ignore the alpha test, assuming standard alpha blending. +static inline bool AlphaTestIsNeedless(const PixelFuncID &pixelID) { + switch (pixelID.AlphaTestFunc()) { + case GE_COMP_NEVER: + case GE_COMP_EQUAL: + case GE_COMP_LESS: + case GE_COMP_LEQUAL: + return false; + + case GE_COMP_ALWAYS: + return true; + + case GE_COMP_NOTEQUAL: + case GE_COMP_GREATER: + case GE_COMP_GEQUAL: + if (pixelID.alphaTestRef != 0 || pixelID.hasAlphaTestMask) + return false; + return true; + } + + return false; +} + +bool UseDrawSinglePixel5551(const PixelFuncID &pixelID) { + if (pixelID.clearMode || pixelID.colorTest || pixelID.stencilTest) + return false; + if (!AlphaTestIsNeedless(pixelID) || pixelID.DepthTestFunc() != GE_COMP_ALWAYS) + return false; + if (pixelID.FBFormat() != GE_FORMAT_5551 || !pixelID.alphaBlend) + return false; + // We skip blending when alpha = FF, so we can't allow other blend modes. + if (pixelID.AlphaBlendEq() != GE_BLENDMODE_MUL_AND_ADD || pixelID.AlphaBlendSrc() != PixelBlendFactor::SRCALPHA) + return false; + if (pixelID.AlphaBlendDst() != PixelBlendFactor::INVSRCALPHA) + return false; + if (pixelID.dithering || pixelID.applyLogicOp || pixelID.applyColorWriteMask) + return false; + + return true; +} + static inline Vec4IntResult SOFTRAST_CALL ModulateRGBA(Vec4IntArg prim_in, Vec4IntArg texcolor_in, const SamplerID &samplerID) { Vec4 out; Vec4 prim_color = prim_in; @@ -79,29 +120,6 @@ static inline Vec4IntResult SOFTRAST_CALL ModulateRGBA(Vec4IntArg prim_in, Vec4I return ToVec4IntResult(out); } -// Check if we can safely ignore the alpha test, assuming standard alpha blending. -static inline bool AlphaTestIsNeedless(const PixelFuncID &pixelID) { - switch (pixelID.AlphaTestFunc()) { - case GE_COMP_NEVER: - case GE_COMP_EQUAL: - case GE_COMP_LESS: - case GE_COMP_LEQUAL: - return false; - - case GE_COMP_ALWAYS: - return true; - - case GE_COMP_NOTEQUAL: - case GE_COMP_GREATER: - case GE_COMP_GEQUAL: - if (pixelID.alphaTestRef != 0 || pixelID.hasAlphaTestMask) - return false; - return true; - } - - return false; -} - void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &state) { const u8 *texptr = state.texptr[0]; @@ -155,20 +173,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran pos0.y = scissorTL.y; } - if (!pixelID.stencilTest && - pixelID.DepthTestFunc() == GE_COMP_ALWAYS && - !pixelID.applyLogicOp && - !pixelID.colorTest && - !pixelID.dithering && - pixelID.alphaBlend && - pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD && - pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && - pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && - AlphaTestIsNeedless(pixelID) && - samplerID.useTextureAlpha && - samplerID.TexFunc() == GE_TEXFUNC_MODULATE && - !pixelID.applyColorWriteMask && - pixelID.FBFormat() == GE_FORMAT_5551) { + if (UseDrawSinglePixel5551(pixelID) && samplerID.TexFunc() == GE_TEXFUNC_MODULATE && samplerID.useTextureAlpha) { if (isWhite) { int t = t_start; for (int y = pos0.y; y < pos1.y; y++) { @@ -246,18 +251,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran if (pos1.y > scissorBR.y) pos1.y = scissorBR.y + 1; if (pos0.x < scissorTL.x) pos0.x = scissorTL.x; if (pos0.y < scissorTL.y) pos0.y = scissorTL.y; - if (!pixelID.stencilTest && - pixelID.DepthTestFunc() == GE_COMP_ALWAYS && - !pixelID.applyLogicOp && - !pixelID.colorTest && - !pixelID.dithering && - pixelID.alphaBlend && - pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD && - pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && - pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && - AlphaTestIsNeedless(pixelID) && - !pixelID.applyColorWriteMask && - pixelID.FBFormat() == GE_FORMAT_5551) { + if (UseDrawSinglePixel5551(pixelID)) { if (Vec4::FromRGBA(v1.color0).a() == 0) return; From c47d7eab38e7dfc9ac610af9b5f2c4e4fb0504aa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 18:19:22 -0700 Subject: [PATCH 185/326] softgpu: Simply 5551 blending fast path. Since it only supports multiply and add, let's just stick with that. --- GPU/Software/DrawPixel.cpp | 200 +++++++++++++++++++++++++++ GPU/Software/Rasterizer.cpp | 200 --------------------------- GPU/Software/Rasterizer.h | 3 - GPU/Software/RasterizerRectangle.cpp | 52 +++++-- 4 files changed, 244 insertions(+), 211 deletions(-) diff --git a/GPU/Software/DrawPixel.cpp b/GPU/Software/DrawPixel.cpp index 912af5c67a..f47ea6656a 100644 --- a/GPU/Software/DrawPixel.cpp +++ b/GPU/Software/DrawPixel.cpp @@ -400,6 +400,206 @@ static inline u32 ApplyLogicOp(GELogicOp op, u32 old_color, u32 new_color) { return new_color; } +static inline Vec3 GetSourceFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) { + switch (factor) { + case PixelBlendFactor::OTHERCOLOR: + return dst.rgb(); + + case PixelBlendFactor::INVOTHERCOLOR: + return Vec3::AssignToAll(255) - dst.rgb(); + + case PixelBlendFactor::SRCALPHA: +#if defined(_M_SSE) + return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))); +#else + return Vec3::AssignToAll(source.a()); +#endif + + case PixelBlendFactor::INVSRCALPHA: +#if defined(_M_SSE) + return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)))); +#else + return Vec3::AssignToAll(255 - source.a()); +#endif + + case PixelBlendFactor::DSTALPHA: + return Vec3::AssignToAll(dst.a()); + + case PixelBlendFactor::INVDSTALPHA: + return Vec3::AssignToAll(255 - dst.a()); + + case PixelBlendFactor::DOUBLESRCALPHA: + return Vec3::AssignToAll(2 * source.a()); + + case PixelBlendFactor::DOUBLEINVSRCALPHA: + return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255)); + + case PixelBlendFactor::DOUBLEDSTALPHA: + return Vec3::AssignToAll(2 * dst.a()); + + case PixelBlendFactor::DOUBLEINVDSTALPHA: + return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255)); + + case PixelBlendFactor::FIX: + default: + // All other dest factors (> 10) are treated as FIXA. + return Vec3::FromRGB(fix); + + case PixelBlendFactor::ZERO: + return Vec3::AssignToAll(0); + + case PixelBlendFactor::ONE: + return Vec3::AssignToAll(255); + } +} + +static inline Vec3 GetDestFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) { + switch (factor) { + case PixelBlendFactor::OTHERCOLOR: + return source.rgb(); + + case PixelBlendFactor::INVOTHERCOLOR: + return Vec3::AssignToAll(255) - source.rgb(); + + case PixelBlendFactor::SRCALPHA: +#if defined(_M_SSE) + return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))); +#else + return Vec3::AssignToAll(source.a()); +#endif + + case PixelBlendFactor::INVSRCALPHA: +#if defined(_M_SSE) + return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)))); +#else + return Vec3::AssignToAll(255 - source.a()); +#endif + + case PixelBlendFactor::DSTALPHA: + return Vec3::AssignToAll(dst.a()); + + case PixelBlendFactor::INVDSTALPHA: + return Vec3::AssignToAll(255 - dst.a()); + + case PixelBlendFactor::DOUBLESRCALPHA: + return Vec3::AssignToAll(2 * source.a()); + + case PixelBlendFactor::DOUBLEINVSRCALPHA: + return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255)); + + case PixelBlendFactor::DOUBLEDSTALPHA: + return Vec3::AssignToAll(2 * dst.a()); + + case PixelBlendFactor::DOUBLEINVDSTALPHA: + return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255)); + + case PixelBlendFactor::FIX: + default: + // All other dest factors (> 10) are treated as FIXB. + return Vec3::FromRGB(fix); + + case PixelBlendFactor::ZERO: + return Vec3::AssignToAll(0); + + case PixelBlendFactor::ONE: + return Vec3::AssignToAll(255); + } +} + +// Removed inline here - it was never chosen to be inlined by the compiler anyway, too complex. +static Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &source, const Vec4 &dst) { + // Note: These factors cannot go below 0, but they can go above 255 when doubling. + Vec3 srcfactor = GetSourceFactor(pixelID.AlphaBlendSrc(), source, dst, pixelID.cached.alphaBlendSrc); + Vec3 dstfactor = GetDestFactor(pixelID.AlphaBlendDst(), source, dst, pixelID.cached.alphaBlendDst); + + switch (pixelID.AlphaBlendEq()) { + case GE_BLENDMODE_MUL_AND_ADD: + { +#if defined(_M_SSE) + // We switch to 16 bit to use mulhi, and we use 4 bits of decimal to make the 16 bit shift free. + const __m128i half = _mm_set1_epi16(1 << 3); + + const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); + const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); + const __m128i s = _mm_mulhi_epi16(srgb, sf); + + const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); + const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); + const __m128i d = _mm_mulhi_epi16(drgb, df); + + return Vec3(_mm_unpacklo_epi16(_mm_adds_epi16(s, d), _mm_setzero_si128())); +#else + static constexpr Vec3 half = Vec3::AssignToAll(1); + Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; + Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; + return lhs + rhs; +#endif + } + + case GE_BLENDMODE_MUL_AND_SUBTRACT: + { +#if defined(_M_SSE) + const __m128i half = _mm_set1_epi16(1 << 3); + + const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); + const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); + const __m128i s = _mm_mulhi_epi16(srgb, sf); + + const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); + const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); + const __m128i d = _mm_mulhi_epi16(drgb, df); + + return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(s, d), _mm_setzero_si128()), _mm_setzero_si128())); +#else + static constexpr Vec3 half = Vec3::AssignToAll(1); + Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; + Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; + return lhs - rhs; +#endif + } + + case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: + { +#if defined(_M_SSE) + const __m128i half = _mm_set1_epi16(1 << 3); + + const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); + const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); + const __m128i s = _mm_mulhi_epi16(srgb, sf); + + const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); + const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); + const __m128i d = _mm_mulhi_epi16(drgb, df); + + return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(d, s), _mm_setzero_si128()), _mm_setzero_si128())); +#else + static constexpr Vec3 half = Vec3::AssignToAll(1); + Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; + Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; + return rhs - lhs; +#endif + } + + case GE_BLENDMODE_MIN: + return Vec3(std::min(source.r(), dst.r()), + std::min(source.g(), dst.g()), + std::min(source.b(), dst.b())); + + case GE_BLENDMODE_MAX: + return Vec3(std::max(source.r(), dst.r()), + std::max(source.g(), dst.g()), + std::max(source.b(), dst.b())); + + case GE_BLENDMODE_ABSDIFF: + return Vec3(::abs(source.r() - dst.r()), + ::abs(source.g() - dst.g()), + ::abs(source.b() - dst.b())); + + default: + return source.rgb(); + } +} + template void SOFTRAST_CALL DrawSinglePixel(int x, int y, int z, int fog, Vec4IntArg color_in, const PixelFuncID &pixelID) { Vec4 prim_color = Vec4(color_in).Clamp(0, 255); diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index c4eca9f93a..c1084f31d5 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -270,206 +270,6 @@ static inline bool IsRightSideOrFlatBottomLine(const Vec2& vertex, const Ve } } -static inline Vec3 GetSourceFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) { - switch (factor) { - case PixelBlendFactor::OTHERCOLOR: - return dst.rgb(); - - case PixelBlendFactor::INVOTHERCOLOR: - return Vec3::AssignToAll(255) - dst.rgb(); - - case PixelBlendFactor::SRCALPHA: -#if defined(_M_SSE) - return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))); -#else - return Vec3::AssignToAll(source.a()); -#endif - - case PixelBlendFactor::INVSRCALPHA: -#if defined(_M_SSE) - return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)))); -#else - return Vec3::AssignToAll(255 - source.a()); -#endif - - case PixelBlendFactor::DSTALPHA: - return Vec3::AssignToAll(dst.a()); - - case PixelBlendFactor::INVDSTALPHA: - return Vec3::AssignToAll(255 - dst.a()); - - case PixelBlendFactor::DOUBLESRCALPHA: - return Vec3::AssignToAll(2 * source.a()); - - case PixelBlendFactor::DOUBLEINVSRCALPHA: - return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255)); - - case PixelBlendFactor::DOUBLEDSTALPHA: - return Vec3::AssignToAll(2 * dst.a()); - - case PixelBlendFactor::DOUBLEINVDSTALPHA: - return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255)); - - case PixelBlendFactor::FIX: - default: - // All other dest factors (> 10) are treated as FIXA. - return Vec3::FromRGB(fix); - - case PixelBlendFactor::ZERO: - return Vec3::AssignToAll(0); - - case PixelBlendFactor::ONE: - return Vec3::AssignToAll(255); - } -} - -static inline Vec3 GetDestFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) { - switch (factor) { - case PixelBlendFactor::OTHERCOLOR: - return source.rgb(); - - case PixelBlendFactor::INVOTHERCOLOR: - return Vec3::AssignToAll(255) - source.rgb(); - - case PixelBlendFactor::SRCALPHA: -#if defined(_M_SSE) - return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))); -#else - return Vec3::AssignToAll(source.a()); -#endif - - case PixelBlendFactor::INVSRCALPHA: -#if defined(_M_SSE) - return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)))); -#else - return Vec3::AssignToAll(255 - source.a()); -#endif - - case PixelBlendFactor::DSTALPHA: - return Vec3::AssignToAll(dst.a()); - - case PixelBlendFactor::INVDSTALPHA: - return Vec3::AssignToAll(255 - dst.a()); - - case PixelBlendFactor::DOUBLESRCALPHA: - return Vec3::AssignToAll(2 * source.a()); - - case PixelBlendFactor::DOUBLEINVSRCALPHA: - return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255)); - - case PixelBlendFactor::DOUBLEDSTALPHA: - return Vec3::AssignToAll(2 * dst.a()); - - case PixelBlendFactor::DOUBLEINVDSTALPHA: - return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255)); - - case PixelBlendFactor::FIX: - default: - // All other dest factors (> 10) are treated as FIXB. - return Vec3::FromRGB(fix); - - case PixelBlendFactor::ZERO: - return Vec3::AssignToAll(0); - - case PixelBlendFactor::ONE: - return Vec3::AssignToAll(255); - } -} - -// Removed inline here - it was never chosen to be inlined by the compiler anyway, too complex. -Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &source, const Vec4 &dst) { - // Note: These factors cannot go below 0, but they can go above 255 when doubling. - Vec3 srcfactor = GetSourceFactor(pixelID.AlphaBlendSrc(), source, dst, pixelID.cached.alphaBlendSrc); - Vec3 dstfactor = GetDestFactor(pixelID.AlphaBlendDst(), source, dst, pixelID.cached.alphaBlendDst); - - switch (pixelID.AlphaBlendEq()) { - case GE_BLENDMODE_MUL_AND_ADD: - { -#if defined(_M_SSE) - // We switch to 16 bit to use mulhi, and we use 4 bits of decimal to make the 16 bit shift free. - const __m128i half = _mm_set1_epi16(1 << 3); - - const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); - const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); - const __m128i s = _mm_mulhi_epi16(srgb, sf); - - const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); - const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); - const __m128i d = _mm_mulhi_epi16(drgb, df); - - return Vec3(_mm_unpacklo_epi16(_mm_adds_epi16(s, d), _mm_setzero_si128())); -#else - static constexpr Vec3 half = Vec3::AssignToAll(1); - Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; - Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; - return lhs + rhs; -#endif - } - - case GE_BLENDMODE_MUL_AND_SUBTRACT: - { -#if defined(_M_SSE) - const __m128i half = _mm_set1_epi16(1 << 3); - - const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); - const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); - const __m128i s = _mm_mulhi_epi16(srgb, sf); - - const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); - const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); - const __m128i d = _mm_mulhi_epi16(drgb, df); - - return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(s, d), _mm_setzero_si128()), _mm_setzero_si128())); -#else - static constexpr Vec3 half = Vec3::AssignToAll(1); - Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; - Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; - return lhs - rhs; -#endif - } - - case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: - { -#if defined(_M_SSE) - const __m128i half = _mm_set1_epi16(1 << 3); - - const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half); - const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half); - const __m128i s = _mm_mulhi_epi16(srgb, sf); - - const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half); - const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half); - const __m128i d = _mm_mulhi_epi16(drgb, df); - - return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(d, s), _mm_setzero_si128()), _mm_setzero_si128())); -#else - static constexpr Vec3 half = Vec3::AssignToAll(1); - Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024; - Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024; - return rhs - lhs; -#endif - } - - case GE_BLENDMODE_MIN: - return Vec3(std::min(source.r(), dst.r()), - std::min(source.g(), dst.g()), - std::min(source.b(), dst.b())); - - case GE_BLENDMODE_MAX: - return Vec3(std::max(source.r(), dst.r()), - std::max(source.g(), dst.g()), - std::max(source.b(), dst.b())); - - case GE_BLENDMODE_ABSDIFF: - return Vec3(::abs(source.r() - dst.r()), - ::abs(source.g() - dst.g()), - ::abs(source.b() - dst.b())); - - default: - return source.rgb(); - } -} - static inline Vec4IntResult SOFTRAST_CALL ApplyTexturing(float s, float t, int x, int y, Vec4IntArg prim_color, int texlevel, int frac_texlevel, bool bilinear, const RasterizerState &state) { const u8 **tptr0 = const_cast(&state.texptr[texlevel]); const uint16_t *bufw0 = &state.texbufw[texlevel]; diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index 93e89f9596..6c350e222b 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -76,7 +76,4 @@ void ClearRectangle(const VertexData &v0, const VertexData &v1, const BinCoords bool GetCurrentTexture(GPUDebugBuffer &buffer, int level); -// Shared functions with RasterizerRectangle.cpp -Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &source, const Vec4 &dst); - } // namespace Rasterizer diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 0322ae185f..f312addea1 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -32,18 +32,54 @@ extern bool currentDialogActive; namespace Rasterizer { +// This essentially AlphaBlendingResult() with fixed src.a / 1 - src.a factors and ADD equation. +// It allows us to skip round trips between 32-bit and 16-bit color values. +static uint32_t StandardAlphaBlend(uint32_t source, uint32_t dst) { +#if defined(_M_SSE) + const __m128i alpha = _mm_cvtsi32_si128(source >> 24); + // Keep the alpha lane of the srcfactor zero, so we keep dest alpha. + const __m128i srcfactor = _mm_shufflelo_epi16(alpha, _MM_SHUFFLE(1, 0, 0, 0)); + const __m128i dstfactor = _mm_sub_epi16(_mm_set1_epi16(255), srcfactor); + + const __m128i z = _mm_setzero_si128(); + const __m128i sourcevec = _mm_unpacklo_epi8(_mm_cvtsi32_si128(source), z); + const __m128i dstvec = _mm_unpacklo_epi8(_mm_cvtsi32_si128(dst), z); + + // We switch to 16 bit to use mulhi, and we use 4 bits of decimal to make the 16 bit shift free. + const __m128i half = _mm_set1_epi16(1 << 3); + + const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(sourcevec, 4), half); + const __m128i sf = _mm_add_epi16(_mm_slli_epi16(srcfactor, 4), half); + const __m128i s = _mm_mulhi_epi16(srgb, sf); + + const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(dstvec, 4), half); + const __m128i df = _mm_add_epi16(_mm_slli_epi16(dstfactor, 4), half); + const __m128i d = _mm_mulhi_epi16(drgb, df); + + const __m128i blended16 = _mm_adds_epi16(s, d); + return _mm_cvtsi128_si32(_mm_packus_epi16(blended16, blended16)); +#else + Vec3 srcfactor = Vec3::AssignToAll(source >> 24); + Vec3 dstfactor = Vec3::AssignToAll(255 - (source >> 24)); + + static constexpr Vec3 half = Vec3::AssignToAll(1); + Vec3 lhs = ((Vec3::FromRGB(source) * 2 + half) * (srcfactor * 2 + half)) / 1024; + Vec3 rhs = ((Vec3::FromRGB(dst) * 2 + half) * (dstfactor * 2 + half)) / 1024; + Vec3 blended = lhs + rhs; + + return clamp_u8(blended.r()) | (clamp_u8(blended.g()) << 8) | (clamp_u8(blended.b()) << 16); +#endif +} + // Through mode, with the specific Darkstalker settings. -inline void DrawSinglePixel5551(u16 *pixel, const u32 color_in, const PixelFuncID &pixelID) { +inline void DrawSinglePixel5551(u16 *pixel, const u32 color_in) { u32 new_color; // Because of this check, we only support src.a / 1-src.a blending. if ((color_in >> 24) == 255) { new_color = color_in & 0xFFFFFF; } else { const u32 old_color = RGBA5551ToRGBA8888(*pixel); - const Vec4 dst = Vec4::FromRGBA(old_color); - Vec3 blended = AlphaBlendingResult(pixelID, Vec4::FromRGBA(color_in), dst); - // ToRGB() always automatically clamps. - new_color = blended.ToRGB(); + new_color = StandardAlphaBlend(color_in, old_color); } new_color |= (*pixel & 0x8000) ? 0xff000000 : 0x00000000; *pixel = RGBA8888ToRGBA5551(new_color); @@ -182,7 +218,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran for (int x = pos0.x; x < pos1.x; x++) { u32 tex_color = Vec4(fetchFunc(s, t, texptr, texbufw, 0, state.samplerID)).ToRGBA(); if (tex_color & 0xFF000000) { - DrawSinglePixel5551(pixel, tex_color, pixelID); + DrawSinglePixel5551(pixel, tex_color); } s += ds; pixel++; @@ -200,7 +236,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran Vec4 tex_color = fetchFunc(s, t, texptr, texbufw, 0, state.samplerID); prim_color = Vec4(ModulateRGBA(ToVec4IntArg(prim_color), ToVec4IntArg(tex_color), state.samplerID)); if (prim_color.a() > 0) { - DrawSinglePixel5551(pixel, prim_color.ToRGBA(), pixelID); + DrawSinglePixel5551(pixel, prim_color.ToRGBA()); } s += ds; pixel++; @@ -258,7 +294,7 @@ void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &ran for (int y = pos0.y; y < pos1.y; y++) { u16 *pixel = fb.Get16Ptr(pos0.x, y, pixelID.cached.framebufStride); for (int x = pos0.x; x < pos1.x; x++) { - DrawSinglePixel5551(pixel, v1.color0, pixelID); + DrawSinglePixel5551(pixel, v1.color0); pixel++; } } From 444781c7b041f000d46f4fb05f27fc4215422123 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 18:37:33 -0700 Subject: [PATCH 186/326] softgpu: Fix triangle strip with partial rects. Seen in Wild Arms XF shop menu. --- GPU/Software/TransformUnit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index d6628044f7..5a33b2865a 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -721,7 +721,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (Rasterizer::DetectRectangleFromStrip(binner_->State(), data_, &tl, &br)) { Clipper::ProcessRect(data_[tl], data_[br], *binner_); start_vtx += 2; - skip_count = 0; + skip_count = 2; if (base + 4 >= vertex_count) { start_vtx = vertex_count; break; @@ -731,6 +731,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G // TODO: Maybe should give detection two halves? data_[0] = data_[2]; data_[1] = data_[3]; + data_index_ = 2; } else { // Go into triangle mode. Unfortunately, we re-read the verts. break; From 1aa684175962d83a5674def986378c33c19e49f9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 19:26:52 -0700 Subject: [PATCH 187/326] softgpu: Increase queued prims. We made them smaller, so we can queue more of them in the same space. Helps a little bit. --- GPU/Software/BinManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index 15c09bccf7..d651dfe577 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -228,8 +228,8 @@ protected: static constexpr int QUEUED_STATES = 4096; // These are 1KB each, so half an MB. static constexpr int QUEUED_CLUTS = 512; - // About 320 KB, but we have usually 16 or less of them, so 5 MB - 20 MB. - static constexpr int QUEUED_PRIMS = 1024; + // About 360 KB, but we have usually 16 or less of them, so 5 MB - 22 MB. + static constexpr int QUEUED_PRIMS = 2048; typedef BinQueue BinStateQueue; typedef BinQueue BinClutQueue; From 24560eef5cad36241ef019db10094ee3ba04a831 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 24 Sep 2022 19:57:45 -0700 Subject: [PATCH 188/326] softgpu: Avoid waiting for a thread to drain. If we can, we want to keep the thread queues healthy, but not full. Reduce the amount we push on a typical drain to avoid the Wait(). --- GPU/Software/BinManager.cpp | 20 ++++++++++++++------ GPU/Software/BinManager.h | 6 +++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index 8ad233b61c..d36c8137b3 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -406,7 +406,7 @@ void BinManager::AddPoint(const VertexData &v0) { Expand(range); } -void BinManager::Drain() { +void BinManager::Drain(bool flushing) { PROFILE_THIS_SCOPE("bin_drain"); // If the waitable has fully drained, we can update our binning decisions. @@ -446,6 +446,7 @@ void BinManager::Drain() { queue_.SkipNext(); } } else { + int max = flushing ? QUEUED_PRIMS : QUEUED_PRIMS / 2; while (!queue_.Empty()) { const BinItem &item = queue_.PeekNext(); for (int i = 0; i < (int)taskRanges_.size(); ++i) { @@ -453,17 +454,24 @@ void BinManager::Drain() { if (range.Invalid()) continue; - // This shouldn't often happen, but if it does, wait for space. - if (taskQueues_[i].Full()) - waitable_->Wait(); + if (taskQueues_[i].NearFull()) { + // This shouldn't often happen, but if it does, wait for space. + if (taskQueues_[i].Full()) + waitable_->Wait(); + // If we're not flushing and not near full, let's just continue later. + // Near full means we'd drain on next prim, so better to finish it now. + else if (!flushing && !queue_.NearFull()) + max = 0; + } BinItem &taskItem = taskQueues_[i].PeekPush(); taskItem = item; taskItem.range = range; taskQueues_[i].PushPeeked(); - } queue_.SkipNext(); + if (--max <= 0) + break; } int threads = 0; @@ -491,7 +499,7 @@ void BinManager::Flush(const char *reason) { double st; if (coreCollectDebugStats) st = time_now_d(); - Drain(); + Drain(true); waitable_->Wait(); taskRanges_.clear(); tasksSplit_ = false; diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h index d651dfe577..d874f0d3c8 100644 --- a/GPU/Software/BinManager.h +++ b/GPU/Software/BinManager.h @@ -132,6 +132,10 @@ struct BinQueue { return size_ == N - 1; } + bool NearFull() const { + return size_ >= N - 2; + } + bool Empty() const { return size_ == 0; } @@ -195,7 +199,7 @@ public: void AddLine(const VertexData &v0, const VertexData &v1); void AddPoint(const VertexData &v0); - void Drain(); + void Drain(bool flushing = false); void Flush(const char *reason); bool HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, uint32_t h); // Assumes you've also checked for a write (writes are partial so are automatically reads.) From 355bd37e17f601d30edc1f4ca9220d3a9581a022 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sun, 25 Sep 2022 09:25:37 +0200 Subject: [PATCH 189/326] OpenXR - Pico specific code refactor --- Common/VR/VRBase.cpp | 86 +++++++++++--------------------------------- 1 file changed, 21 insertions(+), 65 deletions(-) diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index 673b1bcc97..d80f2de4c6 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -6,12 +6,6 @@ #include #ifdef OPENXR_PLATFORM_PICO -#define XR_PICO_ANDROID_CONTROLLER_FUNCTION_EXT_ENABLE_EXTENSION_NAME "XR_PICO_android_controller_function_ext_enable" -#define XR_PICO_VIEW_STATE_EXT_ENABLE_EXTENSION_NAME "XR_PICO_view_state_ext_enable" -#define XR_PICO_FRAME_END_INFO_EXT_EXTENSION_NAME "XR_PICO_frame_end_info_ext" -#define XR_PICO_CONFIGS_EXT_EXTENSION_NAME "XR_PICO_configs_ext" -#define XR_PICO_RESET_SENSOR_EXTENSION_NAME "XR_PICO_reset_sensor" - enum ConfigsSetEXT { UNREAL_VERSION = 0, @@ -28,62 +22,20 @@ enum ConfigsSetEXT typedef enum { - PXR_HMD_3DOF = 0, - PXR_HMD_6DOF -} PxrHmdDof; + PXR_TRACKING_3DOF = 0, + PXR_TRACKING_6DOF = 1 +} PxrTrackingDof; -typedef enum -{ - PXR_CONTROLLER_3DOF = 0, - PXR_CONTROLLER_6DOF -} PxrControllerDof; - -typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) ( -XrSession session, -enum ConfigsSetEXT configIndex, -char * configData); -PFN_xrSetConfigPICO pfnXrSetConfigPICO; - -//cmc ext function ,not use from 2021/07 typedef XrResult (XRAPI_PTR *PFN_xrSetEngineVersionPico)(XrInstance instance,const char* version); typedef XrResult (XRAPI_PTR *PFN_xrStartCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState); typedef XrResult (XRAPI_PTR *PFN_xrStopCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState); +typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) (XrSession instance, enum ConfigsSetEXT configIndex, char* configData); -XrInstance mControllerInstance; -PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = NULL; -PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = NULL; -PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = NULL; +PFN_xrSetConfigPICO pfnXrSetConfigPICO = nullptr; +PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = nullptr; +PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = nullptr; +PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = nullptr; -inline void InitializeGraphicDeivce(XrInstance mInstance) { - mControllerInstance = mInstance; - xrGetInstanceProcAddr(mInstance, "xrSetEngineVersionPico", (PFN_xrVoidFunction*)(&pfnXrSetEngineVersionPico)); - xrGetInstanceProcAddr(mInstance, "xrStartCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStartCVControllerThreadPico)); - xrGetInstanceProcAddr(mInstance, "xrStopCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStopCVControllerThreadPico)); -} - -inline int Pxr_SetEngineVersion(const char *version) { - if (pfnXrSetEngineVersionPico != NULL) { - return pfnXrSetEngineVersionPico(mControllerInstance,version); - } else { - return -1; - } -} - -inline int Pxr_StartCVControllerThread(int headSensorState, int handSensorState) { - if (pfnXrStartCVControllerThreadPico != NULL) { - return pfnXrStartCVControllerThreadPico(mControllerInstance,headSensorState, handSensorState); - } else { - return -1; - } -} - -inline int Pxr_StopCVControllerThread(int headSensorState, int handSensorState) { - if (pfnXrStopCVControllerThreadPico != NULL) { - return pfnXrStopCVControllerThreadPico(mControllerInstance,headSensorState, handSensorState); - } else { - return -1; - } -} #endif static engine_t vr_engine; @@ -97,11 +49,11 @@ const char* const requiredExtensionNames[] = { XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, #endif #ifdef OPENXR_PLATFORM_PICO - XR_PICO_ANDROID_CONTROLLER_FUNCTION_EXT_ENABLE_EXTENSION_NAME, - XR_PICO_VIEW_STATE_EXT_ENABLE_EXTENSION_NAME, - XR_PICO_FRAME_END_INFO_EXT_EXTENSION_NAME, - XR_PICO_CONFIGS_EXT_EXTENSION_NAME, - XR_PICO_RESET_SENSOR_EXTENSION_NAME, + "XR_PICO_android_controller_function_ext_enable", + "XR_PICO_view_state_ext_enable", + "XR_PICO_frame_end_info_ext", + "XR_PICO_configs_ext", + "XR_PICO_reset_sensor", #endif XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME}; const uint32_t numRequiredExtensions = @@ -154,10 +106,12 @@ void VR_Init( ovrJava java ) { } #ifdef OPENXR_PLATFORM_PICO - InitializeGraphicDeivce(vr_engine.appState.Instance); + xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrSetEngineVersionPico", (PFN_xrVoidFunction*)(&pfnXrSetEngineVersionPico)); + xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStartCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStartCVControllerThreadPico)); + xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStopCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStopCVControllerThreadPico)); xrGetInstanceProcAddr(vr_engine.appState.Instance,"xrSetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrSetConfigPICO)); - Pxr_SetEngineVersion("2.8.0.1"); - Pxr_StartCVControllerThread(PXR_HMD_6DOF, PXR_CONTROLLER_6DOF); + if (pfnXrSetEngineVersionPico != nullptr) pfnXrSetEngineVersionPico(vr_engine.appState.Instance, "2.8.0.1"); + if (pfnXrStartCVControllerThreadPico != nullptr) pfnXrStartCVControllerThreadPico(vr_engine.appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF); #endif XrInstanceProperties instanceInfo; @@ -205,7 +159,9 @@ void VR_Init( ovrJava java ) { void VR_Destroy( engine_t* engine ) { if (engine == &vr_engine) { #ifdef OPENXR_PLATFORM_PICO - Pxr_StopCVControllerThread(PXR_HMD_6DOF, PXR_CONTROLLER_6DOF); + if (pfnXrStopCVControllerThreadPico != nullptr) { + pfnXrStopCVControllerThreadPico(engine->appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF); + } #endif xrDestroyInstance(engine->appState.Instance); ovrApp_Destroy(&engine->appState); From 70c5ca62e676ccb5ca6d884b5d2addfbd873571e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 09:53:50 +0200 Subject: [PATCH 190/326] Remove debug log. Add some new debug log though, unrelated to this PR, for fb clut + fb texture. Plus a couple asserts. --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 5 +++++ Common/GPU/Vulkan/VulkanRenderManager.cpp | 2 -- GPU/Common/TextureCacheCommon.cpp | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 6b8d6548b3..eafb470ec4 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -1750,6 +1750,8 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) { vkCmdCopyImage(cmd, src->color.image, src->color.layout, dst->color.image, dst->color.layout, 1, ©); } if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + _dbg_assert_(src->depth.image != VK_NULL_HANDLE); + _dbg_assert_(dst->depth.image != VK_NULL_HANDLE); copy.srcSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); copy.dstSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); vkCmdCopyImage(cmd, src->depth.image, src->depth.layout, dst->depth.image, dst->depth.layout, 1, ©); @@ -1804,6 +1806,9 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) { // We can't copy only depth or only stencil unfortunately. if (step.blit.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + _dbg_assert_(src->depth.image != VK_NULL_HANDLE); + _dbg_assert_(dst->depth.image != VK_NULL_HANDLE); + if (src->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_); } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index d769b40a15..b3e7f7fc0b 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -157,8 +157,6 @@ VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRe if (createDepthStencilBuffer) { CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag).c_str()); - } else { - NOTICE_LOG(G3D, "creating target without depth: %dx%d", _width, _height); } // We create the actual framebuffer objects on demand, because some combinations might not make sense. diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index f7c67bb2a1..1b7d8d5986 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -569,6 +569,10 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { nextTexture_ = nullptr; nextNeedsRebuild_ = false; + + if (hasClutGPU) { + WARN_LOG_ONCE(clut_fb, G3D, "Render-to-CLUT combined with framebuffer texture at %08x - Not yet supported", texaddr); + } SetTextureFramebuffer(bestCandidate); // sets curTexture3D return nullptr; } From 212598fc2763b4745a0b9877a3a36713e2ce5bd2 Mon Sep 17 00:00:00 2001 From: GABO1423 <35014183+GABO1423@users.noreply.github.com> Date: Sun, 25 Sep 2022 10:04:56 -0400 Subject: [PATCH 191/326] (UWP) Remove expandedResources --- UWP/PackageGold.appxmanifest | 1 - UWP/PackageNormal.appxmanifest | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/UWP/PackageGold.appxmanifest b/UWP/PackageGold.appxmanifest index c2b898a502..d428bcd738 100644 --- a/UWP/PackageGold.appxmanifest +++ b/UWP/PackageGold.appxmanifest @@ -35,6 +35,5 @@ - diff --git a/UWP/PackageNormal.appxmanifest b/UWP/PackageNormal.appxmanifest index 3ab4dbe2f8..ef79928e4c 100644 --- a/UWP/PackageNormal.appxmanifest +++ b/UWP/PackageNormal.appxmanifest @@ -29,8 +29,7 @@ - + - From 282505c70f1806b3b007126c6fdd0416b754d49c Mon Sep 17 00:00:00 2001 From: sum2012 Date: Sun, 25 Sep 2022 23:00:03 +0800 Subject: [PATCH 192/326] Trying to improve sceMpegAvcDecodeYCbCr Fix #11060 --- Core/HLE/sceMpeg.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 8aa429ba1d..9d829a9039 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -36,6 +36,8 @@ #include "GPU/GPUState.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/Core.h" +#include "Core/System.h" +#include "Core/FileSystems/MetaFileSystem.h" // MPEG AVC elementary stream. static const int MPEG_AVC_ES_SIZE = 2048; // MPEG packet size. @@ -1361,8 +1363,15 @@ static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initA // Flush structs back to memory avcAu.write(auAddr); - // Save the current frame's status to initAddr - Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); + // Save the current frame's status to initAddr + PSPFileInfo fileInfo = pspFileSystem.GetFileInfo("disc0:\\PSP_GAME\\USRDIR\\module\\mpeg.prx"); + if (fileInfo.exists) { + Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); + } + else { + // Sunday Vs Magazine Shuuketsu! Choujou Daikessen expcect, issue #11060 + Memory::Write_U32(1, initAddr); + } ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; DEBUG_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); From e30e45fa2603c8c17f176efef013524609403caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 23:23:53 +0200 Subject: [PATCH 193/326] Minor cleanup in TinySet --- Common/Data/Collections/TinySet.h | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Common/Data/Collections/TinySet.h b/Common/Data/Collections/TinySet.h index 9b4d91030d..4daf8279a3 100644 --- a/Common/Data/Collections/TinySet.h +++ b/Common/Data/Collections/TinySet.h @@ -10,21 +10,21 @@ struct TinySet { ~TinySet() { delete slowLookup_; } inline void insert(const T &t) { // Fast linear scan. - for (int i = 0; i < fastCount; i++) { + for (int i = 0; i < fastCount_; i++) { if (fastLookup_[i] == t) return; // We already have it. } // Fast insertion - if (fastCount < MaxFastSize) { - fastLookup_[fastCount++] = t; + if (fastCount_ < MaxFastSize) { + fastLookup_[fastCount_++] = t; return; } // Fall back to slow path. insertSlow(t); } inline void push_back(const T &t) { - if (fastCount < MaxFastSize) { - fastLookup_[fastCount++] = t; + if (fastCount_ < MaxFastSize) { + fastLookup_[fastCount_++] = t; return; } if (!slowLookup_) { @@ -33,8 +33,8 @@ struct TinySet { slowLookup_->push_back(t); } inline T *add_back() { - if (fastCount < MaxFastSize) { - return &fastLookup_[fastCount++]; + if (fastCount_ < MaxFastSize) { + return &fastLookup_[fastCount_++]; } if (!slowLookup_) { slowLookup_ = new std::vector(); @@ -48,9 +48,9 @@ struct TinySet { if (size() + otherSize <= MaxFastSize) { // Fast case for (size_t i = 0; i < otherSize; i++) { - fastLookup_[fastCount + i] = other.fastLookup_[i]; + fastLookup_[fastCount_ + i] = other.fastLookup_[i]; } - fastCount += other.fastCount; + fastCount_ += other.fastCount_; } else { for (size_t i = 0; i < otherSize; i++) { push_back(other[i]); @@ -58,7 +58,7 @@ struct TinySet { } } bool contains(T t) const { - for (int i = 0; i < fastCount; i++) { + for (int i = 0; i < fastCount_; i++) { if (fastLookup_[i] == t) return true; } @@ -72,7 +72,7 @@ struct TinySet { } bool contains(const TinySet &otherSet) { // Awkward, kind of ruins the fun. - for (int i = 0; i < fastCount; i++) { + for (int i = 0; i < fastCount_; i++) { if (otherSet.contains(fastLookup_[i])) return true; } @@ -85,16 +85,17 @@ struct TinySet { return false; } void clear() { + // TODO: Keep slowLookup_ around? That would be more similar to real vector behavior. delete slowLookup_; slowLookup_ = nullptr; - fastCount = 0; + fastCount_ = 0; } bool empty() const { - return fastCount == 0; + return fastCount_ == 0; } size_t size() const { if (!slowLookup_) { - return fastCount; + return fastCount_; } else { return slowLookup_->size() + MaxFastSize; } @@ -129,7 +130,7 @@ private: } slowLookup_->push_back(t); } + int fastCount_ = 0; // first in the struct just so it's more visible in the VS debugger. T fastLookup_[MaxFastSize]; - int fastCount = 0; std::vector *slowLookup_ = nullptr; }; From 894ab456779ccbde652f1490d8d4eb6a5d691144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 23:24:29 +0200 Subject: [PATCH 194/326] Vulkan: Add debug names to our command buffers, so you can see which one errored in validation --- Common/GPU/Vulkan/VulkanFrameData.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 52fd21c893..447630e0af 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -2,6 +2,7 @@ #include "VulkanFrameData.h" #include "Common/Log.h" +#include "Common/StringUtils.h" void FrameData::Init(VulkanContext *vulkan, int index) { this->index = index; @@ -26,12 +27,19 @@ void FrameData::Init(VulkanContext *vulkan, int index) { res = vkAllocateCommandBuffers(device, &cmd_alloc, &presentCmd); _dbg_assert_(res == VK_SUCCESS); + vulkan->SetDebugName(initCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("initCmd%d", index).c_str()); + vulkan->SetDebugName(mainCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("mainCmd%d", index).c_str()); + vulkan->SetDebugName(presentCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("presentCmd%d", index).c_str()); + // Creating the frame fence with true so they can be instantly waited on the first frame fence = vulkan->CreateFence(true); + vulkan->SetDebugName(fence, VK_OBJECT_TYPE_FENCE, StringFromFormat("fence%d", index).c_str()); readyForFence = true; - // This fence one is used for synchronizing readbacks. Does not need preinitialization. + // This fence is used for synchronizing readbacks. Does not need preinitialization. + // TODO: Put this in frameDataShared, only one is needed. readbackFence = vulkan->CreateFence(false); + vulkan->SetDebugName(fence, VK_OBJECT_TYPE_FENCE, "readbackFence"); VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO }; query_ci.queryCount = MAX_TIMESTAMP_QUERIES; @@ -41,9 +49,6 @@ void FrameData::Init(VulkanContext *vulkan, int index) { void FrameData::Destroy(VulkanContext *vulkan) { VkDevice device = vulkan->GetDevice(); - // TODO: I don't think free-ing command buffers is necessary before destroying a pool. - vkFreeCommandBuffers(device, cmdPoolInit, 1, &initCmd); - vkFreeCommandBuffers(device, cmdPoolMain, 1, &mainCmd); vkDestroyCommandPool(device, cmdPoolInit, nullptr); vkDestroyCommandPool(device, cmdPoolMain, nullptr); vkDestroyFence(device, fence, nullptr); From a7642bac15385f4bca1ba004bc1036131c6f1ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 23:24:54 +0200 Subject: [PATCH 195/326] Replace faulty layout transition deduplication algorithm. --- Common/GPU/Vulkan/VulkanQueueRunner.h | 4 ++++ Common/GPU/Vulkan/VulkanRenderManager.cpp | 17 ++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 65654d01f5..54430cd891 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -163,6 +163,10 @@ struct TransitionRequest { VKRFramebuffer *fb; VkImageAspectFlags aspect; // COLOR or DEPTH VkImageLayout targetLayout; + + bool operator == (const TransitionRequest &other) const { + return fb == other.fb && aspect == other.aspect && targetLayout == other.targetLayout; + } }; class VKRRenderPass; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index a64ce17fc6..49abbde2d0 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1137,6 +1137,10 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int attachment) { _dbg_assert_(curRenderStep_ != nullptr); + + // We don't support texturing from stencil, neither do we support texturing from depth|stencil together (nonsensical). + _dbg_assert_(aspectBit == VK_IMAGE_ASPECT_COLOR_BIT || aspectBit == VK_IMAGE_ASPECT_DEPTH_BIT); + // Mark the dependency, check for required transitions, and return the image. // Optimization: If possible, use final*Layout to put the texture into the correct layout "early". @@ -1163,15 +1167,10 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in // Track dependencies fully. curRenderStep_->dependencies.insert(fb); - if (!curRenderStep_->preTransitions.empty() && - curRenderStep_->preTransitions.back().fb == fb && - curRenderStep_->preTransitions.back().targetLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { - // We're done. - return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView; - } else { - curRenderStep_->preTransitions.push_back({ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }); - return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView; - } + // Add this pretransition unless we already have it. + TransitionRequest rq{ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; + curRenderStep_->preTransitions.insert(rq); // Note that insert avoids inserting duplicates. + return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView; } // Called on main thread. From 7adba20fac63a15bbcab5a962ae10418e29b0b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 13:59:52 +0200 Subject: [PATCH 196/326] Experiment: Generate "Ubershaders" that can handle all lighting configurations This drastically reduces the shader compile stutter that happens when a lot of new light setups are created, like on the first punch in Tekken 6. There's more stuff that might benefit from being made dynamic like this. These branches are very cheap on modern GPUs since they're branching on a uniform variable, so no divergence. Only tested on Vulkan. I think we'll need to keep the old path too for gpus like Mali-450... --- GPU/Common/ShaderCommon.h | 7 +- GPU/Common/ShaderId.cpp | 21 ++- GPU/Common/ShaderId.h | 6 +- GPU/Common/ShaderUniforms.cpp | 24 +++- GPU/Common/ShaderUniforms.h | 4 +- GPU/Common/VertexShaderGenerator.cpp | 204 ++++++++++++++++++--------- GPU/GPUCommon.cpp | 27 +++- GPU/GPUCommon.h | 8 ++ GPU/GPUState.h | 3 +- 9 files changed, 218 insertions(+), 86 deletions(-) diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index a4adfb70ea..d6dfbbc3f8 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -88,12 +88,13 @@ enum : uint64_t { DIRTY_COLORWRITEMASK = 1ULL << 36, DIRTY_MIPBIAS = 1ULL << 37, + DIRTY_LIGHT_CONTROL = 1ULL << 38, - // space for 4 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS. + // space for 1 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS. DIRTY_BONE_UNIFORMS = 0xFF000000ULL, - DIRTY_ALL_UNIFORMS = 0x3FFFFFFFFFULL, + DIRTY_ALL_UNIFORMS = 0x7FFFFFFFFFULL, DIRTY_ALL_LIGHTS = DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3, // Other dirty elements that aren't uniforms! @@ -113,6 +114,8 @@ enum : uint64_t { // TODO: Should we also add DIRTY_FRAMEBUF here? It kinda generally takes care of itself. DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS, + // Note that the top 8 bits (54-63) cannot be dirtied through the commonCommandTable due to packing of other flags. + DIRTY_ALL = 0xFFFFFFFFFFFFFFFF }; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 6345816604..7530e74aaf 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -41,6 +41,9 @@ std::string VertexShaderDesc(const VShaderID &id) { if (id.Bit(VS_BIT_LIGHTING_ENABLE)) { desc << "Light: "; } + if (id.Bit(VS_BIT_LIGHT_UBERSHADER)) { + desc << "LightUberShader "; + } for (int i = 0; i < 4; i++) { bool enabled = id.Bit(VS_BIT_LIGHT0_ENABLE + i) && id.Bit(VS_BIT_LIGHTING_ENABLE); if (enabled || (uvgMode == GE_TEXMAP_ENVIRONMENT_MAP && (ls0 == i || ls1 == i))) { @@ -125,13 +128,17 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, // doShadeMapping is stored as UVGenMode, and light type doesn't matter for shade mapping. id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate()); id.SetBit(VS_BIT_LIGHTING_ENABLE); - // Light bits - for (int i = 0; i < 4; i++) { - bool chanEnabled = gstate.isLightChanEnabled(i) != 0; - id.SetBit(VS_BIT_LIGHT0_ENABLE + i, chanEnabled); - if (chanEnabled) { - id.SetBits(VS_BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i)); - id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i)); + if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) { + id.SetBit(VS_BIT_LIGHT_UBERSHADER); + } else { + // Light bits + for (int i = 0; i < 4; i++) { + bool chanEnabled = gstate.isLightChanEnabled(i) != 0; + id.SetBit(VS_BIT_LIGHT0_ENABLE + i, chanEnabled); + if (chanEnabled) { + id.SetBits(VS_BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i)); + id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i)); + } } } } diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 999faf2e44..a27d7ad7fa 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -33,7 +33,11 @@ enum VShaderBit : uint8_t { VS_BIT_BONES = 22, // 3 should be enough, not 8 // 25 - 29 are free. VS_BIT_ENABLE_BONES = 30, - // 31 is free. + + // If this is set along with LIGHTING_ENABLE, all other lighting bits below + // are passed to the shader directly instead. + VS_BIT_LIGHT_UBERSHADER = 31, + VS_BIT_LIGHT0_COMP = 32, // 2 bits VS_BIT_LIGHT0_TYPE = 34, // 2 bits VS_BIT_LIGHT1_COMP = 36, // 2 bits diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index c3480e6fef..7d49c90234 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -279,8 +279,30 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) { Uint8x3ToFloat4_Alpha(ub->materialSpecular, gstate.materialspecular, std::max(0.0f, getFloat24(gstate.materialspecularcoef))); } if (dirtyUniforms & DIRTY_MATEMISSIVE) { - Uint8x3ToFloat4(ub->materialEmissive, gstate.materialemissive); + // We're not touching the fourth f32 here, because we store an u32 of control bits in it. + float temp[4]; + Uint8x3ToFloat4(temp, gstate.materialemissive); + memcpy(ub->materialEmissive, temp, 12); } + + if (dirtyUniforms & DIRTY_LIGHT_CONTROL) { + // Bit organization + // Bottom 4 bits are enable bits for each light. + // Then, for each light, comes 2 bits for "comp" and 2 bits for "type". + uint32_t lightControl = 0; + for (int i = 0; i < 4; i++) { + if (gstate.isLightChanEnabled(i)) { + lightControl |= 1 << i; + } + + u32 computation = (u32)gstate.getLightComputation(i); // 2 bits + u32 type = (u32)gstate.getLightType(i); // 2 bits + lightControl |= computation << (4 + i * 4); + lightControl |= type << (4 + i * 4 + 2); + } + ub->lightControl = lightControl; + } + for (int i = 0; i < 4; i++) { if (dirtyUniforms & (DIRTY_LIGHT0 << i)) { if (gstate.isDirectionalLight(i)) { diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index f64fc3417b..583a72513f 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -80,7 +80,8 @@ struct UB_VS_Lights { float ambientColor[4]; float materialDiffuse[4]; float materialSpecular[4]; - float materialEmissive[4]; + float materialEmissive[3]; + uint32_t lightControl; float lpos[4][4]; float ldir[4][4]; float latt[4][4]; @@ -95,6 +96,7 @@ R"( vec4 u_ambient; vec3 u_matdiffuse; vec4 u_matspecular; vec3 u_matemissive; + uint u_lightControl; // light ubershader vec3 u_lightpos0; vec3 u_lightpos1; vec3 u_lightpos2; diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 126d188500..99eb0e0b86 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -185,6 +185,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE); int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3); + bool lightUberShader = id.Bit(VS_BIT_LIGHT_UBERSHADER); + if (lightUberShader) { + _dbg_assert_(compat.bitwiseOps); + } + // Apparently we don't support bezier/spline together with bones. bool doBezier = id.Bit(VS_BIT_BEZIER) && !enableBones && useHWTransform; bool doSpline = id.Bit(VS_BIT_SPLINE) && !enableBones && useHWTransform; @@ -524,12 +529,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag *uniformMask |= DIRTY_UVSCALEOFFSET; } for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_OFF) { + if (lightUberShader || doLight[i] != LIGHT_OFF) { // This is needed for shade mapping WRITE(p, "uniform vec3 u_lightpos%i;\n", i); *uniformMask |= DIRTY_LIGHT0 << i; } - if (doLight[i] == LIGHT_FULL) { + if (lightUberShader || doLight[i] == LIGHT_FULL) { *uniformMask |= DIRTY_LIGHT0 << i; GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); @@ -728,7 +733,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " vec4 basis_u = tess_weights_u[weight_idx.x].basis;\n"); WRITE(p, " vec4 basis_v = tess_weights_v[weight_idx.y].basis;\n"); WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n"); - } else { WRITE(p, " int index_u, index_v;\n"); for (int i = 0; i < 4; i++) { @@ -987,6 +991,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag anySpots = true; } + if (lightUberShader) { + anySpots = true; + diffuseIsZero = false; + specularIsZero = false; + distanceNeeded = true; + } + if (!specularIsZero) { WRITE(p, " lowp vec3 lightSum1 = splat3(0.0);\n"); } @@ -1004,76 +1015,131 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } } - // Calculate lights if needed. If shade mapping is enabled, lights may need to be - // at least partially calculated. - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_FULL) - continue; - - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2)); - - if (type == GE_LIGHTTYPE_DIRECTIONAL) { - // We prenormalize light positions for directional lights. - WRITE(p, " toLight = u_lightpos%i;\n", i); - } else { - WRITE(p, " toLight = u_lightpos%i - worldpos;\n", i); - WRITE(p, " distance = length(toLight);\n"); - WRITE(p, " toLight /= distance;\n"); + if (lightUberShader) { + // TODO: Actually loop in the shader. For now, we write it all out. + for (int i = 0; i < 4; i++) { + p.F("if ((u_lightControl & %d) != 0) {\n", 1 << i); + p.F(" uint type = (u_lightControl >> %d) & 3;\n", 4 + 4 * i); + p.F(" uint comp = (u_lightControl >> %d) & 3;\n", 4 + 4 * i + 2); + p.C(" if (type == 0) {\n"); // GE_LIGHTTYPE_DIRECTIONAL + p.F(" toLight = u_lightpos%d;\n", i); + p.C(" } else {\n"); + p.F(" toLight = u_lightpos%d - worldpos;\n", i); + p.F(" distance = length(toLight);\n", i); + p.F(" toLight /= distance;\n", i); + p.C(" }\n"); + p.C(" ldot = dot(toLight, worldnormal);\n"); + p.C(" if (comp == 2) {\n"); // GE_LIGHTCOMP_ONLYPOWDIFFUSE + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + p.C(" }\n"); + p.C(" switch (type) {\n");// Attenuation + p.C(" case 1:\n"); // GE_LIGHTTYPE_POINT + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); + p.C(" break;\n"); + p.C(" case 2:\n"); // GE_LIGHTTYPE_SPOT + p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); + p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i); + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); + p.C(" } else {\n"); + p.C(" lightScale = 0.0;\n"); + p.C(" }\n"); + p.C(" break;\n"); + p.C(" default:\n"); // GE_LIGHTTYPE_DIRECTIONAL + p.C(" lightScale = 1.0;\n"); + p.C(" break;\n"); + p.C(" }\n"); + p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + p.C(" if (comp == 1) {\n"); // do specular + p.C(" if (ldot >= 0.0) {\n"); + p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + p.C(" if (ldot > 0.0)\n"); + p.F(" lightSum1 += u_lightspecular%i * %s * ldot * lightScale;\n", i, specularStr); + p.C(" }\n"); + p.C(" }\n"); + p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse) * lightScale;\n", i, ambientStr); + p.C(" }\n"); } + } else { + // Calculate lights if needed. If shade mapping is enabled, lights may need to be + // at least partially calculated. + for (int i = 0; i < 4; i++) { + if (doLight[i] != LIGHT_FULL) + continue; - bool doSpecular = comp == GE_LIGHTCOMP_BOTH; - bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; + GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); + GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - WRITE(p, " ldot = dot(toLight, worldnormal);\n"); - if (poweredDiffuse) { - // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. - // Seen in Tales of the World: Radiant Mythology (#2424.) - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); + if (type == GE_LIGHTTYPE_DIRECTIONAL) { + // We prenormalize light positions for directional lights. + p.F(" toLight = u_lightpos%i;\n", i); + } else { + p.F(" toLight = u_lightpos%i - worldpos;\n", i); + p.C(" distance = length(toLight);\n"); + p.C(" toLight /= distance;\n"); + } + + bool doSpecular = comp == GE_LIGHTCOMP_BOTH; + bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; + + p.C(" ldot = dot(toLight, worldnormal);\n"); + if (poweredDiffuse) { + // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. + // Seen in Tales of the World: Radiant Mythology (#2424.) + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + } + + const char *timesLightScale = " * lightScale"; + + // Attenuation + switch (type) { + case GE_LIGHTTYPE_DIRECTIONAL: + timesLightScale = ""; + break; + case GE_LIGHTTYPE_POINT: + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); + break; + case GE_LIGHTTYPE_SPOT: + case GE_LIGHTTYPE_UNKNOWN: + p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); + p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i); + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); + p.C(" } else {\n"); + p.C(" lightScale = 0.0;\n"); + p.C(" }\n"); + break; + default: + // ILLEGAL + break; + } + + p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + if (doSpecular) { + p.C(" if (ldot >= 0.0) {\n"); + p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + p.C(" if (ldot > 0.0)\n"); + p.F(" lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); + p.C(" }\n"); + } + p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); } - - const char *timesLightScale = " * lightScale"; - - // Attenuation - switch (type) { - case GE_LIGHTTYPE_DIRECTIONAL: - timesLightScale = ""; - break; - case GE_LIGHTTYPE_POINT: - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); - break; - case GE_LIGHTTYPE_SPOT: - case GE_LIGHTTYPE_UNKNOWN: - WRITE(p, " angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); - WRITE(p, " if (angle >= u_lightangle_spotCoef%i.x) {\n", i); - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); - WRITE(p, " } else {\n"); - WRITE(p, " lightScale = 0.0;\n"); - WRITE(p, " }\n"); - break; - default: - // ILLEGAL - break; - } - - WRITE(p, " diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); - if (doSpecular) { - WRITE(p, " if (ldot >= 0.0) {\n"); - WRITE(p, " ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); - WRITE(p, " if (ldot > 0.0)\n"); - WRITE(p, " lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); - WRITE(p, " }\n"); - } - WRITE(p, " lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); } if (enableLighting) { diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index a5fd84e696..bb78a5be96 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -103,10 +103,10 @@ const CommonCommandTableEntry commonCommandTable[] = { // 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 }, - { 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_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 }, @@ -450,6 +450,21 @@ void GPUCommon::UpdateCmdInfo() { 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.Supports(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); + } + } } void GPUCommon::BeginHostFrame() { @@ -3202,6 +3217,10 @@ u32 GPUCommon::CheckGPUFeatures() const { features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; } + if (draw_->GetDeviceCaps().fragmentShaderInt32Supported) { + features |= GPU_USE_LIGHT_UBERSHADER; + } + if (PSP_CoreParameter().compat.flags().ClearToRAM) { features |= GPU_USE_CLEAR_RAM_HACK; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 1127e38d91..36c27d72d7 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -314,6 +314,14 @@ protected: 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]; diff --git a/GPU/GPUState.h b/GPU/GPUState.h index db4a04d3cd..a10cfe790e 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -469,7 +469,8 @@ struct UVScale { // Might want to move this mechanism into the backend later. enum { GPU_SUPPORTS_DUALSOURCE_BLEND = FLAG_BIT(0), - // Free bits: 1-2 + GPU_USE_LIGHT_UBERSHADER = FLAG_BIT(1), + // Free bit: 2 GPU_SUPPORTS_VS_RANGE_CULLING = FLAG_BIT(3), GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4), GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5), From 96f054f0985e5dcd92f41af6585e29f7f44a7ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 18:30:27 +0200 Subject: [PATCH 197/326] Fix light ubershader for D3D11 and OpenGL, GLES unsigned/signed stuff --- GPU/Common/ShaderUniforms.cpp | 36 +++++---- GPU/Common/ShaderUniforms.h | 5 +- GPU/Common/VertexShaderGenerator.cpp | 111 ++++++++++++++------------- GPU/GLES/ShaderManagerGLES.cpp | 5 +- GPU/GLES/ShaderManagerGLES.h | 1 + 5 files changed, 85 insertions(+), 73 deletions(-) diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 7d49c90234..8bf1f69356 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -267,6 +267,24 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView } } +uint32_t PackLightControlBits() { + // Bit organization + // Bottom 4 bits are enable bits for each light. + // Then, for each light, comes 2 bits for "comp" and 2 bits for "type". + uint32_t lightControl = 0; + for (int i = 0; i < 4; i++) { + if (gstate.isLightChanEnabled(i)) { + lightControl |= 1 << i; + } + + u32 computation = (u32)gstate.getLightComputation(i); // 2 bits + u32 type = (u32)gstate.getLightType(i); // 2 bits + lightControl |= computation << (4 + i * 4); + lightControl |= type << (4 + i * 4 + 2); + } + return lightControl; +} + void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) { // Lighting if (dirtyUniforms & DIRTY_AMBIENT) { @@ -284,25 +302,9 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) { Uint8x3ToFloat4(temp, gstate.materialemissive); memcpy(ub->materialEmissive, temp, 12); } - if (dirtyUniforms & DIRTY_LIGHT_CONTROL) { - // Bit organization - // Bottom 4 bits are enable bits for each light. - // Then, for each light, comes 2 bits for "comp" and 2 bits for "type". - uint32_t lightControl = 0; - for (int i = 0; i < 4; i++) { - if (gstate.isLightChanEnabled(i)) { - lightControl |= 1 << i; - } - - u32 computation = (u32)gstate.getLightComputation(i); // 2 bits - u32 type = (u32)gstate.getLightType(i); // 2 bits - lightControl |= computation << (4 + i * 4); - lightControl |= type << (4 + i * 4 + 2); - } - ub->lightControl = lightControl; + ub->lightControl = PackLightControlBits(); } - for (int i = 0; i < 4; i++) { if (dirtyUniforms & (DIRTY_LIGHT0 << i)) { if (gstate.isDirectionalLight(i)) { diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 583a72513f..ab71309e93 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -13,7 +13,7 @@ enum : uint64_t { DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_COLORWRITEMASK | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA | DIRTY_BEZIERSPLINE | DIRTY_DEPAL, DIRTY_LIGHT_UNIFORMS = - DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 | + DIRTY_LIGHT_CONTROL | DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 | DIRTY_MATDIFFUSE | DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE | DIRTY_AMBIENT, }; @@ -96,7 +96,7 @@ R"( vec4 u_ambient; vec3 u_matdiffuse; vec4 u_matspecular; vec3 u_matemissive; - uint u_lightControl; // light ubershader + uint u_lightControl; // light ubershader vec3 u_lightpos0; vec3 u_lightpos1; vec3 u_lightpos2; @@ -143,3 +143,4 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms); void BoneUpdateUniforms(UB_VS_Bones *ub, uint64_t dirtyUniforms); +uint32_t PackLightControlBits(); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 99eb0e0b86..a5d3adf933 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -329,6 +329,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (doTexture) { WRITE(p, "vec4 u_uvscaleoffset : register(c%i);\n", CONST_VS_UVSCALEOFFSET); } + // No need for light ubershader support here, D3D9 doesn't do it. for (int i = 0; i < 4; i++) { if (doLight[i] != LIGHT_OFF) { // This is needed for shade mapping @@ -423,7 +424,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " vec3 v_color1 : COLOR1;\n"); if (enableFog) { - WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); + WRITE(p, " float v_fogdepth : TEXCOORD1;\n"); } if (compat.shaderLanguage == HLSL_D3D9) { WRITE(p, " vec4 gl_Position : POSITION;\n"); @@ -528,6 +529,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "uniform vec4 u_uvscaleoffset;\n"); *uniformMask |= DIRTY_UVSCALEOFFSET; } + if (lightUberShader) { + p.C("uniform uint u_lightControl;\n"); + *uniformMask |= DIRTY_LIGHT_CONTROL; + } for (int i = 0; i < 4; i++) { if (lightUberShader || doLight[i] != LIGHT_OFF) { // This is needed for shade mapping @@ -539,17 +544,17 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - if (type != GE_LIGHTTYPE_DIRECTIONAL) + if (lightUberShader || type != GE_LIGHTTYPE_DIRECTIONAL) WRITE(p, "uniform mediump vec3 u_lightatt%i;\n", i); - if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { + if (lightUberShader || type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { WRITE(p, "uniform mediump vec3 u_lightdir%i;\n", i); WRITE(p, "uniform mediump vec2 u_lightangle_spotCoef%i;\n", i); } WRITE(p, "uniform lowp vec3 u_lightambient%i;\n", i); WRITE(p, "uniform lowp vec3 u_lightdiffuse%i;\n", i); - if (comp == GE_LIGHTCOMP_BOTH) { + if (lightUberShader || comp == GE_LIGHTCOMP_BOTH) { WRITE(p, "uniform lowp vec3 u_lightspecular%i;\n", i); } } @@ -1018,55 +1023,55 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lightUberShader) { // TODO: Actually loop in the shader. For now, we write it all out. for (int i = 0; i < 4; i++) { - p.F("if ((u_lightControl & %d) != 0) {\n", 1 << i); - p.F(" uint type = (u_lightControl >> %d) & 3;\n", 4 + 4 * i); - p.F(" uint comp = (u_lightControl >> %d) & 3;\n", 4 + 4 * i + 2); - p.C(" if (type == 0) {\n"); // GE_LIGHTTYPE_DIRECTIONAL - p.F(" toLight = u_lightpos%d;\n", i); - p.C(" } else {\n"); - p.F(" toLight = u_lightpos%d - worldpos;\n", i); - p.F(" distance = length(toLight);\n", i); - p.F(" toLight /= distance;\n", i); - p.C(" }\n"); - p.C(" ldot = dot(toLight, worldnormal);\n"); - p.C(" if (comp == 2) {\n"); // GE_LIGHTCOMP_ONLYPOWDIFFUSE - p.C(" if (u_matspecular.a <= 0.0) {\n"); - p.C(" ldot = 1.0;\n"); - p.C(" } else {\n"); - p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - p.C(" }\n"); - p.C(" }\n"); - p.C(" switch (type) {\n");// Attenuation - p.C(" case 1:\n"); // GE_LIGHTTYPE_POINT - p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); - p.C(" break;\n"); - p.C(" case 2:\n"); // GE_LIGHTTYPE_SPOT - p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); - p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i); - p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); - p.C(" } else {\n"); - p.C(" lightScale = 0.0;\n"); - p.C(" }\n"); - p.C(" break;\n"); - p.C(" default:\n"); // GE_LIGHTTYPE_DIRECTIONAL - p.C(" lightScale = 1.0;\n"); - p.C(" break;\n"); - p.C(" }\n"); - p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); - p.C(" if (comp == 1) {\n"); // do specular - p.C(" if (ldot >= 0.0) {\n"); - p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); - p.C(" if (u_matspecular.a <= 0.0) {\n"); - p.C(" ldot = 1.0;\n"); - p.C(" } else {\n"); - p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - p.C(" }\n"); - p.C(" if (ldot > 0.0)\n"); - p.F(" lightSum1 += u_lightspecular%i * %s * ldot * lightScale;\n", i, specularStr); - p.C(" }\n"); - p.C(" }\n"); - p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse) * lightScale;\n", i, ambientStr); - p.C(" }\n"); + p.F(" if ((u_lightControl & %du) != 0u) { \n", 1 << i); + p.F(" uint type = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i); + p.F(" uint comp = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i + 2); + p.C(" if (type == 0u) {\n"); // GE_LIGHTTYPE_DIRECTIONAL + p.F(" toLight = u_lightpos%d;\n", i); + p.C(" } else {\n"); + p.F(" toLight = u_lightpos%d - worldpos;\n", i); + p.F(" distance = length(toLight);\n", i); + p.F(" toLight /= distance;\n", i); + p.C(" }\n"); + p.C(" ldot = dot(toLight, worldnormal);\n"); + p.C(" if (comp == 2u) {\n"); // GE_LIGHTCOMP_ONLYPOWDIFFUSE + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + p.C(" }\n"); + p.C(" switch (type) {\n"); // Attenuation + p.C(" case 1u:\n"); // GE_LIGHTTYPE_POINT + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); + p.C(" break;\n"); + p.C(" case 2u:\n"); // GE_LIGHTTYPE_SPOT + p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); + p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i); + p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); + p.C(" } else {\n"); + p.C(" lightScale = 0.0;\n"); + p.C(" }\n"); + p.C(" break;\n"); + p.C(" default:\n"); // GE_LIGHTTYPE_DIRECTIONAL + p.C(" lightScale = 1.0;\n"); + p.C(" break;\n"); + p.C(" }\n"); + p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + p.C(" if (comp == 1u) {\n"); // do specular + p.C(" if (ldot >= 0.0) {\n"); + p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); + p.C(" if (u_matspecular.a <= 0.0) {\n"); + p.C(" ldot = 1.0;\n"); + p.C(" } else {\n"); + p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + p.C(" }\n"); + p.C(" if (ldot > 0.0)\n"); + p.F(" lightSum1 += u_lightspecular%i * %s * ldot * lightScale;\n", i, specularStr); + p.C(" }\n"); + p.C(" }\n"); + p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse) * lightScale;\n", i, ambientStr); + p.C(" }\n"); } } else { // Calculate lights if needed. If shade mapping is enabled, lights may need to be diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 92dfa9488f..a0a8aac16d 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -152,6 +152,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, queries.push_back({ &u_uvscaleoffset, "u_uvscaleoffset" }); queries.push_back({ &u_texclamp, "u_texclamp" }); queries.push_back({ &u_texclampoff, "u_texclampoff" }); + queries.push_back({ &u_lightControl, "u_lightControl" }); for (int i = 0; i < 4; i++) { static const char * const lightPosNames[4] = { "u_lightpos0", "u_lightpos1", "u_lightpos2", "u_lightpos3", }; @@ -471,7 +472,6 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu } render_->SetUniformF(&u_fogcoef, 2, fogcoef); } - if (dirty & DIRTY_UVSCALEOFFSET) { const float invW = 1.0f / (float)gstate_c.curTextureWidth; const float invH = 1.0f / (float)gstate_c.curTextureHeight; @@ -605,6 +605,9 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu } // Lighting + if (dirty & DIRTY_LIGHT_CONTROL) { + render_->SetUniformUI1(&u_lightControl, PackLightControlBits()); + } if (dirty & DIRTY_AMBIENT) { SetColorUniform3Alpha(render_, &u_ambient, gstate.ambientcolor, gstate.getAmbientA()); } diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index 101326ee9b..2dd715cc0b 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -97,6 +97,7 @@ public: int u_texclampoff; // Lighting + int u_lightControl; int u_ambient; int u_matambientalpha; int u_matdiffuse; From f30252f8d5bfced81bdebc30d8c1af8a4a8ae26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 18:54:27 +0200 Subject: [PATCH 198/326] Oops! Also, testfix --- GPU/Common/VertexShaderGenerator.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index a5d3adf933..8263457d60 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -185,9 +185,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE); int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3); - bool lightUberShader = id.Bit(VS_BIT_LIGHT_UBERSHADER); - if (lightUberShader) { - _dbg_assert_(compat.bitwiseOps); + bool lightUberShader = id.Bit(VS_BIT_LIGHT_UBERSHADER) && enableLighting; // checking lighting here for the shader test's benefit, in reality if ubershader is set, lighting is set. + if (lightUberShader && !compat.bitwiseOps) { + *errorString = "Light ubershader requires bitwise ops in shader language"; + return false; } // Apparently we don't support bezier/spline together with bones. @@ -1022,10 +1023,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lightUberShader) { // TODO: Actually loop in the shader. For now, we write it all out. + // u_lightControl is computed in PackLightControlBits(). for (int i = 0; i < 4; i++) { p.F(" if ((u_lightControl & %du) != 0u) { \n", 1 << i); - p.F(" uint type = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i); - p.F(" uint comp = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i + 2); + p.F(" uint comp = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i); + p.F(" uint type = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i + 2); p.C(" if (type == 0u) {\n"); // GE_LIGHTTYPE_DIRECTIONAL p.F(" toLight = u_lightpos%d;\n", i); p.C(" } else {\n"); From c80f325912c10260a3dc0c6ae9c32793a3a1c5e0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 00:24:12 -0700 Subject: [PATCH 199/326] GPU: Fix SSE4 Vec3f normalize. Was sometimes adding in garbage data, which could create NANs. --- GPU/Math3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Math3D.cpp b/GPU/Math3D.cpp index 7da413829f..ecb959ec2c 100644 --- a/GPU/Math3D.cpp +++ b/GPU/Math3D.cpp @@ -121,7 +121,8 @@ __m128 SSENormalizeMultiplierSSE2(__m128 v) #endif __m128 SSENormalizeMultiplierSSE4(__m128 v) { - return _mm_rsqrt_ps(_mm_dp_ps(v, v, 0xFF)); + // This is only used for Vec3f, so ignore the 4th component, might be garbage. + return _mm_rsqrt_ps(_mm_dp_ps(v, v, 0x77)); } __m128 SSENormalizeMultiplier(bool useSSE4, __m128 v) From 9d1355e13737a6a7911f1caa281d7decabb6746b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 09:30:54 +0200 Subject: [PATCH 200/326] Always do the vertex shader part of the fog computation. In #16104, we drastically reduced the number of shader variants for games that use flexible lighting setups. I looked at a few games and it seems that a lot of games have the same shaders with fog on/off, while fog is super cheap to compute. So let's just always do it, reducing vertex shader variants further (though the amount of pipelines will probably remain the same, since we still specialize the fragment shader). Might also be worth adding a dynamic bool for the fragment shader, but if so, doing it separately. --- GPU/Common/ShaderId.cpp | 3 -- GPU/Common/ShaderId.h | 1 - GPU/Common/ShaderUniforms.h | 8 ++--- GPU/Common/VertexShaderGenerator.cpp | 51 ++++++++++------------------ GPU/GLES/ShaderManagerGLES.cpp | 1 - GPU/GPUCommon.cpp | 2 +- GPU/Vulkan/PipelineManagerVulkan.cpp | 3 +- GPU/Vulkan/ShaderManagerVulkan.cpp | 1 - 8 files changed, 23 insertions(+), 47 deletions(-) diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 7530e74aaf..5ed727a638 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -22,7 +22,6 @@ std::string VertexShaderDesc(const VShaderID &id) { if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T "; if (id.Bit(VS_BIT_HAS_NORMAL)) desc << "N "; if (id.Bit(VS_BIT_LMODE)) desc << "LM "; - if (id.Bit(VS_BIT_ENABLE_FOG)) desc << "Fog "; if (id.Bit(VS_BIT_NORM_REVERSE)) desc << "RevN "; if (id.Bit(VS_BIT_DO_TEXTURE)) desc << "Tex "; int uvgMode = id.Bits(VS_BIT_UVGEN_MODE, 2); @@ -83,14 +82,12 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, _assert_(hasNormal); } - bool enableFog = gstate.isFogEnabled() && !isModeThrough && !gstate.isModeClear(); bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough && !gstate.isModeClear(); bool vertexRangeCulling = gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING) && !isModeThrough && gstate_c.submitType == SubmitType::DRAW; // neither hw nor sw spline/bezier. See #11692 VShaderID id; id.SetBit(VS_BIT_LMODE, lmode); - id.SetBit(VS_BIT_ENABLE_FOG, enableFog); id.SetBit(VS_BIT_IS_THROUGH, isModeThrough); id.SetBit(VS_BIT_HAS_COLOR, hasColor); id.SetBit(VS_BIT_VERTEX_RANGE_CULLING, vertexRangeCulling); diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index a27d7ad7fa..b63f93739e 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -12,7 +12,6 @@ enum VShaderBit : uint8_t { VS_BIT_LMODE = 0, VS_BIT_IS_THROUGH = 1, - VS_BIT_ENABLE_FOG = 2, VS_BIT_HAS_COLOR = 3, VS_BIT_DO_TEXTURE = 4, VS_BIT_VERTEX_RANGE_CULLING = 5, diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index ab71309e93..3872461437 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -36,7 +36,7 @@ struct UB_VS_FS_Base { uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. uint32_t colorWriteMask; float mipBias; // Fragment data - float fogColor[4]; // .w is unused + float fogColor[4]; float texEnvColor[4]; // .w is unused int alphaColorRef[4]; int colorTestMask[4]; @@ -46,7 +46,7 @@ struct UB_VS_FS_Base { float texClampOffset[4]; // .zw are unused }; -static const char *ub_baseStr = +static const char * const ub_baseStr = R"( mat4 u_proj; mat4 u_proj_through; mat3x4 u_view; @@ -91,7 +91,7 @@ struct UB_VS_Lights { float lightSpecular[4][4]; }; -static const char *ub_vs_lightsStr = +static const char * const ub_vs_lightsStr = R"( vec4 u_ambient; vec3 u_matdiffuse; vec4 u_matspecular; @@ -133,7 +133,7 @@ struct UB_VS_Bones { float bones[8][12]; }; -static const char *ub_vs_bonesStr = +static const char * const ub_vs_bonesStr = R"( mat3x4 u_bone0; mat3x4 u_bone1; mat3x4 u_bone2; mat3x4 u_bone3; mat3x4 u_bone4; mat3x4 u_bone5; mat3x4 u_bone6; mat3x4 u_bone7; mat3x4 u_bone8; )"; diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 8263457d60..8de59ab191 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -177,7 +177,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform; bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform; - bool enableFog = id.Bit(VS_BIT_ENABLE_FOG); bool flipNormal = id.Bit(VS_BIT_NORM_REVERSE); int ls0 = id.Bits(VS_BIT_LS0, 2); int ls1 = id.Bits(VS_BIT_LS1, 2); @@ -258,7 +257,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (useHWTransform && hasNormal) WRITE(p, "layout (location = %d) in vec3 normal;\n", (int)PspAttributeLocation::NORMAL); - if (!useHWTransform && enableFog) + if (!useHWTransform) WRITE(p, "layout (location = %d) in float fog;\n", (int)PspAttributeLocation::NORMAL); if (doTexture && hasTexcoord) { @@ -283,11 +282,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "layout (location = 0) out highp vec3 v_texcoord;\n"); } - if (enableFog) { - // See the fragment shader generator - WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n"); - } - + WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n"); WRITE(p, "invariant gl_Position;\n"); } else if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) { @@ -306,9 +301,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "float u_rotation : register(c%i);\n", CONST_VS_ROTATION); } - if (enableFog) { - WRITE(p, "vec2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF); - } + WRITE(p, "vec2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF); if (useHWTransform || !hasColor) WRITE(p, "vec4 u_matambientalpha : register(c%i);\n", CONST_VS_MATAMBIENTALPHA); // matambient + matalpha @@ -409,9 +402,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lmode) { WRITE(p, " vec3 color1 : COLOR1;\n"); } - if (enableFog) { - WRITE(p, " float fog : NORMAL;\n"); - } + WRITE(p, " float fog : NORMAL;\n"); WRITE(p, "};\n"); } @@ -424,9 +415,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lmode) WRITE(p, " vec3 v_color1 : COLOR1;\n"); - if (enableFog) { - WRITE(p, " float v_fogdepth : TEXCOORD1;\n"); - } + WRITE(p, " float v_fogdepth : TEXCOORD1;\n"); if (compat.shaderLanguage == HLSL_D3D9) { WRITE(p, " vec4 gl_Position : POSITION;\n"); } else { @@ -468,7 +457,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "%s mediump vec3 normal;\n", compat.attribute); *attrMask |= 1 << ATTR_NORMAL; } - if (!useHWTransform && enableFog) { + if (!useHWTransform) { WRITE(p, "%s highp float fog;\n", compat.attribute); *attrMask |= 1 << ATTR_NORMAL; } @@ -584,10 +573,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "uniform lowp vec4 u_matambientalpha;\n"); // matambient + matalpha *uniformMask |= DIRTY_MATAMBIENTALPHA; } - if (enableFog) { - WRITE(p, "uniform highp vec2 u_fogcoef;\n"); - *uniformMask |= DIRTY_FOGCOEF; - } + WRITE(p, "uniform highp vec2 u_fogcoef;\n"); + *uniformMask |= DIRTY_FOGCOEF; if (!isModeThrough) { WRITE(p, "uniform highp vec4 u_depthRange;\n"); @@ -605,13 +592,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_vs, highpTexcoord ? "highp" : "mediump"); } - if (enableFog) { - // See the fragment shader generator - if (highpFog) { - WRITE(p, "%s highp float v_fogdepth;\n", compat.varying_vs); - } else { - WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs); - } + // See the fragment shader generator + if (highpFog) { + WRITE(p, "%s highp float v_fogdepth;\n", compat.varying_vs); + } else { + WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs); } } @@ -821,7 +806,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } else { WRITE(p, " vec4 position = In.position;\n"); } - if (!useHWTransform && enableFog) { + if (!useHWTransform) { WRITE(p, " float fog = In.fog;\n"); } if (enableBones) { @@ -847,9 +832,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lmode) WRITE(p, " %sv_color1 = splat3(0.0);\n", compat.vsOutPrefix); } - if (enableFog) { - WRITE(p, " %sv_fogdepth = fog;\n", compat.vsOutPrefix); - } + WRITE(p, " %sv_fogdepth = fog;\n", compat.vsOutPrefix); if (isModeThrough) { // The proj_through matrix already has the rotation, if needed. WRITE(p, " vec4 outPos = mul(u_proj_through, vec4(position.xyz, 1.0));\n"); @@ -1023,6 +1006,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (lightUberShader) { // TODO: Actually loop in the shader. For now, we write it all out. + // Will need to change how the data is stored to loop efficiently. // u_lightControl is computed in PackLightControlBits(). for (int i = 0; i < 4; i++) { p.F(" if ((u_lightControl & %du) != 0u) { \n", 1 << i); @@ -1271,8 +1255,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } // Compute fogdepth - if (enableFog) - WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix); + WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix); } if (clipClampedDepth) { diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index a0a8aac16d..2dbd9a6bd4 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -841,7 +841,6 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, if (ls == nullptr) { _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); _dbg_assert_(FSID.Bit(FS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE)); - _dbg_assert_(FSID.Bit(FS_BIT_ENABLE_FOG) == VSID.Bit(VS_BIT_ENABLE_FOG)); _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); // Check if we can link these. diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index bb78a5be96..00ac51f9c1 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -89,7 +89,7 @@ const CommonCommandTableEntry commonCommandTable[] = { // 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 }, { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE }, { 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. diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index c965130113..2ea530d25a 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -270,8 +270,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, } else { bool needsUV = vs->GetID().Bit(VS_BIT_DO_TEXTURE); bool needsColor1 = vs->GetID().Bit(VS_BIT_LMODE); - bool needsFog = vs->GetID().Bit(VS_BIT_ENABLE_FOG); - attributeCount = SetupVertexAttribsPretransformed(attrs, needsUV, needsColor1, needsFog); + attributeCount = SetupVertexAttribsPretransformed(attrs, needsUV, needsColor1, true); vertexStride = (int)sizeof(TransformedVertex); } diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index a9504977d1..acdeba7b4a 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -250,7 +250,6 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); _dbg_assert_(FSID.Bit(FS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE)); - _dbg_assert_(FSID.Bit(FS_BIT_ENABLE_FOG) == VSID.Bit(VS_BIT_ENABLE_FOG)); _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); // Just update uniforms if this is the same shader as last time. From 9c63973c9a10b47d5ca63ceb3779c89805b95f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 10:39:09 +0200 Subject: [PATCH 201/326] Minor debugger fix --- Windows/GEDebugger/TabState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index 63a7199ed6..0a50b4fd19 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -447,7 +447,7 @@ void FormatStateRow(wchar_t *dest, const TabStateRow &info, u32 value, bool enab swprintf(dest, 255, L"%S", texformats[value]); } else if ((value & 0xF) < (u32)ARRAY_SIZE(texformats)) { - swprintf(dest, 255, L"%S (extra bits %06x)", texformats[value & 0xF], value); + swprintf(dest, 255, L"%S (extra bits %06x)", texformats[value & 0xF], value & ~0xF); } else { swprintf(dest, 255, L"%06x", value); From 196f8e3461304da10c6ab8851cd1b5dbec0830d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 10:39:27 +0200 Subject: [PATCH 202/326] Prepare for dynamic mat update --- Common/GPU/Vulkan/VulkanDebug.cpp | 1 - GPU/Common/VertexShaderGenerator.cpp | 28 +++++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp index b52e4396cb..83b3d974c0 100644 --- a/Common/GPU/Vulkan/VulkanDebug.cpp +++ b/Common/GPU/Vulkan/VulkanDebug.cpp @@ -93,4 +93,3 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( // keep that behavior here. return false; } - diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 8de59ab191..5d2da2d06a 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -949,14 +949,16 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // TODO: Declare variables for dots for shade mapping if needed. - const char *ambientStr = (matUpdate & 1) && hasColor ? "color0" : "u_matambientalpha"; - const char *diffuseStr = (matUpdate & 2) && hasColor ? "color0.rgb" : "u_matdiffuse"; - const char *specularStr = (matUpdate & 4) && hasColor ? "color0.rgb" : "u_matspecular.rgb"; + const char *srcCol = "color0"; if (doBezier || doSpline) { // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. - ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "u_matambientalpha"; - diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "u_matdiffuse"; - specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "u_matspecular.rgb"; + srcCol = "tess"; + } + + p.F(" vec4 ambientColor = %s;\n", (matUpdate & 1) && hasColor ? srcCol : "u_matambientalpha"); + if (enableLighting) { + p.F(" vec3 diffuseColor = %s.rgb;\n", (matUpdate & 2) && hasColor ? srcCol : "u_matdiffuse"); + p.F(" vec3 specularColor = %s.rgb;\n", (matUpdate & 4) && hasColor ? srcCol : "u_matspecular"); } bool diffuseIsZero = true; @@ -964,7 +966,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool distanceNeeded = false; bool anySpots = false; if (enableLighting) { - WRITE(p, " lowp vec4 lightSum0 = u_ambient * %s + vec4(u_matemissive, 0.0);\n", ambientStr); + p.C(" lowp vec4 lightSum0 = u_ambient * ambientColor + vec4(u_matemissive, 0.0);\n"); for (int i = 0; i < 4; i++) { GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); @@ -1043,7 +1045,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag p.C(" lightScale = 1.0;\n"); p.C(" break;\n"); p.C(" }\n"); - p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + p.F(" diffuse = (u_lightdiffuse%i * diffuseColor) * max(ldot, 0.0);\n", i); p.C(" if (comp == 1u) {\n"); // do specular p.C(" if (ldot >= 0.0) {\n"); p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); @@ -1053,10 +1055,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); p.C(" }\n"); p.C(" if (ldot > 0.0)\n"); - p.F(" lightSum1 += u_lightspecular%i * %s * ldot * lightScale;\n", i, specularStr); + p.F(" lightSum1 += u_lightspecular%i * specularColor * ldot * lightScale;\n", i); p.C(" }\n"); p.C(" }\n"); - p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse) * lightScale;\n", i, ambientStr); + p.F(" lightSum0.rgb += (u_lightambient%i * ambientColor.rgb + diffuse) * lightScale;\n", i); p.C(" }\n"); } } else { @@ -1116,7 +1118,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag break; } - p.F(" diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + p.F(" diffuse = (u_lightdiffuse%i * diffuseColor) * max(ldot, 0.0);\n", i); if (doSpecular) { p.C(" if (ldot >= 0.0) {\n"); p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); @@ -1126,10 +1128,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); p.C(" }\n"); p.C(" if (ldot > 0.0)\n"); - p.F(" lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); + p.F(" lightSum1 += u_lightspecular%i * specularColor * ldot %s;\n", i, timesLightScale); p.C(" }\n"); } - p.F(" lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); + p.F(" lightSum0.rgb += (u_lightambient%i * ambientColor.rgb + diffuse)%s;\n", i, timesLightScale); } } From 76f03d30bf4c741d5dfb9c179f8c4e66b148d10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 11:07:19 +0200 Subject: [PATCH 203/326] Remove suspicious dirty flag --- GPU/GPUCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 00ac51f9c1..ce09871c6a 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -111,7 +111,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { 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 | DIRTY_FRAGMENTSHADER_STATE }, // TODO: This should not need to dirty the fragment shader? + { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, // These change both shaders so need flushing. { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, From ad1021ea4b3a695980068c83a765941085dd0f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 11:55:47 +0200 Subject: [PATCH 204/326] Add some recent flags to FragmentShaderDesc --- GPU/Common/ShaderId.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 5ed727a638..d530758759 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -248,6 +248,9 @@ std::string FragmentShaderDesc(const FShaderID &id) { else if (id.Bit(FS_BIT_ALPHA_TEST)) desc << "AlphaTest " << alphaTestFuncs[id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3)] << " "; if (id.Bit(FS_BIT_COLOR_AGAINST_ZERO)) desc << "ColorTest0 " << alphaTestFuncs[id.Bits(FS_BIT_COLOR_TEST_FUNC, 2)] << " "; // first 4 match; else if (id.Bit(FS_BIT_COLOR_TEST)) desc << "ColorTest " << alphaTestFuncs[id.Bits(FS_BIT_COLOR_TEST_FUNC, 2)] << " "; // first 4 match + if (id.Bit(FS_BIT_TEST_DISCARD_TO_ZERO)) desc << "TestDiscardToZero "; + if (id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL)) desc << "StencilDiscardWorkaround "; + if (id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4) != GE_LOGIC_COPY) desc << "ReplaceLogic "; return desc.str(); } From 07ca9e46565e439a47976590d078c84bf561a8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 11:20:17 +0200 Subject: [PATCH 205/326] Fold the "materialUpdate" flag into the light ubershader part. This reduces the number of vertex shaders and thus pipelines by quite a bit more in a few games, like Tekken and GoW, continuing the fight against shader compile stutter. The perf impact should be minimal if not positive due to less pipeline changes. GLES fixes Make the vertex input declarations match (always declare fog input). Fixes D3D11 validation Tess fix --- GPU/Common/FragmentShaderGenerator.cpp | 10 +++------- GPU/Common/ShaderId.cpp | 2 +- GPU/Common/ShaderUniforms.cpp | 5 +++++ GPU/Common/VertexShaderGenerator.cpp | 21 +++++++++++++++------ GPU/GPUCommon.cpp | 8 ++++++++ GPU/Vulkan/ShaderManagerVulkan.cpp | 2 +- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index e2088731af..04d8aa93ed 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -174,9 +174,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "layout (location = 1) %s in lowp vec4 v_color0;\n", shading); if (lmode) WRITE(p, "layout (location = 2) %s in lowp vec3 v_color1;\n", shading); - if (enableFog) { - WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n"); - } + WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n"); if (doTexture) { WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n"); } @@ -279,9 +277,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (lmode) { WRITE(p, " vec3 v_color1: COLOR1;\n"); } - if (enableFog) { - WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); - } + WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); if (needFragCoord) { if (compat.shaderLanguage == HLSL_D3D11) { WRITE(p, " vec4 pixelPos : SV_POSITION;\n"); @@ -394,8 +390,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (enableFog) { *uniformMask |= DIRTY_FOGCOLOR; WRITE(p, "uniform vec3 u_fogcolor;\n"); - WRITE(p, "%s %s float v_fogdepth;\n", compat.varying_fs, highpFog ? "highp" : "mediump"); } + WRITE(p, "%s %s float v_fogdepth;\n", compat.varying_fs, highpFog ? "highp" : "mediump"); if (doTexture) { WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump"); } diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index d530758759..bb8dd54237 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -123,11 +123,11 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, if (gstate.isLightingEnabled()) { // doShadeMapping is stored as UVGenMode, and light type doesn't matter for shade mapping. - id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate()); id.SetBit(VS_BIT_LIGHTING_ENABLE); if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) { id.SetBit(VS_BIT_LIGHT_UBERSHADER); } else { + id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate()); // Light bits for (int i = 0; i < 4; i++) { bool chanEnabled = gstate.isLightChanEnabled(i) != 0; diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 8bf1f69356..f5cc8ca6c9 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -271,6 +271,8 @@ uint32_t PackLightControlBits() { // Bit organization // Bottom 4 bits are enable bits for each light. // Then, for each light, comes 2 bits for "comp" and 2 bits for "type". + // At the end, at bit 20, we put the three material update bits. + uint32_t lightControl = 0; for (int i = 0; i < 4; i++) { if (gstate.isLightChanEnabled(i)) { @@ -282,6 +284,9 @@ uint32_t PackLightControlBits() { lightControl |= computation << (4 + i * 4); lightControl |= type << (4 + i * 4 + 2); } + + lightControl |= gstate.getMaterialUpdate() << 20; + return lightControl; } diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 5d2da2d06a..4b0a16977f 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -552,7 +552,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (enableLighting) { WRITE(p, "uniform lowp vec4 u_ambient;\n"); *uniformMask |= DIRTY_AMBIENT; - if ((matUpdate & 2) == 0 || !hasColor) { + if (lightUberShader || (matUpdate & 2) == 0 || !hasColor) { WRITE(p, "uniform lowp vec3 u_matdiffuse;\n"); *uniformMask |= DIRTY_MATDIFFUSE; } @@ -952,13 +952,22 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag const char *srcCol = "color0"; if (doBezier || doSpline) { // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. - srcCol = "tess"; + srcCol = "tess.col"; } - p.F(" vec4 ambientColor = %s;\n", (matUpdate & 1) && hasColor ? srcCol : "u_matambientalpha"); - if (enableLighting) { - p.F(" vec3 diffuseColor = %s.rgb;\n", (matUpdate & 2) && hasColor ? srcCol : "u_matdiffuse"); - p.F(" vec3 specularColor = %s.rgb;\n", (matUpdate & 4) && hasColor ? srcCol : "u_matspecular"); + if (lightUberShader && hasColor) { + p.F(" vec4 ambientColor = ((u_lightControl & (1u << 20u)) != 0u) ? %s : u_matambientalpha;\n", srcCol); + if (enableLighting) { + p.F(" vec3 diffuseColor = ((u_lightControl & (1u << 21u)) != 0u) ? %s.rgb : u_matdiffuse;\n", srcCol); + p.F(" vec3 specularColor = ((u_lightControl & (1u << 22u)) != 0u) ? %s.rgb : u_matspecular.rgb;\n", srcCol); + } + } else { + // This path also takes care of the lightUberShader && !hasColor path, because all comparisons fail. + p.F(" vec4 ambientColor = %s;\n", (matUpdate & 1) && hasColor ? srcCol : "u_matambientalpha"); + if (enableLighting) { + p.F(" vec3 diffuseColor = %s.rgb;\n", (matUpdate & 2) && hasColor ? srcCol : "u_matdiffuse"); + p.F(" vec3 specularColor = %s.rgb;\n", (matUpdate & 4) && hasColor ? srcCol : "u_matspecular"); + } } bool diffuseIsZero = true; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index ce09871c6a..56dbbbcf59 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -465,6 +465,14 @@ void GPUCommon::UpdateCmdInfo() { cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE); } } + + if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) { + cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_VERTEXSHADER_STATE); + cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_LIGHT_CONTROL); + } else { + cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_LIGHT_CONTROL); + cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_VERTEXSHADER_STATE); + } } void GPUCommon::BeginHostFrame() { diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index acdeba7b4a..6c39280a82 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -376,7 +376,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 27 +#define CACHE_VERSION 28 struct VulkanCacheHeader { uint32_t magic; uint32_t version; From f4b71e2dc72970f9a8b8f3b59d3db1465ca5c802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 10:33:14 +0200 Subject: [PATCH 206/326] Fragment shader uniforms: Pack color mask in 32 bits instead of expand to 128 bits. Allows us to save 16 bytes from the main uniform buffer, since there's free 32-bit spaces here and there to use. --- GPU/Common/FragmentShaderGenerator.cpp | 22 +++++++++++++++------- GPU/Common/ShaderUniforms.cpp | 2 +- GPU/Common/ShaderUniforms.h | 8 ++++---- GPU/GLES/ShaderManagerGLES.cpp | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 04d8aa93ed..42ce18162e 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -354,7 +354,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "uniform vec4 u_alphacolorref;\n"); if (compat.bitwiseOps && ((enableColorTest && !colorTestAgainstZero) || (enableAlphaTest && !alphaTestAgainstZero))) { *uniformMask |= DIRTY_ALPHACOLORMASK; - WRITE(p, "uniform ivec4 u_alphacolormask;\n"); + WRITE(p, "uniform uint u_alphacolormask;\n"); } } } @@ -458,6 +458,12 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "}\n"); } + if (compat.bitwiseOps && enableColorTest) { + p.C("ivec3 unpackIVec3(highp uint x) {\n"); + p.C(" return ivec3(x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF);\n"); + p.C("}\n"); + } + // PowerVR needs a custom modulo function. For some reason, this has far higher precision than the builtin one. if ((gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) && needShaderTexClamp) { WRITE(p, "float mymod(float a, float b) { return a - b * floor(a / b); }\n"); @@ -873,7 +879,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; if (alphaTestFuncs[alphaTestFunc][0] != '#') { if (compat.bitwiseOps) { - WRITE(p, " if ((roundAndScaleTo255i(v.a) & u_alphacolormask.a) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); + WRITE(p, " if ((roundAndScaleTo255i(v.a) & int(u_alphacolormask >> 24)) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { // Work around bad PVR driver problem where equality check + discard just doesn't work. if (alphaTestFunc != GE_COMP_NOTEQUAL) { @@ -932,8 +938,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (compat.shaderLanguage == HLSL_D3D11) { const char *test = colorTestFuncs[colorTestFunc]; WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uvec3 v_masked = v_scaled & u_alphacolormask.rgb;\n"); - WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & u_alphacolormask.rgb;\n"); + WRITE(p, " uvec3 colormask = unpackIVec3(u_alphacolormask);\n"); + WRITE(p, " uvec3 v_masked = v_scaled & colormask;\n"); + WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & colormask;\n"); // We have to test the components separately, or we get incorrect results. See #10629. WRITE(p, " if (v_masked.r %s colorTestRef.r && v_masked.g %s colorTestRef.g && v_masked.b %s colorTestRef.b) %s\n", test, test, test, discardStatement); } else if (compat.shaderLanguage == HLSL_D3D9) { @@ -943,12 +950,13 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) %s\n", test, test, test, discardStatement); } else if (compat.bitwiseOps) { WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); + WRITE(p, " uvec3 colormask = unpackIVec3(u_alphacolormask);\n"); if (compat.shaderLanguage == GLSL_VULKAN) { // Apparently GLES3 does not support vector bitwise ops, but Vulkan does? - WRITE(p, " if ((v_scaled & u_alphacolormask.rgb) %s (u_alphacolorref.rgb & u_alphacolormask.rgb)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + WRITE(p, " if ((v_scaled & colormask) %s (u_alphacolorref.rgb & colormask)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); } else { - const char *maskedFragColor = "ivec3(v_scaled.r & u_alphacolormask.r, v_scaled.g & u_alphacolormask.g, v_scaled.b & u_alphacolormask.b)"; - const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & u_alphacolormask.r, int(u_alphacolorref.g) & u_alphacolormask.g, int(u_alphacolorref.b) & u_alphacolormask.b)"; + const char *maskedFragColor = "ivec3(v_scaled.r & colormask.r, v_scaled.g & colormask.g, v_scaled.b & colormask.b)"; + const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & colormask.r, int(u_alphacolorref.g) & colormask.g, int(u_alphacolorref.b) & colormask.b)"; WRITE(p, " if (%s %s %s) %s\n", maskedFragColor, colorTestFuncs[colorTestFunc], maskedColorRef, discardStatement); } } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index f5cc8ca6c9..05683e5040 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -80,7 +80,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView Uint8x3ToInt4_Alpha(ub->alphaColorRef, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask()); } if (dirtyUniforms & DIRTY_ALPHACOLORMASK) { - Uint8x3ToInt4_Alpha(ub->colorTestMask, gstate.getColorTestMask(), gstate.getAlphaTestMask()); + ub->colorTestMask = gstate.getColorTestMask() | (gstate.getAlphaTestMask() << 24); } if (dirtyUniforms & DIRTY_FOGCOLOR) { Uint8x3ToFloat4(ub->fogColor, gstate.fogcolor); diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 3872461437..d17e62bc0b 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -36,10 +36,10 @@ struct UB_VS_FS_Base { uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. uint32_t colorWriteMask; float mipBias; // Fragment data - float fogColor[4]; - float texEnvColor[4]; // .w is unused + float fogColor[4]; // .w is unused + float texEnvColor[3]; + uint32_t colorTestMask; int alphaColorRef[4]; - int colorTestMask[4]; float blendFixA[4]; // .w is unused float blendFixB[4]; // .w is unused float texClamp[4]; @@ -66,8 +66,8 @@ R"( mat4 u_proj; float u_mipBias; vec3 u_fogcolor; vec3 u_texenv; + uint u_alphacolormask; ivec4 u_alphacolorref; - ivec4 u_alphacolormask; vec3 u_blendFixA; vec3 u_blendFixB; vec4 u_texclamp; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 2dbd9a6bd4..ccf15e9268 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -448,7 +448,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu SetColorUniform3Alpha255(render_, &u_alphacolorref, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask()); } if (dirty & DIRTY_ALPHACOLORMASK) { - SetColorUniform3iAlpha(render_, &u_alphacolormask, gstate.colortestmask, gstate.getAlphaTestMask()); + render_->SetUniformUI1(&u_alphacolormask, gstate.getColorTestMask() | (gstate.getAlphaTestMask() << 24)); } if (dirty & DIRTY_COLORWRITEMASK) { render_->SetUniformUI1(&u_colorWriteMask, ~((gstate.pmska << 24) | (gstate.pmskc & 0xFFFFFF))); From cfa427c37a0425c12445980ea35aafddab270b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 10:43:17 +0200 Subject: [PATCH 207/326] Shuffle constants around, squeezing them into gaps. Saves another 16 bytes. --- GPU/Common/ShaderUniforms.h | 23 +++++++++++------------ GPU/Vulkan/DrawEngineVulkan.cpp | 2 ++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index d17e62bc0b..14887ed263 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -20,7 +20,8 @@ enum : uint64_t { // TODO: Split into two structs, one for software transform and one for hardware transform, to save space. // Currently 512 bytes. Probably can't get to 256 (nVidia's UBO alignment). // Every line here is a 4-float. -struct UB_VS_FS_Base { + +struct alignas(16) UB_VS_FS_Base { float proj[16]; float proj_through[16]; float view[12]; @@ -29,7 +30,6 @@ struct UB_VS_FS_Base { float uvScaleOffset[4]; float depthRange[4]; // Rotation is used only for software transform. - float fogCoef[2]; float stencil; float rotation; float matAmbient[4]; float cullRangeMin[4]; float cullRangeMax[4]; @@ -40,10 +40,11 @@ struct UB_VS_FS_Base { float texEnvColor[3]; uint32_t colorTestMask; int alphaColorRef[4]; - float blendFixA[4]; // .w is unused - float blendFixB[4]; // .w is unused + float blendFixA[3]; float stencil; + float blendFixB[3]; float rotation; float texClamp[4]; - float texClampOffset[4]; // .zw are unused + float texClampOffset[2]; // .zw are unused + float fogCoef[2]; }; static const char * const ub_baseStr = @@ -54,9 +55,6 @@ R"( mat4 u_proj; mat3x4 u_texmtx; vec4 u_uvscaleoffset; vec4 u_depthRange; - vec2 u_fogcoef; - float u_stencilReplaceValue; - float u_rotation; vec4 u_matambientalpha; vec4 u_cullRangeMin; vec4 u_cullRangeMax; @@ -68,15 +66,16 @@ R"( mat4 u_proj; vec3 u_texenv; uint u_alphacolormask; ivec4 u_alphacolorref; - vec3 u_blendFixA; - vec3 u_blendFixB; + vec3 u_blendFixA; float u_stencilReplaceValue; + vec3 u_blendFixB; float u_rotation; vec4 u_texclamp; vec2 u_texclampoff; + vec2 u_fogcoef; )"; // 512 bytes. Would like to shrink more. Some colors only have 8-bit precision and we expand // them to float unnecessarily, could just as well expand in the shader. -struct UB_VS_Lights { +struct alignas(16) UB_VS_Lights { float ambientColor[4]; float materialDiffuse[4]; float materialSpecular[4]; @@ -129,7 +128,7 @@ R"( vec4 u_ambient; // With some cleverness, we could get away with uploading just half this when only the four or five first // bones are being used. This is 384b. -struct UB_VS_Bones { +struct alignas(16) UB_VS_Bones { float bones[8][12]; }; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index d086b60921..fc2fff8ed9 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -91,6 +91,8 @@ DrawEngineVulkan::DrawEngineVulkan(Draw::DrawContext *draw) indexGen.Setup(decIndex); InitDeviceObjects(); + + INFO_LOG(G3D, "sizeof(UB_VS_FS_Base) = %d", (int)sizeof(UB_VS_FS_Base)); } void DrawEngineVulkan::InitDeviceObjects() { From fc30b04430229ce84dda5fe5876ba1cdc17f6414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 17:53:56 +0200 Subject: [PATCH 208/326] ShaderUniforms: cleanup, put every "4-float" on a line for clarity --- GPU/Common/ShaderUniforms.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 14887ed263..04b6437dff 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -17,10 +17,8 @@ enum : uint64_t { DIRTY_MATDIFFUSE | DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE | DIRTY_AMBIENT, }; -// TODO: Split into two structs, one for software transform and one for hardware transform, to save space. -// Currently 512 bytes. Probably can't get to 256 (nVidia's UBO alignment). +// Currently 480 bytes. Probably can't get to 256 (nVidia's UBO alignment, also common in other vendors). // Every line here is a 4-float. - struct alignas(16) UB_VS_FS_Base { float proj[16]; float proj_through[16]; @@ -37,14 +35,12 @@ struct alignas(16) UB_VS_FS_Base { uint32_t colorWriteMask; float mipBias; // Fragment data float fogColor[4]; // .w is unused - float texEnvColor[3]; - uint32_t colorTestMask; + float texEnvColor[3]; uint32_t colorTestMask; int alphaColorRef[4]; float blendFixA[3]; float stencil; float blendFixB[3]; float rotation; float texClamp[4]; - float texClampOffset[2]; // .zw are unused - float fogCoef[2]; + float texClampOffset[2]; float fogCoef[2]; }; static const char * const ub_baseStr = From d9f74d2fb7cb5b018b229dadf76424991d36b4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Sep 2022 17:56:10 +0200 Subject: [PATCH 209/326] ivec->uvec, comment fix --- GPU/Common/FragmentShaderGenerator.cpp | 6 +++--- GPU/Vulkan/DrawEngineVulkan.cpp | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 42ce18162e..3f4cb608a7 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -459,8 +459,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (compat.bitwiseOps && enableColorTest) { - p.C("ivec3 unpackIVec3(highp uint x) {\n"); - p.C(" return ivec3(x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF);\n"); + p.C("uvec3 unpackUVec3(highp uint x) {\n"); + p.C(" return uvec3(x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF);\n"); p.C("}\n"); } @@ -938,7 +938,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (compat.shaderLanguage == HLSL_D3D11) { const char *test = colorTestFuncs[colorTestFunc]; WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uvec3 colormask = unpackIVec3(u_alphacolormask);\n"); + WRITE(p, " uvec3 colormask = unpackUVec3(u_alphacolormask);\n"); WRITE(p, " uvec3 v_masked = v_scaled & colormask;\n"); WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & colormask;\n"); // We have to test the components separately, or we get incorrect results. See #10629. diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index fc2fff8ed9..d086b60921 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -91,8 +91,6 @@ DrawEngineVulkan::DrawEngineVulkan(Draw::DrawContext *draw) indexGen.Setup(decIndex); InitDeviceObjects(); - - INFO_LOG(G3D, "sizeof(UB_VS_FS_Base) = %d", (int)sizeof(UB_VS_FS_Base)); } void DrawEngineVulkan::InitDeviceObjects() { From a19a057e8c110e42b04700e2ef4f8d10879beb6e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 25 Sep 2022 20:22:31 -0700 Subject: [PATCH 210/326] GPU: Consistently use uvec3 for colortest. --- GPU/Common/FragmentShaderGenerator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 3f4cb608a7..acba19bb0d 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -183,7 +183,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n"); } if (enableColorTest && !colorTestAgainstZero) { - WRITE(p, "ivec3 roundAndScaleTo255iv(in highp vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); + WRITE(p, "uvec3 roundAndScaleTo255iv(in highp vec3 x) { return uvec3(floor(x * 255.0 + 0.5)); }\n"); } WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n"); @@ -408,7 +408,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (enableColorTest && !colorTestAgainstZero) { if (compat.bitwiseOps) { - WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); + WRITE(p, "uvec3 roundAndScaleTo255iv(in vec3 x) { return uvec3(floor(x * 255.0 + 0.5)); }\n"); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); } else { @@ -949,8 +949,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " vec3 colortest = roundAndScaleTo255v(v.rgb);\n"); WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) %s\n", test, test, test, discardStatement); } else if (compat.bitwiseOps) { - WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uvec3 colormask = unpackIVec3(u_alphacolormask);\n"); + WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); + WRITE(p, " uvec3 colormask = unpackUVec3(u_alphacolormask);\n"); if (compat.shaderLanguage == GLSL_VULKAN) { // Apparently GLES3 does not support vector bitwise ops, but Vulkan does? WRITE(p, " if ((v_scaled & colormask) %s (u_alphacolorref.rgb & colormask)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); From 4329aaa31cbfe304b5dd9c589ec2925882b81c62 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 25 Sep 2022 20:34:35 -0700 Subject: [PATCH 211/326] GPU: Apply color test mask as a uint. This is simpler and allows us to unify paths better. --- GPU/Common/FragmentShaderGenerator.cpp | 38 ++++++++------------------ 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index acba19bb0d..297cccfb4b 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -183,7 +183,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n"); } if (enableColorTest && !colorTestAgainstZero) { - WRITE(p, "uvec3 roundAndScaleTo255iv(in highp vec3 x) { return uvec3(floor(x * 255.0 + 0.5)); }\n"); + WRITE(p, "uint roundAndScaleTo8x4(in highp vec3 x) { uvec3 u = uvec3(floor(x * 255.0 + 0.5)); return u.r | (u.g << 8) | (u.b << 16); }\n"); } WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n"); @@ -262,7 +262,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (enableColorTest) { if (compat.shaderLanguage == HLSL_D3D11) { - WRITE(p, "uvec3 roundAndScaleTo255iv(float3 x) { return (floor(x * 255.0f + 0.5f)); }\n"); + WRITE(p, "uint roundAndScaleTo8x4(float3 x) { uvec3 u = (floor(x * 255.0f + 0.5f)); return u.r | (u.g << 8) | (u.b << 16); }\n"); } else { WRITE(p, "vec3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); } @@ -408,7 +408,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (enableColorTest && !colorTestAgainstZero) { if (compat.bitwiseOps) { - WRITE(p, "uvec3 roundAndScaleTo255iv(in vec3 x) { return uvec3(floor(x * 255.0 + 0.5)); }\n"); + WRITE(p, "uint roundAndScaleTo8x4(in vec3 x) { uvec3 u = uvec3(floor(x * 255.0 + 0.5)); return u.r | (u.g << 8) | (u.b << 16); }\n"); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); } else { @@ -933,36 +933,22 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } } else { const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; - if (colorTestFuncs[colorTestFunc][0] != '#') { + const char *test = colorTestFuncs[colorTestFunc]; + if (test[0] != '#') { // TODO: Unify these paths better. - if (compat.shaderLanguage == HLSL_D3D11) { - const char *test = colorTestFuncs[colorTestFunc]; - WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uvec3 colormask = unpackUVec3(u_alphacolormask);\n"); - WRITE(p, " uvec3 v_masked = v_scaled & colormask;\n"); - WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & colormask;\n"); - // We have to test the components separately, or we get incorrect results. See #10629. - WRITE(p, " if (v_masked.r %s colorTestRef.r && v_masked.g %s colorTestRef.g && v_masked.b %s colorTestRef.b) %s\n", test, test, test, discardStatement); - } else if (compat.shaderLanguage == HLSL_D3D9) { - const char *test = colorTestFuncs[colorTestFunc]; + if (compat.shaderLanguage == HLSL_D3D9) { // TODO: Use a texture to lookup bitwise ops instead? WRITE(p, " vec3 colortest = roundAndScaleTo255v(v.rgb);\n"); WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) %s\n", test, test, test, discardStatement); } else if (compat.bitwiseOps) { - WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uvec3 colormask = unpackUVec3(u_alphacolormask);\n"); - if (compat.shaderLanguage == GLSL_VULKAN) { - // Apparently GLES3 does not support vector bitwise ops, but Vulkan does? - WRITE(p, " if ((v_scaled & colormask) %s (u_alphacolorref.rgb & colormask)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); - } else { - const char *maskedFragColor = "ivec3(v_scaled.r & colormask.r, v_scaled.g & colormask.g, v_scaled.b & colormask.b)"; - const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & colormask.r, int(u_alphacolorref.g) & colormask.g, int(u_alphacolorref.b) & colormask.b)"; - WRITE(p, " if (%s %s %s) %s\n", maskedFragColor, colorTestFuncs[colorTestFunc], maskedColorRef, discardStatement); - } + WRITE(p, " uint v_uint = roundAndScaleTo8x4(v.rgb);\n"); + WRITE(p, " uint v_masked = v_uint & u_alphacolormask;\n"); + WRITE(p, " uint colorTestRef = roundAndScaleTo8x4(u_alphacolorref.rgb) & u_alphacolormask;\n"); + WRITE(p, " if (v_masked %s colorTestRef) %s\n", test, discardStatement); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { - WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", test, discardStatement); } else { - WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", test, discardStatement); } } else { WRITE(p, " %s\n", discardStatement); From 4e31a469f02277e4e3b88dd1fa2a7e2f3305f649 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 07:40:17 -0700 Subject: [PATCH 212/326] Mpeg: Correct Au update size. --- Core/HLE/sceMpeg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 8aa429ba1d..e938e82605 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -125,7 +125,7 @@ struct SceMpegLLI }; void SceMpegAu::read(u32 addr) { - Memory::Memcpy(this, addr, sizeof(this), "SceMpegAu"); + Memory::Memcpy(this, addr, sizeof(*this), "SceMpegAu"); pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32); dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32); } @@ -133,7 +133,7 @@ void SceMpegAu::read(u32 addr) { void SceMpegAu::write(u32 addr) { pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32); dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32); - Memory::Memcpy(addr, this, sizeof(this), "SceMpegAu"); + Memory::Memcpy(addr, this, sizeof(*this), "SceMpegAu"); } /* From 1c0d66aef72c22aaa2001ad4470e755b411c6c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 20:47:55 +0200 Subject: [PATCH 213/326] Add compatibility flag for loading pixels on framebuffer create using nearest filtering Solves the last problem with the speedometers - so we can finally say: Fixes #8509 Render-to-CLUT for speedometers renders on top of an image that just comes from the underlying memory, so it's been drawn to the framebuffer with DrawPixels. That adds filtering so at higher resolutions, there's some blurring of the CLUT, causing artifacts. We can solve this two ways: either we force on lower-resolution-for-effects for Ridge Racer games, or we use nearest filtering when doing DrawPixels of the memory under a framebuffer. For best result, we do the latter. (The speedometers look even better with nearest filtering, but that's a more general issue of UI looking better that way). --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 6 +++++- assets/compat.ini | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index f86ebbbcea..94b4279a39 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -109,6 +109,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin); CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn); CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT); + CheckSetting(iniFile, gameID, "NearestFilteringOnFramebufferCreate", &flags_.NearestFilteringOnFramebufferCreate); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 0fee77b805..bd824b916d 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -89,6 +89,7 @@ struct CompatFlags { bool SplitFramebufferMargin; bool ForceLowerResolutionForEffectsOn; bool AllowDownloadCLUT; + bool NearestFilteringOnFramebufferCreate; }; struct VRCompat { diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 330eda4796..9a666f83f8 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1061,7 +1061,11 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int DrawTextureFlags flags; if (useBufferedRendering_ && vfb && vfb->fbo) { - flags = channel == RASTER_COLOR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; + if (channel == RASTER_DEPTH || PSP_CoreParameter().compat.flags().NearestFilteringOnFramebufferCreate) { + flags = DRAWTEX_NEAREST; + } else { + flags = DRAWTEX_LINEAR; + } draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); diff --git a/assets/compat.ini b/assets/compat.ini index 8457a6a36f..a86f7562b3 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1283,6 +1283,27 @@ ULJM05494 = true NPJH50143 = true ULJM05738 = true +[NearestFilteringOnFramebufferCreate] +# Ridge Racer speedometer dynamic CLUT problem - they rely on some palette entries +# from memory, and render to the rest of the palette. The palette entries loaded from memory +# must not be blurred by filtering, so nearest it is. See issue #8509 + +# Ridge Racer +ULJS00001 = true +ULUS10001 = true +UCKS45002 = true +UCES00002 = true +ULJS19002 = true +UCKS45053 = true +NPJH50140 = true + +# Ridge Racer 2 +ULJS00080 = true +UCKS45032 = true +UCES00422 = true +UCAS40273 = true +NPJH50366 = true + [AllowDownloadCLUT] # Temporary compatibility option, while working on the GPU CLUT-from-framebuffer path. # Not required for any games now that it works, but might be useful for development. From 9b46adb98501d321e43dc67761e38988da96e2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Sep 2022 22:51:16 +0200 Subject: [PATCH 214/326] Fix color test. Fixes the new color test bug reported in #13324, though doesn't fix that issue (didn't confirm it still is one). --- Common/GPU/Vulkan/VulkanFrameData.h | 16 ++++++++-------- GPU/Common/FragmentShaderGenerator.cpp | 7 +++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 9fdd865ebf..049f8d765c 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -48,16 +48,16 @@ struct FrameData { std::condition_variable fenceCondVar; bool readyForFence = true; - VkFence fence; - VkFence readbackFence; // Strictly speaking we might only need one global of these. + VkFence fence = VK_NULL_HANDLE; + VkFence readbackFence = VK_NULL_HANDLE; // Strictly speaking we might only need one global of these. // These are on different threads so need separate pools. - VkCommandPool cmdPoolInit; // Written to from main thread - VkCommandPool cmdPoolMain; // Written to from render thread, which also submits + VkCommandPool cmdPoolInit = VK_NULL_HANDLE; // Written to from main thread + VkCommandPool cmdPoolMain = VK_NULL_HANDLE; // Written to from render thread, which also submits - VkCommandBuffer initCmd; - VkCommandBuffer mainCmd; - VkCommandBuffer presentCmd; + VkCommandBuffer initCmd = VK_NULL_HANDLE; + VkCommandBuffer mainCmd = VK_NULL_HANDLE; + VkCommandBuffer presentCmd = VK_NULL_HANDLE; bool hasInitCommands = false; bool hasMainCommands = false; @@ -73,7 +73,7 @@ struct FrameData { // Profiling. QueueProfileContext profile; - bool profilingEnabled_; + bool profilingEnabled_ = false; void Init(VulkanContext *vulkan, int index); void Destroy(VulkanContext *vulkan); diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 297cccfb4b..5cf923be46 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -184,6 +184,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (enableColorTest && !colorTestAgainstZero) { WRITE(p, "uint roundAndScaleTo8x4(in highp vec3 x) { uvec3 u = uvec3(floor(x * 255.0 + 0.5)); return u.r | (u.g << 8) | (u.b << 16); }\n"); + WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n"); } WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n"); @@ -263,6 +264,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (enableColorTest) { if (compat.shaderLanguage == HLSL_D3D11) { WRITE(p, "uint roundAndScaleTo8x4(float3 x) { uvec3 u = (floor(x * 255.0f + 0.5f)); return u.r | (u.g << 8) | (u.b << 16); }\n"); + WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n"); } else { WRITE(p, "vec3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); } @@ -408,7 +410,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } if (enableColorTest && !colorTestAgainstZero) { if (compat.bitwiseOps) { - WRITE(p, "uint roundAndScaleTo8x4(in vec3 x) { uvec3 u = uvec3(floor(x * 255.0 + 0.5)); return u.r | (u.g << 8) | (u.b << 16); }\n"); + WRITE(p, "uint roundAndScaleTo8x4(in vec3 x) { uvec3 u = uvec3(floor(x * 255.99)); return u.r | (u.g << 8) | (u.b << 16); }\n"); + WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n"); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); } else { @@ -943,7 +946,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } else if (compat.bitwiseOps) { WRITE(p, " uint v_uint = roundAndScaleTo8x4(v.rgb);\n"); WRITE(p, " uint v_masked = v_uint & u_alphacolormask;\n"); - WRITE(p, " uint colorTestRef = roundAndScaleTo8x4(u_alphacolorref.rgb) & u_alphacolormask;\n"); + WRITE(p, " uint colorTestRef = packFloatsTo8x4(u_alphacolorref.rgb) & u_alphacolormask;\n"); WRITE(p, " if (v_masked %s colorTestRef) %s\n", test, discardStatement); } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", test, discardStatement); From b3c0f177e2cbe40b098247f38b62dc703891544b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 25 Sep 2022 23:04:00 -0700 Subject: [PATCH 215/326] softgpu: Save last tc/normal in vertex reading. Matches PSP behavior, reusing last set values. --- GPU/Software/Rasterizer.cpp | 2 -- GPU/Software/TransformUnit.cpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index c1084f31d5..f72d28e1db 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -226,7 +226,6 @@ static inline u8 ClampFogDepth(float fogdepth) { static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) { // All UV gen modes, by the time they get here, behave the same. - // TODO: What happens if vertex has no texture coordinates? // Note that for environment mapping, texture coordinates have been calculated during lighting float q0 = 1.f / v0.clippos.w; float q1 = 1.f / v1.clippos.w; @@ -241,7 +240,6 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& static inline void GetTextureCoordinates(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) { // All UV gen modes, by the time they get here, behave the same. - // TODO: What happens if vertex has no texture coordinates? // Note that for environment mapping, texture coordinates have been calculated during lighting. float q0 = 1.f / v0.clippos.w; float q1 = 1.f / v1.clippos.w; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 5a33b2865a..91cbcae827 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -317,24 +317,31 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState &state) { PROFILE_THIS_SCOPE("read_vert"); + // If we ever thread this, we'll have to change this. VertexData vertex; ModelCoords pos; // VertexDecoder normally scales z, but we want it unscaled. vreader.ReadPosThroughZ16(pos.AsArray()); + static Vec2f lastTC; if (state.readUV) { vreader.ReadUV(vertex.texturecoords.AsArray()); + lastTC = vertex.texturecoords; } else { - vertex.texturecoords.SetZero(); + vertex.texturecoords = lastTC; } - Vec3 normal; + Vec3f normal; + static Vec3f lastnormal; if (vreader.hasNormal()) { vreader.ReadNrm(normal.AsArray()); + lastnormal = normal; if (state.negateNormals) normal = -normal; + } else { + normal = lastnormal; } if (state.readWeights) { From 34a805601732280e0ece4f9ef4e7772dcf6bfa6e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 25 Sep 2022 23:27:53 -0700 Subject: [PATCH 216/326] GPU: Correct normalized zero normal proj map. Unlike lighting, this does not use 0, 0, 1. --- GPU/Common/SoftwareTransformCommon.cpp | 2 +- GPU/Common/VertexShaderGenerator.cpp | 4 ++-- GPU/Software/TransformUnit.cpp | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 562769d660..7e0e660052 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -372,7 +372,7 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt break; case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized normal as source - source = normal.NormalizedOr001(cpu_info.bSSE4_1); + source = normal.Normalized(cpu_info.bSSE4_1); if (!reader.hasNormal()) { ERROR_LOG_REPORT(G3D, "Normal projection mapping without normal?"); } diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 4b0a16977f..2a71611b1e 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1231,9 +1231,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag break; case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source if ((doBezier || doSpline) && hasNormalTess) - temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormalTess ? "-" : ""); + temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 0.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormalTess ? "-" : ""); else if (hasNormal) - temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : ""); + temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 0.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : ""); else temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; break; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 91cbcae827..dab785a1ea 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -430,7 +430,8 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState break; case GE_PROJMAP_NORMALIZED_NORMAL: - source = normal.NormalizedOr001(cpu_info.bSSE4_1); + // This does not use 0, 0, 1 if length is zero. + source = normal.Normalized(cpu_info.bSSE4_1); break; case GE_PROJMAP_NORMAL: From 97ae4ae7129558bd45716f34fe1fb5ab883e14d3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 01:00:54 -0700 Subject: [PATCH 217/326] GPU: Correct flat normal projection mapping. --- GPU/Common/SoftwareTransformCommon.cpp | 47 ++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 7e0e660052..5e3c0c5850 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -168,6 +168,29 @@ void SoftwareTransform::SetProjMatrix(float mtx[14], bool invertedX, bool invert projMatrix_.translateAndScale(trans, scale); } +static void ReadWeightedNormal(Vec3f &source, VertexReader &reader, u32 vertType, bool skinningEnabled) { + if (reader.hasNormal()) + reader.ReadNrm(source.AsArray()); + if (skinningEnabled) { + float weights[8]; + reader.ReadWeights(weights); + + // Have to recalculate this, unfortunately. Please use software skinning... + Vec3f nsum(0, 0, 0); + for (int i = 0; i < vertTypeGetNumBoneWeights(vertType); i++) { + if (weights[i] != 0.0f) { + Vec3f norm; + Norm3ByMatrix43(norm.AsArray(), source.AsArray(), gstate.boneMatrix + i * 12); + nsum += norm * weights[i]; + } + } + + source = nsum; + } + if (gstate.areNormalsReversed()) + source = -source; +} + void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int maxIndex, SoftwareTransformResult *result) { u8 *decoded = params_.decoded; TransformedVertex *transformed = params_.transformed; @@ -284,7 +307,7 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt } } else { float weights[8]; - // TODO: For flat, are weights from the provoking used for color/normal? + // For flat, we need the vertex weights. reader.Goto(index); reader.ReadWeights(weights); @@ -358,10 +381,8 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt case GE_TEXMAP_TEXTURE_MATRIX: { - // TODO: What's the correct behavior with flat shading? Provoked normal or real normal? - // Projection mapping - Vec3f source; + Vec3f source(0.0f, 0.0f, 1.0f); switch (gstate.getUVProjMode()) { case GE_PROJMAP_POSITION: // Use model space XYZ as source source = pos; @@ -372,14 +393,28 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt break; case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized normal as source - source = normal.Normalized(cpu_info.bSSE4_1); + // Flat uses the vertex normal, not provoking. + if (provokeIndOffset == 0) { + source = normal.Normalized(cpu_info.bSSE4_1); + } else { + reader.Goto(index); + ReadWeightedNormal(source, reader, vertType, skinningEnabled); + source.Normalize(); + } if (!reader.hasNormal()) { ERROR_LOG_REPORT(G3D, "Normal projection mapping without normal?"); } break; case GE_PROJMAP_NORMAL: // Use non-normalized normal as source! - source = normal; + // Flat uses the vertex normal, not provoking. + if (provokeIndOffset == 0) { + source = normal; + } else { + // Need to read the normal for this vertex and weight it again.. + reader.Goto(index); + ReadWeightedNormal(source, reader, vertType, skinningEnabled); + } if (!reader.hasNormal()) { ERROR_LOG_REPORT(G3D, "Normal projection mapping without normal?"); } From 8376176b2f7ae9e7f5e78f42b869bef33b4ac8c6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 16:50:40 -0700 Subject: [PATCH 218/326] softgpu: Split clippos out of rasterization vert. We don't use it, except w, at all in rasterization, so no need to keep it in the bin queue. --- GPU/Software/Clipper.cpp | 91 ++++++++++++++------------- GPU/Software/Clipper.h | 8 +-- GPU/Software/Rasterizer.cpp | 10 +-- GPU/Software/RasterizerRectangle.cpp | 92 ++++++++++++++-------------- GPU/Software/RasterizerRectangle.h | 8 +-- GPU/Software/TransformUnit.cpp | 79 ++++++++++++------------ GPU/Software/TransformUnit.h | 49 ++++++++------- 7 files changed, 174 insertions(+), 163 deletions(-) diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp index 6fcb2fe47b..39b7dd650a 100644 --- a/GPU/Software/Clipper.cpp +++ b/GPU/Software/Clipper.cpp @@ -45,7 +45,7 @@ inline bool different_signs(float x, float y) { return ((x <= 0 && y > 0) || (x > 0 && y <= 0)); } -inline float clip_dotprod(const VertexData &vert, float A, float B, float C, float D) { +inline float clip_dotprod(const ClipVertexData &vert, float A, float B, float C, float D) { return (vert.clippos.x * A + vert.clippos.y * B + vert.clippos.z * C + vert.clippos.w * D); } @@ -131,7 +131,7 @@ static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) { return false; } -void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner) { +void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) { if (!binner.State().throughMode) { // If any verts were outside range, throw the entire prim away. if (v0.OutsideRange() || v1.OutsideRange()) @@ -149,37 +149,37 @@ void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner) else if (outsidePos >= 2 || outsideNeg >= 2) return; - if (v0.fogdepth != v1.fogdepth) { + if (v0.v.fogdepth != v1.v.fogdepth) { // Rectangles seem to always use nearest along X for fog depth, but reversed. // TODO: Check exactness of middle. - VertexData vhalf0 = v1; - vhalf0.screenpos.x = v0.screenpos.x + (v1.screenpos.x - v0.screenpos.x) / 2; + VertexData vhalf0 = v1.v; + vhalf0.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2; - VertexData vhalf1 = v1; - vhalf1.screenpos.x = v0.screenpos.x + (v1.screenpos.x - v0.screenpos.x) / 2; - vhalf1.screenpos.y = v0.screenpos.y; + VertexData vhalf1 = v1.v; + vhalf1.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2; + vhalf1.screenpos.y = v0.v.screenpos.y; - VertexData vrev1 = v1; - vrev1.fogdepth = v0.fogdepth; + VertexData vrev1 = v1.v; + vrev1.fogdepth = v0.v.fogdepth; - binner.AddRect(v0, vhalf0); + binner.AddRect(v0.v, vhalf0); binner.AddRect(vhalf1, vrev1); } else { - binner.AddRect(v0, v1); + binner.AddRect(v0.v, v1.v); } } else { // through mode handling - if (Rasterizer::RectangleFastPath(v0, v1, binner)) { + if (Rasterizer::RectangleFastPath(v0.v, v1.v, binner)) { return; } else if (gstate.isModeClear() && !gstate.isDitherEnabled()) { - binner.AddClearRect(v0, v1); + binner.AddClearRect(v0.v, v1.v); } else { - binner.AddRect(v0, v1); + binner.AddRect(v0.v, v1.v); } } } -void ProcessPoint(const VertexData &v0, BinManager &binner) { +void ProcessPoint(const ClipVertexData &v0, BinManager &binner) { // If any verts were outside range, throw the entire prim away. if (!binner.State().throughMode) { if (v0.OutsideRange()) @@ -187,13 +187,13 @@ void ProcessPoint(const VertexData &v0, BinManager &binner) { } // Points need no clipping. Will be bounds checked in the rasterizer (which seems backwards?) - binner.AddPoint(v0); + binner.AddPoint(v0.v); } -void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) { +void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) { if (binner.State().throughMode) { // Actually, should clip this one too so we don't need to do bounds checks in the rasterizer. - binner.AddLine(v0, v1); + binner.AddLine(v0.v, v1.v); return; } @@ -216,24 +216,26 @@ void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) int mask1 = CalcClipMask(v1.clippos); int mask = mask0 | mask1; if ((mask & CLIP_NEG_Z_BIT) == 0) { - binner.AddLine(v0, v1); + binner.AddLine(v0.v, v1.v); return; } - VertexData ClippedVertices[2] = { v0, v1 }; - VertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] }; + ClipVertexData ClippedVertices[2] = { v0, v1 }; + ClipVertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] }; bool clipped = false; CLIP_LINE(CLIP_NEG_Z_BIT, 0, 0, 1, 1); - VertexData data[2] = { *Vertices[0], *Vertices[1] }; + ClipVertexData data[2] = { *Vertices[0], *Vertices[1] }; if (clipped) { - data[0].screenpos = TransformUnit::ClipToScreen(data[0].clippos); - data[1].screenpos = TransformUnit::ClipToScreen(data[1].clippos); + data[0].v.screenpos = TransformUnit::ClipToScreen(data[0].clippos); + data[1].v.screenpos = TransformUnit::ClipToScreen(data[1].clippos); + data[0].v.clipw = data[0].clippos.w; + data[1].v.clipw = data[1].clippos.w; } - binner.AddLine(data[0], data[1]); + binner.AddLine(data[0].v, data[1].v); } -void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner) { +void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner) { int mask = 0; if (!binner.State().throughMode) { // If any verts were outside range, throw the entire prim away. @@ -262,20 +264,20 @@ void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexDat if ((mask & CLIP_NEG_Z_BIT) == 0) { if (gstate.getShadeMode() == GE_SHADE_FLAT) { // So that the order of clipping doesn't matter... - VertexData corrected2 = v2; - corrected2.color0 = provoking.color0; - corrected2.color1 = provoking.color1; - binner.AddTriangle(v0, v1, corrected2); + VertexData corrected2 = v2.v; + corrected2.color0 = provoking.v.color0; + corrected2.color1 = provoking.v.color1; + binner.AddTriangle(v0.v, v1.v, corrected2); } else { - binner.AddTriangle(v0, v1, v2); + binner.AddTriangle(v0.v, v1.v, v2.v); } return; } enum { NUM_CLIPPED_VERTICES = 3, NUM_INDICES = NUM_CLIPPED_VERTICES + 3 }; - VertexData* Vertices[NUM_INDICES]; - VertexData ClippedVertices[NUM_INDICES]; + ClipVertexData* Vertices[NUM_INDICES]; + ClipVertexData ClippedVertices[NUM_INDICES]; for (int i = 0; i < NUM_INDICES; ++i) Vertices[i] = &ClippedVertices[i]; @@ -319,22 +321,25 @@ void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexDat for (int i = 0; i + 3 <= numIndices; i += 3) { if (indices[i] != SKIP_FLAG) { - VertexData &subv0 = *Vertices[indices[i + 0]]; - VertexData &subv1 = *Vertices[indices[i + 1]]; - VertexData &subv2 = *Vertices[indices[i + 2]]; + ClipVertexData &subv0 = *Vertices[indices[i + 0]]; + ClipVertexData &subv1 = *Vertices[indices[i + 1]]; + ClipVertexData &subv2 = *Vertices[indices[i + 2]]; if (clipped) { - subv0.screenpos = TransformUnit::ClipToScreen(subv0.clippos); - subv1.screenpos = TransformUnit::ClipToScreen(subv1.clippos); - subv2.screenpos = TransformUnit::ClipToScreen(subv2.clippos); + subv0.v.screenpos = TransformUnit::ClipToScreen(subv0.clippos); + subv1.v.screenpos = TransformUnit::ClipToScreen(subv1.clippos); + subv2.v.screenpos = TransformUnit::ClipToScreen(subv2.clippos); + subv0.v.clipw = subv0.clippos.w; + subv1.v.clipw = subv1.clippos.w; + subv2.v.clipw = subv2.clippos.w; } if (gstate.getShadeMode() == GE_SHADE_FLAT) { // So that the order of clipping doesn't matter... - subv2.color0 = provoking.color0; - subv2.color1 = provoking.color1; + subv2.v.color0 = provoking.v.color0; + subv2.v.color1 = provoking.v.color1; } - binner.AddTriangle(subv0, subv1, subv2); + binner.AddTriangle(subv0.v, subv1.v, subv2.v); } } } diff --git a/GPU/Software/Clipper.h b/GPU/Software/Clipper.h index c79fe7d730..587df83a42 100644 --- a/GPU/Software/Clipper.h +++ b/GPU/Software/Clipper.h @@ -26,9 +26,9 @@ class BinManager; namespace Clipper { -void ProcessPoint(const VertexData &v0, BinManager &binner); -void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner); -void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner); -void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner); +void ProcessPoint(const ClipVertexData &v0, BinManager &binner); +void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner); +void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner); +void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner); } diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index f72d28e1db..2da86fb45b 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -227,8 +227,8 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& // All UV gen modes, by the time they get here, behave the same. // Note that for environment mapping, texture coordinates have been calculated during lighting - float q0 = 1.f / v0.clippos.w; - float q1 = 1.f / v1.clippos.w; + float q0 = 1.f / v0.clipw; + float q1 = 1.f / v1.clipw; float wq0 = p * q0; float wq1 = (1.0f - p) * q1; @@ -241,9 +241,9 @@ static inline void GetTextureCoordinates(const VertexData &v0, const VertexData // All UV gen modes, by the time they get here, behave the same. // Note that for environment mapping, texture coordinates have been calculated during lighting. - float q0 = 1.f / v0.clippos.w; - float q1 = 1.f / v1.clippos.w; - float q2 = 1.f / v2.clippos.w; + float q0 = 1.f / v0.clipw; + float q1 = 1.f / v1.clipw; + float q2 = 1.f / v2.clipw; Vec4 wq0 = w0.Cast() * q0; Vec4 wq1 = w1.Cast() * q1; Vec4 wq2 = w2.Cast() * q2; diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index f312addea1..3703678169 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -393,16 +393,16 @@ bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &b return false; } -static bool AreCoordsRectangleCompatible(const RasterizerState &state, const VertexData &data0, const VertexData &data1) { - if (data1.color0 != data0.color0) +static bool AreCoordsRectangleCompatible(const RasterizerState &state, const ClipVertexData &data0, const ClipVertexData &data1) { + if (data1.v.color0 != data0.v.color0) return false; - if (data1.screenpos.z != data0.screenpos.z) { + if (data1.v.screenpos.z != data0.v.screenpos.z) { // Sometimes, we don't actually care about z. if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS) return false; } if (!state.throughMode) { - if (data1.color1 != data0.color1) + if (data1.v.color1 != data0.v.color1) return false; // This means it should be culled, outside range. if (data1.OutsideRange() || data0.OutsideRange()) @@ -414,26 +414,26 @@ static bool AreCoordsRectangleCompatible(const RasterizerState &state, const Ver if (data1.clippos.w - halftexel > data0.clippos.w || data1.clippos.w + halftexel < data0.clippos.w) return false; } - if (state.pixelID.applyFog && data1.fogdepth != data0.fogdepth) { + if (state.pixelID.applyFog && data1.v.fogdepth != data0.v.fogdepth) { // Similar to w, this only matters if they're farther apart than 1/255. static constexpr float foghalfstep = 0.5f / 255.0f; - if (data1.fogdepth - foghalfstep > data0.fogdepth || data1.fogdepth + foghalfstep < data0.fogdepth) + if (data1.v.fogdepth - foghalfstep > data0.v.fogdepth || data1.v.fogdepth + foghalfstep < data0.v.fogdepth) return false; } } return true; } -bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData data[4], int *tlIndex, int *brIndex) { +bool DetectRectangleFromStrip(const RasterizerState &state, const ClipVertexData data[4], int *tlIndex, int *brIndex) { // Color and Z must be flat. Also find the TL and BR meanwhile. int tl = 0, br = 0; for (int i = 1; i < 4; ++i) { if (!AreCoordsRectangleCompatible(state, data[i], data[0])) return false; - if (data[i].screenpos.x <= data[tl].screenpos.x && data[i].screenpos.y <= data[tl].screenpos.y) + if (data[i].v.screenpos.x <= data[tl].v.screenpos.x && data[i].v.screenpos.y <= data[tl].v.screenpos.y) tl = i; - if (data[i].screenpos.x >= data[br].screenpos.x && data[i].screenpos.y >= data[br].screenpos.y) + if (data[i].v.screenpos.x >= data[br].v.screenpos.x && data[i].v.screenpos.y >= data[br].v.screenpos.y) br = i; } @@ -442,36 +442,36 @@ bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData dat // OK, now let's look at data to detect rectangles. There are a few possibilities // but we focus on Darkstalkers for now. - if (data[0].screenpos.x == data[1].screenpos.x && - data[0].screenpos.y == data[2].screenpos.y && - data[2].screenpos.x == data[3].screenpos.x && - data[1].screenpos.y == data[3].screenpos.y) { + if (data[0].v.screenpos.x == data[1].v.screenpos.x && + data[0].v.screenpos.y == data[2].v.screenpos.y && + data[2].v.screenpos.x == data[3].v.screenpos.x && + data[1].v.screenpos.y == data[3].v.screenpos.y) { // Okay, this is in the shape of a rectangle, but what about texture? if (!state.enableTextures) return true; - if (data[0].texturecoords.x == data[1].texturecoords.x && - data[0].texturecoords.y == data[2].texturecoords.y && - data[2].texturecoords.x == data[3].texturecoords.x && - data[1].texturecoords.y == data[3].texturecoords.y) { + if (data[0].v.texturecoords.x == data[1].v.texturecoords.x && + data[0].v.texturecoords.y == data[2].v.texturecoords.y && + data[2].v.texturecoords.x == data[3].v.texturecoords.x && + data[1].v.texturecoords.y == data[3].v.texturecoords.y) { // It's a rectangle! return true; } return false; } // There's the other vertex order too... - if (data[0].screenpos.x == data[2].screenpos.x && - data[0].screenpos.y == data[1].screenpos.y && - data[1].screenpos.x == data[3].screenpos.x && - data[2].screenpos.y == data[3].screenpos.y) { + if (data[0].v.screenpos.x == data[2].v.screenpos.x && + data[0].v.screenpos.y == data[1].v.screenpos.y && + data[1].v.screenpos.x == data[3].v.screenpos.x && + data[2].v.screenpos.y == data[3].v.screenpos.y) { // Okay, this is in the shape of a rectangle, but what about texture? if (!state.enableTextures) return true; - if (data[0].texturecoords.x == data[2].texturecoords.x && - data[0].texturecoords.y == data[1].texturecoords.y && - data[1].texturecoords.x == data[3].texturecoords.x && - data[2].texturecoords.y == data[3].texturecoords.y) { + if (data[0].v.texturecoords.x == data[2].v.texturecoords.x && + data[0].v.texturecoords.y == data[1].v.texturecoords.y && + data[1].v.texturecoords.x == data[3].v.texturecoords.x && + data[2].v.texturecoords.y == data[3].v.texturecoords.y) { // It's a rectangle! return true; } @@ -480,7 +480,7 @@ bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData dat return false; } -bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data, int c, int *tlIndex, int *brIndex) { +bool DetectRectangleFromFan(const RasterizerState &state, const ClipVertexData *data, int c, int *tlIndex, int *brIndex) { // Color and Z must be flat. for (int i = 1; i < c; ++i) { if (!AreCoordsRectangleCompatible(state, data[i], data[0])) @@ -489,8 +489,8 @@ bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data // Check for the common case: a single TL-TR-BR-BL. if (c == 4) { - const auto &pos0 = data[0].screenpos, &pos1 = data[1].screenpos; - const auto &pos2 = data[2].screenpos, &pos3 = data[3].screenpos; + const auto &pos0 = data[0].v.screenpos, &pos1 = data[1].v.screenpos; + const auto &pos2 = data[2].v.screenpos, &pos3 = data[3].v.screenpos; if (pos0.x == pos3.x && pos1.x == pos2.x && pos0.y == pos1.y && pos3.y == pos2.y) { // Looking like yes. Set TL/BR based on y order first... *tlIndex = pos0.y > pos3.y ? 2 : 0; @@ -505,13 +505,13 @@ bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data if (!state.enableTextures) return true; - const auto &textl = data[*tlIndex].texturecoords, &textr = data[*tlIndex ^ 1].texturecoords; - const auto &texbl = data[*brIndex ^ 1].texturecoords, &texbr = data[*brIndex].texturecoords; + const auto &textl = data[*tlIndex].v.texturecoords, &textr = data[*tlIndex ^ 1].v.texturecoords; + const auto &texbl = data[*brIndex ^ 1].v.texturecoords, &texbr = data[*brIndex].v.texturecoords; if (textl.x == texbl.x && textr.x == texbr.x && textl.y == textr.y && texbl.y == texbr.y) { // Okay, the texture is also good, but let's avoid rotation issues. - const auto &postl = data[*tlIndex].screenpos; - const auto &posbr = data[*brIndex].screenpos; + const auto &postl = data[*tlIndex].v.screenpos; + const auto &posbr = data[*brIndex].v.screenpos; return textl.y < texbr.y && postl.y < posbr.y && textl.x < texbr.x && postl.x < posbr.x; } } @@ -520,26 +520,26 @@ bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data return false; } -bool DetectRectangleFromPair(const RasterizerState &state, const VertexData data[6], int *tlIndex, int *brIndex) { +bool DetectRectangleFromPair(const RasterizerState &state, const ClipVertexData data[6], int *tlIndex, int *brIndex) { // Color and Z must be flat. Also find the TL and BR meanwhile. int tl = 0, br = 0; for (int i = 1; i < 6; ++i) { if (!AreCoordsRectangleCompatible(state, data[i], data[0])) return false; - if (data[i].screenpos.x <= data[tl].screenpos.x && data[i].screenpos.y <= data[tl].screenpos.y) + if (data[i].v.screenpos.x <= data[tl].v.screenpos.x && data[i].v.screenpos.y <= data[tl].v.screenpos.y) tl = i; - if (data[i].screenpos.x >= data[br].screenpos.x && data[i].screenpos.y >= data[br].screenpos.y) + if (data[i].v.screenpos.x >= data[br].v.screenpos.x && data[i].v.screenpos.y >= data[br].v.screenpos.y) br = i; } *tlIndex = tl; *brIndex = br; - auto xat = [&](int i) { return data[i].screenpos.x; }; - auto yat = [&](int i) { return data[i].screenpos.y; }; - auto uat = [&](int i) { return data[i].texturecoords.x; }; - auto vat = [&](int i) { return data[i].texturecoords.y; }; + auto xat = [&](int i) { return data[i].v.screenpos.x; }; + auto yat = [&](int i) { return data[i].v.screenpos.y; }; + auto uat = [&](int i) { return data[i].v.texturecoords.x; }; + auto vat = [&](int i) { return data[i].v.texturecoords.y; }; // A likely order would be: TL, TR, BR, TL, BR, BL. We'd have the last index of each. // TODO: Make more generic. @@ -567,12 +567,12 @@ bool DetectRectangleFromPair(const RasterizerState &state, const VertexData data return false; } -bool DetectRectangleThroughModeSlices(const RasterizerState &state, const VertexData data[4]) { +bool DetectRectangleThroughModeSlices(const RasterizerState &state, const ClipVertexData data[4]) { // Color and Z must be flat. for (int i = 1; i < 4; ++i) { - if (!(data[i].color0 == data[0].color0)) + if (!(data[i].v.color0 == data[0].v.color0)) return false; - if (!(data[i].screenpos.z == data[0].screenpos.z)) { + if (!(data[i].v.screenpos.z == data[0].v.screenpos.z)) { // Sometimes, we don't actually care about z. if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS) return false; @@ -580,15 +580,15 @@ bool DetectRectangleThroughModeSlices(const RasterizerState &state, const Vertex } // Games very commonly use vertical strips of rectangles. Detect and combine. - const auto &tl1 = data[0].screenpos, &br1 = data[1].screenpos; - const auto &tl2 = data[2].screenpos, &br2 = data[3].screenpos; + const auto &tl1 = data[0].v.screenpos, &br1 = data[1].v.screenpos; + const auto &tl2 = data[2].v.screenpos, &br2 = data[3].v.screenpos; if (tl1.y == tl2.y && br1.y == br2.y && br1.y > tl1.y) { if (br1.x == tl2.x && tl1.x < br1.x && tl2.x < br2.x) { if (!state.enableTextures) return true; - const auto &textl1 = data[0].texturecoords, &texbr1 = data[1].texturecoords; - const auto &textl2 = data[2].texturecoords, &texbr2 = data[3].texturecoords; + const auto &textl1 = data[0].v.texturecoords, &texbr1 = data[1].v.texturecoords; + const auto &textl2 = data[2].v.texturecoords, &texbr2 = data[3].v.texturecoords; if (textl1.y != textl2.y || texbr1.y != texbr2.y || textl1.y > texbr1.y) return false; if (texbr1.x != textl2.x || textl1.x > texbr1.x || textl2.x > texbr2.x) diff --git a/GPU/Software/RasterizerRectangle.h b/GPU/Software/RasterizerRectangle.h index 6842215338..19e4b42b48 100644 --- a/GPU/Software/RasterizerRectangle.h +++ b/GPU/Software/RasterizerRectangle.h @@ -20,8 +20,8 @@ namespace Rasterizer { bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &binner); void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &state); - bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData data[4], int *tlIndex, int *brIndex); - bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data, int c, int *tlIndex, int *brIndex); - bool DetectRectangleFromPair(const RasterizerState &state, const VertexData data[6], int *tlIndex, int *brIndex); - bool DetectRectangleThroughModeSlices(const RasterizerState &state, const VertexData data[4]); + bool DetectRectangleFromStrip(const RasterizerState &state, const ClipVertexData data[4], int *tlIndex, int *brIndex); + bool DetectRectangleFromFan(const RasterizerState &state, const ClipVertexData *data, int c, int *tlIndex, int *brIndex); + bool DetectRectangleFromPair(const RasterizerState &state, const ClipVertexData data[6], int *tlIndex, int *brIndex); + bool DetectRectangleThroughModeSlices(const RasterizerState &state, const ClipVertexData data[4]); } diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index dab785a1ea..10a31c8bfb 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -102,22 +102,22 @@ void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVert transformUnit.SubmitPrimitive(nullptr, nullptr, prim, 0, vertTypeID, nullptr, this); for (int i = 0; i < vertexCount; i++) { - VertexData vert; + ClipVertexData vert; vert.clippos = ClipCoords(buffer[i].pos); - vert.texturecoords.x = buffer[i].u; - vert.texturecoords.y = buffer[i].v; + vert.v.texturecoords.x = buffer[i].u; + vert.v.texturecoords.y = buffer[i].v; if (gstate.isModeThrough()) { - vert.texturecoords.x *= gstate.getTextureWidth(0); - vert.texturecoords.y *= gstate.getTextureHeight(0); + vert.v.texturecoords.x *= gstate.getTextureWidth(0); + vert.v.texturecoords.y *= gstate.getTextureHeight(0); } else { vert.clippos.z *= 1.0f / 65535.0f; } - vert.color0 = buffer[i].color0_32; - vert.color1 = gstate.isUsingSecondaryColor() && !gstate.isModeThrough() ? buffer[i].color1_32 : 0; - vert.fogdepth = buffer[i].fog; - vert.screenpos.x = (int)(buffer[i].x * 16.0f); - vert.screenpos.y = (int)(buffer[i].y * 16.0f); - vert.screenpos.z = (u16)(u32)buffer[i].z; + vert.v.color0 = buffer[i].color0_32; + vert.v.color1 = gstate.isUsingSecondaryColor() && !gstate.isModeThrough() ? buffer[i].color1_32 : 0; + vert.v.fogdepth = buffer[i].fog; + vert.v.screenpos.x = (int)(buffer[i].x * 16.0f); + vert.v.screenpos.y = (int)(buffer[i].y * 16.0f); + vert.v.screenpos.z = (u16)(u32)buffer[i].z; transformUnit.SubmitImmVertex(vert, this); } @@ -315,10 +315,10 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { state->roundToScreen = &ClipToScreenInternal; } -VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState &state) { +ClipVertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState &state) { PROFILE_THIS_SCOPE("read_vert"); // If we ever thread this, we'll have to change this. - VertexData vertex; + ClipVertexData vertex; ModelCoords pos; // VertexDecoder normally scales z, but we want it unscaled. @@ -326,10 +326,10 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState static Vec2f lastTC; if (state.readUV) { - vreader.ReadUV(vertex.texturecoords.AsArray()); - lastTC = vertex.texturecoords; + vreader.ReadUV(vertex.v.texturecoords.AsArray()); + lastTC = vertex.v.texturecoords; } else { - vertex.texturecoords = lastTC; + vertex.v.texturecoords = lastTC; } Vec3f normal; @@ -366,12 +366,12 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState } if (vreader.hasColor0()) { - vreader.ReadColor0_8888((u8 *)&vertex.color0); + vreader.ReadColor0_8888((u8 *)&vertex.v.color0); } else { - vertex.color0 = gstate.getMaterialAmbientRGBA(); + vertex.v.color0 = gstate.getMaterialAmbientRGBA(); } - vertex.color1 = 0; + vertex.v.color1 = 0; if (state.enableTransform) { WorldCoords worldpos; @@ -396,18 +396,19 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState screenScaled = vertex.clippos.xyz() * state.screenScale / vertex.clippos.w + state.screenAdd; #endif bool outside_range_flag = false; - vertex.screenpos = state.roundToScreen(screenScaled, vertex.clippos, &outside_range_flag); + vertex.v.screenpos = state.roundToScreen(screenScaled, vertex.clippos, &outside_range_flag); if (outside_range_flag) { // We use this, essentially, as the flag. - vertex.screenpos.x = 0x7FFFFFFF; + vertex.v.screenpos.x = 0x7FFFFFFF; return vertex; } if (state.enableFog) { - vertex.fogdepth = Dot(state.posToFog, Vec4f(pos, 1.0f)); + vertex.v.fogdepth = Dot(state.posToFog, Vec4f(pos, 1.0f)); } else { - vertex.fogdepth = 1.0f; + vertex.v.fogdepth = 1.0f; } + vertex.v.clipw = vertex.clippos.w; Vec3 worldnormal; if (vreader.hasNormal()) { @@ -426,7 +427,7 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState break; case GE_PROJMAP_UV: - source = Vec3f(vertex.texturecoords, 0.0f); + source = Vec3f(vertex.v.texturecoords, 0.0f); break; case GE_PROJMAP_NORMALIZED_NORMAL: @@ -444,23 +445,23 @@ VertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformState break; } - // TODO: What about uv scale and offset? + // Note that UV scale/offset are not used in this mode. Vec3 stq = Vec3ByMatrix43(source, gstate.tgenMatrix); float z_recip = 1.0f / stq.z; - vertex.texturecoords = Vec2f(stq.x * z_recip, stq.y * z_recip); + vertex.v.texturecoords = Vec2f(stq.x * z_recip, stq.y * z_recip); } else if (state.uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP) { - Lighting::GenerateLightST(vertex, worldnormal); + Lighting::GenerateLightST(vertex.v, worldnormal); } PROFILE_THIS_SCOPE("light"); if (state.enableLighting) - Lighting::Process(vertex, worldpos, worldnormal, state.lightingState); + Lighting::Process(vertex.v, worldpos, worldnormal, state.lightingState); } else { - vertex.screenpos.x = (int)(pos[0] * SCREEN_SCALE_FACTOR); - vertex.screenpos.y = (int)(pos[1] * SCREEN_SCALE_FACTOR); - vertex.screenpos.z = pos[2]; - vertex.clippos.w = 1.f; - vertex.fogdepth = 1.f; + vertex.v.screenpos.x = (int)(pos[0] * SCREEN_SCALE_FACTOR); + vertex.v.screenpos.y = (int)(pos[1] * SCREEN_SCALE_FACTOR); + vertex.v.screenpos.z = pos[2]; + vertex.v.clipw = 1.0f; + vertex.v.fogdepth = 1.0f; } return vertex; @@ -511,7 +512,7 @@ public: } } - inline VertexData Read(int vtx) { + inline ClipVertexData Read(int vtx) { if (useIndices_) { if (useCache_) { return cached_[conv_(vtx) - lowerBound_]; @@ -531,13 +532,13 @@ protected: TransformUnit &transform_; uint16_t lowerBound_; uint16_t upperBound_; - static std::vector cached_; + static std::vector cached_; bool useIndices_ = false; bool useCache_ = false; }; // Static to reduce allocations mid-frame. -std::vector SoftwareVertexReader::cached_; +std::vector SoftwareVertexReader::cached_; void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine) { @@ -580,7 +581,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G if (vreader.IsThrough() && cullType == CullType::OFF && prim_type == GE_PRIM_TRIANGLES && data_index_ == 0 && vertex_count >= 6 && ((vertex_count) % 6) == 0) { // Some games send rectangles as a series of regular triangles. // We look for this, but only in throughmode. - VertexData buf[6]; + ClipVertexData buf[6]; int buf_index = data_index_; for (int i = 0; i < data_index_; ++i) { buf[i] = data_[i]; @@ -831,7 +832,7 @@ void TransformUnit::SubmitPrimitive(const void* vertices, const void* indices, G } } -void TransformUnit::SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine *drawEngine) { +void TransformUnit::SubmitImmVertex(const ClipVertexData &vert, SoftwareDrawEngine *drawEngine) { // Where we put it is different for STRIP/FAN types. switch (prev_prim_) { case GE_PRIM_POINTS: @@ -872,7 +873,7 @@ void TransformUnit::SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine * isImmDraw_ = false; } -void TransformUnit::SendTriangle(CullType cullType, const VertexData *verts, int provoking) { +void TransformUnit::SendTriangle(CullType cullType, const ClipVertexData *verts, int provoking) { if (cullType == CullType::OFF) { Clipper::ProcessTriangle(verts[0], verts[1], verts[2], verts[provoking], *binner_); Clipper::ProcessTriangle(verts[2], verts[1], verts[0], verts[provoking], *binner_); diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index 644dd0079a..9983bae101 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -78,28 +78,33 @@ struct DrawingCoords { s16 y; }; -struct VertexData { - void Lerp(float t, const VertexData &a, const VertexData &b) { - clippos = ::Lerp(a.clippos, b.clippos, t); - // Ignore screenpos because Lerp() is only used pre-calculation of screenpos. - texturecoords = ::Lerp(a.texturecoords, b.texturecoords, t); - fogdepth = ::Lerp(a.fogdepth, b.fogdepth, t); - - u16 t_int = (u16)(t * 256); - color0 = LerpInt, 256>(Vec4::FromRGBA(a.color0), Vec4::FromRGBA(b.color0), t_int).ToRGBA(); - color1 = LerpInt, 256>(Vec3::FromRGB(a.color1), Vec3::FromRGB(b.color1), t_int).ToRGB(); - } - - bool OutsideRange() const { - return screenpos.x == 0x7FFFFFFF; - } - - ClipCoords clippos; +struct alignas(16) VertexData { Vec2 texturecoords; uint32_t color0; uint32_t color1; - ScreenCoords screenpos; // TODO: Shouldn't store this ? + ScreenCoords screenpos; float fogdepth; + float clipw; +}; + +struct ClipVertexData { + void Lerp(float t, const ClipVertexData &a, const ClipVertexData &b) { + clippos = ::Lerp(a.clippos, b.clippos, t); + // Ignore screenpos because Lerp() is only used pre-calculation of screenpos. + v.texturecoords = ::Lerp(a.v.texturecoords, b.v.texturecoords, t); + v.fogdepth = ::Lerp(a.v.fogdepth, b.v.fogdepth, t); + + u16 t_int = (u16)(t * 256); + v.color0 = LerpInt, 256>(Vec4::FromRGBA(a.v.color0), Vec4::FromRGBA(b.v.color0), t_int).ToRGBA(); + v.color1 = LerpInt, 256>(Vec3::FromRGB(a.v.color1), Vec3::FromRGB(b.v.color1), t_int).ToRGB(); + } + + bool OutsideRange() const { + return v.screenpos.x == 0x7FFFFFFF; + } + + ClipCoords clippos; + VertexData v; }; class VertexReader; @@ -130,7 +135,7 @@ public: static ScreenCoords DrawingToScreen(const DrawingCoords &coords, u16 z); void SubmitPrimitive(const void* vertices, const void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine); - void SubmitImmVertex(const VertexData &vert, SoftwareDrawEngine *drawEngine); + void SubmitImmVertex(const ClipVertexData &vert, SoftwareDrawEngine *drawEngine); bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices); @@ -144,14 +149,14 @@ public: SoftDirty GetDirty(); private: - VertexData ReadVertex(VertexReader &vreader, const TransformState &state); - void SendTriangle(CullType cullType, const VertexData *verts, int provoking = 2); + ClipVertexData ReadVertex(VertexReader &vreader, const TransformState &state); + void SendTriangle(CullType cullType, const ClipVertexData *verts, int provoking = 2); u8 *decoded_ = nullptr; BinManager *binner_ = nullptr; // Normally max verts per prim is 3, but we temporarily need 4 to detect rectangles from strips. - VertexData data_[4]; + ClipVertexData data_[4]; // This is the index of the next vert in data (or higher, may need modulus.) int data_index_ = 0; GEPrimitiveType prev_prim_ = GE_PRIM_POINTS; From 6282f8b05f509aaf29471f580c4818ccdb16232e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 17:13:14 -0700 Subject: [PATCH 219/326] softgpu: Expand texture coords to include q. We'll need this to correctly project. --- GPU/Software/Rasterizer.cpp | 11 +++++++---- GPU/Software/RasterizerRectangle.cpp | 5 ++++- GPU/Software/TransformUnit.cpp | 9 ++++++--- GPU/Software/TransformUnit.h | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 2da86fb45b..8101e63f00 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -233,6 +233,7 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& float wq1 = (1.0f - p) * q1; float q_recip = 1.0f / (wq0 + wq1); + // TODO: Handle projection. s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip; t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip; } @@ -249,6 +250,7 @@ static inline void GetTextureCoordinates(const VertexData &v0, const VertexData Vec4 wq2 = w2.Cast() * q2; Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal(); + // TODO: Handle projection. s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip); t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip); } @@ -770,8 +772,9 @@ void DrawRectangle(const VertexData &v0, const VertexData &v1, const BinCoords & Vec2f stx(0.0f, 0.0f); Vec2f sty(0.0f, 0.0f); if (state.enableTextures) { - Vec2f tc0 = v0.texturecoords; - Vec2f tc1 = v1.texturecoords; + // TODO: Handle projection. + Vec2f tc0 = v0.texturecoords.uv(); + Vec2f tc1 = v1.texturecoords.uv(); if (state.throughMode) { // For levels > 0, mipmapping is always based on level 0. Simpler to scale first. tc0.s() *= 1.0f / (float)(1 << state.samplerID.width0Shift); @@ -1268,8 +1271,8 @@ void DrawLine(const VertexData &v0, const VertexData &v1, const BinCoords &range float s, s1; float t, t1; if (state.throughMode) { - Vec2 tc = (v0.texturecoords * (float)(steps - i) + v1.texturecoords * (float)i) / steps1; - Vec2 tc1 = (v0.texturecoords * (float)(steps - i - 1) + v1.texturecoords * (float)(i + 1)) / steps1; + Vec2 tc = (v0.texturecoords.uv() * (float)(steps - i) + v1.texturecoords.uv() * (float)i) / steps1; + Vec2 tc1 = (v0.texturecoords.uv() * (float)(steps - i - 1) + v1.texturecoords.uv() * (float)(i + 1)) / steps1; s = tc.s() * (1.0f / (float)(1 << state.samplerID.width0Shift)); s1 = tc1.s() * (1.0f / (float)(1 << state.samplerID.width0Shift)); diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 3703678169..5cc40566d6 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -356,7 +356,7 @@ bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &b // Currently only works for TL/BR, which is the most common but not required. bool orient_check = xdiff >= 0 && ydiff >= 0; // We already have a fast path for clear in ClearRectangle. - bool state_check = state.throughMode && !state.pixelID.clearMode && !state.samplerID.hasAnyMips && NoClampOrWrap(state, v0.texturecoords) && NoClampOrWrap(state, v1.texturecoords); + bool state_check = state.throughMode && !state.pixelID.clearMode && !state.samplerID.hasAnyMips && NoClampOrWrap(state, v0.texturecoords.uv()) && NoClampOrWrap(state, v1.texturecoords.uv()); // This doesn't work well with offset drawing, see #15876. Through never has a subpixel offset. bool subpixel_check = ((v0.screenpos.x | v0.screenpos.y | v1.screenpos.x | v1.screenpos.y) & 0xF) == 0; if ((coord_check || !state.enableTextures) && orient_check && state_check && subpixel_check) { @@ -414,6 +414,9 @@ static bool AreCoordsRectangleCompatible(const RasterizerState &state, const Cli if (data1.clippos.w - halftexel > data0.clippos.w || data1.clippos.w + halftexel < data0.clippos.w) return false; } + // If we're projecting textures, only allow an exact match for simplicity. + if (state.enableTextures && data1.v.texturecoords.q() != data0.v.texturecoords.q()) + return false; if (state.pixelID.applyFog && data1.v.fogdepth != data0.v.fogdepth) { // Similar to w, this only matters if they're farther apart than 1/255. static constexpr float foghalfstep = 0.5f / 255.0f; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 10a31c8bfb..9fd3540cf9 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -106,12 +106,14 @@ void SoftwareDrawEngine::DispatchSubmitImm(GEPrimitiveType prim, TransformedVert vert.clippos = ClipCoords(buffer[i].pos); vert.v.texturecoords.x = buffer[i].u; vert.v.texturecoords.y = buffer[i].v; + vert.v.texturecoords.z = buffer[i].uv_w; if (gstate.isModeThrough()) { vert.v.texturecoords.x *= gstate.getTextureWidth(0); vert.v.texturecoords.y *= gstate.getTextureHeight(0); } else { vert.clippos.z *= 1.0f / 65535.0f; } + vert.v.clipw = buffer[i].pos_w; vert.v.color0 = buffer[i].color0_32; vert.v.color1 = gstate.isUsingSecondaryColor() && !gstate.isModeThrough() ? buffer[i].color1_32 : 0; vert.v.fogdepth = buffer[i].fog; @@ -324,9 +326,10 @@ ClipVertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformS // VertexDecoder normally scales z, but we want it unscaled. vreader.ReadPosThroughZ16(pos.AsArray()); - static Vec2f lastTC; + static Vec3Packedf lastTC; if (state.readUV) { vreader.ReadUV(vertex.v.texturecoords.AsArray()); + vertex.v.texturecoords.q() = 0.0f; lastTC = vertex.v.texturecoords; } else { vertex.v.texturecoords = lastTC; @@ -427,7 +430,7 @@ ClipVertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformS break; case GE_PROJMAP_UV: - source = Vec3f(vertex.v.texturecoords, 0.0f); + source = Vec3f(vertex.v.texturecoords.uv(), 0.0f); break; case GE_PROJMAP_NORMALIZED_NORMAL: @@ -448,7 +451,7 @@ ClipVertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformS // Note that UV scale/offset are not used in this mode. Vec3 stq = Vec3ByMatrix43(source, gstate.tgenMatrix); float z_recip = 1.0f / stq.z; - vertex.v.texturecoords = Vec2f(stq.x * z_recip, stq.y * z_recip); + vertex.v.texturecoords = Vec3Packedf(stq.x * z_recip, stq.y * z_recip, 1.0f); } else if (state.uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP) { Lighting::GenerateLightST(vertex.v, worldnormal); } diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index 9983bae101..b3d2e9b06d 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -79,12 +79,12 @@ struct DrawingCoords { }; struct alignas(16) VertexData { - Vec2 texturecoords; + Vec3Packedf texturecoords; + float clipw; uint32_t color0; uint32_t color1; ScreenCoords screenpos; float fogdepth; - float clipw; }; struct ClipVertexData { From faa6c2d46154e47dbacea3976ab4af6be6d2356c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 18:12:20 -0700 Subject: [PATCH 220/326] softgpu: Implement triangle texture projection. --- GPU/Software/Rasterizer.cpp | 47 +++++++++++++++++++++++++++++----- GPU/Software/Rasterizer.h | 1 + GPU/Software/SoftGpu.cpp | 2 +- GPU/Software/TransformUnit.cpp | 10 +++----- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 8101e63f00..950642f6d4 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -129,6 +129,7 @@ void ComputeRasterizerState(RasterizerState *state) { state->mipFilt = gstate.isMipmapFilteringEnabled(); state->minFilt = gstate.isMinifyFilteringEnabled(); state->magFilt = gstate.isMagnifyFilteringEnabled(); + state->textureProj = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; } state->shadeGouraud = gstate.getShadeMode() == GE_SHADE_GOURAUD; @@ -224,8 +225,6 @@ static inline u8 ClampFogDepth(float fogdepth) { } static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) { - // All UV gen modes, by the time they get here, behave the same. - // Note that for environment mapping, texture coordinates have been calculated during lighting float q0 = 1.f / v0.clipw; float q1 = 1.f / v1.clipw; @@ -233,14 +232,26 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& float wq1 = (1.0f - p) * q1; float q_recip = 1.0f / (wq0 + wq1); - // TODO: Handle projection. + s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip; + t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip; +} + +static inline void GetTextureCoordinatesProj(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) { + // This is for texture matrix projection. + float q0 = 1.f / v0.clipw; + float q1 = 1.f / v1.clipw; + float wq0 = p * q0; + float wq1 = (1.0f - p) * q1; + + float q_recip = 1.0f / (wq0 + wq1); + float q = (v0.texturecoords.q() * wq0 + v1.texturecoords.q() * wq1) * q_recip; + q_recip *= 1.0f / q; + s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip; t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip; } static inline void GetTextureCoordinates(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) { - // All UV gen modes, by the time they get here, behave the same. - // Note that for environment mapping, texture coordinates have been calculated during lighting. float q0 = 1.f / v0.clipw; float q1 = 1.f / v1.clipw; @@ -250,7 +261,23 @@ static inline void GetTextureCoordinates(const VertexData &v0, const VertexData Vec4 wq2 = w2.Cast() * q2; Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal(); - // TODO: Handle projection. + s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip); + t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip); +} + +static inline void GetTextureCoordinatesProj(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) { + // This is for texture matrix projection. + float q0 = 1.f / v0.clipw; + float q1 = 1.f / v1.clipw; + float q2 = 1.f / v2.clipw; + Vec4 wq0 = w0.Cast() * q0; + Vec4 wq1 = w1.Cast() * q1; + Vec4 wq2 = w2.Cast() * q2; + + Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal(); + Vec4 q = Interpolate(v0.texturecoords.q(), v1.texturecoords.q(), v2.texturecoords.q(), wq0, wq1, wq2, q_recip); + q_recip = q_recip * q.Reciprocal(); + s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip); t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip); } @@ -676,6 +703,9 @@ void DrawTriangleSlice( // For levels > 0, mipmapping is always based on level 0. Simpler to scale first. s *= 1.0f / (float)(1 << state.samplerID.width0Shift); t *= 1.0f / (float)(1 << state.samplerID.height0Shift); + } else if (state.textureProj) { + // Texture coordinate interpolation must definitely be perspective-correct. + GetTextureCoordinatesProj(v0, v1, v2, w0, w1, w2, wsum_recip, s, t); } else { // Texture coordinate interpolation must definitely be perspective-correct. GetTextureCoordinates(v0, v1, v2, w0, w1, w2, wsum_recip, s, t); @@ -961,6 +991,8 @@ void DrawPoint(const VertexData &v0, const BinCoords &range, const RasterizerSta if (state.throughMode) { s *= 1.0f / (float)(1 << state.samplerID.width0Shift); t *= 1.0f / (float)(1 << state.samplerID.height0Shift); + } else if (state.textureProj) { + GetTextureCoordinatesProj(v0, v0, 0.0f, s, t); } else { // Texture coordinate interpolation must definitely be perspective-correct. GetTextureCoordinates(v0, v0, 0.0f, s, t); @@ -1278,6 +1310,9 @@ void DrawLine(const VertexData &v0, const VertexData &v1, const BinCoords &range s1 = tc1.s() * (1.0f / (float)(1 << state.samplerID.width0Shift)); t = tc.t() * (1.0f / (float)(1 << state.samplerID.height0Shift)); t1 = tc1.t() * (1.0f / (float)(1 << state.samplerID.height0Shift)); + } else if (state.textureProj) { + GetTextureCoordinatesProj(v0, v1, (float)(steps - i) / steps1, s, t); + GetTextureCoordinatesProj(v0, v1, (float)(steps - i - 1) / steps1, s1, t1); } else { // Texture coordinate interpolation must definitely be perspective-correct. GetTextureCoordinates(v0, v1, (float)(steps - i) / steps1, s, t); diff --git a/GPU/Software/Rasterizer.h b/GPU/Software/Rasterizer.h index 6c350e222b..bd0b820283 100644 --- a/GPU/Software/Rasterizer.h +++ b/GPU/Software/Rasterizer.h @@ -54,6 +54,7 @@ struct RasterizerState { bool minFilt : 1; bool magFilt : 1; bool antialiasLines : 1; + bool textureProj : 1; }; #if defined(SOFTGPU_MEMORY_TAGGING_DETAILED) || defined(SOFTGPU_MEMORY_TAGGING_BASIC) diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index ecef1ab647..21f77d4269 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -160,7 +160,7 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = { { GE_CMD_LOGICOP, 0, SoftDirty::PIXEL_BASIC | SoftDirty::PIXEL_CACHED }, { GE_CMD_LOGICOPENABLE, 0, SoftDirty::PIXEL_BASIC | SoftDirty::PIXEL_CACHED }, - { GE_CMD_TEXMAPMODE, 0, SoftDirty::TRANSFORM_BASIC }, + { GE_CMD_TEXMAPMODE, 0, SoftDirty::TRANSFORM_BASIC | SoftDirty::RAST_TEX }, // These are read on every SubmitPrim, no need for dirtying or flushing. { GE_CMD_TEXSCALEU }, diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 9fd3540cf9..633af978ef 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -261,6 +261,8 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) { state->negateNormals = gstate.areNormalsReversed(); state->uvGenMode = gstate.getUVGenMode(); + if (state->uvGenMode == GE_TEXMAP_UNKNOWN) + state->uvGenMode = GE_TEXMAP_TEXTURE_COORDS; if (state->enableTransform) { bool canSkipWorldPos = true; @@ -441,17 +443,11 @@ ClipVertexData TransformUnit::ReadVertex(VertexReader &vreader, const TransformS case GE_PROJMAP_NORMAL: source = normal; break; - - default: - source = Vec3f::AssignToAll(0.0f); - ERROR_LOG_REPORT(G3D, "Software: Unsupported UV projection mode %x", gstate.getUVProjMode()); - break; } // Note that UV scale/offset are not used in this mode. Vec3 stq = Vec3ByMatrix43(source, gstate.tgenMatrix); - float z_recip = 1.0f / stq.z; - vertex.v.texturecoords = Vec3Packedf(stq.x * z_recip, stq.y * z_recip, 1.0f); + vertex.v.texturecoords = Vec3Packedf(stq.x, stq.y, stq.z); } else if (state.uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP) { Lighting::GenerateLightST(vertex.v, worldnormal); } From 23af9be9f450409e999b643f5fa3d21b6a044718 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 26 Sep 2022 18:44:39 -0700 Subject: [PATCH 221/326] softgpu: Handle rectangle texture projection. --- GPU/Software/Clipper.cpp | 65 +++++++++++++++++++++++++++++++++++-- GPU/Software/Rasterizer.cpp | 2 +- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp index 39b7dd650a..2581cdf4c2 100644 --- a/GPU/Software/Clipper.cpp +++ b/GPU/Software/Clipper.cpp @@ -131,6 +131,60 @@ static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) { return false; } +static void RotateUV(const VertexData &tl, const VertexData &br, VertexData &tr, VertexData &bl) { + const int x1 = tl.screenpos.x; + const int x2 = br.screenpos.x; + const int y1 = tl.screenpos.y; + const int y2 = br.screenpos.y; + + if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) { + std::swap(bl.texturecoords, tr.texturecoords); + } +} + +// This is used for rectangle texture projection, which is very uncommon. +// To avoid complicating the common rectangle path, this just uses triangles. +static void AddTriangleRect(const VertexData &v0, const VertexData &v1, BinManager &binner) { + VertexData buf[4]; + buf[0] = v1; + buf[0].screenpos = ScreenCoords(v0.screenpos.x, v0.screenpos.y, v1.screenpos.z); + buf[0].texturecoords = v0.texturecoords; + + buf[1] = v1; + buf[1].screenpos = ScreenCoords(v0.screenpos.x, v1.screenpos.y, v1.screenpos.z); + buf[1].texturecoords = Vec3Packed(v0.texturecoords.x, v1.texturecoords.y, v0.texturecoords.z); + + buf[2] = v1; + buf[2].screenpos = ScreenCoords(v1.screenpos.x, v0.screenpos.y, v1.screenpos.z); + buf[2].texturecoords = Vec3Packed(v1.texturecoords.x, v0.texturecoords.y, v1.texturecoords.z); + + buf[3] = v1; + + VertexData *topleft = &buf[0]; + VertexData *topright = &buf[1]; + VertexData *bottomleft = &buf[2]; + VertexData *bottomright = &buf[3]; + + // DrawTriangle always culls, so sort out the drawing order. + for (int i = 0; i < 4; ++i) { + if (buf[i].screenpos.x < topleft->screenpos.x && buf[i].screenpos.y < topleft->screenpos.y) + topleft = &buf[i]; + if (buf[i].screenpos.x > topright->screenpos.x && buf[i].screenpos.y < topright->screenpos.y) + topright = &buf[i]; + if (buf[i].screenpos.x < bottomleft->screenpos.x && buf[i].screenpos.y > bottomleft->screenpos.y) + bottomleft = &buf[i]; + if (buf[i].screenpos.x > bottomright->screenpos.x && buf[i].screenpos.y > bottomright->screenpos.y) + bottomright = &buf[i]; + } + + RotateUV(v0, v1, *topright, *bottomleft); + + binner.AddTriangle(*topleft, *topright, *bottomleft); + binner.AddTriangle(*bottomleft, *topright, *topleft); + binner.AddTriangle(*topright, *bottomright, *bottomleft); + binner.AddTriangle(*bottomleft, *bottomright, *topright); +} + void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) { if (!binner.State().throughMode) { // If any verts were outside range, throw the entire prim away. @@ -162,8 +216,15 @@ void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager VertexData vrev1 = v1.v; vrev1.fogdepth = v0.v.fogdepth; - binner.AddRect(v0.v, vhalf0); - binner.AddRect(vhalf1, vrev1); + if (binner.State().textureProj) { + AddTriangleRect(v0.v, vhalf0, binner); + AddTriangleRect(vhalf1, vrev1, binner); + } else { + binner.AddRect(v0.v, vhalf0); + binner.AddRect(vhalf1, vrev1); + } + } else if (binner.State().textureProj) { + AddTriangleRect(v0.v, v1.v, binner); } else { binner.AddRect(v0.v, v1.v); } diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 950642f6d4..e07d7dd05a 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -802,7 +802,7 @@ void DrawRectangle(const VertexData &v0, const VertexData &v1, const BinCoords & Vec2f stx(0.0f, 0.0f); Vec2f sty(0.0f, 0.0f); if (state.enableTextures) { - // TODO: Handle projection. + // Note: texture projection is not handled here, those always turn into triangles. Vec2f tc0 = v0.texturecoords.uv(); Vec2f tc1 = v1.texturecoords.uv(); if (state.throughMode) { From e538f5a441707c352959cbc81026184a5d9b9f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Sep 2022 10:09:52 +0200 Subject: [PATCH 222/326] Better bit scrambling when computing draw call IDs for vertex cache. Fixes #13324 --- GPU/Common/DrawEngineCommon.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 5f4256158c..f884486c65 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -717,6 +717,16 @@ uint64_t DrawEngineCommon::ComputeHash() { return fullhash; } +// Cheap bit scrambler from https://nullprogram.com/blog/2018/07/31/ +inline uint32_t lowbias32_r(uint32_t x) { + x ^= x >> 16; + x *= 0x43021123U; + x ^= x >> 15 ^ x >> 30; + x *= 0x1d69e2a5U; + x ^= x >> 16; + return x; +} + // vertTypeID is the vertex type but with the UVGen mode smashed into the top bits. void DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) { if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawCalls >= MAX_DEFERRED_DRAW_CALLS || vertexCountInDrawCalls_ + vertexCount > VERTEX_BUFFER_MAX) { @@ -745,10 +755,10 @@ void DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimiti if (g_Config.bVertexCache) { u32 dhash = dcid_; dhash = __rotl(dhash ^ (u32)(uintptr_t)verts, 13); - dhash = __rotl(dhash ^ (u32)(uintptr_t)inds, 13); - dhash = __rotl(dhash ^ (u32)vertTypeID, 13); - dhash = __rotl(dhash ^ (u32)vertexCount, 13); - dcid_ = dhash ^ (u32)prim; + dhash = __rotl(dhash ^ (u32)(uintptr_t)inds, 19); + dhash = __rotl(dhash ^ (u32)vertTypeID, 7); + dhash = __rotl(dhash ^ (u32)vertexCount, 11); + dcid_ = lowbias32_r(dhash ^ (u32)prim); } DeferredDrawCall &dc = drawCalls[numDrawCalls]; From f37d0622e2dde1219c57795bb356a5aa0716dea6 Mon Sep 17 00:00:00 2001 From: sum2012 Date: Tue, 27 Sep 2022 21:07:02 +0800 Subject: [PATCH 223/326] Change to mpegLibVersion --- Core/HLE/sceMpeg.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 9d829a9039..8fc374ec29 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -1363,15 +1363,14 @@ static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initA // Flush structs back to memory avcAu.write(auAddr); - // Save the current frame's status to initAddr - PSPFileInfo fileInfo = pspFileSystem.GetFileInfo("disc0:\\PSP_GAME\\USRDIR\\module\\mpeg.prx"); - if (fileInfo.exists) { - Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); - } - else { - // Sunday Vs Magazine Shuuketsu! Choujou Daikessen expcect, issue #11060 + if (mpegLibVersion >= 0x010A) { + // Sunday Vs Magazine Shuuketsu! Choujou Daikessen expect, issue #11060 Memory::Write_U32(1, initAddr); } + else { + // Save the current frame's status to initAddr + Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr); + } ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS; DEBUG_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr); From 117ce25de0d5abdc1e1d44452b035863752ff2e4 Mon Sep 17 00:00:00 2001 From: sum2012 Date: Tue, 27 Sep 2022 21:08:56 +0800 Subject: [PATCH 224/326] Forget remove unused include --- Core/HLE/sceMpeg.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 8fc374ec29..fa46628c54 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -36,8 +36,6 @@ #include "GPU/GPUState.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/Core.h" -#include "Core/System.h" -#include "Core/FileSystems/MetaFileSystem.h" // MPEG AVC elementary stream. static const int MPEG_AVC_ES_SIZE = 2048; // MPEG packet size. From a9b6001daa0ee4a173e2d99576d4f282afb7432d Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 27 Sep 2022 18:04:21 +0200 Subject: [PATCH 225/326] OpenXR - Code formatting --- Common/VR/VRBase.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index d80f2de4c6..af79a4e745 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -6,8 +6,7 @@ #include #ifdef OPENXR_PLATFORM_PICO -enum ConfigsSetEXT -{ +enum ConfigsSetEXT { UNREAL_VERSION = 0, TRACKING_ORIGIN, OPENGL_NOERROR, @@ -20,11 +19,10 @@ enum ConfigsSetEXT MRC_TEXTURE_ID = 9, }; -typedef enum -{ +enum PxrTrackingDof { PXR_TRACKING_3DOF = 0, PXR_TRACKING_6DOF = 1 -} PxrTrackingDof; +}; typedef XrResult (XRAPI_PTR *PFN_xrSetEngineVersionPico)(XrInstance instance,const char* version); typedef XrResult (XRAPI_PTR *PFN_xrStartCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState); @@ -35,7 +33,6 @@ PFN_xrSetConfigPICO pfnXrSetConfigPICO = nullptr; PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = nullptr; PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = nullptr; PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = nullptr; - #endif static engine_t vr_engine; From a7542e0320c53250cfe5719e11fc9797641e4898 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 27 Sep 2022 18:44:15 +0200 Subject: [PATCH 226/326] OpenXR - Initialization on Pico fixed --- Common/VR/VRBase.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp index af79a4e745..4782408765 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -87,7 +87,14 @@ void VR_Init( ovrJava java ) { XrInstanceCreateInfo instanceCreateInfo; memset(&instanceCreateInfo, 0, sizeof(instanceCreateInfo)); instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO; +#ifdef OPENXR_PLATFORM_PICO + XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR}; + instanceCreateInfoAndroid.applicationVM = java.Vm; + instanceCreateInfoAndroid.applicationActivity = java.ActivityObject; + instanceCreateInfo.next = (XrBaseInStructure*)&instanceCreateInfoAndroid; +#else instanceCreateInfo.next = NULL; +#endif instanceCreateInfo.createFlags = 0; instanceCreateInfo.applicationInfo = appInfo; instanceCreateInfo.enabledApiLayerCount = 0; From 1d5fc2feaa12adbf173cd95816a5e00076ea7385 Mon Sep 17 00:00:00 2001 From: Stuart Kenny Date: Tue, 27 Sep 2022 20:22:58 +0100 Subject: [PATCH 227/326] Remove alpha ignore in xbrz texture shaders. --- assets/shaders/tex_2xbrz.csh | 5 ----- assets/shaders/tex_4xbrz.csh | 5 ----- 2 files changed, 10 deletions(-) diff --git a/assets/shaders/tex_2xbrz.csh b/assets/shaders/tex_2xbrz.csh index f4a12c4cfe..639efb582e 100644 --- a/assets/shaders/tex_2xbrz.csh +++ b/assets/shaders/tex_2xbrz.csh @@ -100,11 +100,6 @@ void applyScaling(uvec2 origxy) { ivec4 blendResult = ivec4(BLEND_NONE); - // Preserve old alpha-ignoring behavior? Keep or delete? - for (int i = 0; i < 9; i++) { - v[i] &= 0xFFFFFF; - } - // Preprocess corners // Pixel Tap Mapping: --|--|--|--|-- // --|--|07|08|-- diff --git a/assets/shaders/tex_4xbrz.csh b/assets/shaders/tex_4xbrz.csh index a19a28a5f5..bd22097ef8 100644 --- a/assets/shaders/tex_4xbrz.csh +++ b/assets/shaders/tex_4xbrz.csh @@ -100,11 +100,6 @@ void applyScaling(uvec2 origxy) { ivec4 blendResult = ivec4(BLEND_NONE); - // Preserve old alpha-ignoring behavior? Keep or delete? - for (int i = 0; i < 9; i++) { - v[i] &= 0xFFFFFF; - } - // Preprocess corners // Pixel Tap Mapping: --|--|--|--|-- // --|--|07|08|-- From ca5c69d3dd5c4a5b98737eb906475940ce61dd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 27 Sep 2022 23:40:50 +0200 Subject: [PATCH 228/326] Vulkan: Better debug names for RENDER passes. --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 8 +++++++- GPU/Common/FramebufferManagerCommon.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 05e2f947cc..428d66e2cc 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -559,7 +559,13 @@ void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frame if (emitLabels) { VkDebugUtilsLabelEXT labelInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; - labelInfo.pLabelName = step.tag; + char temp[128]; + if (step.stepType == VKRStepType::RENDER && step.render.framebuffer) { + snprintf(temp, sizeof(temp), "%s: %s", step.tag, step.render.framebuffer->Tag()); + labelInfo.pLabelName = temp; + } else { + labelInfo.pLabelName = step.tag; + } vkCmdBeginDebugUtilsLabelEXT(frameData.mainCmd, &labelInfo); } diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 9a666f83f8..b4db88965a 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -964,7 +964,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe if (useBufferedRendering_) { if (vfb->fbo) { shaderManager_->DirtyLastShader(); - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "FramebufferSwitch"); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "FBSwitch"); } else { // This should only happen very briefly when toggling useBufferedRendering_. ResizeFramebufFBO(vfb, vfb->width, vfb->height, true); From 05de5c7f3d0c85f3d8db6cf7c3f9bfdf0b329710 Mon Sep 17 00:00:00 2001 From: Lubos Date: Tue, 27 Sep 2022 23:54:46 +0200 Subject: [PATCH 229/326] OpenXR - Support for Pico controllers --- Common/VR/VRInput.cpp | 264 +++++++++++++++--------------------------- 1 file changed, 95 insertions(+), 169 deletions(-) diff --git a/Common/VR/VRInput.cpp b/Common/VR/VRInput.cpp index 63fc97a030..2fc8816f61 100644 --- a/Common/VR/VRInput.cpp +++ b/Common/VR/VRInput.cpp @@ -26,7 +26,6 @@ XrActionSet runningActionSet; XrSpace leftControllerAimSpace = XR_NULL_HANDLE; XrSpace rightControllerAimSpace = XR_NULL_HANDLE; int inputInitialized = 0; -int useSimpleProfile = 0; int in_vrEventTime = 0; double lastframetime = 0; @@ -248,182 +247,109 @@ void IN_VRInit( engine_t *engine ) { handPoseRightAction = CreateAction(runningActionSet, XR_ACTION_TYPE_POSE_INPUT, "hand_pose_right", NULL, 1, &rightHandPath); XrPath interactionProfilePath = XR_NULL_PATH; - XrPath interactionProfilePathTouch = XR_NULL_PATH; - XrPath interactionProfilePathKHRSimple = XR_NULL_PATH; +#ifdef OPENXR_PLATFORM_QUEST + OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath)); +#elif OPENXR_PLATFORM_PICO + OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/pico/neo3_controller", &interactionProfilePath)); +#endif + // Map bindings + XrActionSuggestedBinding bindings[32]; // large enough for all profiles + int currBinding = 0; - OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePathTouch)); - OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/khr/simple_controller", &interactionProfilePathKHRSimple)); +#ifdef OPENXR_PLATFORM_QUEST + bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger"); + bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger"); + bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click"); +#elif OPENXR_PLATFORM_PICO + bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger/click"); + bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger/click"); + bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/back/click"); + bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/right/input/back/click"); +#endif + bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click"); + bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click"); + bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click"); + bindings[currBinding++] = ActionSuggestedBinding(buttonBAction, "/user/hand/right/input/b/click"); + bindings[currBinding++] = ActionSuggestedBinding(gripLeftAction, "/user/hand/left/input/squeeze/value"); + bindings[currBinding++] = ActionSuggestedBinding(gripRightAction, "/user/hand/right/input/squeeze/value"); + bindings[currBinding++] = ActionSuggestedBinding(moveOnLeftJoystickAction, "/user/hand/left/input/thumbstick"); + bindings[currBinding++] = ActionSuggestedBinding(moveOnRightJoystickAction, "/user/hand/right/input/thumbstick"); + bindings[currBinding++] = ActionSuggestedBinding(thumbstickLeftClickAction, "/user/hand/left/input/thumbstick/click"); + bindings[currBinding++] = ActionSuggestedBinding(thumbstickRightClickAction, "/user/hand/right/input/thumbstick/click"); + bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic"); + bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic"); + bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose"); + bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose"); - // Toggle this to force simple as a first choice, otherwise use it as a last resort - if (useSimpleProfile) { - ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller"); - interactionProfilePath = interactionProfilePathKHRSimple; - } else { - // Query Set - XrActionSet queryActionSet = CreateActionSet(1, "query_action_set", "Action Set used to query device caps"); - XrAction dummyAction = CreateAction(queryActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "dummy_action", "Dummy Action", 0, NULL); + XrInteractionProfileSuggestedBinding suggestedBindings = {}; + suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; + suggestedBindings.next = NULL; + suggestedBindings.interactionProfile = interactionProfilePath; + suggestedBindings.suggestedBindings = bindings; + suggestedBindings.countSuggestedBindings = currBinding; + OXR(xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings)); - // Map bindings - XrActionSuggestedBinding bindings[1]; - int currBinding = 0; - bindings[currBinding++] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/system/click"); + // Attach actions + XrSessionActionSetsAttachInfo attachInfo = {}; + attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO; + attachInfo.next = NULL; + attachInfo.countActionSets = 1; + attachInfo.actionSets = &runningActionSet; + OXR(xrAttachSessionActionSets(engine->appState.Session, &attachInfo)); - XrInteractionProfileSuggestedBinding suggestedBindings = {}; - suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; - suggestedBindings.next = NULL; - suggestedBindings.suggestedBindings = bindings; - suggestedBindings.countSuggestedBindings = currBinding; + // Enumerate actions + XrPath actionPathsBuffer[32]; + char stringBuffer[256]; + XrAction actionsToEnumerate[] = { + indexLeftAction, + indexRightAction, + menuAction, + buttonAAction, + buttonBAction, + buttonXAction, + buttonYAction, + gripLeftAction, + gripRightAction, + moveOnLeftJoystickAction, + moveOnRightJoystickAction, + thumbstickLeftClickAction, + thumbstickRightClickAction, + vibrateLeftFeedback, + vibrateRightFeedback, + handPoseLeftAction, + handPoseRightAction + }; + for (XrAction & i : actionsToEnumerate) { + XrBoundSourcesForActionEnumerateInfo enumerateInfo = {}; + enumerateInfo.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO; + enumerateInfo.next = NULL; + enumerateInfo.action = i; - // Try all - suggestedBindings.interactionProfile = interactionProfilePathTouch; - XrResult suggestTouchResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings); - OXR(suggestTouchResult); + // Get Count + uint32_t countOutput = 0; + OXR(xrEnumerateBoundSourcesForAction(engine->appState.Session, &enumerateInfo, 0 /* request size */, &countOutput, NULL)); + ALOGV("xrEnumerateBoundSourcesForAction action=%lld count=%u", (long long)enumerateInfo.action, countOutput); - if (XR_SUCCESS == suggestTouchResult) { - ALOGV("xrSuggestInteractionProfileBindings found bindings for QUEST controller"); - interactionProfilePath = interactionProfilePathTouch; - } - - if (interactionProfilePath == XR_NULL_PATH) { - // Simple as a fallback - bindings[0] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/select/click"); - suggestedBindings.interactionProfile = interactionProfilePathKHRSimple; - XrResult suggestKHRSimpleResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings); - OXR(suggestKHRSimpleResult); - if (XR_SUCCESS == suggestKHRSimpleResult) { - ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller"); - interactionProfilePath = interactionProfilePathKHRSimple; - } else { - ALOGE("xrSuggestInteractionProfileBindings did NOT find any bindings."); - exit(1); - } - } - } - - // Action creation - { - // Map bindings - XrActionSuggestedBinding bindings[32]; // large enough for all profiles - int currBinding = 0; - - { - if (interactionProfilePath == interactionProfilePathTouch) { - bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger"); - bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger"); - bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonBAction, "/user/hand/right/input/b/click"); - bindings[currBinding++] = ActionSuggestedBinding(gripLeftAction, "/user/hand/left/input/squeeze/value"); - bindings[currBinding++] = ActionSuggestedBinding(gripRightAction, "/user/hand/right/input/squeeze/value"); - bindings[currBinding++] = ActionSuggestedBinding(moveOnLeftJoystickAction, "/user/hand/left/input/thumbstick"); - bindings[currBinding++] = ActionSuggestedBinding(moveOnRightJoystickAction, "/user/hand/right/input/thumbstick"); - bindings[currBinding++] = ActionSuggestedBinding(thumbstickLeftClickAction, "/user/hand/left/input/thumbstick/click"); - bindings[currBinding++] = ActionSuggestedBinding(thumbstickRightClickAction, "/user/hand/right/input/thumbstick/click"); - bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic"); - bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic"); - bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose"); - bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose"); - } - - if (interactionProfilePath == interactionProfilePathKHRSimple) { - bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/select/click"); - bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/select/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/left/input/menu/click"); - bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/right/input/menu/click"); - bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic"); - bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic"); - bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose"); - bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose"); - } - } - - XrInteractionProfileSuggestedBinding suggestedBindings = {}; - suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; - suggestedBindings.next = NULL; - suggestedBindings.interactionProfile = interactionProfilePath; - suggestedBindings.suggestedBindings = bindings; - suggestedBindings.countSuggestedBindings = currBinding; - OXR(xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings)); - - // Attach actions - XrSessionActionSetsAttachInfo attachInfo = {}; - attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO; - attachInfo.next = NULL; - attachInfo.countActionSets = 1; - attachInfo.actionSets = &runningActionSet; - OXR(xrAttachSessionActionSets(engine->appState.Session, &attachInfo)); - - // Enumerate actions - XrPath actionPathsBuffer[32]; - char stringBuffer[256]; - XrAction actionsToEnumerate[] = { - indexLeftAction, - indexRightAction, - menuAction, - buttonAAction, - buttonBAction, - buttonXAction, - buttonYAction, - gripLeftAction, - gripRightAction, - moveOnLeftJoystickAction, - moveOnRightJoystickAction, - thumbstickLeftClickAction, - thumbstickRightClickAction, - vibrateLeftFeedback, - vibrateRightFeedback, - handPoseLeftAction, - handPoseRightAction - }; - for (XrAction & i : actionsToEnumerate) { - XrBoundSourcesForActionEnumerateInfo enumerateInfo = {}; - enumerateInfo.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO; - enumerateInfo.next = NULL; - enumerateInfo.action = i; - - // Get Count - uint32_t countOutput = 0; + if (countOutput < 32) { OXR(xrEnumerateBoundSourcesForAction( - engine->appState.Session, &enumerateInfo, 0 /* request size */, &countOutput, NULL)); - ALOGV( - "xrEnumerateBoundSourcesForAction action=%lld count=%u", - (long long)enumerateInfo.action, - countOutput); + engine->appState.Session, &enumerateInfo, 32, &countOutput, actionPathsBuffer)); + for (uint32_t a = 0; a < countOutput; ++a) { + XrInputSourceLocalizedNameGetInfo nameGetInfo = {}; + nameGetInfo.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO; + nameGetInfo.next = NULL; + nameGetInfo.sourcePath = actionPathsBuffer[a]; + nameGetInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT | + XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT | + XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT; - if (countOutput < 32) { - OXR(xrEnumerateBoundSourcesForAction( - engine->appState.Session, &enumerateInfo, 32, &countOutput, actionPathsBuffer)); - for (uint32_t a = 0; a < countOutput; ++a) { - XrInputSourceLocalizedNameGetInfo nameGetInfo = {}; - nameGetInfo.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO; - nameGetInfo.next = NULL; - nameGetInfo.sourcePath = actionPathsBuffer[a]; - nameGetInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT | - XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT | - XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT; - - uint32_t stringCount = 0u; - OXR(xrGetInputSourceLocalizedName( - engine->appState.Session, &nameGetInfo, 0, &stringCount, NULL)); - if (stringCount < 256) { - OXR(xrGetInputSourceLocalizedName( - engine->appState.Session, &nameGetInfo, 256, &stringCount, stringBuffer)); - char pathStr[256]; - uint32_t strLen = 0; - OXR(xrPathToString( - engine->appState.Instance, - actionPathsBuffer[a], - (uint32_t)sizeof(pathStr), - &strLen, - pathStr)); - ALOGV( - " -> path = %lld `%s` -> `%s`", - (long long)actionPathsBuffer[a], - pathStr, - stringBuffer); - } + uint32_t stringCount = 0u; + OXR(xrGetInputSourceLocalizedName(engine->appState.Session, &nameGetInfo, 0, &stringCount, NULL)); + if (stringCount < 256) { + OXR(xrGetInputSourceLocalizedName(engine->appState.Session, &nameGetInfo, 256, &stringCount, stringBuffer)); + char pathStr[256]; + uint32_t strLen = 0; + OXR(xrPathToString(engine->appState.Instance, actionPathsBuffer[a], (uint32_t)sizeof(pathStr), &strLen, pathStr)); + ALOGV(" -> path = %lld `%s` -> `%s`", (long long)actionPathsBuffer[a], pathStr, stringBuffer); } } } From 409b9234d7a584cea874ee9a9d89a0ff4ce9d774 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 28 Sep 2022 00:01:18 +0200 Subject: [PATCH 230/326] OpenXR - Fix the build on Quest --- 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 4782408765..4bc4723a11 100644 --- a/Common/VR/VRBase.cpp +++ b/Common/VR/VRBase.cpp @@ -39,13 +39,13 @@ static engine_t vr_engine; int vr_initialized = 0; const char* const requiredExtensionNames[] = { - XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, #ifdef OPENXR_HAS_PERFORMANCE_EXTENSION XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, #endif #ifdef OPENXR_PLATFORM_PICO + XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, "XR_PICO_android_controller_function_ext_enable", "XR_PICO_view_state_ext_enable", "XR_PICO_frame_end_info_ext", From 464fc446e3f8d75c61730e50e6fc7057afe64b06 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 28 Sep 2022 00:10:30 +0200 Subject: [PATCH 231/326] OpenXR - Fix FBO colors on Pico --- Common/VR/VRFramebuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp index 70f2ad02aa..9dab8411e3 100644 --- a/Common/VR/VRFramebuffer.cpp +++ b/Common/VR/VRFramebuffer.cpp @@ -76,7 +76,7 @@ bool ovrFramebuffer_Create(XrSession session, ovrFramebuffer* frameBuffer, int w frameBuffer->DepthSwapChain.Height = swapChainCreateInfo.height; // Create the color swapchain. - swapChainCreateInfo.format = GL_RGBA8; + swapChainCreateInfo.format = GL_SRGB8_ALPHA8; swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->ColorSwapChain.Handle)); OXR(xrEnumerateSwapchainImages(frameBuffer->ColorSwapChain.Handle, 0, &frameBuffer->TextureSwapChainLength, NULL)); From b60217d962178677b484d685352ca43ca3e88061 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 27 Sep 2022 19:02:18 -0700 Subject: [PATCH 232/326] Debugger: Fix doc typo for disasm/assemble names. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 79a0fe70a8..0c256ded14 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -379,7 +379,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { json.pop(); } -// Search disassembly for some text (cpu.searchDisasm) +// Search disassembly for some text (memory.searchDisasm) // // Parameters: // - thread: optional number indicating the thread id (may not affect search much.) @@ -461,7 +461,7 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { json.writeNull("address"); } -// Assemble an instruction (cpu.assemble) +// Assemble an instruction (memory.assemble) // // Parameters: // - address: number indicating the address to write to. From 3b0c0f2916401b197cef222457c7b5a783a5b73e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 27 Sep 2022 19:14:32 -0700 Subject: [PATCH 233/326] Debugger: Add API to scan memory for funcs. For example, when you know a range is being used or when you've deleted the functions. --- Core/Debugger/WebSocket/HLESubscriber.cpp | 31 +++++++++++++++++++++++ Core/Debugger/WebSocket/HLESubscriber.h | 1 + 2 files changed, 32 insertions(+) diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp index fe3cde43ef..2e4a7be55e 100644 --- a/Core/Debugger/WebSocket/HLESubscriber.cpp +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -22,6 +22,7 @@ #include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/WebSocket/HLESubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MemMap.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSStackWalk.h" @@ -36,6 +37,7 @@ DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) { map["hle.func.add"] = &WebSocketHLEFuncAdd; map["hle.func.remove"] = &WebSocketHLEFuncRemove; map["hle.func.rename"] = &WebSocketHLEFuncRename; + map["hle.func.scan"] = &WebSocketHLEFuncScan; map["hle.module.list"] = &WebSocketHLEModuleList; map["hle.backtrace"] = &WebSocketHLEBacktrace; @@ -403,6 +405,35 @@ void WebSocketHLEFuncRename(DebuggerRequest &req) { json.writeString("name", name); } +// Auto-detect functions in a memory range (hle.func.scan) +// +// Parameters: +// - address: unsigned integer address within function to rename. +// - size: unsigned integer size in bytes for scan. +// +// Response (same event name) with no extra data. +void WebSocketHLEFuncScan(DebuggerRequest &req) { + if (!g_symbolMap) + return req.Fail("CPU not active"); + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + + u32 addr; + if (!req.ParamU32("address", &addr)) + return; + u32 size; + if (!req.ParamU32("size", &size)) + return; + + if (!Memory::IsValidRange(addr, size)) + return req.Fail("Address or size outside valid memory"); + + bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true); + MIPSAnalyst::FinalizeScan(insertSymbols); + + req.Respond(); +} + // List all known user modules (hle.module.list) // // No parameters. diff --git a/Core/Debugger/WebSocket/HLESubscriber.h b/Core/Debugger/WebSocket/HLESubscriber.h index 6d473c3563..e70b4c8ff0 100644 --- a/Core/Debugger/WebSocket/HLESubscriber.h +++ b/Core/Debugger/WebSocket/HLESubscriber.h @@ -28,5 +28,6 @@ void WebSocketHLEFuncList(DebuggerRequest &req); void WebSocketHLEFuncAdd(DebuggerRequest &req); void WebSocketHLEFuncRemove(DebuggerRequest &req); void WebSocketHLEFuncRename(DebuggerRequest &req); +void WebSocketHLEFuncScan(DebuggerRequest &req); void WebSocketHLEModuleList(DebuggerRequest &req); void WebSocketHLEBacktrace(DebuggerRequest &req); From 38818f9f6e8983b753f8fda5489b0aa7c588439a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 27 Sep 2022 19:22:41 -0700 Subject: [PATCH 234/326] GLES: Fix colortest/logicop uint/int conversion. Shown well in #16119. --- Common/GPU/OpenGL/GLQueueRunner.cpp | 4 ++-- GPU/Common/FragmentShaderGenerator.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index c7f0bc93b9..80222bbfbf 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -252,8 +252,8 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski ERROR_LOG(G3D, "Could not link program:\n %s", infoLog.c_str()); ERROR_LOG(G3D, "VS desc:\n%s", vsDesc.c_str()); ERROR_LOG(G3D, "FS desc:\n%s", fsDesc.c_str()); - ERROR_LOG(G3D, "VS:\n%s\n", vsCode); - ERROR_LOG(G3D, "FS:\n%s\n", fsCode); + ERROR_LOG(G3D, "VS:\n%s\n", LineNumberString(vsCode).c_str()); + ERROR_LOG(G3D, "FS:\n%s\n", LineNumberString(fsCode).c_str()); #ifdef _WIN32 OutputDebugStringUTF8(infoLog.c_str()); diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 5cf923be46..2f8f248dbd 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -463,7 +463,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (compat.bitwiseOps && enableColorTest) { p.C("uvec3 unpackUVec3(highp uint x) {\n"); - p.C(" return uvec3(x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF);\n"); + p.C(" return uvec3(x & 0xFFU, (x >> 8) & 0xFFU, (x >> 16) & 0xFFU);\n"); p.C("}\n"); } @@ -1151,7 +1151,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu case GE_LOGIC_COPY_INVERTED: p.C(" v32 = (~v32 & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; case GE_LOGIC_OR_INVERTED: p.C(" v32 = ((~v32 | d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; case GE_LOGIC_NAND: p.C(" v32 = (~(v32 & d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; - case GE_LOGIC_SET: p.C(" v32 |= 0x00FFFFFF;\n"); break; + case GE_LOGIC_SET: p.C(" v32 |= 0x00FFFFFFu;\n"); break; } // Note that the mask has already been flipped to the PC way - 1 means write. From 95d2083f0415826e9cf86a6244fc27c010e9f83c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 27 Sep 2022 20:20:46 -0700 Subject: [PATCH 235/326] Ge: Move matrix reading into GPU. Let's keep managing its state / registers internal. --- Core/HLE/sceGe.cpp | 50 +++++++++------------------------------------- GPU/GPUCommon.cpp | 36 +++++++++++++++++++++++++++++++++ GPU/GPUCommon.h | 1 + GPU/GPUInterface.h | 1 + 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 81667a448e..1fcfc23b64 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -518,49 +518,17 @@ u32 sceGeRestoreContext(u32 ctxAddr) { return 0; } -static void __GeCopyMatrix(u32 matrixPtr, float *mtx, u32 size) { - for (u32 i = 0; i < size / sizeof(float); ++i) { - Memory::Write_U32(toFloat24(mtx[i]), matrixPtr + i * sizeof(float)); - } -} - static int sceGeGetMtx(int type, u32 matrixPtr) { - if (!Memory::IsValidAddress(matrixPtr)) { - ERROR_LOG(SCEGE, "sceGeGetMtx(%d, %08x) - bad matrix ptr", type, matrixPtr); - return -1; + int size = type == GE_MTX_PROJECTION ? 16 : 12; + if (!Memory::IsValidRange(matrixPtr, size * sizeof(float))) { + return hleLogError(SCEGE, -1, "bad matrix ptr"); } - INFO_LOG(SCEGE, "sceGeGetMtx(%d, %08x)", type, matrixPtr); - switch (type) { - case GE_MTX_BONE0: - case GE_MTX_BONE1: - case GE_MTX_BONE2: - case GE_MTX_BONE3: - case GE_MTX_BONE4: - case GE_MTX_BONE5: - case GE_MTX_BONE6: - case GE_MTX_BONE7: - { - int n = type - GE_MTX_BONE0; - __GeCopyMatrix(matrixPtr, gstate.boneMatrix + n * 12, 12 * sizeof(float)); - } - break; - case GE_MTX_TEXGEN: - __GeCopyMatrix(matrixPtr, gstate.tgenMatrix, 12 * sizeof(float)); - break; - case GE_MTX_WORLD: - __GeCopyMatrix(matrixPtr, gstate.worldMatrix, 12 * sizeof(float)); - break; - case GE_MTX_VIEW: - __GeCopyMatrix(matrixPtr, gstate.viewMatrix, 12 * sizeof(float)); - break; - case GE_MTX_PROJECTION: - __GeCopyMatrix(matrixPtr, gstate.projMatrix, 16 * sizeof(float)); - break; - default: - return SCE_KERNEL_ERROR_INVALID_INDEX; - } - return 0; + u32 *dest = (u32 *)Memory::GetPointerWriteUnchecked(matrixPtr); + if (!gpu || !gpu->GetMatrix24(GEMatrixType(type), dest)) + return hleLogError(SCEGE, SCE_KERNEL_ERROR_INVALID_INDEX, "invalid matrix"); + + return hleLogSuccessInfoI(SCEGE, 0); } static u32 sceGeGetCmd(int cmd) { @@ -631,7 +599,7 @@ const HLEFunction sceGe_user[] = { {0X1F6752AD, &WrapU_V, "sceGeEdramGetSize", 'x', "" }, {0XB77905EA, &WrapU_I, "sceGeEdramSetAddrTranslation", 'x', "i" }, {0XDC93CFEF, &WrapU_I, "sceGeGetCmd", 'x', "i" }, - {0X57C8945B, &WrapI_IU, "sceGeGetMtx", 'i', "ix" }, + {0X57C8945B, &WrapI_IU, "sceGeGetMtx", 'i', "ip" }, {0X438A385A, &WrapU_U, "sceGeSaveContext", 'x', "x" }, {0X0BF608FB, &WrapU_U, "sceGeRestoreContext", 'x', "x" }, {0X5FB86AB0, &WrapI_U, "sceGeListDeQueue", 'i', "x" }, diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 56dbbbcf59..13fc85b19c 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -731,6 +731,42 @@ int GPUCommon::GetStack(int index, u32 stackPtr) { return currentList->stackptr; } +static void CopyMatrix24(u32 *result, float *mtx, u32 count) { + for (u32 i = 0; i < count; ++i) { + result[i] = toFloat24(mtx[i]); + } +} + +bool GPUCommon::GetMatrix24(GEMatrixType type, u32 *result) { + switch (type) { + case GE_MTX_BONE0: + case GE_MTX_BONE1: + case GE_MTX_BONE2: + case GE_MTX_BONE3: + case GE_MTX_BONE4: + case GE_MTX_BONE5: + case GE_MTX_BONE6: + case GE_MTX_BONE7: + CopyMatrix24(result, gstate.boneMatrix + (type - GE_MTX_BONE0) * 12, 12); + break; + case GE_MTX_TEXGEN: + CopyMatrix24(result, gstate.tgenMatrix, 12); + break; + case GE_MTX_WORLD: + CopyMatrix24(result, gstate.worldMatrix, 12); + break; + case GE_MTX_VIEW: + CopyMatrix24(result, gstate.viewMatrix, 12); + break; + case GE_MTX_PROJECTION: + CopyMatrix24(result, gstate.projMatrix, 16); + break; + default: + return false; + } + return true; +} + u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) { // TODO Check the stack values in missing arg and ajust the stack depth diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 36c27d72d7..480fe1df09 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -111,6 +111,7 @@ public: int ListSync(int listid, int mode) override; u32 DrawSync(int mode) override; int GetStack(int index, u32 stackPtr) override; + bool GetMatrix24(GEMatrixType type, u32 *result) override; void DoState(PointerWrap &p) override; bool BusyDrawing() override; u32 Continue() override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 2ce7c48620..54d5492ea4 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -198,6 +198,7 @@ public: virtual u32 Continue() = 0; virtual u32 Break(int mode) = 0; virtual int GetStack(int index, u32 stackPtr) = 0; + virtual bool GetMatrix24(GEMatrixType type, u32 *result) = 0; virtual void InterruptStart(int listid) = 0; virtual void InterruptEnd(int listid) = 0; From 6b20c0318d3baf1d2d2ab3ed4db63742ac8ae77a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 27 Sep 2022 22:29:55 -0700 Subject: [PATCH 236/326] softgpu: Correct matrix value update wrapping. The values read back when saving a context or getting matrix data are set differently than the actual values used for rendering. This implements the wrapping and bleeding between matrices within softgpu, but leaves hardware rendering to only use the rendering registers for speed. --- Core/HLE/sceGe.cpp | 6 +- GPU/GPUCommon.cpp | 79 ++++++++++------- GPU/GPUCommon.h | 18 +++- GPU/GPUInterface.h | 3 +- GPU/GPUState.cpp | 34 ++++++-- GPU/Software/SoftGpu.cpp | 177 ++++++++++++++++++++++++++++----------- GPU/Software/SoftGpu.h | 8 ++ 7 files changed, 233 insertions(+), 92 deletions(-) diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 1fcfc23b64..5afe38e02f 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -524,8 +524,10 @@ static int sceGeGetMtx(int type, u32 matrixPtr) { return hleLogError(SCEGE, -1, "bad matrix ptr"); } - u32 *dest = (u32 *)Memory::GetPointerWriteUnchecked(matrixPtr); - if (!gpu || !gpu->GetMatrix24(GEMatrixType(type), dest)) + u32_le *dest = (u32_le *)Memory::GetPointerWriteUnchecked(matrixPtr); + // Note: this reads the CPU-visible matrix values, which may differ from the actual used values. + // They only differ when more DATA commands are sent than are valid for a matrix. + if (!gpu || !gpu->GetMatrix24(GEMatrixType(type), dest, 0)) return hleLogError(SCEGE, SCE_KERNEL_ERROR_INVALID_INDEX, "invalid matrix"); return hleLogSuccessInfoI(SCEGE, 0); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 13fc85b19c..49e6d5dcf3 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -425,6 +425,7 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : UpdateCmdInfo(); UpdateVsyncInterval(true); + ResetMatrices(); PPGeSetDrawContext(draw); } @@ -731,13 +732,13 @@ int GPUCommon::GetStack(int index, u32 stackPtr) { return currentList->stackptr; } -static void CopyMatrix24(u32 *result, float *mtx, u32 count) { +static void CopyMatrix24(u32_le *result, const float *mtx, u32 count, u32 cmdbits) { for (u32 i = 0; i < count; ++i) { - result[i] = toFloat24(mtx[i]); + result[i] = toFloat24(mtx[i]) | cmdbits; } } -bool GPUCommon::GetMatrix24(GEMatrixType type, u32 *result) { +bool GPUCommon::GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) { switch (type) { case GE_MTX_BONE0: case GE_MTX_BONE1: @@ -747,19 +748,19 @@ bool GPUCommon::GetMatrix24(GEMatrixType type, u32 *result) { case GE_MTX_BONE5: case GE_MTX_BONE6: case GE_MTX_BONE7: - CopyMatrix24(result, gstate.boneMatrix + (type - GE_MTX_BONE0) * 12, 12); + CopyMatrix24(result, gstate.boneMatrix + (type - GE_MTX_BONE0) * 12, 12, cmdbits); break; case GE_MTX_TEXGEN: - CopyMatrix24(result, gstate.tgenMatrix, 12); + CopyMatrix24(result, gstate.tgenMatrix, 12, cmdbits); break; case GE_MTX_WORLD: - CopyMatrix24(result, gstate.worldMatrix, 12); + CopyMatrix24(result, gstate.worldMatrix, 12, cmdbits); break; case GE_MTX_VIEW: - CopyMatrix24(result, gstate.viewMatrix, 12); + CopyMatrix24(result, gstate.viewMatrix, 12, cmdbits); break; case GE_MTX_PROJECTION: - CopyMatrix24(result, gstate.projMatrix, 16); + CopyMatrix24(result, gstate.projMatrix, 16, cmdbits); break; default: return false; @@ -767,6 +768,20 @@ bool GPUCommon::GetMatrix24(GEMatrixType type, u32 *result) { return true; } +void GPUCommon::ResetMatrices() { + // This means we restored a context, so update the visible matrix data. + for (size_t i = 0; i < ARRAY_SIZE(gstate.boneMatrix); ++i) + matrixVisible.bone[i] = toFloat24(gstate.boneMatrix[i]); + for (size_t i = 0; i < ARRAY_SIZE(gstate.worldMatrix); ++i) + matrixVisible.world[i] = toFloat24(gstate.worldMatrix[i]); + for (size_t i = 0; i < ARRAY_SIZE(gstate.viewMatrix); ++i) + matrixVisible.view[i] = toFloat24(gstate.viewMatrix[i]); + for (size_t i = 0; i < ARRAY_SIZE(gstate.projMatrix); ++i) + matrixVisible.proj[i] = toFloat24(gstate.projMatrix[i]); + for (size_t i = 0; i < ARRAY_SIZE(gstate.tgenMatrix); ++i) + matrixVisible.tgen[i] = toFloat24(gstate.tgenMatrix[i]); +} + u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) { // TODO Check the stack values in missing arg and ajust the stack depth @@ -1389,7 +1404,7 @@ void GPUCommon::DoExecuteCall(u32 target) { // Check for the end if ((Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA && (Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET && - (gstate.boneMatrixNumber & 0x7F) <= 96 - 12) { + (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) { // Yep, pretty sure this is a bone matrix call. Double check stall first. if (target > currentList->stall || target + 12 * 4 < currentList->stall) { FastLoadBoneMatrix(target); @@ -1923,7 +1938,7 @@ void GPUCommon::Execute_Prim(u32 op, u32 diff) { (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 & 0x7F) <= 96 - 12) { + (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) { FastLoadBoneMatrix(target); } else { goto bail; @@ -2167,7 +2182,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { int i = 0; // We must record the individual data commands while debugRecording_. - bool fastLoad = !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; @@ -2188,7 +2203,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { } const int count = i; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op + count) & 0xF); + 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); @@ -2197,7 +2212,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) { // Note: it's uncommon to get here now, see above. - int num = gstate.worldmtxnum & 0xF; + int num = gstate.worldmtxnum & 0x00FFFFFF; u32 newVal = op << 8; if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) { Flush(); @@ -2205,7 +2220,7 @@ void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) { gstate_c.Dirty(DIRTY_WORLDMATRIX); } num++; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0xF); + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; } @@ -2216,7 +2231,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { const int end = 12 - (op & 0xF); int i = 0; - bool fastLoad = !debugRecording_; + bool fastLoad = !debugRecording_ && end > 0; if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { fastLoad = false; } @@ -2236,7 +2251,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { } const int count = i; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op + count) & 0xF); + 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); @@ -2245,7 +2260,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) { // Note: it's uncommon to get here now, see above. - int num = gstate.viewmtxnum & 0xF; + int num = gstate.viewmtxnum & 0x00FFFFFF; u32 newVal = op << 8; if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) { Flush(); @@ -2253,7 +2268,7 @@ void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) { gstate_c.Dirty(DIRTY_VIEWMATRIX); } num++; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0xF); + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; } @@ -2284,7 +2299,7 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { } const int count = i; - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op + count) & 0x1F); + 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); @@ -2293,16 +2308,16 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { void GPUCommon::Execute_ProjMtxData(u32 op, u32 diff) { // Note: it's uncommon to get here now, see above. - int num = gstate.projmtxnum & 0x1F; // NOTE: Changed from 0xF to catch overflows + int num = gstate.projmtxnum & 0x00FFFFFF; u32 newVal = op << 8; - if (num < 0x10 && newVal != ((const u32 *)gstate.projMatrix)[num]) { + 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 & 0xF); + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; } @@ -2313,7 +2328,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { const int end = 12 - (op & 0xF); int i = 0; - bool fastLoad = !debugRecording_; + bool fastLoad = !debugRecording_ && end > 0; if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { fastLoad = false; } @@ -2333,7 +2348,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { } const int count = i; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op + count) & 0xF); + 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); @@ -2342,7 +2357,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) { // Note: it's uncommon to get here now, see above. - int num = gstate.texmtxnum & 0xF; + int num = gstate.texmtxnum & 0x00FFFFFF; u32 newVal = op << 8; if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { Flush(); @@ -2350,7 +2365,7 @@ void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) { 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 & 0xF); + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; } @@ -2400,7 +2415,7 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { } const int count = i; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op + count) & 0x7F); + 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); @@ -2409,7 +2424,7 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) { // Note: it's uncommon to get here now, see above. - int num = gstate.boneMatrixNumber & 0x7F; + 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! @@ -2422,7 +2437,7 @@ void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) { ((u32 *)gstate.boneMatrix)[num] = newVal; } num++; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x7F); + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; } @@ -2661,7 +2676,7 @@ struct DisplayList_v2 { }; void GPUCommon::DoState(PointerWrap &p) { - auto s = p.Section("GPUCommon", 1, 4); + auto s = p.Section("GPUCommon", 1, 5); if (!s) return; @@ -2733,6 +2748,10 @@ void GPUCommon::DoState(PointerWrap &p) { Do(p, isbreak); Do(p, drawCompleteTicks); Do(p, busyTicks); + + if (s >= 5) { + Do(p, matrixVisible.all); + } } void GPUCommon::InterruptStart(int listid) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 480fe1df09..bd03692fda 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -111,7 +111,8 @@ public: int ListSync(int listid, int mode) override; u32 DrawSync(int mode) override; int GetStack(int index, u32 stackPtr) override; - bool GetMatrix24(GEMatrixType type, u32 *result) override; + bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) override; + void ResetMatrices() override; void DoState(PointerWrap &p) override; bool BusyDrawing() override; u32 Continue() override; @@ -366,6 +367,21 @@ protected: uint32_t immFlags_ = 0; bool immFirstSent_ = false; + // Whe 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. + union { + struct { + u32 bone[12 * 8]; + u32 world[12]; + u32 view[12]; + u32 proj[16]; + u32 tgen[12]; + }; + u32 all[12 * 8 + 12 + 12 + 16 + 12]; + } matrixVisible; + std::string reportingPrimaryInfo_; std::string reportingFullInfo_; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 54d5492ea4..2e1bc406d4 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -198,7 +198,8 @@ public: virtual u32 Continue() = 0; virtual u32 Break(int mode) = 0; virtual int GetStack(int index, u32 stackPtr) = 0; - virtual bool GetMatrix24(GEMatrixType type, u32 *result) = 0; + virtual bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) = 0; + virtual void ResetMatrices() = 0; virtual void InterruptStart(int listid) = 0; virtual void InterruptEnd(int listid) = 0; diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp index cec0f0c5d1..926b37a78c 100644 --- a/GPU/GPUState.cpp +++ b/GPU/GPUState.cpp @@ -24,6 +24,7 @@ #include "Core/System.h" #include "Core/MemMap.h" #include "GPU/ge_constants.h" +#include "GPU/GPUInterface.h" #include "GPU/GPUState.h" #ifdef _M_SSE @@ -85,11 +86,20 @@ static const CmdRange contextCmdRanges[] = { // Skip: {0xFA, 0xFF}, }; -static u32_le *SaveMatrix(u32_le *cmds, const float *mtx, int sz, int numcmd, int datacmd) { +static u32_le *SaveMatrix(u32_le *cmds, GEMatrixType type, int sz, int numcmd, int datacmd) { + if (!gpu) + return cmds; + *cmds++ = numcmd << 24; - for (int i = 0; i < sz; ++i) { - *cmds++ = (datacmd << 24) | toFloat24(mtx[i]); + // This saves the CPU-visible values, not the actual used ones, which may differ. + // Note that Restore overwrites both values. + if (type == GE_MTX_BONE0) { + for (int i = 0; i < 8; ++i) + gpu->GetMatrix24(GEMatrixType(GE_MTX_BONE0 + i), cmds + i * 12, datacmd << 24); + } else { + gpu->GetMatrix24(type, cmds, datacmd << 24); } + cmds += sz; return cmds; } @@ -117,6 +127,9 @@ void GPUgstate::Reset() { memset(gstate.tgenMatrix, 0, sizeof(gstate.tgenMatrix)); memset(gstate.boneMatrix, 0, sizeof(gstate.boneMatrix)); + if (gpu) + gpu->ResetMatrices(); + savedContextVersion = 1; } @@ -152,11 +165,11 @@ void GPUgstate::Save(u32_le *ptr) { memcpy(matrices, projMatrix, sizeof(projMatrix)); matrices += sizeof(projMatrix); memcpy(matrices, tgenMatrix, sizeof(tgenMatrix)); matrices += sizeof(tgenMatrix); } else { - cmds = SaveMatrix(cmds, boneMatrix, ARRAY_SIZE(boneMatrix), GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA); - cmds = SaveMatrix(cmds, worldMatrix, ARRAY_SIZE(worldMatrix), GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA); - cmds = SaveMatrix(cmds, viewMatrix, ARRAY_SIZE(viewMatrix), GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA); - cmds = SaveMatrix(cmds, projMatrix, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA); - cmds = SaveMatrix(cmds, tgenMatrix, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA); + cmds = SaveMatrix(cmds, GE_MTX_BONE0, ARRAY_SIZE(boneMatrix), GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA); + cmds = SaveMatrix(cmds, GE_MTX_WORLD, ARRAY_SIZE(worldMatrix), GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA); + cmds = SaveMatrix(cmds, GE_MTX_VIEW, ARRAY_SIZE(viewMatrix), GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA); + cmds = SaveMatrix(cmds, GE_MTX_PROJECTION, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA); + cmds = SaveMatrix(cmds, GE_MTX_TEXGEN, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA); *cmds++ = boneMatrixNumber; *cmds++ = worldmtxnum; @@ -199,7 +212,7 @@ void GPUgstate::FastLoadBoneMatrix(u32 addr) { #endif num += 12; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x7F); + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); } void GPUgstate::Restore(u32_le *ptr) { @@ -244,6 +257,9 @@ void GPUgstate::Restore(u32_le *ptr) { projmtxnum = *cmds++; texmtxnum = *cmds++; } + + if (gpu) + gpu->ResetMatrices(); } bool vertTypeIsSkinningEnabled(u32 vertType) { diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 21f77d4269..78f30a74f5 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -341,16 +341,16 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = { { GE_CMD_DITH2, 0, SoftDirty::PIXEL_DITHER }, { GE_CMD_DITH3, 0, SoftDirty::PIXEL_DITHER }, - { GE_CMD_WORLDMATRIXNUMBER }, + { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_WorldMtxNum }, { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_WorldMtxData }, - { GE_CMD_VIEWMATRIXNUMBER }, + { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_ViewMtxNum }, { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_ViewMtxData }, - { GE_CMD_PROJMATRIXNUMBER }, + { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_ProjMtxNum }, { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_ProjMtxData }, // Currently not state. - { GE_CMD_TGENMATRIXNUMBER }, + { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_TgenMtxNum }, { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_TgenMtxData }, - { GE_CMD_BONEMATRIXNUMBER }, + { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_BoneMtxNum }, { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_BoneMtxData }, // Vertex Screen/Texture/Color @@ -1040,83 +1040,162 @@ void SoftGPU::Execute_VertexType(u32 op, u32 diff) { } } +void SoftGPU::Execute_WorldMtxNum(u32 op, u32 diff) { + // Setting 0xFFFFF0 will reset to 0. + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); +} + +void SoftGPU::Execute_ViewMtxNum(u32 op, u32 diff) { + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF); +} + +void SoftGPU::Execute_ProjMtxNum(u32 op, u32 diff) { + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF); +} + +void SoftGPU::Execute_TgenMtxNum(u32 op, u32 diff) { + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF); +} + +void SoftGPU::Execute_BoneMtxNum(u32 op, u32 diff) { + // Setting any bits outside 0x7F are ignored and resets the internal counter. + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F); +} + void SoftGPU::Execute_WorldMtxData(u32 op, u32 diff) { - int num = gstate.worldmtxnum & 0xF; - u32 *target = num < 12 ? (u32 *)&gstate.worldMatrix[num] : (u32 *)&gstate.viewMatrix[num - 12]; - u32 newVal = op << 8; - if (newVal != *target) { - *target = newVal; - dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + int num = gstate.worldmtxnum & 0x00FFFFFF; + if (num < 12) { + u32 *target = (u32 *)&gstate.worldMatrix[num]; + u32 newVal = op << 8; + if (newVal != *target) { + *target = newVal; + dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + } } + + // Also update the CPU visible values, which update differently. + u32 *target = &matrixVisible.all[12 * 8 + (num & 0xF)]; + *target = op & 0x00FFFFFF; + num++; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0xF); + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; } void SoftGPU::Execute_ViewMtxData(u32 op, u32 diff) { - int num = gstate.viewmtxnum & 0xF; - u32 *target = num < 12 ? (u32 *)&gstate.viewMatrix[num] : (u32 *)&gstate.projMatrix[num - 12]; - u32 newVal = op << 8; - if (newVal != *target) { - *target = newVal; - dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + int num = gstate.viewmtxnum & 0x00FFFFFF; + if (num < 12) { + u32 *target = (u32 *)&gstate.viewMatrix[num]; + u32 newVal = op << 8; + if (newVal != *target) { + *target = newVal; + dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + } } + + // Also update the CPU visible values, which update differently. + u32 *target = &matrixVisible.all[12 * 8 + 12 + (num & 0xF)]; + *target = op & 0x00FFFFFF; + num++; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0xF); + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; } void SoftGPU::Execute_ProjMtxData(u32 op, u32 diff) { - int num = gstate.projmtxnum & 0xF; - u32 *target = (u32 *)&gstate.projMatrix[num]; - u32 newVal = op << 8; - if (newVal != *target) { - *target = newVal; - dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + int num = gstate.projmtxnum & 0x00FFFFFF; + if (num < 16) { + u32 *target = (u32 *)&gstate.projMatrix[num]; + u32 newVal = op << 8; + if (newVal != *target) { + *target = newVal; + dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + } } + + // Also update the CPU visible values, which update differently. + u32 *target = &matrixVisible.all[12 * 8 + 12 + 12 + (num & 0xF)]; + *target = op & 0x00FFFFFF; + num++; - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0xF); + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; } void SoftGPU::Execute_TgenMtxData(u32 op, u32 diff) { - int num = gstate.texmtxnum & 0xF; - u32 newVal = op << 8; - // Doesn't wrap to any other matrix. - if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { - ((u32 *)gstate.tgenMatrix)[num] = newVal; + int num = gstate.texmtxnum & 0x00FFFFFF; + if (num < 12) { + u32 *target = (u32 *)&gstate.tgenMatrix[num]; + u32 newVal = op << 8; // No dirtying, read during vertex read. + *target = newVal; } + + // Doesn't wrap to any other matrix. + if ((num & 0xF) < 12) { + matrixVisible.tgen[num & 0xF] = op & 0x00FFFFFF; + } + num++; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0xF); + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; } void SoftGPU::Execute_BoneMtxData(u32 op, u32 diff) { - int num = gstate.boneMatrixNumber & 0x7F; - u32 *target; + int num = gstate.boneMatrixNumber & 0x00FFFFFF; + if (num < 96) { - target = (u32 *)&gstate.boneMatrix[num]; - } else if (num < 96 + 12) { - target = (u32 *)&gstate.worldMatrix[num - 96]; - } else if (num < 96 + 12 + 12) { - target = (u32 *)&gstate.viewMatrix[num - 96 - 12]; - } else { - target = (u32 *)&gstate.projMatrix[num - 96 - 12 - 12]; + u32 *target = (u32 *)&gstate.boneMatrix[num]; + u32 newVal = op << 8; + // No dirtying, we read bone data during vertex read. + *target = newVal; } - u32 newVal = op << 8; - if (newVal != *target) { - *target = newVal; - // Dirty if it overflowed. We read bone data during vertex read. - if (num >= 96) - dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; - } + // Also update the CPU visible values, which update differently. + u32 *target = &matrixVisible.all[(num & 0x7F)]; + *target = op & 0x00FFFFFF; + num++; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x7F); + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; } +static void CopyMatrix24(u32_le *result, const u32 *mtx, u32 count, u32 cmdbits) { + for (u32 i = 0; i < count; ++i) { + result[i] = mtx[i] | cmdbits; + } +} + +bool SoftGPU::GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) { + switch (type) { + case GE_MTX_BONE0: + case GE_MTX_BONE1: + case GE_MTX_BONE2: + case GE_MTX_BONE3: + case GE_MTX_BONE4: + case GE_MTX_BONE5: + case GE_MTX_BONE6: + case GE_MTX_BONE7: + CopyMatrix24(result, matrixVisible.bone + (type - GE_MTX_BONE0) * 12, 12, cmdbits); + break; + case GE_MTX_TEXGEN: + CopyMatrix24(result, matrixVisible.tgen, 12, cmdbits); + break; + case GE_MTX_WORLD: + CopyMatrix24(result, matrixVisible.world, 12, cmdbits); + break; + case GE_MTX_VIEW: + CopyMatrix24(result, matrixVisible.view, 12, cmdbits); + break; + case GE_MTX_PROJECTION: + CopyMatrix24(result, matrixVisible.proj, 16, cmdbits); + break; + default: + return false; + } + return true; +} + void SoftGPU::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { GPUCommon::Execute_ImmVertexAlphaPrim(op, diff); // We won't flush as often as hardware renderers, so we want to flush right away. diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 9944f2b789..ee944e459a 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -181,12 +181,20 @@ public: // Overridden to change flushing behavior. void Execute_Call(u32 op, u32 diff); + void Execute_WorldMtxNum(u32 op, u32 diff); + void Execute_ViewMtxNum(u32 op, u32 diff); + void Execute_ProjMtxNum(u32 op, u32 diff); + void Execute_TgenMtxNum(u32 op, u32 diff); + void Execute_BoneMtxNum(u32 op, u32 diff); + void Execute_WorldMtxData(u32 op, u32 diff); void Execute_ViewMtxData(u32 op, u32 diff); void Execute_ProjMtxData(u32 op, u32 diff); void Execute_TgenMtxData(u32 op, u32 diff); void Execute_BoneMtxData(u32 op, u32 diff); + bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) override; + void Execute_ImmVertexAlphaPrim(u32 op, u32 diff); typedef void (SoftGPU::*CmdFunc)(u32 op, u32 diff); From 34d1661c48203fe369cd1de38b5167e4a16ba69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Sep 2022 13:40:57 +0200 Subject: [PATCH 237/326] Quiet the Vulkan miniprofiler (for texture uploads etc) a bit --- Common/GPU/Vulkan/VulkanProfiler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanProfiler.cpp b/Common/GPU/Vulkan/VulkanProfiler.cpp index 36305484da..fccea843b8 100644 --- a/Common/GPU/Vulkan/VulkanProfiler.cpp +++ b/Common/GPU/Vulkan/VulkanProfiler.cpp @@ -34,13 +34,13 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm static const char * const indent[4] = { "", " ", " ", " " }; if (!scopes_.empty()) { - NOTICE_LOG(G3D, "Profiling events this frame:"); + INFO_LOG(G3D, "Profiling events this frame:"); } // Log it all out. for (auto &scope : scopes_) { if (scope.endQueryId == -1) { - NOTICE_LOG(G3D, "Unclosed scope: %s", scope.name.c_str()); + WARN_LOG(G3D, "Unclosed scope: %s", scope.name.c_str()); continue; } uint64_t startTime = results[scope.startQueryId]; @@ -50,7 +50,7 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm double milliseconds = (double)delta * timestampConversionFactor; - NOTICE_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name.c_str(), milliseconds); + INFO_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name.c_str(), milliseconds); } scopes_.clear(); From de51d067f210e2ce9a3d8dd222791762e622da60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Sep 2022 13:41:41 +0200 Subject: [PATCH 238/326] If a framebuffer starts using a different depth buffer than before, re-point. Fixes depth artifacts in Silent Hill: Origins. See issue #16126 --- GPU/Common/FramebufferManagerCommon.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index b4db88965a..807472703d 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -551,6 +551,15 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { bool newlyUsingDepth = (currentRenderVfb_->usageFlags & FB_USAGE_RENDER_DEPTH) == 0; currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; + uint32_t boundDepthBuffer = gstate.getDepthBufAddress() & 0x3FFFFFFF; + if (currentRenderVfb_->z_address != boundDepthBuffer) { + WARN_LOG(G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.", + currentRenderVfb_->fb_address, currentRenderVfb_->fb_stride, currentRenderVfb_->z_address, boundDepthBuffer); + // Technically, here we should copy away the depth buffer to another framebuffer that uses that z_address, or maybe + // even write it back to RAM. However, this is rare. Silent Hill is one example, see #16126. + currentRenderVfb_->z_address = boundDepthBuffer; + } + // If this first draw call is anything other than a clear, "resolve" the depth buffer, // by copying from any overlapping buffers with fresher content. if (!isClearingDepth && useBufferedRendering_) { @@ -1541,9 +1550,9 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, bool creating = old.bufferWidth == 0; if (creating) { - WARN_LOG(FRAMEBUF, "Creating %s FBO at %08x/%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force); + WARN_LOG(FRAMEBUF, "Creating %s FBO at %08x/%08x stride=%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->z_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force); } else { - WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%d from %dx%d to %dx%d (force=%d, skipCopy=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force, (int)skipCopy); + WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%08x stride=%d from %dx%d to %dx%d (force=%d, skipCopy=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->z_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force, (int)skipCopy); } // During hardware rendering, we always render at full color depth even if the game wouldn't on real hardware. From 3adad176cc8b3be05a14d3f6347eb104031b953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Sep 2022 13:56:03 +0200 Subject: [PATCH 239/326] Add Silent Hill: Origins to compatibility setting BlockTransferAllowCreateFB. Eliminates a readback with (so far) no perceptible bad effects. --- assets/compat.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index a86f7562b3..2e2f4efe24 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -709,6 +709,14 @@ ULUS10428 = true ULES01375 = true ULUS10429 = true +# Silent Hill: Origins +# Avoids readback. +ULES00869 = true +ULUS10285 = true +ULKS46161 = true +ULJM05281 = true +NPJH50051 = true + [IntraVRAMBlockTransferAllowCreateFB] # Final Fantasy - Type 0 ULJM05900 = true From bd759790b025df70fcd869f1043ec0d25a77fa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Sep 2022 14:09:40 +0200 Subject: [PATCH 240/326] Update the Vulkan debug names when reassigning depth buffers. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 22 ++++++++++++++++++++-- Common/GPU/Vulkan/VulkanRenderManager.h | 2 ++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 3 +++ Common/GPU/thin3d.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 14 +++++++++----- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 49abbde2d0..b0d8327301 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -153,16 +153,34 @@ VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRe _dbg_assert_(tag); CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); - vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag).c_str()); if (createDepthStencilBuffer) { CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); - vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag).c_str()); } + UpdateTag(tag); + // We create the actual framebuffer objects on demand, because some combinations might not make sense. // Framebuffer objects are just pointers to a set of images, so no biggie. } +void VKRFramebuffer::UpdateTag(const char *newTag) { + char name[128]; + snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str()); + vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name); + vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE_VIEW, name); + if (depth.image) { + snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str()); + vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name); + vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE_VIEW, name); + } + for (int rpType = 0; rpType < RP_TYPE_COUNT; rpType++) { + if (framebuf[rpType]) { + snprintf(name, sizeof(name), "fb_%s", tag_.c_str()); + vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name); + } + } +} + VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) { if (framebuf[(int)rpType]) { return framebuf[(int)rpType]; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 5059324116..66692d99a7 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -57,6 +57,8 @@ public: return tag_.c_str(); } + void UpdateTag(const char *newTag); + // TODO: Hide. VulkanContext *vulkan_; private: diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index ceba56f78b..111cea1d2b 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1490,6 +1490,9 @@ public: buf_ = nullptr; } VKRFramebuffer *GetFB() const { return buf_; } + void UpdateTag(const char *newTag) override { + buf_->UpdateTag(newTag); + } private: VKRFramebuffer *buf_; }; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 0fba6ee6f2..ee0d50602a 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -411,6 +411,7 @@ class Framebuffer : public RefCountedObject { public: int Width() { return width_; } int Height() { return height_; } + virtual void UpdateTag(const char *tag) {} protected: int width_ = -1, height_ = -1; }; diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 807472703d..f97f2e91c0 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -49,6 +49,10 @@ #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" +static size_t FormatFramebufferName(VirtualFramebuffer *vfb, char *tag, size_t len) { + return snprintf(tag, len, "FB_%08x_%08x_%dx%d_%s", vfb->fb_address, vfb->z_address, vfb->bufferWidth, vfb->bufferHeight, GeBufferFormatToString(vfb->fb_format)); +} + FramebufferManagerCommon::FramebufferManagerCommon(Draw::DrawContext *draw) : draw_(draw), draw2D_(draw_) { presentation_ = new PresentationCommon(draw); @@ -553,11 +557,15 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { uint32_t boundDepthBuffer = gstate.getDepthBufAddress() & 0x3FFFFFFF; if (currentRenderVfb_->z_address != boundDepthBuffer) { - WARN_LOG(G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.", + WARN_LOG_N_TIMES(z_reassign, 5, G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.", currentRenderVfb_->fb_address, currentRenderVfb_->fb_stride, currentRenderVfb_->z_address, boundDepthBuffer); + // Technically, here we should copy away the depth buffer to another framebuffer that uses that z_address, or maybe // even write it back to RAM. However, this is rare. Silent Hill is one example, see #16126. currentRenderVfb_->z_address = boundDepthBuffer; + char tag[128]; + FormatFramebufferName(currentRenderVfb_, tag, sizeof(tag)); + currentRenderVfb_->fbo->UpdateTag(tag); } // If this first draw call is anything other than a clear, "resolve" the depth buffer, @@ -1495,10 +1503,6 @@ void FramebufferManagerCommon::DecimateFBOs() { } } -static size_t FormatFramebufferName(VirtualFramebuffer *vfb, char *tag, size_t len) { - return snprintf(tag, len, "FB_%08x_%08x_%dx%d_%s", vfb->fb_address, vfb->z_address, vfb->bufferWidth, vfb->bufferHeight, GeBufferFormatToString(vfb->fb_format)); -} - // Requires width/height to be set already. void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, int h, bool force, bool skipCopy) { _dbg_assert_(w > 0); From d2de89e778f5c316dd75361280b50b4bb5975ec2 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 28 Sep 2022 16:22:59 +0200 Subject: [PATCH 241/326] OpenXR - Add README.md about the SDK --- ext/openxr/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ext/openxr/README.md diff --git a/ext/openxr/README.md b/ext/openxr/README.md new file mode 100644 index 0000000000..a8ac9b926b --- /dev/null +++ b/ext/openxr/README.md @@ -0,0 +1,14 @@ +### Introduction + +OpenXR is an interface defined by Khronos Group. Implementation of the interface is done by each VR headset vendor differently. + +### Files + +The used interface (headers in C language) is an older version because Pico doesn't support the latest one yet. +The Implementations are taken from the vendor specific SDKs. + +### Links + +Khronos OpenXR SDK: https://github.com/KhronosGroup/OpenXR-SDK/ +Oculus OpenXR SDK: https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/ +Pico OpenXR SDK https://developer-global.pico-interactive.com/sdk?deviceId=1&platformId=3&itemId=11 From 088272c0c4a797609f2810831f99b927340b1f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubo=C5=A1=20Von=C3=A1sek?= Date: Wed, 28 Sep 2022 16:24:26 +0200 Subject: [PATCH 242/326] Update README.md --- ext/openxr/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/openxr/README.md b/ext/openxr/README.md index a8ac9b926b..13db1d3ade 100644 --- a/ext/openxr/README.md +++ b/ext/openxr/README.md @@ -9,6 +9,6 @@ The Implementations are taken from the vendor specific SDKs. ### Links -Khronos OpenXR SDK: https://github.com/KhronosGroup/OpenXR-SDK/ -Oculus OpenXR SDK: https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/ -Pico OpenXR SDK https://developer-global.pico-interactive.com/sdk?deviceId=1&platformId=3&itemId=11 +- Khronos OpenXR SDK: https://github.com/KhronosGroup/OpenXR-SDK/ +- Oculus OpenXR SDK: https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/ +- Pico OpenXR SDK https://developer-global.pico-interactive.com/sdk?deviceId=1&platformId=3&itemId=11 From 8534b8d7ca678c6ad41f5f835cbe1de601174010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Sep 2022 16:44:40 +0200 Subject: [PATCH 243/326] Typo fix --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index b0d8327301..47d4d1896e 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -167,11 +167,11 @@ void VKRFramebuffer::UpdateTag(const char *newTag) { char name[128]; snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str()); vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name); - vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE_VIEW, name); + vulkan_->SetDebugName(color.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name); if (depth.image) { snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str()); vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name); - vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE_VIEW, name); + vulkan_->SetDebugName(depth.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name); } for (int rpType = 0; rpType < RP_TYPE_COUNT; rpType++) { if (framebuf[rpType]) { From d31f129407fa229e79f2beaa5172b643ee6d4ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 29 Sep 2022 18:51:21 +0200 Subject: [PATCH 244/326] Add hidden ini file setting that you can use to hide the menu bar. Windows-only. Useful together with some video recording software. Might make some actual UI later. --- Core/Config.cpp | 1 + Core/Config.h | 1 + Windows/MainWindow.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 3798362ef8..45e396c9b7 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -610,6 +610,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("PauseWhenMinimized", &g_Config.bPauseWhenMinimized, false, true, true), ConfigSetting("DumpDecryptedEboots", &g_Config.bDumpDecryptedEboot, false, true, true), ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false), + ConfigSetting("ShowMenuBar", &g_Config.bShowMenuBar, true, true, false), ReportedConfigSetting("MemStickInserted", &g_Config.bMemStickInserted, true, true, true), ConfigSetting("EnablePlugins", &g_Config.bLoadPlugins, true, true, true), diff --git a/Core/Config.h b/Core/Config.h index 36ec3a760d..8a7f519621 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -191,6 +191,7 @@ public: int iWindowY; int iWindowWidth; // Windows and other windowed environments int iWindowHeight; + bool bShowMenuBar; // Windows-only float fUITint; float fUISaturation; diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index f483a55d9f..ebe755b979 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -353,8 +353,8 @@ namespace MainWindow ::SetWindowLong(hWnd, GWL_STYLE, dwStyle); - // Remove the menu bar. This can trigger WM_SIZE - ::SetMenu(hWnd, goingFullscreen ? NULL : menu); + // Remove the menu bar. This can trigger WM_SIZE because the contents change size. + ::SetMenu(hWnd, goingFullscreen || !g_Config.bShowMenuBar ? NULL : menu); if (g_Config.UseFullScreen() != goingFullscreen) { g_Config.bFullScreen = goingFullscreen; From f8f52565b9af2f9e9245aa40a23c8a95b2937530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 29 Sep 2022 21:36:20 +0200 Subject: [PATCH 245/326] Remove redundant call to SetMenu --- Windows/MainWindow.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index ebe755b979..733e270db3 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -334,9 +334,6 @@ namespace MainWindow dwStyle &= ~WS_POPUP; // Re-add caption and border styles. dwStyle |= WS_OVERLAPPEDWINDOW; - - // Put back the menu bar. - ::SetMenu(hWnd, menu); } else { // If the window was maximized before going fullscreen, make sure to restore first // in order not to have the taskbar show up on top of PPSSPP. From a72a425df6e9f8e25f1d1641ece0828d8062e64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 30 Sep 2022 00:02:04 +0200 Subject: [PATCH 246/326] Use compat.ini to avoid readbacks in Silent Hill: Shattered Memories as well. NOTE: I'm not 100% convinced this is ok, there might be CPU graphics manipulations here. But in practice, in the current state where shadows don't work, it'll do. Thanks mrfixit2001. --- assets/compat.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/compat.ini b/assets/compat.ini index 2e2f4efe24..6df16cff48 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -717,6 +717,14 @@ ULKS46161 = true ULJM05281 = true NPJH50051 = true +# Silent Hill: Shattered Memories +# Avoids readback. +ULUS10450 = true +ULES01352 = true +ULJM05564 = true +NPJH50148 = true +ULAS42189 = true + [IntraVRAMBlockTransferAllowCreateFB] # Final Fantasy - Type 0 ULJM05900 = true From 904fb38003d6e38603fd6d6cad22c24011a06ba1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 29 Sep 2022 21:36:53 -0700 Subject: [PATCH 247/326] GPU: Restore matrices with dirtying. Without this, it's possible we might not notice or apply a change whether in uniforms or etc. --- GPU/GPUCommon.cpp | 31 ++++++++++++++++++++++++++++++- GPU/GPUState.cpp | 24 +++++++++++------------- GPU/Software/SoftGpu.cpp | 5 +++++ GPU/Software/SoftGpu.h | 1 + 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 49e6d5dcf3..b1e435f7ed 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -780,6 +780,9 @@ void GPUCommon::ResetMatrices() { matrixVisible.proj[i] = toFloat24(gstate.projMatrix[i]); for (size_t i = 0; i < ARRAY_SIZE(gstate.tgenMatrix); ++i) matrixVisible.tgen[i] = toFloat24(gstate.tgenMatrix[i]); + + // Assume all the matrices changed, so dirty things related to them. + gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX | DIRTY_PROJMATRIX | DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE | DIRTY_BONE_UNIFORMS); } u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) { @@ -1341,7 +1344,8 @@ void GPUCommon::Execute_Iaddr(u32 op, u32 diff) { } void GPUCommon::Execute_Origin(u32 op, u32 diff) { - gstate_c.offsetAddr = currentList->pc; + if (currentList) + gstate_c.offsetAddr = currentList->pc; } void GPUCommon::Execute_Jump(u32 op, u32 diff) { @@ -2175,6 +2179,11 @@ void GPUCommon::Execute_BlockTransferStart(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)); @@ -2225,6 +2234,11 @@ void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) { } 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)); @@ -2273,6 +2287,11 @@ void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) { } 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)); @@ -2322,6 +2341,11 @@ void GPUCommon::Execute_ProjMtxData(u32 op, u32 diff) { } 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)); @@ -2370,6 +2394,11 @@ void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) { } 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)); diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp index 926b37a78c..374816fa76 100644 --- a/GPU/GPUState.cpp +++ b/GPU/GPUState.cpp @@ -127,9 +127,6 @@ void GPUgstate::Reset() { memset(gstate.tgenMatrix, 0, sizeof(gstate.tgenMatrix)); memset(gstate.boneMatrix, 0, sizeof(gstate.boneMatrix)); - if (gpu) - gpu->ResetMatrices(); - savedContextVersion = 1; } @@ -143,6 +140,7 @@ void GPUgstate::Save(u32_le *ptr) { u32_le *cmds = ptr + 17; for (size_t i = 0; i < ARRAY_SIZE(contextCmdRanges); ++i) { for (int n = contextCmdRanges[i].start; n <= contextCmdRanges[i].end; ++n) { + // We'll run ReapplyGfxState after this to process dirtying. *cmds++ = cmdmem[n]; } } @@ -171,11 +169,11 @@ void GPUgstate::Save(u32_le *ptr) { cmds = SaveMatrix(cmds, GE_MTX_PROJECTION, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA); cmds = SaveMatrix(cmds, GE_MTX_TEXGEN, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA); - *cmds++ = boneMatrixNumber; - *cmds++ = worldmtxnum; - *cmds++ = viewmtxnum; - *cmds++ = projmtxnum; - *cmds++ = texmtxnum; + *cmds++ = boneMatrixNumber & 0xFF00007F; + *cmds++ = worldmtxnum & 0xFF00000F; + *cmds++ = viewmtxnum & 0xFF00000F; + *cmds++ = projmtxnum & 0xFF00000F; + *cmds++ = texmtxnum & 0xFF00000F; *cmds++ = GE_CMD_END << 24; } } @@ -251,11 +249,11 @@ void GPUgstate::Restore(u32_le *ptr) { cmds = LoadMatrix(cmds, projMatrix, ARRAY_SIZE(projMatrix)); cmds = LoadMatrix(cmds, tgenMatrix, ARRAY_SIZE(tgenMatrix)); - boneMatrixNumber = *cmds++; - worldmtxnum = *cmds++; - viewmtxnum = *cmds++; - projmtxnum = *cmds++; - texmtxnum = *cmds++; + boneMatrixNumber = (*cmds++) & 0xFF00007F; + worldmtxnum = (*cmds++) & 0xFF00000F; + viewmtxnum = (*cmds++) & 0xFF00000F; + projmtxnum = (*cmds++) & 0xFF00000F; + texmtxnum = (*cmds++) & 0xFF00000F; } if (gpu) diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 78f30a74f5..65065fa75c 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1196,6 +1196,11 @@ bool SoftGPU::GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) { return true; } +void SoftGPU::ResetMatrices() { + GPUCommon::ResetMatrices(); + dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; +} + void SoftGPU::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { GPUCommon::Execute_ImmVertexAlphaPrim(op, diff); // We won't flush as often as hardware renderers, so we want to flush right away. diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index ee944e459a..29ce1e5934 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -194,6 +194,7 @@ public: void Execute_BoneMtxData(u32 op, u32 diff); bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) override; + void ResetMatrices() override; void Execute_ImmVertexAlphaPrim(u32 op, u32 diff); From 7cf05d0a46a31d1c491ac2d47734c1bf34b5ecd2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 29 Sep 2022 22:29:30 -0700 Subject: [PATCH 248/326] GPU: Fix missed dirtying when fast loading tgen. --- GPU/GPUCommon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index b1e435f7ed..1ff4b30b11 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2363,7 +2363,8 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { if (dst[i] != newVal) { Flush(); dst[i] = newVal; - gstate_c.Dirty(DIRTY_TEXMATRIX); + // We check the matrix to see if we need projection. + gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); } if (++i >= end) { break; From dc90a5a851c7f79d8f73e5492cc2251dbd572a4b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 29 Sep 2022 21:47:56 -0700 Subject: [PATCH 249/326] softgpu: Avoid projecting textures in common case. Several games appear to intentionally set the matrix flat. --- GPU/Software/Rasterizer.cpp | 6 ++++++ GPU/Software/SoftGpu.cpp | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index e07d7dd05a..d59c1fb0cf 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -130,6 +130,12 @@ void ComputeRasterizerState(RasterizerState *state) { state->minFilt = gstate.isMinifyFilteringEnabled(); state->magFilt = gstate.isMagnifyFilteringEnabled(); state->textureProj = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; + if (state->textureProj) { + // We may be able to optimize this off. This is actually kinda common. + if (gstate.tgenMatrix[2] == 0.0f && gstate.tgenMatrix[5] == 0.0f && gstate.tgenMatrix[8] == 0.0f && gstate.tgenMatrix[11] == 1.0f) { + state->textureProj = false; + } + } } state->shadeGouraud = gstate.getShadeMode() == GE_SHADE_GOURAUD; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 65065fa75c..cddfa557d9 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1127,8 +1127,11 @@ void SoftGPU::Execute_TgenMtxData(u32 op, u32 diff) { if (num < 12) { u32 *target = (u32 *)&gstate.tgenMatrix[num]; u32 newVal = op << 8; - // No dirtying, read during vertex read. - *target = newVal; + if (newVal != *target) { + *target = newVal; + // This is mainly used in vertex read, but also affects if we enable texture projection. + dirtyFlags_ |= SoftDirty::RAST_TEX; + } } // Doesn't wrap to any other matrix. @@ -1198,7 +1201,7 @@ bool SoftGPU::GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) { void SoftGPU::ResetMatrices() { GPUCommon::ResetMatrices(); - dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX | SoftDirty::RAST_TEX; } void SoftGPU::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { From 6468e0f03e6f38366a9ad148973a0c3cb3eb37cf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 29 Sep 2022 22:00:13 -0700 Subject: [PATCH 250/326] softjit: Fix dst blend shift. Example: src * dst.a + dst * one, still requires a shift back. --- GPU/Software/DrawPixelX86.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Software/DrawPixelX86.cpp b/GPU/Software/DrawPixelX86.cpp index ed70b9c466..32e6aeb835 100644 --- a/GPU/Software/DrawPixelX86.cpp +++ b/GPU/Software/DrawPixelX86.cpp @@ -1159,7 +1159,7 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) { if (id.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE) PXOR(dstReg, R(dstReg)); } else if (id.AlphaBlendDst() == PixelBlendFactor::ONE) { - if (blendState.dstColorAsFactor) + if (blendState.dstColorAsFactor || blendState.usesDstAlpha) PSRLW(dstReg, 4); } From 77696573f43ffe3dccabd94d3ec4b9ecbbb74fb4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 30 Sep 2022 00:19:21 -0700 Subject: [PATCH 251/326] GE Debugger: Correct rounded coords in vertex list. Were previously rounding to pixel, not subpixel. Also, show out of range values for clarity on clamping/culling. --- GPU/Software/TransformUnit.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 633af978ef..7d1d28d07d 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -981,6 +981,9 @@ bool TransformUnit::GetCurrentSimpleVertices(int count, std::vector= 0xFFFF ? z : (float)screenPos.z; } if (gstate.vertType & GE_VTYPE_COL_MASK) { From fec708489a9f15a7c08b4fb52eb3d3f20015be62 Mon Sep 17 00:00:00 2001 From: lainon Date: Fri, 30 Sep 2022 12:26:30 +0300 Subject: [PATCH 252/326] Correct cleaning string and remove unused vars --- Common/Data/Encoding/Compression.cpp | 1 - Common/File/Path.cpp | 1 - Common/GPU/D3D9/D3D9ShaderCompiler.cpp | 2 +- Common/GPU/ShaderTranslation.cpp | 2 +- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 4 ---- Core/Config.cpp | 8 ++++---- Core/Core.cpp | 6 +++--- Core/Debugger/DisassemblyManager.cpp | 8 ++++---- Core/Debugger/WebSocket/WebSocketUtils.cpp | 2 +- Core/Dialog/SavedataParam.cpp | 2 +- Core/FileSystems/ISOFileSystem.cpp | 2 +- Core/FileSystems/VirtualDiscFileSystem.cpp | 1 - Core/Loaders.cpp | 2 +- Core/MIPS/MIPSAsm.cpp | 2 +- Core/System.cpp | 2 +- Core/Util/GameManager.cpp | 2 +- Core/WebServer.cpp | 2 +- GPU/Common/PostShader.cpp | 4 ++-- GPU/Common/TextureCacheCommon.cpp | 2 -- UI/CwCheatScreen.cpp | 1 - UI/EmuScreen.cpp | 2 +- UI/GameSettingsScreen.cpp | 2 +- UI/Store.cpp | 2 +- UI/TextureUtil.cpp | 4 ++-- Windows/Debugger/CtrlDisAsmView.cpp | 2 +- Windows/Debugger/CtrlMemView.cpp | 2 +- Windows/InputBox.cpp | 4 ++-- Windows/MainWindowMenu.cpp | 2 +- Windows/main.cpp | 2 +- android/jni/TestRunner.cpp | 4 ++-- headless/Headless.cpp | 2 +- unittest/UnitTest.cpp | 2 +- 32 files changed, 38 insertions(+), 48 deletions(-) diff --git a/Common/Data/Encoding/Compression.cpp b/Common/Data/Encoding/Compression.cpp index 751a9595cf..3e2e499a18 100644 --- a/Common/Data/Encoding/Compression.cpp +++ b/Common/Data/Encoding/Compression.cpp @@ -95,7 +95,6 @@ bool decompress_string(const std::string& str, std::string *dest) { inflateEnd(&zs); if (ret != Z_STREAM_END) { // an error occurred that was not EOF - std::ostringstream oss; ERROR_LOG(IO, "Exception during zlib decompression: (%i) %s", ret, zs.msg); return false; } diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index ba3372e78a..dd01ac26a7 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -357,7 +357,6 @@ bool Path::ComputePathTo(const Path &other, std::string &path) const { return true; } - std::string diff; if (type_ == PathType::CONTENT_URI) { AndroidContentURI a(path_); AndroidContentURI b(other.path_); diff --git a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp index f41ea3911d..5a647196fe 100644 --- a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp +++ b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp @@ -47,7 +47,7 @@ LPD3DBLOB CompileShaderToByteCodeD3D9(const char *code, const char *target, std: pShaderCode = nullptr; } } else { - *errorMessage = ""; + (*errorMessage).clear(); } return pShaderCode; diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp index 34de213451..84cd69afe5 100644 --- a/Common/GPU/ShaderTranslation.cpp +++ b/Common/GPU/ShaderTranslation.cpp @@ -233,7 +233,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan return false; #endif - *errorMessage = ""; + (*errorMessage).clear(); glslang::TProgram program; const char *shaderStrings[1]{}; diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 428d66e2cc..3eee8f7ad7 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -1348,7 +1348,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c int n = 0; int stage = 0; - VkImageMemoryBarrier barriers[2]{}; if (step.render.framebuffer->color.layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { recordBarrier_.TransitionImage( step.render.framebuffer->color.image, @@ -1758,9 +1757,6 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) { // The barrier code doesn't handle this case. We'd need to transition to GENERAL to do an intra-image copy. _dbg_assert_(step.blit.src != step.blit.dst); - VkImageMemoryBarrier srcBarriers[2]{}; - VkImageMemoryBarrier dstBarriers[2]{}; - VKRFramebuffer *src = step.blit.src; VKRFramebuffer *dst = step.blit.dst; diff --git a/Core/Config.cpp b/Core/Config.cpp index 45e396c9b7..248bcb2852 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1424,7 +1424,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { // build of PPSSPP, receive an upgrade notice, then start a newer version, and still receive the upgrade notice, // even if said newer version is >= the upgrade found online. if ((dismissedVersion == upgradeVersion) || (versionsValid && (installed >= upgrade))) { - upgradeMessage = ""; + upgradeMessage.clear(); } // Check for new version on every 10 runs. @@ -1636,16 +1636,16 @@ void Config::DownloadCompletedCallback(http::Download &download) { if (installed >= upgrade) { INFO_LOG(LOADER, "Version check: Already up to date, erasing any upgrade message"); - g_Config.upgradeMessage = ""; + g_Config.upgradeMessage.clear(); g_Config.upgradeVersion = upgrade.ToString(); - g_Config.dismissedVersion = ""; + g_Config.dismissedVersion.clear(); return; } if (installed < upgrade && dismissed != upgrade) { g_Config.upgradeMessage = "New version of PPSSPP available!"; g_Config.upgradeVersion = upgrade.ToString(); - g_Config.dismissedVersion = ""; + g_Config.dismissedVersion.clear(); } } diff --git a/Core/Core.cpp b/Core/Core.cpp index 52a1df8f4c..c1bb28cefd 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -447,7 +447,7 @@ void Core_MemoryException(u32 address, u32 pc, MemoryExceptionType type) { ExceptionInfo &e = g_exceptionInfo; e = {}; e.type = ExceptionType::MEMORY; - e.info = ""; + e.info.clear(); e.memory_type = type; e.address = address; e.pc = pc; @@ -483,7 +483,7 @@ void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) { ExceptionInfo &e = g_exceptionInfo; e = {}; e.type = ExceptionType::BAD_EXEC_ADDR; - e.info = ""; + e.info.clear(); e.exec_type = type; e.address = address; e.pc = pc; @@ -498,7 +498,7 @@ void Core_Break(u32 pc) { ExceptionInfo &e = g_exceptionInfo; e = {}; e.type = ExceptionType::BREAK; - e.info = ""; + e.info.clear(); e.pc = pc; if (!g_Config.bIgnoreBadMemAccess) { diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 511aeee82a..ab0e10aa9b 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -316,7 +316,7 @@ void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLin dest.params = "Disassembly failure"; } else { dest.name = "-"; - dest.params = ""; + dest.params.clear(); } } @@ -1003,7 +1003,7 @@ void DisassemblyData::createLines() lines[currentLineStart] = entry; lineAddresses.push_back(currentLineStart); - currentLine = ""; + currentLine.clear(); currentLineStart = pos-1; inString = false; } @@ -1028,7 +1028,7 @@ void DisassemblyData::createLines() lines[currentLineStart] = entry; lineAddresses.push_back(currentLineStart); - currentLine = ""; + currentLine.clear(); currentLineStart = pos-1; inString = false; } @@ -1099,7 +1099,7 @@ void DisassemblyData::createLines() lines[currentLineStart] = entry; lineAddresses.push_back(currentLineStart); - currentLine = ""; + currentLine.clear(); currentLineStart = currentPos; } diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index cc49e33336..8d6be87b9e 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -262,7 +262,7 @@ bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerPa return true; } else if (tag == JSON_NULL) { if (required) { - *out = ""; + (*out).clear(); } return true; } else if (tag == JSON_NUMBER) { diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index 5a04ebc882..a4f437d40c 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -258,7 +258,7 @@ std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param, inline static std::string FixedToString(const char *str, size_t n) { if (!str) { - return std::string(""); + return std::string(); } else { return std::string(str, strnlen(str, n)); } diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp index a873f3f19b..0d57bf2848 100644 --- a/Core/FileSystems/ISOFileSystem.cpp +++ b/Core/FileSystems/ISOFileSystem.cpp @@ -148,7 +148,7 @@ ISOFileSystem::ISOFileSystem(IHandleAllocator *_hAlloc, BlockDevice *_blockDevic if (!blockDevice->ReadBlock(16, (u8*)&desc)) blockDevice->NotifyReadError(); - entireISO.name = ""; + entireISO.name.clear(); entireISO.isDirectory = false; entireISO.startingPosition = 0; entireISO.size = _blockDevice->GetNumBlocks(); diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index dd41e95855..c3a339172d 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -81,7 +81,6 @@ void VirtualDiscFileSystem::LoadFileListIndex() { return; } - std::string buf; static const int MAX_LINE_SIZE = 2048; char linebuf[MAX_LINE_SIZE]{}; while (fgets(linebuf, MAX_LINE_SIZE, f)) { diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 09ac48f47f..02625a16c5 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -60,7 +60,7 @@ FileLoader *ConstructFileLoader(const Path &filename) { // TODO : improve, look in the file more IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorString) { - *errorString = ""; + (*errorString).clear(); if (fileLoader == nullptr) { *errorString = "Invalid fileLoader"; return IdentifiedFileType::ERROR_IDENTIFYING; diff --git a/Core/MIPS/MIPSAsm.cpp b/Core/MIPS/MIPSAsm.cpp index 97d34a4f18..f95d0d1fc0 100644 --- a/Core/MIPS/MIPSAsm.cpp +++ b/Core/MIPS/MIPSAsm.cpp @@ -79,7 +79,7 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) g_symbolMap->GetLabels(args.labels); } - errorText = L""; + errorText.clear(); if (!runArmips(args)) { for (size_t i = 0; i < errors.size(); i++) diff --git a/Core/System.cpp b/Core/System.cpp index 9c7519edd6..7f96ffb7c5 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -426,7 +426,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { if (g_CoreParameter.graphicsContext == nullptr) { g_CoreParameter.graphicsContext = temp; } - g_CoreParameter.errorString = ""; + g_CoreParameter.errorString.clear(); pspIsIniting = true; PSP_SetLoading("Loading game..."); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index b6f4ccbd65..e808f07b6c 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -674,7 +674,7 @@ bool GameManager::InstallRawISO(const Path &file, const std::string &originalNam void GameManager::ResetInstallError() { if (!installInProgress_) { - installError_ = ""; + installError_.clear(); } } diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 3d6d9b37cb..4aff212eba 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -297,7 +297,7 @@ static void ForwardDebuggerRequest(const http::Request &request) { // Check if this is a websocket request... std::string upgrade; if (!request.GetHeader("upgrade", &upgrade)) { - upgrade = ""; + upgrade.clear(); } // Yes - proceed with the socket. diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index ed86397521..ad02d603b4 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -55,7 +55,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct off.name = "Off"; off.section = "Off"; for (size_t i = 0; i < ARRAY_SIZE(off.settings); ++i) { - off.settings[i].name = ""; + off.settings[i].name.clear(); off.settings[i].value = 0.0f; off.settings[i].minValue = -1.0f; off.settings[i].maxValue = 1.0f; @@ -174,7 +174,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct section.Get("UsePreviousFrame", &info.usePreviousFrame, false); if (info.parent == "Off") - info.parent = ""; + info.parent.clear(); for (size_t i = 0; i < ARRAY_SIZE(info.settings); ++i) { auto &setting = info.settings[i]; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 1b7d8d5986..d30a82d7b8 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -867,8 +867,6 @@ void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, Fram // Try to match the new framebuffer to existing textures. // Backwards from the "usual" texturing case so can't share a utility function. - std::vector candidates; - u64 cacheKey = (u64)fb_addr << 32; // If it has a clut, those are the low 32 bits, so it'll be inside this range. // Also, if it's a subsample of the buffer, it'll also be within the FBO. diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 8b4ba9d146..dee7a11166 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -199,7 +199,6 @@ UI::EventReturn CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { WARN_LOG(COMMON, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); return UI::EVENT_DONE; } - std::string line; std::vector title; std::vector newList; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 14ee1ced6c..7ed519f7c0 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1049,7 +1049,7 @@ void EmuScreen::update() { errLoadingFile.append(err->T(errorMessage_.c_str())); screenManager()->push(new PromptScreen(errLoadingFile, "OK", "")); - errorMessage_ = ""; + errorMessage_.clear(); quit_ = true; return; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 0c1c0d009e..d58e23c704 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1488,7 +1488,7 @@ void GameSettingsScreen::TriggerRestart(const char *why) { std::string param = "--gamesettings"; if (editThenRestore_) { // We won't pass the gameID, so don't resume back into settings. - param = ""; + param.clear(); } else if (!gamePath_.empty()) { param += " \"" + ReplaceAll(ReplaceAll(gamePath_.ToString(), "\\", "\\\\"), "\"", "\\\"") + "\""; } diff --git a/UI/Store.cpp b/UI/Store.cpp index 8329ed22d9..86a084da95 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -502,7 +502,7 @@ void StoreScreen::CreateViews() { selectedItem->Press(); } else { - lastSelectedName_ = ""; + lastSelectedName_.clear(); } } root_->Add(content); diff --git a/UI/TextureUtil.cpp b/UI/TextureUtil.cpp index 9a5b377a90..80dfb59e1a 100644 --- a/UI/TextureUtil.cpp +++ b/UI/TextureUtil.cpp @@ -151,7 +151,7 @@ bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType typ size_t fileSize; uint8_t *buffer = VFSReadFile(filename.c_str(), &fileSize); if (!buffer) { - filename_ = ""; + filename_.clear(); ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str()); return false; } @@ -159,7 +159,7 @@ bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType typ if (retval) { filename_ = filename; } else { - filename_ = ""; + filename_.clear(); ERROR_LOG(IO, "Failed to load texture '%s'", filename.c_str()); } delete[] buffer; diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 5aca43fbcf..242fb60af5 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -191,7 +191,7 @@ CtrlDisAsmView::CtrlDisAsmView(HWND _wnd) matchAddress = -1; searching = false; - searchQuery = ""; + searchQuery.clear(); windowStart = curAddress; whiteBackground = false; displaySymbols = true; diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index c8f8a3f8df..e5af6aaa8d 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -49,7 +49,7 @@ CtrlMemView::CtrlMemView(HWND _wnd) curAddress = 0; debugger = 0; - searchQuery = ""; + searchQuery.clear(); matchAddress = -1; searching = false; diff --git a/Windows/InputBox.cpp b/Windows/InputBox.cpp index 1d4fd02e7e..632a8134fe 100644 --- a/Windows/InputBox.cpp +++ b/Windows/InputBox.cpp @@ -49,12 +49,12 @@ bool InputBox_GetString(HINSTANCE hInst, HWND hParent, const wchar_t *title, con if (defaultValue.size() < 255) textBoxContents = ConvertUTF8ToWString(defaultValue); else - textBoxContents = L""; + textBoxContents.clear(); if (title != NULL) windowTitle = title; else - windowTitle = L""; + windowTitle.clear(); if (IDOK == DialogBox(hInst, (LPCWSTR)IDD_INPUTBOX, hParent, InputBoxFunc)) { outvalue = ConvertWStringToUTF8(out); diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 6aba9d275a..384bb9a0f7 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -911,7 +911,7 @@ namespace MainWindow { if (lastSlash) { fn = lastSlash + 1; } else { - fn = ""; + fn.clear(); } PSPFileInfo info = pspFileSystem.GetFileInfo(filename); diff --git a/Windows/main.cpp b/Windows/main.cpp index e3888dfe4c..90e216f283 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -211,7 +211,7 @@ std::string System_GetProperty(SystemProperty prop) { if (wstr) retval = ConvertWStringToUTF8(wstr); else - retval = ""; + retval.clear(); GlobalUnlock(handle); CloseClipboard(); } diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index d032a9db75..64e6f7761c 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -107,7 +107,7 @@ bool RunTests() { // Never report from tests. std::string savedReportHost = g_Config.sReportHost; - g_Config.sReportHost = ""; + g_Config.sReportHost.clear(); for (size_t i = 0; i < ARRAY_SIZE(testsToRun); i++) { std::string testName = testsToRun[i]; @@ -116,7 +116,7 @@ bool RunTests() { INFO_LOG(SYSTEM, "Preparing to execute '%s'", testName.c_str()); std::string error_string; - output = ""; + 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; diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 1deebd95cd..dcfad07e8d 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -411,7 +411,7 @@ int main(int argc, const char* argv[]) g_Config.bFirstRun = false; g_Config.bIgnoreBadMemAccess = true; // Never report from tests. - g_Config.sReportHost = ""; + g_Config.sReportHost.clear(); g_Config.bAutoSaveSymbolMap = false; g_Config.iRenderingMode = FB_BUFFERED_MODE; g_Config.bHardwareTransform = true; diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 4f8239a11b..bb3d9d112b 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -634,7 +634,7 @@ static bool TestPath() { Path path3 = path2 / "foo/bar"; EXPECT_EQ_STR(path3.WithExtraExtension(".txt").ToString(), std::string("/asdf/jkl/foo/bar.txt")); - EXPECT_EQ_STR(Path("foo.bar/hello").GetFileExtension(), std::string("")); + EXPECT_EQ_STR(Path("foo.bar/hello").GetFileExtension(), std::string()); EXPECT_EQ_STR(Path("foo.bar/hello.txt").WithReplacedExtension(".txt", ".html").ToString(), std::string("foo.bar/hello.html")); EXPECT_EQ_STR(Path("C:\\Yo").NavigateUp().ToString(), std::string("C:")); From b304551747e391113c5fd2bea4e63713f995a3f6 Mon Sep 17 00:00:00 2001 From: lainon Date: Fri, 30 Sep 2022 12:31:32 +0300 Subject: [PATCH 253/326] Code readability, vec reserve() and remove excess c_str() --- Common/Data/Text/I18n.cpp | 2 +- Common/File/Path.cpp | 2 +- Common/GPU/OpenGL/thin3d_gl.cpp | 1 + Core/Debugger/WebSocket/WebSocketUtils.cpp | 2 +- Core/Dialog/PSPNetconfDialog.cpp | 12 ++++++------ Core/Dialog/PSPNpSigninDialog.cpp | 12 ++++++------ Core/Dialog/SavedataParam.cpp | 13 ++++++------- Core/ELF/ParamSFO.cpp | 2 +- Core/FileSystems/VirtualDiscFileSystem.cpp | 2 +- Core/HLE/proAdhoc.cpp | 3 ++- Core/HLE/sceNet.cpp | 2 +- Core/MIPS/MIPSAnalyst.cpp | 1 + GPU/GLES/ShaderManagerGLES.cpp | 2 ++ GPU/Software/DrawPixelX86.cpp | 1 + UI/ChatScreen.cpp | 2 +- UI/GameSettingsScreen.cpp | 4 ++-- UI/MemStickScreen.cpp | 2 +- UI/RemoteISOScreen.cpp | 2 +- UI/Theme.cpp | 2 +- Windows/Debugger/CtrlMemView.cpp | 2 ++ Windows/Debugger/Debugger_MemoryDlg.cpp | 2 +- Windows/W32Util/ShellUtil.cpp | 2 +- 22 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Common/Data/Text/I18n.cpp b/Common/Data/Text/I18n.cpp index 0ede4c516d..00771fe119 100644 --- a/Common/Data/Text/I18n.cpp +++ b/Common/Data/Text/I18n.cpp @@ -39,7 +39,7 @@ const char *I18NCategory::T(const char *key, const char *def) { if (def) missedKeyLog_[key] = def; else - missedKeyLog_[key] = modifiedKey.c_str(); + missedKeyLog_[key] = modifiedKey; // INFO_LOG(SYSTEM, "Missed translation key in %s: %s", name_.c_str(), key); return def ? def : key; } diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index dd01ac26a7..39a31e164e 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -271,7 +271,7 @@ bool Path::CanNavigateUp() const { if (type_ == PathType::CONTENT_URI) { return AndroidContentURI(path_).CanNavigateUp(); } - if (path_ == "/" || path_ == "") { + if (path_ == "/" || path_.empty()) { return false; } if (type_ == PathType::HTTP) { diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 02decfe05c..3ecd1695c6 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -1203,6 +1203,7 @@ bool OpenGLPipeline::LinkShaders() { } std::vector semantics; + semantics.reserve(8); // Bind all the common vertex data points. Mismatching ones will be ignored. semantics.push_back({ SEM_POSITION, "Position" }); semantics.push_back({ SEM_COLOR0, "Color0" }); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index 8d6be87b9e..be1a1371a9 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -216,7 +216,7 @@ bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType t *out = true; return true; } - if (s == "0" || s == "false" || (s == "" && allowLoose)) { + if (s == "0" || s == "false" || (s.empty() && allowLoose)) { *out = false; return true; } diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index 1e0535f301..079ff28358 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -194,21 +194,21 @@ void PSPNetconfDialog::DisplayMessage(std::string text1, std::string text2a, std PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2)); PPGeDrawTextWrapped(text1.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle); - if (text2a != "") { - if (text2b != "") + if (!text2a.empty()) { + if (!text2b.empty()) PPGeDrawTextWrapped(text2a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleRight); else PPGeDrawTextWrapped(text2a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyle); } - if (text2b != "") + if (!text2b.empty()) PPGeDrawTextWrapped(text2b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleLeft); - if (text3a != "") { - if (text3b != "") + if (!text3a.empty()) { + if (!text3b.empty()) PPGeDrawTextWrapped(text3a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleRight); else PPGeDrawTextWrapped(text3a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyle); } - if (text3b != "") + if (!text3b.empty()) PPGeDrawTextWrapped(text3b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleLeft); PPGeScissorReset(); diff --git a/Core/Dialog/PSPNpSigninDialog.cpp b/Core/Dialog/PSPNpSigninDialog.cpp index 3c015046db..48245c2008 100644 --- a/Core/Dialog/PSPNpSigninDialog.cpp +++ b/Core/Dialog/PSPNpSigninDialog.cpp @@ -191,21 +191,21 @@ void PSPNpSigninDialog::DisplayMessage(std::string text1, std::string text2a, st PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2)); PPGeDrawTextWrapped(text1.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle); - if (text2a != "") { - if (text2b != "") + if (!text2a.empty()) { + if (!text2b.empty()) PPGeDrawTextWrapped(text2a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleRight); else PPGeDrawTextWrapped(text2a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyle); } - if (text2b != "") + if (!text2b.empty()) PPGeDrawTextWrapped(text2b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleLeft); - if (text3a != "") { - if (text3b != "") + if (!text3a.empty()) { + if (!text3b.empty()) PPGeDrawTextWrapped(text3a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleRight); else PPGeDrawTextWrapped(text3a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyle); } - if (text3b != "") + if (!text3b.empty()) PPGeDrawTextWrapped(text3b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleLeft); PPGeScissorReset(); diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index a4f437d40c..67ef655ccf 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -353,11 +353,11 @@ int SavedataParam::DeleteData(SceUtilitySavedataParam* param) { if (!pspFileSystem.GetFileInfo(sfoPath).exists) return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN; - if (fileName != "" && !pspFileSystem.GetFileInfo(filePath).exists) { + if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) { return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND; } - if (fileName == "") { + if (fileName.empty()) { return 0; } @@ -533,7 +533,7 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD // copy back save name in request strncpy(param->saveName, saveDirName.c_str(), 20); - if (fileName == "") { + if (fileName.empty()) { delete[] cryptedData; } else { if (!WritePSPFile(filePath, data_, saveSize)) { @@ -583,13 +583,12 @@ int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveD std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName)); std::string fileName = GetFileName(param); std::string filePath = dirPath + "/" + fileName; - std::string sfoPath = dirPath + "/" + SFO_FILENAME; if (!pspFileSystem.GetFileInfo(dirPath).exists) { return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA : SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA; } - if (fileName != "" && !pspFileSystem.GetFileInfo(filePath).exists) { + if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) { return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND : SCE_UTILITY_SAVEDATA_ERROR_LOAD_FILE_NOT_FOUND; } @@ -637,7 +636,7 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin std::string filename = GetFileName(param); std::string filePath = dirPath + "/" + filename; // Blank filename always means success, if secureVersion was correct. - if (filename == "") + if (filename.empty()) return 0; s64 readSize; @@ -1576,7 +1575,7 @@ void SavedataParam::SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, std:: saveInfo.idx = 0; saveInfo.modif_time = info.mtime; - std::string saveDir = savrDir == "" ? GetGameName(pspParam) + saveName : savrDir; + std::string saveDir = savrDir.empty() ? GetGameName(pspParam) + saveName : savrDir; saveInfo.saveDir = saveDir; // Start with a blank slate. diff --git a/Core/ELF/ParamSFO.cpp b/Core/ELF/ParamSFO.cpp index 71d07dcaca..a2c002f46e 100644 --- a/Core/ELF/ParamSFO.cpp +++ b/Core/ELF/ParamSFO.cpp @@ -283,7 +283,7 @@ std::string ParamSFOData::GenerateFakeID(std::string filename) { // Generates fake gameID for homebrew based on it's folder name. // Should probably not be a part of ParamSFO, but it'll be called in same places. std::string file = PSP_CoreParameter().fileToStart.ToString(); - if (filename != "") + if (!filename.empty()) file = filename; std::size_t lslash = file.find_last_of("/"); diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index c3a339172d..9a9e0f8c8b 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -329,7 +329,7 @@ int VirtualDiscFileSystem::OpenFile(std::string filename, FileAccess access, con entry.size = 0; entry.startOffset = 0; - if (filename == "") + if (filename.empty()) { entry.type = VFILETYPE_ISO; entry.fileIndex = -1; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index dcacbcc5bd..8959b337b2 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1325,7 +1325,7 @@ void sendChat(std::string chatString) { if (IsSocketReady((int)metasocket, false, true) > 0) { int chatResult = send((int)metasocket, (const char*)&chat, sizeof(chat), MSG_NOSIGNAL); NOTICE_LOG(SCENET, "Send Chat %s to Adhoc Server", chat.message); - std::string name = g_Config.sNickName.c_str(); + std::string name = g_Config.sNickName; std::lock_guard guard(chatLogLock); chatLog.push_back(name.substr(0, 8) + ": " + chat.message); @@ -1891,6 +1891,7 @@ uint32_t getLocalIp(int sock) { static std::vector> InitPrivateIPRanges() { struct sockaddr_in saNet {}, saMask{}; std::vector> ip_ranges; + ip_ranges.reserve(5); if (1 == inet_pton(AF_INET, "192.168.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.255.0.0", &(saMask.sin_addr))) ip_ranges.push_back({saNet.sin_addr.s_addr, saMask.sin_addr.s_addr}); diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index c2a8987408..13b87f6b6b 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -691,7 +691,7 @@ static u32 sceWlanGetEtherAddr(u32 addrAddr) { addr[0] &= 0xfc; } else { // Read MAC Address from config - if (!ParseMacAddress(g_Config.sMACAddress.c_str(), addr)) { + if (!ParseMacAddress(g_Config.sMACAddress, addr)) { ERROR_LOG(SCENET, "Error parsing mac address %s", g_Config.sMACAddress.c_str()); Memory::Memset(addrAddr, 0, 6); } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 2c4e11bea4..2b047a42b0 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -1420,6 +1420,7 @@ skip: std::vector GetOutputRegs(MIPSOpcode op) { std::vector vec; + vec.reserve(3); MIPSInfo info = MIPSGetInfo(op); if (info & OUT_RD) vec.push_back(MIPS_GET_RD(op)); if (info & OUT_RT) vec.push_back(MIPS_GET_RT(op)); diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index ccf15e9268..3a620a4d91 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -84,6 +84,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, std::vector semantics; + semantics.reserve(7); semantics.push_back({ ATTR_POSITION, "position" }); semantics.push_back({ ATTR_TEXCOORD, "texcoord" }); if (useHWTransform_) @@ -185,6 +186,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, availableUniforms = vs->GetUniformMask() | fs->GetUniformMask(); std::vector initialize; + initialize.reserve(7); initialize.push_back({ &u_tex, 0, TEX_SLOT_PSP_TEXTURE }); initialize.push_back({ &u_fbotex, 0, TEX_SLOT_SHADERBLEND_SRC }); initialize.push_back({ &u_testtex, 0, TEX_SLOT_ALPHATEST }); diff --git a/GPU/Software/DrawPixelX86.cpp b/GPU/Software/DrawPixelX86.cpp index 32e6aeb835..53f9c94279 100644 --- a/GPU/Software/DrawPixelX86.cpp +++ b/GPU/Software/DrawPixelX86.cpp @@ -1737,6 +1737,7 @@ bool PixelJitCache::Jit_ApplyLogicOp(const PixelFuncID &id, RegCache::Reg colorR } std::vector finishes; + finishes.reserve(11); FixupBranch skipTable = J(true); const u8 *tableValues[16]{}; diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp index 01421ee733..3a12597d11 100644 --- a/UI/ChatScreen.cpp +++ b/UI/ChatScreen.cpp @@ -138,7 +138,7 @@ void ChatMenu::UpdateChat() { uint32_t textcolor = 0xFFFFFF; uint32_t infocolor = 0xFDD835; - std::string name = g_Config.sNickName.c_str(); + std::string name = g_Config.sNickName; std::string displayname = i.substr(0, i.find(':')); std::string chattext = i.substr(displayname.length()); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index d58e23c704..486d502d4d 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -256,7 +256,7 @@ void GameSettingsScreen::CreateViews() { root_->Add(settingInfo_); // Show it again if we recreated the view - if (oldSettingInfo_ != "") { + if (!oldSettingInfo_.empty()) { settingInfo_->Show(oldSettingInfo_, nullptr); } @@ -1222,7 +1222,7 @@ UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnOpenMemStick(UI::EventParams &e) { - OpenDirectory(File::ResolvePath(g_Config.memStickDirectory.ToString().c_str()).c_str()); + OpenDirectory(File::ResolvePath(g_Config.memStickDirectory.ToString()).c_str()); return UI::EVENT_DONE; } diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index ba61ad193a..78c4cf24fd 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -259,7 +259,7 @@ void MemStickScreen::CreateViews() { privateString = StringFromFormat("%s (%s)", iz->T("Skip for now"), privateString.c_str()); } - leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString.c_str()))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); if (choice_ == CHOICE_PRIVATE_DIRECTORY) { AddExplanation(leftColumn, (MemStickScreen::Choice)choice_); } diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 4497f169db..7d60e507a4 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -176,7 +176,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort }; // Try last server first, if it is set - if (g_Config.iLastRemoteISOPort && g_Config.sLastRemoteISOServer != "") { + if (g_Config.iLastRemoteISOPort && !g_Config.sLastRemoteISOServer.empty()) { if (TryServer(g_Config.sLastRemoteISOServer.c_str(), g_Config.iLastRemoteISOPort)) { return true; } diff --git a/UI/Theme.cpp b/UI/Theme.cpp index 978eeb3d0b..464c4ab2db 100644 --- a/UI/Theme.cpp +++ b/UI/Theme.cpp @@ -139,7 +139,7 @@ static void LoadThemeInfo(const std::vector &directories) { std::string tmpPath; section.Get("UIAtlas", &tmpPath, ""); - if (tmpPath != "") { + if (!tmpPath.empty()) { tmpPath = (path / tmpPath).ToString(); File::FileInfo tmpInfo; diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index e5af6aaa8d..220ad0a99e 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -768,6 +768,7 @@ std::vector CtrlMemView::searchString(const std::string &searchQuery) { std::vector> memoryAreas; memoryAreas.push_back(std::pair(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd())); + memoryAreas.reserve(3); // Ignore the video memory mirrors. memoryAreas.push_back(std::pair(PSP_GetVidMemBase(), 0x04200000)); memoryAreas.push_back(std::pair(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd())); @@ -821,6 +822,7 @@ void CtrlMemView::search(bool continueSearch) } std::vector> memoryAreas; + memoryAreas.reserve(3); // Ignore the video memory mirrors. memoryAreas.push_back(std::pair(PSP_GetVidMemBase(), 0x04200000)); memoryAreas.push_back(std::pair(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd())); diff --git a/Windows/Debugger/Debugger_MemoryDlg.cpp b/Windows/Debugger/Debugger_MemoryDlg.cpp index d5358ab1bc..d016afaafa 100644 --- a/Windows/Debugger/Debugger_MemoryDlg.cpp +++ b/Windows/Debugger/Debugger_MemoryDlg.cpp @@ -195,7 +195,7 @@ BOOL CMemoryDlg::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { switch (HIWORD(wParam)) { case BN_CLICKED: GetWindowText(searchBoxHdl, temp, 255); - std::vector results = memView->searchString(ConvertWStringToUTF8(temp).c_str()); + std::vector results = memView->searchString(ConvertWStringToUTF8(temp)); if (results.size() > 0){ searchBoxRedraw(results); } diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index c6d3a2bc49..151cd643ce 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -199,7 +199,7 @@ namespace W32Util switch (type_) { case DIR: filename_ = BrowseForFolder(parent_, title_.c_str()); - result_ = filename_ != ""; + result_ = !filename_.empty(); complete_ = true; break; From c953bf7fc7cfa30c175fb60cb46c2ac348f87cc9 Mon Sep 17 00:00:00 2001 From: lainon Date: Fri, 30 Sep 2022 12:32:49 +0300 Subject: [PATCH 254/326] Fixed bug and memleaks --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 1 + Core/HLE/proAdhoc.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 1 + GPU/Common/ShaderId.cpp | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 111cea1d2b..e45c148851 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1079,6 +1079,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char gDesc.fragmentShader = vkshader->Get(); } else { ERROR_LOG(G3D, "Bad stage"); + delete pipeline; return nullptr; } } diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 8959b337b2..5996197bda 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1929,7 +1929,7 @@ void getLocalMac(SceNetEtherAddr * addr){ mac[0] &= 0xfc; } else - if (!ParseMacAddress(g_Config.sMACAddress.c_str(), mac)) { + if (!ParseMacAddress(g_Config.sMACAddress, mac)) { ERROR_LOG(SCENET, "Error parsing mac address %s", g_Config.sMACAddress.c_str()); memset(&mac, 0, sizeof(mac)); } diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index f97f2e91c0..8f74dd2103 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1958,6 +1958,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, channel == RASTER_DEPTH ? true : false, name }); if (!nvfb->fbo) { ERROR_LOG(FRAMEBUF, "Error creating FBO! %d x %d", nvfb->renderWidth, nvfb->renderHeight); + delete nvfb; return nullptr; } bvfbs_.push_back(nvfb); diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index bb8dd54237..6849ba759e 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -227,7 +227,7 @@ std::string FragmentShaderDesc(const FShaderID &id) { case STENCIL_VALUE_INCR_4: desc << "StenIncr4 "; break; case STENCIL_VALUE_INCR_8: desc << "StenIncr8 "; break; case STENCIL_VALUE_DECR_4: desc << "StenDecr4 "; break; - case STENCIL_VALUE_DECR_8: desc << "StenDecr4 "; break; + case STENCIL_VALUE_DECR_8: desc << "StenDecr8 "; break; default: desc << "StenUnknown "; break; } } else if (id.Bit(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE)) { From 3cdf72b68b92eca8585b91e9496b67e956c91ddc Mon Sep 17 00:00:00 2001 From: lainon Date: Fri, 30 Sep 2022 12:35:28 +0300 Subject: [PATCH 255/326] Better readability and optimization insertion into container by replacing 'insert' -> 'emplace', 'push_back' -> 'emplace_back' --- Common/Data/Format/IniFile.cpp | 4 ++-- Common/File/DirListing.cpp | 4 ++-- Common/GPU/OpenGL/GLFeatures.cpp | 4 ++-- Common/GPU/Vulkan/thin3d_vulkan.cpp | 2 +- Common/LogReporting.cpp | 2 +- Common/StringUtils.cpp | 6 ++--- Core/Debugger/SymbolMap.cpp | 32 +++++++++++++-------------- Core/HLE/proAdhoc.cpp | 2 +- Core/HLE/sceKernelMemory.cpp | 4 ++-- Core/HLE/sceKernelMutex.cpp | 2 +- Core/HLE/sceKernelThread.cpp | 2 +- Core/HW/Display.cpp | 2 +- Core/MIPS/JitCommon/JitBlockCache.cpp | 4 ++-- Core/MIPS/MIPSAnalyst.cpp | 2 +- GPU/Common/PostShader.cpp | 2 +- GPU/Debugger/Breakpoints.cpp | 2 +- GPU/Debugger/Debugger.cpp | 6 ++--- GPU/GLES/ShaderManagerGLES.cpp | 2 +- Windows/Debugger/CtrlMemView.cpp | 12 +++++----- Windows/W32Util/ShellUtil.cpp | 2 +- 20 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp index 32fcf43b68..cd2a658e5c 100644 --- a/Common/Data/Format/IniFile.cpp +++ b/Common/Data/Format/IniFile.cpp @@ -206,7 +206,7 @@ void Section::Set(const char* key, const char* newValue) else { // The key did not already exist in this section - let's add it. - lines.push_back(std::string(key) + " = " + EscapeComments(newValue)); + lines.emplace_back(std::string(key) + " = " + EscapeComments(newValue)); } } @@ -272,7 +272,7 @@ void Section::Set(const char* key, const std::vector& newValues) } void Section::AddComment(const std::string &comment) { - lines.push_back("# " + comment); + lines.emplace_back("# " + comment); } bool Section::Get(const char* key, std::vector& values) diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp index abe5ff3af8..2f7178847b 100644 --- a/Common/File/DirListing.cpp +++ b/Common/File/DirListing.cpp @@ -148,7 +148,7 @@ std::vector ApplyFilter(std::vector files, const std::string tmp; while (*filter) { if (*filter == ':') { - filters.insert("." + tmp); + filters.emplace("." + tmp); tmp.clear(); } else { tmp.push_back(*filter); @@ -156,7 +156,7 @@ std::vector ApplyFilter(std::vector files, const filter++; } if (!tmp.empty()) - filters.insert("." + tmp); + filters.emplace("." + tmp); } auto pred = [&](const File::FileInfo &info) { diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index bf9a639e67..aa79ac5941 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -51,7 +51,7 @@ static void ParseExtensionsString(const std::string& str, std::set size_t next = 0; for (size_t pos = 0, len = str.length(); pos < len; ++pos) { if (str[pos] == ' ') { - output.insert(str.substr(next, pos - next)); + output.emplace(str.substr(next, pos - next)); // Skip the delimiter itself. next = pos + 1; } @@ -60,7 +60,7 @@ static void ParseExtensionsString(const std::string& str, std::set if (next == 0 && str.length() != 0) { output.insert(str); } else if (next < str.length()) { - output.insert(str.substr(next)); + output.emplace(str.substr(next)); } } diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index e45c148851..4fddf1252e 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1432,7 +1432,7 @@ std::vector VKContext::GetFeatureList() const { AddFeature(features, "occlusionQueryPrecise", available.occlusionQueryPrecise, enabled.occlusionQueryPrecise); AddFeature(features, "multiDrawIndirect", available.multiDrawIndirect, enabled.multiDrawIndirect); - features.push_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat)); + features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat)); return features; } diff --git a/Common/LogReporting.cpp b/Common/LogReporting.cpp index 2189b1c7e2..0fcc78d0ad 100644 --- a/Common/LogReporting.cpp +++ b/Common/LogReporting.cpp @@ -35,7 +35,7 @@ bool ShouldLogNTimes(const char *identifier, int count) { std::lock_guard lock(logNTimesLock); auto iter = logNTimes.find(identifier); if (iter == logNTimes.end()) { - logNTimes.insert(std::pair(identifier, 1)); + logNTimes.emplace(identifier, 1); return true; } else { if (iter->second >= count) { diff --git a/Common/StringUtils.cpp b/Common/StringUtils.cpp index f88f6194ab..3a9e29c61a 100644 --- a/Common/StringUtils.cpp +++ b/Common/StringUtils.cpp @@ -273,7 +273,7 @@ void SplitString(const std::string& str, const char delim, std::vector& output) if (str[pos] == '\"' || str[pos] == '\'') { if (even) { //quoted text - output.push_back(str.substr(next, pos - next)); + output.emplace_back(str.substr(next, pos - next)); even = 0; } else { //non quoted text diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp index e276e9be27..b215a92ab4 100644 --- a/Core/Debugger/SymbolMap.cpp +++ b/Core/Debugger/SymbolMap.cpp @@ -439,7 +439,7 @@ void SymbolMap::AddModule(const char *name, u32 address, u32 size) { // Just reactivate that one. it->start = address; it->size = size; - activeModuleEnds.insert(std::make_pair(it->start + it->size, *it)); + activeModuleEnds.emplace(it->start + it->size, *it); activeNeedUpdate_ = true; return; } @@ -452,7 +452,7 @@ void SymbolMap::AddModule(const char *name, u32 address, u32 size) { mod.index = (int)modules.size() + 1; modules.push_back(mod); - activeModuleEnds.insert(std::make_pair(mod.start + mod.size, mod)); + activeModuleEnds.emplace(mod.start + mod.size, mod); activeNeedUpdate_ = true; } @@ -559,7 +559,7 @@ void SymbolMap::AddFunction(const char* name, u32 address, u32 size, int moduleI auto active = activeFunctions.find(address); if (active != activeFunctions.end() && active->second.module == moduleIndex) { activeFunctions.erase(active); - activeFunctions.insert(std::make_pair(address, existing->second)); + activeFunctions.emplace(address, existing->second); } } else { FunctionEntry func; @@ -570,7 +570,7 @@ void SymbolMap::AddFunction(const char* name, u32 address, u32 size, int moduleI functions[symbolKey] = func; if (IsModuleActive(moduleIndex)) { - activeFunctions.insert(std::make_pair(address, func)); + activeFunctions.emplace(address, func); } } @@ -716,27 +716,27 @@ void SymbolMap::UpdateActiveSymbols() { for (auto it = functions.begin(), end = functions.end(); it != end; ++it) { const auto mod = activeModuleIndexes.find(it->second.module); if (it->second.module == 0) { - activeFunctions.insert(std::make_pair(it->second.start, it->second)); + activeFunctions.emplace(it->second.start, it->second); } else if (mod != activeModuleIndexes.end()) { - activeFunctions.insert(std::make_pair(mod->second + it->second.start, it->second)); + activeFunctions.emplace(mod->second + it->second.start, it->second); } } for (auto it = labels.begin(), end = labels.end(); it != end; ++it) { const auto mod = activeModuleIndexes.find(it->second.module); if (it->second.module == 0) { - activeLabels.insert(std::make_pair(it->second.addr, it->second)); + activeLabels.emplace(it->second.addr, it->second); } else if (mod != activeModuleIndexes.end()) { - activeLabels.insert(std::make_pair(mod->second + it->second.addr, it->second)); + activeLabels.emplace(mod->second + it->second.addr, it->second); } } for (auto it = data.begin(), end = data.end(); it != end; ++it) { const auto mod = activeModuleIndexes.find(it->second.module); if (it->second.module == 0) { - activeData.insert(std::make_pair(it->second.start, it->second)); + activeData.emplace(it->second.start, it->second); } else if (mod != activeModuleIndexes.end()) { - activeData.insert(std::make_pair(mod->second + it->second.start, it->second)); + activeData.emplace(mod->second + it->second.start, it->second); } } @@ -757,7 +757,7 @@ bool SymbolMap::SetFunctionSize(u32 startAddress, u32 newSize) { if (func != functions.end()) { func->second.size = newSize; activeFunctions.erase(funcInfo); - activeFunctions.insert(std::make_pair(startAddress, func->second)); + activeFunctions.emplace(startAddress, func->second); } } @@ -829,7 +829,7 @@ void SymbolMap::AddLabel(const char* name, u32 address, int moduleIndex) { auto active = activeLabels.find(address); if (active != activeLabels.end() && active->second.module == moduleIndex) { activeLabels.erase(active); - activeLabels.insert(std::make_pair(address, label)); + activeLabels.emplace(address, label); } } } else { @@ -840,7 +840,7 @@ void SymbolMap::AddLabel(const char* name, u32 address, int moduleIndex) { labels[symbolKey] = label; if (IsModuleActive(moduleIndex)) { - activeLabels.insert(std::make_pair(address, label)); + activeLabels.emplace(address, label); } } } @@ -864,7 +864,7 @@ void SymbolMap::SetLabelName(const char* name, u32 address) { auto active = activeLabels.find(address); if (active != activeLabels.end() && active->second.module == label->second.module) { activeLabels.erase(active); - activeLabels.insert(std::make_pair(address, label->second)); + activeLabels.emplace(address, label->second); } } } @@ -947,7 +947,7 @@ void SymbolMap::AddData(u32 address, u32 size, DataType type, int moduleIndex) { auto active = activeData.find(address); if (active != activeData.end() && active->second.module == moduleIndex) { activeData.erase(active); - activeData.insert(std::make_pair(address, existing->second)); + activeData.emplace(address, existing->second); } } else { DataEntry entry; @@ -958,7 +958,7 @@ void SymbolMap::AddData(u32 address, u32 size, DataType type, int moduleIndex) { data[symbolKey] = entry; if (IsModuleActive(moduleIndex)) { - activeData.insert(std::make_pair(address, entry)); + activeData.emplace(address, entry); } } } diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 5996197bda..dfcc535df0 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1328,7 +1328,7 @@ void sendChat(std::string chatString) { std::string name = g_Config.sNickName; std::lock_guard guard(chatLogLock); - chatLog.push_back(name.substr(0, 8) + ": " + chat.message); + chatLog.emplace_back(name.substr(0, 8) + ": " + chat.message); chatMessageGeneration++; } } diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 318cae7d0c..55b7ff7044 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -2007,7 +2007,7 @@ int __KernelFreeTls(TLSPL *tls, SceUID threadID) __KernelResumeThreadFromWait(waitingThreadID, freedAddress); // Gotta watch the thread to quit as well, since they've allocated now. - tlsplThreadEndChecks.insert(std::make_pair(waitingThreadID, uid)); + tlsplThreadEndChecks.emplace(waitingThreadID, uid); // No need to continue or free it, we're done. return 0; @@ -2234,7 +2234,7 @@ int sceKernelGetTlsAddr(SceUID uid) { if (allocBlock != -1) { tls->usage[allocBlock] = threadID; - tlsplThreadEndChecks.insert(std::make_pair(threadID, uid)); + tlsplThreadEndChecks.emplace(threadID, uid); --tls->ntls.freeBlocks; needsClear = true; } diff --git a/Core/HLE/sceKernelMutex.cpp b/Core/HLE/sceKernelMutex.cpp index 2cba7b5ca5..6921ffdf26 100644 --- a/Core/HLE/sceKernelMutex.cpp +++ b/Core/HLE/sceKernelMutex.cpp @@ -213,7 +213,7 @@ static void __KernelMutexAcquireLock(PSPMutex *mutex, int count, SceUID thread) _dbg_assert_msg_((*iter).second != mutex->GetUID(), "Thread %d / mutex %d wasn't removed from mutexHeldLocks properly.", thread, mutex->GetUID()); #endif - mutexHeldLocks.insert(std::make_pair(thread, mutex->GetUID())); + mutexHeldLocks.emplace(thread, mutex->GetUID()); mutex->nm.lockLevel = count; mutex->nm.lockThread = thread; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index ea9caaba1e..f717cd5a3e 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -230,7 +230,7 @@ public: MipsCallManager() : idGen_(0) {} u32 add(MipsCall *call) { u32 id = genId(); - calls_.insert(std::pair(id, call)); + calls_.emplace(id, call); return id; } MipsCall *get(u32 id) { diff --git a/Core/HW/Display.cpp b/Core/HW/Display.cpp index ebabb647d0..1dcb51e18c 100644 --- a/Core/HW/Display.cpp +++ b/Core/HW/Display.cpp @@ -265,7 +265,7 @@ void __DisplayListenVblank(VblankCallback callback) { void __DisplayListenFlip(FlipCallback callback, void *userdata) { std::lock_guard guard(listenersLock); - flipListeners.push_back(std::make_pair(callback, userdata)); + flipListeners.emplace_back(callback, userdata); } void __DisplayForgetFlip(FlipCallback callback, void *userdata) { diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp index b85b336fcd..f5b11ca4a5 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.cpp +++ b/Core/MIPS/JitCommon/JitBlockCache.cpp @@ -198,7 +198,7 @@ void JitBlockCache::ProxyBlock(u32 rootAddress, u32 startAddress, u32 size, cons // Make binary searches and stuff work ok b.normalEntry = codePtr; b.checkedEntry = codePtr; - proxyBlockMap_.insert(std::make_pair(startAddress, num_blocks_)); + proxyBlockMap_.emplace(startAddress, num_blocks_); AddBlockMap(num_blocks_); num_blocks_++; //commit the current block @@ -253,7 +253,7 @@ void JitBlockCache::FinalizeBlock(int block_num, bool block_link) { if (block_link) { for (int i = 0; i < MAX_JIT_BLOCK_EXITS; i++) { if (b.exitAddress[i] != INVALID_EXIT) { - links_to_.insert(std::make_pair(b.exitAddress[i], block_num)); + links_to_.emplace(b.exitAddress[i], block_num); } } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 2b047a42b0..81f87da663 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -771,7 +771,7 @@ namespace MIPSAnalyst { for (auto iter = functions.begin(); iter != functions.end(); iter++) { AnalyzedFunction &f = *iter; if (f.hasHash && f.size > 16) { - hashToFunction.insert(std::make_pair(f.hash, &f)); + hashToFunction.emplace(f.hash, &f); } } } diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index ad02d603b4..2d8529f356 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -187,7 +187,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct // Populate the default setting value. std::string section = StringFromFormat("%sSettingValue%d", info.section.c_str(), i + 1); if (!setting.name.empty() && g_Config.mPostShaderSetting.find(section) == g_Config.mPostShaderSetting.end()) { - g_Config.mPostShaderSetting.insert(std::pair(section, setting.value)); + g_Config.mPostShaderSetting.emplace(section, setting.value); } } diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp index 97fbca57ee..6da92e41ae 100644 --- a/GPU/Debugger/Breakpoints.cpp +++ b/GPU/Debugger/Breakpoints.cpp @@ -339,7 +339,7 @@ void AddAddressBreakpoint(u32 addr, bool temp) { } else { // Remove the temporary marking. breakPCsTemp.erase(addr); - breakPCs.insert(std::make_pair(addr, BreakpointInfo{})); + breakPCs.emplace(addr, BreakpointInfo{}); } breakPCsCount = breakPCs.size(); diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index b505521631..fa55ec6b8e 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -219,9 +219,9 @@ bool SetRestrictPrims(const char *rule) { // If there's nothing yet, add everything else. if (updated.empty()) { if (range.first > 0) - updated.push_back(std::make_pair(0, range.first - 1)); + updated.emplace_back(0, range.first - 1); if (range.second < MAX_PRIMS) - updated.push_back(std::make_pair(range.second + 1, MAX_PRIMS)); + updated.emplace_back(range.second + 1, MAX_PRIMS); continue; } @@ -240,7 +240,7 @@ bool SetRestrictPrims(const char *rule) { // We're slicing a hole in this subrange. int next = sub.second; sub.second = range.first - 1; - updated.push_back(std::make_pair(range.second + 1, next)); + updated.emplace_back(range.second + 1, next); continue; } diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 3a620a4d91..6868b5d988 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -995,7 +995,7 @@ void ShaderManagerGLES::Load(const Path &filename) { if (!f.ReadArray(&fsid, 1)) { return; } - diskCachePending_.link.push_back(std::make_pair(vsid, fsid)); + diskCachePending_.link.emplace_back(vsid, fsid); } // Actual compilation happens in ContinuePrecompile(), called by GPU_GLES's IsReady. diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index 220ad0a99e..d40bdaf483 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -767,11 +767,11 @@ std::vector CtrlMemView::searchString(const std::string &searchQuery) { return searchResAddrs; std::vector> memoryAreas; - memoryAreas.push_back(std::pair(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd())); memoryAreas.reserve(3); + memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); // Ignore the video memory mirrors. - memoryAreas.push_back(std::pair(PSP_GetVidMemBase(), 0x04200000)); - memoryAreas.push_back(std::pair(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd())); + memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); + memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); for (const auto &area : memoryAreas) { const u32 segmentStart = area.first; @@ -824,9 +824,9 @@ void CtrlMemView::search(bool continueSearch) std::vector> memoryAreas; memoryAreas.reserve(3); // Ignore the video memory mirrors. - memoryAreas.push_back(std::pair(PSP_GetVidMemBase(), 0x04200000)); - memoryAreas.push_back(std::pair(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd())); - memoryAreas.push_back(std::pair(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd())); + memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); + memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); + memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); searching = true; redraw(); // so the cursor is disabled diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 151cd643ce..bd53935d27 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -144,7 +144,7 @@ namespace W32Util files.push_back(directory); } else { while (*temp) { - files.push_back(directory + "\\" + ConvertWStringToUTF8(temp)); + files.emplace_back(directory + "\\" + ConvertWStringToUTF8(temp)); temp += wcslen(temp) + 1; } } From c49b91c62bfe9f6f5104466e0f7fcb7ca85ded33 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Aug 2020 18:02:45 -0700 Subject: [PATCH 256/326] armips: Update to latest. --- Core/Core.vcxproj | 14 ++++++++++-- Core/MIPS/MIPSAsm.cpp | 7 +++--- ext/armips | 2 +- ext/libarmips.vcxproj | 43 +++++++++++++---------------------- ext/libarmips.vcxproj.filters | 14 ++++++++++-- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index bdfaea4361..4697e33a40 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -568,7 +568,12 @@ - + + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + @@ -900,7 +905,12 @@ - + + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + diff --git a/Core/MIPS/MIPSAsm.cpp b/Core/MIPS/MIPSAsm.cpp index 97d34a4f18..837e857b68 100644 --- a/Core/MIPS/MIPSAsm.cpp +++ b/Core/MIPS/MIPSAsm.cpp @@ -5,6 +5,7 @@ #include "Common/CommonTypes.h" #include "ext/armips/Core/Assembler.h" +#include "ext/armips/Core/FileManager.h" #include "Common/Data/Encoding/Utf8.h" #include "Core/Debugger/SymbolMap.h" @@ -55,15 +56,15 @@ public: return true; } bool seekPhysical(int64_t physicalAddress) override { return seekVirtual(physicalAddress); } - const std::wstring &getFileName() override { return dummyWFilename_; } + const fs::path &getFileName() override { return dummyFilename_; } private: u64 address; - std::wstring dummyWFilename_; + fs::path dummyFilename_; }; bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) { - StringList errors; + std::vector errors; wchar_t str[64]; swprintf(str,64,L".psp\n.org 0x%08X\n",address); diff --git a/ext/armips b/ext/armips index 7885552b20..0b13ef32c5 160000 --- a/ext/armips +++ b/ext/armips @@ -1 +1 @@ -Subproject commit 7885552b208493a6a0f21663770c446c3ba65576 +Subproject commit 0b13ef32c566781f0566b027ad5681d9320bddf7 diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index b3d288f799..de44145f1a 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -187,14 +187,14 @@ - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName)d $(ProjectName)d64 - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)64\ @@ -211,13 +211,13 @@ _Output\$(ProjectName)\$(Configuration)ARM\ - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName)tests - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) $(ProjectName)tests64 _Output\ _Output\$(ProjectName)\$(Configuration)64\ @@ -235,14 +235,14 @@ _Output\$(ProjectName)\$(Configuration)ARM\ - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName) $(ProjectName)64 - .;$(IncludePath) + .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)64\ @@ -260,7 +260,6 @@ - Use Level3 _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug @@ -269,6 +268,7 @@ true ProgramDatabase + stdcpp17 Windows @@ -280,7 +280,6 @@ - Use Level3 _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) MultiThreadedDebug @@ -290,6 +289,7 @@ true ProgramDatabase + stdcpp17 Windows @@ -341,11 +341,11 @@ - Use Level3 Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) armips + stdcpp17 Windows @@ -354,11 +354,11 @@ - Use Level3 Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) armips + stdcpp17 Windows @@ -395,7 +395,6 @@ Level3 - Use MaxSpeed true true @@ -405,6 +404,7 @@ true false ProgramDatabase + stdcpp17 Windows @@ -416,7 +416,6 @@ Level3 - Use MaxSpeed true true @@ -428,6 +427,7 @@ true false ProgramDatabase + stdcpp17 Windows @@ -510,6 +510,7 @@ + @@ -524,24 +525,11 @@ - - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - Create - + @@ -572,6 +560,7 @@ + @@ -588,11 +577,11 @@ - + diff --git a/ext/libarmips.vcxproj.filters b/ext/libarmips.vcxproj.filters index e1c8ef9a3a..d374c5f4b8 100644 --- a/ext/libarmips.vcxproj.filters +++ b/ext/libarmips.vcxproj.filters @@ -132,7 +132,6 @@ Util - Core @@ -172,6 +171,12 @@ Archs\MIPS + + Core + + + Util + @@ -276,7 +281,6 @@ Util - Core @@ -319,5 +323,11 @@ Archs\MIPS + + Core + + + Util + \ No newline at end of file From faf7fc7aea8252feada74d1e4a30bf224e7fedad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 30 Sep 2022 19:14:26 -0700 Subject: [PATCH 257/326] Build: Update zstd to latest dev. Fixes attribute issue in newer c++ standards. --- ext/zstd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zstd b/ext/zstd index 155d6a58a2..096dccbc2d 160000 --- a/ext/zstd +++ b/ext/zstd @@ -1 +1 @@ -Subproject commit 155d6a58a2a6290710194fd6367d720fca94a923 +Subproject commit 096dccbc2d89a560db0b9892c53ea0c77eff20a1 From 8b7ccfeca57347950ee1ad69ad7e0eb5f0a3b754 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 30 Sep 2022 19:33:03 -0700 Subject: [PATCH 258/326] Core: Remove disallowed register. --- Core/Util/AudioFormat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Util/AudioFormat.h b/Core/Util/AudioFormat.h index 8774e83546..06d53676cd 100644 --- a/Core/Util/AudioFormat.h +++ b/Core/Util/AudioFormat.h @@ -49,7 +49,7 @@ static inline s16 clamp_s16(int i) { static inline s16 ApplySampleVolume(s16 sample, int vol) { #if PPSSPP_ARCH(ARM) && !defined(_MSC_VER) - register int r; + int r; asm volatile("smulwb %0, %1, %2\n\t" \ "ssat %0, #16, %0" \ : "=r"(r) : "r"(vol), "r"(sample)); From ac335ad61a8cdf241c4360d208405b5320426a17 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 17 Jan 2022 11:13:24 -0800 Subject: [PATCH 259/326] armips: Update to UTF-8/c++17 armips. --- CMakeLists.txt | 8 +++--- Core/Core.vcxproj | 8 ++++++ Core/Debugger/SymbolMap.cpp | 6 +++-- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 2 +- Core/MIPS/MIPSAsm.cpp | 22 ++++++++--------- Core/MIPS/MIPSAsm.h | 2 +- Windows/Debugger/CtrlDisAsmView.cpp | 2 +- ext/armips | 2 +- ext/libarmips.vcxproj | 26 ++++++++++++++------ ext/libarmips.vcxproj.filters | 12 +++++++++ unittest/JitHarness.cpp | 2 +- 11 files changed, 63 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a3bdff3ae..cd6e47e7ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ # vim:noexpandtab: -cmake_minimum_required(VERSION 3.6) +cmake_minimum_required(VERSION 3.8) project(PPSSPP) enable_testing() #This is supposed to work but doesn't! if(NOT ANDROID) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) endif() @@ -323,7 +323,7 @@ if(NOT MSVC) add_definitions(-DPNG_ARM_NEON_OPT=0) if(ANDROID) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17") endif() if(CLANG) add_definitions(-Wno-nullability-completeness) @@ -2066,6 +2066,8 @@ endif() target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash ${GlslangLibs} ${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) +target_compile_features(${CoreLibName} PUBLIC cxx_std_17) + if(FFmpeg_FOUND) target_compile_definitions(${CoreLibName} PRIVATE USE_FFMPEG=1) set_target_properties(${CoreLibName} PROPERTIES NO_SYSTEM_FROM_IMPORTED true) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 4697e33a40..56de57769c 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -148,6 +148,7 @@ MultiThreadedDebug Common/DbgNew.h ProgramDatabase + stdcpp17 true @@ -172,6 +173,7 @@ MultiThreadedDebug Common/DbgNew.h ProgramDatabase + stdcpp17 true @@ -196,6 +198,7 @@ MultiThreadedDebug Common/DbgNew.h ProgramDatabase + stdcpp17 true @@ -221,6 +224,7 @@ ProgramDatabase + stdcpp17 true @@ -247,6 +251,7 @@ true false ProgramDatabase + stdcpp17 true @@ -281,6 +286,7 @@ MultiThreaded true ProgramDatabase + stdcpp17 true @@ -312,6 +318,7 @@ MultiThreaded true ProgramDatabase + stdcpp17 true @@ -343,6 +350,7 @@ MultiThreaded true ProgramDatabase + stdcpp17 true diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp index e276e9be27..8f851fc6f5 100644 --- a/Core/Debugger/SymbolMap.cpp +++ b/Core/Debugger/SymbolMap.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include "zlib.h" @@ -1037,8 +1038,9 @@ void SymbolMap::GetLabels(std::vector &dest) { for (auto it = activeLabels.begin(); it != activeLabels.end(); it++) { LabelDefinition entry; entry.value = it->first; - entry.name = ConvertUTF8ToWString(it->second.name); - std::transform(entry.name.begin(), entry.name.end(), entry.name.begin(), ::towlower); + std::string name = it->second.name; + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + entry.name = Identifier(name); dest.push_back(entry); } } diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 0c256ded14..5d766785a2 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -482,7 +482,7 @@ void WebSocketDisasmState::Assemble(DebuggerRequest &req) { return; if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address)) - return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str())); + return req.Fail(StringFromFormat("Could not assemble: %s", MIPSAsm::GetAssembleError().c_str())); JsonWriter &json = req.Respond(); Reporting::NotifyDebugger(); diff --git a/Core/MIPS/MIPSAsm.cpp b/Core/MIPS/MIPSAsm.cpp index 837e857b68..25814af0e6 100644 --- a/Core/MIPS/MIPSAsm.cpp +++ b/Core/MIPS/MIPSAsm.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "Common/CommonTypes.h" @@ -15,9 +16,9 @@ namespace MIPSAsm { - static std::wstring errorText; + static std::string errorText; -std::wstring GetAssembleError() +std::string GetAssembleError() { return errorText; } @@ -62,16 +63,15 @@ private: fs::path dummyFilename_; }; -bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) -{ - std::vector errors; +bool MipsAssembleOpcode(const char *line, DebugInterface *cpu, u32 address) { + std::vector errors; - wchar_t str[64]; - swprintf(str,64,L".psp\n.org 0x%08X\n",address); + char str[64]; + snprintf(str, 64, ".psp\n.org 0x%08X\n", address); ArmipsArguments args; args.mode = ArmipsMode::MEMORY; - args.content = str + ConvertUTF8ToWString(line); + args.content = str + std::string(line); args.silent = true; args.memoryFile.reset(new PspAssemblerFile()); args.errorsResult = &errors; @@ -80,14 +80,14 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address) g_symbolMap->GetLabels(args.labels); } - errorText = L""; + errorText = ""; if (!runArmips(args)) { for (size_t i = 0; i < errors.size(); i++) { errorText += errors[i]; - if (i != errors.size()-1) - errorText += L"\n"; + if (i != errors.size() - 1) + errorText += "\n"; } return false; diff --git a/Core/MIPS/MIPSAsm.h b/Core/MIPS/MIPSAsm.h index 5693ff6c74..27bf3eef08 100644 --- a/Core/MIPS/MIPSAsm.h +++ b/Core/MIPS/MIPSAsm.h @@ -4,5 +4,5 @@ namespace MIPSAsm { bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address); - std::wstring GetAssembleError(); + std::string GetAssembleError(); } diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 5aca43fbcf..88e5ad28ff 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -326,7 +326,7 @@ void CtrlDisAsmView::assembleOpcode(u32 address, std::string defaultText) redraw(); } else { - std::wstring error = MIPSAsm::GetAssembleError(); + std::wstring error = ConvertUTF8ToWString(MIPSAsm::GetAssembleError()); MessageBox(wnd,error.c_str(),L"Error",MB_OK); } } diff --git a/ext/armips b/ext/armips index 0b13ef32c5..b4335f04fa 160000 --- a/ext/armips +++ b/ext/armips @@ -1 +1 @@ -Subproject commit 0b13ef32c566781f0566b027ad5681d9320bddf7 +Subproject commit b4335f04fa62c1b4f7833c70fbb15505b6772274 diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index de44145f1a..d85b5b3f68 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -261,7 +261,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug armips @@ -281,7 +281,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -303,7 +303,7 @@ Use Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -311,6 +311,7 @@ true ProgramDatabase + stdcpp17 Windows @@ -324,7 +325,7 @@ Use Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -332,6 +333,7 @@ true ProgramDatabase + stdcpp17 Windows @@ -372,6 +374,7 @@ Disabled WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) armips + stdcpp17 Windows @@ -386,6 +389,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) armips MultiThreadedDebug + stdcpp17 Windows @@ -398,7 +402,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded armips true @@ -419,7 +423,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded armips false @@ -443,7 +447,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded armips false @@ -451,6 +455,7 @@ true false ProgramDatabase + stdcpp17 Windows @@ -466,7 +471,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded armips false @@ -474,6 +479,7 @@ true false ProgramDatabase + stdcpp17 Windows @@ -516,11 +522,13 @@ + + @@ -567,11 +575,13 @@ + + diff --git a/ext/libarmips.vcxproj.filters b/ext/libarmips.vcxproj.filters index d374c5f4b8..61282421bb 100644 --- a/ext/libarmips.vcxproj.filters +++ b/ext/libarmips.vcxproj.filters @@ -177,6 +177,12 @@ Util + + Core + + + Core + @@ -329,5 +335,11 @@ Util + + Core + + + Core + \ No newline at end of file diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index 6575a1e323..c73af9733b 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -150,7 +150,7 @@ bool TestJit() { for (size_t j = 0; j < ARRAY_SIZE(lines); ++j) { p++; if (!MIPSAsm::MipsAssembleOpcode(lines[j], currentDebugMIPS, addr)) { - printf("ERROR: %ls\n", MIPSAsm::GetAssembleError().c_str()); + printf("ERROR: %s\n", MIPSAsm::GetAssembleError().c_str()); compileSuccess = false; } addr += 4; From 559fdd167f6fe276a78306e38497e10d049e5956 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 30 Sep 2022 18:47:26 -0700 Subject: [PATCH 260/326] armips: Also update UWP and ARM64, etc. --- Core/Core.vcxproj | 8 ++++++ UWP/Armips_UWP/Armips_UWP.vcxproj | 30 ++++++++++++++-------- UWP/CoreUWP/CoreUWP.vcxproj | 42 +++++++++++++++++++++++++++++-- android/jni/Android.mk | 4 +++ android/jni/Locals.mk | 5 +++- ext/CMakeLists.txt | 1 + ext/libarmips.vcxproj | 38 ++++++++++++---------------- ext/vma/vk_mem_alloc.h | 12 +++++++++ libretro/Makefile | 2 +- libretro/Makefile.common | 2 ++ 10 files changed, 108 insertions(+), 36 deletions(-) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 56de57769c..275055ca1d 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -581,6 +581,10 @@ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ @@ -918,6 +922,10 @@ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/ diff --git a/UWP/Armips_UWP/Armips_UWP.vcxproj b/UWP/Armips_UWP/Armips_UWP.vcxproj index a0fcbffe0c..74c4b0b42b 100644 --- a/UWP/Armips_UWP/Armips_UWP.vcxproj +++ b/UWP/Armips_UWP/Armips_UWP.vcxproj @@ -116,41 +116,42 @@ false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips; + $(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; + $(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; + $(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; + $(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; + $(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; + $(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; + $(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; + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(ProjectDir)..\..\ext\armips;$(ProjectDir)..\..\ext\armips\ext\filesystem\include;$(ProjectDir)..\..\ext\armips\ext\tinyformat; NotUsing false false + stdcpp17 Console @@ -163,6 +164,7 @@ NotUsing false false + stdcpp17 Console @@ -175,6 +177,7 @@ NotUsing false false + stdcpp17 Console @@ -187,6 +190,7 @@ NotUsing false false + stdcpp17 Console @@ -199,6 +203,7 @@ NotUsing false false + stdcpp17 Console @@ -211,6 +216,7 @@ NotUsing false false + stdcpp17 Console @@ -223,6 +229,7 @@ NotUsing false false + stdcpp17 Console @@ -235,6 +242,7 @@ NotUsing false false + stdcpp17 Console @@ -285,7 +293,6 @@ - @@ -322,25 +329,28 @@ + + + - + Create diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 0777e28251..e22961611f 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -206,6 +206,7 @@ pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -221,6 +222,7 @@ pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -236,6 +238,7 @@ pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -251,6 +254,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -266,6 +270,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -281,6 +286,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -296,6 +302,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -311,6 +318,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -326,6 +334,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + stdcpp17 Console @@ -341,6 +350,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -356,6 +366,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -371,6 +382,7 @@ 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) USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + stdcpp17 Console @@ -628,7 +640,20 @@ - + + ../../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) + @@ -809,7 +834,20 @@ - + + ../../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) + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index f317315e0a..2f86711855 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -584,14 +584,17 @@ LIBARMIPS_FILES := \ $(SRC)/ext/armips/Commands/CommandSequence.cpp \ $(SRC)/ext/armips/Core/ELF/ElfFile.cpp \ $(SRC)/ext/armips/Core/ELF/ElfRelocator.cpp \ + $(SRC)/ext/armips/Core/Allocations.cpp \ $(SRC)/ext/armips/Core/Assembler.cpp \ $(SRC)/ext/armips/Core/Common.cpp \ $(SRC)/ext/armips/Core/Expression.cpp \ + $(SRC)/ext/armips/Core/ExpressionFunctionHandler.cpp \ $(SRC)/ext/armips/Core/ExpressionFunctions.cpp \ $(SRC)/ext/armips/Core/FileManager.cpp \ $(SRC)/ext/armips/Core/Misc.cpp \ $(SRC)/ext/armips/Core/SymbolData.cpp \ $(SRC)/ext/armips/Core/SymbolTable.cpp \ + $(SRC)/ext/armips/Core/Types.cpp \ $(SRC)/ext/armips/Parser/DirectivesParser.cpp \ $(SRC)/ext/armips/Parser/ExpressionParser.cpp \ $(SRC)/ext/armips/Parser/Parser.cpp \ @@ -600,6 +603,7 @@ LIBARMIPS_FILES := \ $(SRC)/ext/armips/Util/CRC.cpp \ $(SRC)/ext/armips/Util/EncodingTable.cpp \ $(SRC)/ext/armips/Util/FileClasses.cpp \ + $(SRC)/ext/armips/Util/FileSystem.cpp \ $(SRC)/ext/armips/Util/Util.cpp LOCAL_MODULE := libarmips diff --git a/android/jni/Locals.mk b/android/jni/Locals.mk index f589c4f7e2..ef0d118462 100644 --- a/android/jni/Locals.mk +++ b/android/jni/Locals.mk @@ -4,7 +4,7 @@ LOCAL_CFLAGS := -DUSE_FFMPEG -DWITH_UPNP -DUSING_GLES2 -DMOBILE_DEVICE -O3 -fsigned-char -Wall -Wno-multichar -Wno-unused-variable -fno-strict-aliasing -D__STDC_CONSTANT_MACROS -DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS # yes, it's really CPPFLAGS for C++ # deprecated-register is generated by Android default code and causes noise. -LOCAL_CPPFLAGS := -fno-exceptions -std=gnu++11 -fno-rtti -Wno-reorder -Wno-deprecated-register -Wno-nullability-completeness +LOCAL_CPPFLAGS := -fexceptions -std=gnu++17 -fno-rtti -Wno-reorder -Wno-deprecated-register -Wno-nullability-completeness LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/../../Common \ $(LOCAL_PATH)/../.. \ @@ -16,6 +16,9 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/../../ext/libpng17 \ $(LOCAL_PATH)/../../ext/libzip \ $(LOCAL_PATH)/../../ext/zstd/lib \ + $(LOCAL_PATH)/../../ext/armips \ + $(LOCAL_PATH)/../../ext/armips/ext/filesystem/include \ + $(LOCAL_PATH)/../../ext/armips/ext/tinyformat \ $(LOCAL_PATH) LOCAL_STATIC_LIBRARIES := libzip glslang-build miniupnp-build diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 440f3c362c..59be65c0da 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -1,4 +1,5 @@ set(ARMIPS_REGEXP OFF CACHE BOOL "" FORCE) +set(ARMIPS_LIBRARY_ONLY ON CACHE BOOL "" FORCE) add_subdirectory(armips) if(NOT USING_GLES2) diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index d85b5b3f68..8cbb23dfdd 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -187,74 +187,74 @@ - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName)d $(ProjectName)d64 - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)64\ $(ProjectName)dARM64 - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)ARM64\ $(ProjectName)dARM - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)ARM\ - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName)tests - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) $(ProjectName)tests64 _Output\ _Output\$(ProjectName)\$(Configuration)64\ - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) $(ProjectName)testsARM64 _Output\ _Output\$(ProjectName)\$(Configuration)ARM64\ - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) $(ProjectName)testsARM _Output\ _Output\$(ProjectName)\$(Configuration)ARM\ - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)\ $(ProjectName) $(ProjectName)64 - .;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)64\ $(ProjectName)ARM64 - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)ARM64\ $(ProjectName)ARM - .;$(IncludePath) + .;.\armips;.\armips\ext\filesystem\include;.\armips\ext\tinyformat;$(IncludePath) _Output\ _Output\$(ProjectName)\$(Configuration)ARM\ @@ -301,7 +301,6 @@ - Use Level3 _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug @@ -323,7 +322,6 @@ - Use Level3 _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreadedDebug @@ -345,7 +343,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) armips stdcpp17 @@ -358,7 +356,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) armips stdcpp17 @@ -369,10 +367,9 @@ - Use Level3 Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) armips stdcpp17 @@ -383,10 +380,9 @@ - Use Level3 Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) armips MultiThreadedDebug stdcpp17 @@ -443,7 +439,6 @@ Level3 - Use MaxSpeed true true @@ -467,7 +462,6 @@ Level3 - Use MaxSpeed true true diff --git a/ext/vma/vk_mem_alloc.h b/ext/vma/vk_mem_alloc.h index 3dc4cc9dc8..f52dceeaf4 100644 --- a/ext/vma/vk_mem_alloc.h +++ b/ext/vma/vk_mem_alloc.h @@ -26,6 +26,18 @@ // BEGIN PPSSPP HACKS !!!!! #ifdef USE_CRT_DBG #undef new +#endif + +#if defined(__APPLE__) +#include + +#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (!defined(__IPHONE_10_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) +#define VMA_USE_STL_SHARED_MUTEX 0 +#endif +#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && (!defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12) +#define VMA_USE_STL_SHARED_MUTEX 0 +#endif + #endif // END PPSSPP HACKS diff --git a/libretro/Makefile b/libretro/Makefile index 245d6d2267..1fd4f48632 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -577,7 +577,7 @@ else endif ifeq (,$(findstring msvc,$(platform))) - CXXFLAGS += -std=c++11 + CXXFLAGS += -std=c++17 endif ### Finalize ### diff --git a/libretro/Makefile.common b/libretro/Makefile.common index f08bd45633..e7dd40be35 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -25,6 +25,8 @@ INCFLAGS += \ $(FFMPEGINCFLAGS) \ -I$(EXTDIR)/cityhash \ -I$(EXTDIR)/armips \ + -I$(EXTDIR)/armips/ext/filesystem/include \ + -I$(EXTDIR)/armips/ext/tinyformat \ -I$(EXTDIR)/zstd/lib \ -I$(EXTDIR) \ -I$(NATIVEDIR) \ From 2b884de58567a5237dbc8174dcb88804a21af171 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 07:59:17 -0700 Subject: [PATCH 261/326] armips: Use native std::filesystem on Windows/UWP. --- Core/Core.vcxproj | 16 +++++++-------- UWP/Armips_UWP/Armips_UWP.vcxproj | 8 ++++++++ UWP/Armips_UWP/Armips_UWP.vcxproj.filters | 6 ++++-- UWP/CoreUWP/CoreUWP.vcxproj | 24 +++++++++++------------ ext/CMakeLists.txt | 3 +++ ext/libarmips.vcxproj | 24 +++++++++++------------ 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 275055ca1d..25194a1741 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -139,7 +139,7 @@ Level3 ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib - _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) StreamingSIMDExtensions2 Precise true @@ -163,7 +163,7 @@ Level3 ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib - _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) NotSet Precise false @@ -188,7 +188,7 @@ Level3 ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib - _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) NotSet Precise false @@ -213,7 +213,7 @@ Level3 ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib - _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) NotSet Precise false @@ -245,7 +245,7 @@ false StreamingSIMDExtensions2 Precise - USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Speed MultiThreaded true @@ -281,7 +281,7 @@ Speed false true - USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) false MultiThreaded true @@ -313,7 +313,7 @@ Speed false true - USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_LIB;NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_LIB;NDEBUG;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) false MultiThreaded true @@ -345,7 +345,7 @@ Speed false true - USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_LIB;NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_LIB;NDEBUG;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) false MultiThreaded true diff --git a/UWP/Armips_UWP/Armips_UWP.vcxproj b/UWP/Armips_UWP/Armips_UWP.vcxproj index 74c4b0b42b..a836263550 100644 --- a/UWP/Armips_UWP/Armips_UWP.vcxproj +++ b/UWP/Armips_UWP/Armips_UWP.vcxproj @@ -152,6 +152,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -165,6 +166,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -178,6 +180,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -191,6 +194,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -204,6 +208,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -217,6 +222,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -230,6 +236,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console @@ -243,6 +250,7 @@ false false stdcpp17 + _UNICODE;UNICODE;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) Console diff --git a/UWP/Armips_UWP/Armips_UWP.vcxproj.filters b/UWP/Armips_UWP/Armips_UWP.vcxproj.filters index 463cc812fe..37d23a6b09 100644 --- a/UWP/Armips_UWP/Armips_UWP.vcxproj.filters +++ b/UWP/Armips_UWP/Armips_UWP.vcxproj.filters @@ -140,7 +140,10 @@ Util - + + + + @@ -286,7 +289,6 @@ Util - diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index e22961611f..60c5d6bf8a 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -205,7 +205,7 @@ false pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -221,7 +221,7 @@ false pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -237,7 +237,7 @@ false pch.h ../../ffmpeg/Windows10/x86/include;../..;../../ext/native;../../ext/snappy;../../ext/libpng17;../../Common;../../ext/zlib;../../ext/zstd/lib;../../ext;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -253,7 +253,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -269,7 +269,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -285,7 +285,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -301,7 +301,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -317,7 +317,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -333,7 +333,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_ARM64_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1;%(ClCompile.PreprocessorDefinitions) + 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 @@ -349,7 +349,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -365,7 +365,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 @@ -381,7 +381,7 @@ 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) - USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;%(PreprocessorDefinitions) + USE_FFMPEG;WITH_UPNP;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;NOMINMAX;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) stdcpp17 diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 59be65c0da..e5159c989a 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -1,5 +1,8 @@ set(ARMIPS_REGEXP OFF CACHE BOOL "" FORCE) set(ARMIPS_LIBRARY_ONLY ON CACHE BOOL "" FORCE) +if(WIN32) + set(ARMIPS_USE_STD_FILESYSTEM ON CACHE BOOL "" FORCE) +endif() add_subdirectory(armips) if(NOT USING_GLES2) diff --git a/ext/libarmips.vcxproj b/ext/libarmips.vcxproj index 8cbb23dfdd..c0484e63d1 100644 --- a/ext/libarmips.vcxproj +++ b/ext/libarmips.vcxproj @@ -261,7 +261,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreadedDebug armips @@ -281,7 +281,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -302,7 +302,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -323,7 +323,7 @@ Level3 - _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreadedDebug armips false @@ -343,7 +343,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) armips stdcpp17 @@ -356,7 +356,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) armips stdcpp17 @@ -369,7 +369,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) armips stdcpp17 @@ -382,7 +382,7 @@ Level3 Disabled - WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) armips MultiThreadedDebug stdcpp17 @@ -398,7 +398,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreaded armips true @@ -419,7 +419,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreaded armips false @@ -442,7 +442,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreaded armips false @@ -465,7 +465,7 @@ MaxSpeed true true - WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions) MultiThreaded armips false From c3e2e1c13db9469ca7378cd1d818ddf4311a278a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 08:01:01 -0700 Subject: [PATCH 262/326] Build: Raise macOS/iOS min version for c++17. --- CMakeLists.txt | 5 +++-- cmake/Toolchains/ios.cmake | 9 +++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd6e47e7ad..8cec0f281d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -378,8 +378,9 @@ if(NOT MSVC) endif() if(IOS) + set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0") elseif(APPLE AND NOT CMAKE_CROSSCOMPILING) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.8") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -U__STRICT_ANSI__") set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") elseif(NOT ANDROID) @@ -2456,7 +2457,7 @@ if(NOT ANDROID) endif() # packaging and code signing if(IOS) - set(DEPLOYMENT_TARGET 8.0) + set(DEPLOYMENT_TARGET 11.0) file(GLOB IOSAssets ios/assets/*.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@2x.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@3x.png) diff --git a/cmake/Toolchains/ios.cmake b/cmake/Toolchains/ios.cmake index 4198706e0c..45a8a219bc 100644 --- a/cmake/Toolchains/ios.cmake +++ b/cmake/Toolchains/ios.cmake @@ -12,7 +12,7 @@ # PPSSPP platform flags set(MOBILE_DEVICE ON) set(USING_GLES2 ON) -set(IPHONEOS_DEPLOYMENT_TARGET 6.0) +set(IPHONEOS_DEPLOYMENT_TARGET 11.0) add_definitions( -DGL_ETC1_RGB8_OES=0 -U__STRICT_ANSI__ @@ -27,7 +27,7 @@ set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") # Standard settings set(CMAKE_SYSTEM_NAME Darwin) set(CMAKE_SYSTEM_VERSION 1) -set(CMAKE_SYSTEM_PROCESSOR armv7) +set(CMAKE_SYSTEM_PROCESSOR aarch64) set(IOS ON) set(CMAKE_CROSSCOMPILING ON) set(CMAKE_MACOSX_BUNDLE YES) @@ -66,9 +66,7 @@ set(CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS sup # set the architecture for iOS if(IOS_PLATFORM STREQUAL "OS") - # When ffmpeg has been rebuilt for arm64 use: - set(IOS_ARCH "armv7;arm64") - #set(IOS_ARCH "armv7") + set(IOS_ARCH "arm64") else() set(IOS_ARCH "i386;x86_64") endif() @@ -95,4 +93,3 @@ set(CMAKE_SYSTEM_FRAMEWORK_PATH # only search the iOS sdks, not the remainder of the host filesystem set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - From 9ec41436d16c8d11d1bc4e9dd15482dffe7faa74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Oct 2022 19:06:02 +0200 Subject: [PATCH 263/326] ES2 crash fix: Don't draw depth if lacking fragment shader depth write. --- GPU/Common/FramebufferManagerCommon.cpp | 2 +- GPU/Common/TextureCacheCommon.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index f97f2e91c0..a9487dd6f7 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -575,7 +575,7 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081. // Might make this more generic and upload the whole depth buffer if we find it's needed for something. - if (newlyUsingDepth) { + if (newlyUsingDepth && draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) { // Sanity check the depth buffer pointer. if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 1b7d8d5986..d55f890f64 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1252,6 +1252,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { } } + // To turn off dynamic CLUT (for demonstration or testing purposes), add "false &&" to this check. if (chosenFramebuffer && chosenFramebuffer->fbo) { clutRenderAddress_ = bestClutAddress; From 59a489f883dec7f96d4c84aab2f3337b57a33107 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 11:37:10 -0700 Subject: [PATCH 264/326] Draw: Add COLOR1 semantic. --- Common/GPU/D3D11/thin3d_d3d11.cpp | 1 + Common/GPU/D3D9/thin3d_d3d9.cpp | 4 ++++ Common/GPU/OpenGL/thin3d_gl.cpp | 1 + Common/GPU/ShaderTranslation.cpp | 6 ++++-- Common/GPU/ShaderWriter.cpp | 4 +++- Common/GPU/Vulkan/thin3d_vulkan.cpp | 14 +------------- Common/GPU/thin3d.cpp | 4 +++- Common/GPU/thin3d.h | 1 + GPU/Common/VertexShaderGenerator.cpp | 3 --- UI/DevScreens.cpp | 2 +- UI/GPUDriverTestScreen.cpp | 6 ++++-- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index a808386ce9..166a0ebe66 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -686,6 +686,7 @@ const char *semanticToD3D11(int semantic, UINT *index) { switch (semantic) { case SEM_POSITION: return "POSITION"; case SEM_COLOR0: *index = 0; return "COLOR"; + case SEM_COLOR1: *index = 1; return "COLOR"; case SEM_TEXCOORD0: *index = 0; return "TEXCOORD"; case SEM_TEXCOORD1: *index = 1; return "TEXCOORD"; case SEM_NORMAL: return "NORMAL"; diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 0bc8003f1e..377a0b4050 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -945,6 +945,10 @@ static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index) case SEM_COLOR0: *usage = D3DDECLUSAGE_COLOR; break; + case SEM_COLOR1: + *usage = D3DDECLUSAGE_COLOR; + *index = 1; + break; case SEM_TEXCOORD0: *usage = D3DDECLUSAGE_TEXCOORD; break; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 02decfe05c..f757d3f554 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -1206,6 +1206,7 @@ bool OpenGLPipeline::LinkShaders() { // Bind all the common vertex data points. Mismatching ones will be ignored. semantics.push_back({ SEM_POSITION, "Position" }); semantics.push_back({ SEM_COLOR0, "Color0" }); + semantics.push_back({ SEM_COLOR1, "Color1" }); semantics.push_back({ SEM_TEXCOORD0, "TexCoord0" }); semantics.push_back({ SEM_NORMAL, "Normal" }); semantics.push_back({ SEM_TANGENT, "Tangent" }); diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp index 34de213451..54b4fab0f1 100644 --- a/Common/GPU/ShaderTranslation.cpp +++ b/Common/GPU/ShaderTranslation.cpp @@ -152,16 +152,18 @@ std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage return output; } +static_assert(Draw::SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl below."); + bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, ShaderStage stage, std::string *errorMessage) { std::stringstream out; - static struct { + static const struct { ShaderStage stage; const char *needle; const char *replacement; } replacements[] = { { ShaderStage::Vertex, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" }, - { ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 2) in vec2 a_texcoord0;"}, + { ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 3) in vec2 a_texcoord0;"}, { ShaderStage::Vertex, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" }, { ShaderStage::Fragment, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" }, { ShaderStage::Fragment, "texture2D(", "texture(" }, diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp index b260d05fc9..6d3e0c0f50 100644 --- a/Common/GPU/ShaderWriter.cpp +++ b/Common/GPU/ShaderWriter.cpp @@ -68,15 +68,17 @@ static const char * const hlsl_preamble_vs = "#define highp\n" "\n"; -static const char * const semanticNames[8] = { +static const char * const semanticNames[] = { "POSITION", "COLOR0", + "COLOR1", "TEXCOORD0", "TEXCOORD1", "NORMAL", "TANGENT", "BINORMAL", }; +static_assert(ARRAY_SIZE(semanticNames) == Draw::SEM_MAX, "Missing semantic in semanticNames"); // Unsafe. But doesn't matter, we'll use big buffers for shader gen. ShaderWriter & ShaderWriter::F(const char *format, ...) { diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 111cea1d2b..35d620be3c 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -215,8 +215,8 @@ private: }; bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, const uint8_t *data, size_t size) { - vulkan_ = vulkan; // We'll need this to free it later. + vulkan_ = vulkan; source_ = (const char *)data; std::vector spirv; std::string errorMessage; @@ -278,7 +278,6 @@ public: return buf->PushAligned(ubo_, uboSize_, vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment, vkbuf); } - int GetUniformLoc(const char *name); int GetUBOSize() const { return uboSize_; } @@ -1308,17 +1307,6 @@ ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage la } } -int VKPipeline::GetUniformLoc(const char *name) { - int loc = -1; - - // HACK! As we only use one uniform we hardcode it. - if (!strcmp(name, "WorldViewProj")) { - return 0; - } - - return loc; -} - void VKContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) { curPipeline_->SetDynamicUniformData(ub, size); } diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index acaa3dac0b..a81c38ba10 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -435,7 +435,7 @@ vec3 hsv2rgb(vec3 c) { } layout (location = 0) in vec4 pos; layout (location = 1) in vec4 inColor; -layout (location = 2) in vec2 inTexCoord; +layout (location = 3) in vec2 inTexCoord; layout (location = 0) out vec4 outColor; layout (location = 1) out vec2 outTexCoord; out gl_PerVertex { vec4 gl_Position; }; @@ -450,6 +450,8 @@ void main() { )" } }; +static_assert(SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl above."); + const UniformBufferDesc vsTexColBufDesc{ sizeof(VsTexColUB),{ { "WorldViewProj", 0, -1, UniformType::MATRIX4X4, 0 }, { "TintSaturation", 4, -1, UniformType::FLOAT2, 64 }, diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index ee0d50602a..f0c562b2f3 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -114,6 +114,7 @@ enum BufferUsageFlag : int { enum Semantic : int { SEM_POSITION, SEM_COLOR0, + SEM_COLOR1, SEM_TEXCOORD0, SEM_TEXCOORD1, SEM_NORMAL, diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 2a71611b1e..797a6e0e50 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -127,9 +127,6 @@ static const char * const boneWeightDecl[9] = { "layout(location = 3) in vec4 w1;\nlayout(location = 4) in vec4 w2;\n", }; -extern const char *vulkan_glsl_preamble_vs; -extern const char *hlsl_preamble_vs; - bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint32_t *attrMask, uint64_t *uniformMask, std::string *errorString) { *attrMask = 0; *uniformMask = 0; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index ce5bd3fada..e85f1d3adc 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -1144,7 +1144,7 @@ int ShaderListScreen::ListShaders(DebugShaderType shaderType, UI::LinearLayout * using namespace UI; std::vector shaderIds_ = gpu->DebugGetShaderIDs(shaderType); int count = 0; - for (auto id : shaderIds_) { + for (const auto &id : shaderIds_) { Choice *choice = view->Add(new Choice(gpu->DebugGetShaderString(id, shaderType, SHADER_STRING_SHORT_DESC))); choice->SetTag(id); choice->OnClick.Handle(this, &ShaderListScreen::OnShaderClick); diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index b46fd7e42e..023b9e1def 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -122,7 +122,7 @@ static const std::vector vsAdrenoLogicTest = { "} myBufferVals;\n" "layout (location = 0) in vec4 pos;\n" "layout (location = 1) in vec4 inColor;\n" - "layout (location = 2) in vec2 inTexCoord;\n" + "layout (location = 3) in vec2 inTexCoord;\n" "layout (location = 0) out vec4 outColor;\n" "layout (location = 1) out highp vec2 outTexCoord;\n" "out gl_PerVertex { vec4 gl_Position; };\n" @@ -219,7 +219,7 @@ static const std::vector vsFlat = { "} myBufferVals;\n" "layout (location = 0) in vec4 pos;\n" "layout (location = 1) in vec4 inColor;\n" - "layout (location = 2) in vec2 inTexCoord;\n" + "layout (location = 3) in vec2 inTexCoord;\n" "layout (location = 0) flat out lowp vec4 outColor;\n" "layout (location = 1) out highp vec2 outTexCoord;\n" "out gl_PerVertex { vec4 gl_Position; };\n" @@ -231,6 +231,8 @@ static const std::vector vsFlat = { } }; +static_assert(Draw::SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl above."); + GPUDriverTestScreen::GPUDriverTestScreen() { using namespace Draw; } From e5fbc1d39dc5d285bb16e04f305753912ecd6361 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 11:41:03 -0700 Subject: [PATCH 265/326] unittest: Cleanup stage/glsl version. --- unittest/TestShaderGenerators.cpp | 37 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index 8cf73cd8b4..810de85b9e 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -39,7 +39,7 @@ bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs } case ShaderLanguage::GLSL_3xx: { - ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx); return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString); } case ShaderLanguage::HLSL_D3D9: @@ -73,7 +73,7 @@ bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs } case ShaderLanguage::GLSL_3xx: { - ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx); return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, errorString); } case ShaderLanguage::HLSL_D3D9: @@ -91,18 +91,41 @@ bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs } } +static VkShaderStageFlagBits StageToVulkan(ShaderStage stage) { + switch (stage) { + case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT; + case ShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; + case ShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; + case ShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; + } + return VK_SHADER_STAGE_FRAGMENT_BIT; +} + bool TestCompileShader(const char *buffer, ShaderLanguage lang, ShaderStage stage, std::string *errorMessage) { std::vector spirv; switch (lang) { #if PPSSPP_PLATFORM(WINDOWS) case ShaderLanguage::HLSL_D3D11: { - auto output = CompileShaderToBytecodeD3D11(buffer, strlen(buffer), stage == ShaderStage::Vertex ? "vs_4_0" : "ps_4_0", 0); + const char *programType = nullptr; + switch (stage) { + case ShaderStage::Vertex: programType = "vs_4_0"; break; + case ShaderStage::Fragment: programType = "ps_4_0"; break; + case ShaderStage::Geometry: programType = "gs_4_0"; break; + default: return false; + } + auto output = CompileShaderToBytecodeD3D11(buffer, strlen(buffer), programType, 0); return !output.empty(); } case ShaderLanguage::HLSL_D3D9: { - LPD3DBLOB blob = CompileShaderToByteCodeD3D9(buffer, stage == ShaderStage::Vertex ? "vs_3_0" : "ps_3_0", errorMessage); + const char *programType = nullptr; + switch (stage) { + case ShaderStage::Vertex: programType = "vs_3_0"; break; + case ShaderStage::Fragment: programType = "ps_3_0"; break; + default: return false; + } + LPD3DBLOB blob = CompileShaderToByteCodeD3D9(buffer, programType, errorMessage); if (blob) { blob->Release(); return true; @@ -113,11 +136,11 @@ bool TestCompileShader(const char *buffer, ShaderLanguage lang, ShaderStage stag #endif case ShaderLanguage::GLSL_VULKAN: - return GLSLtoSPV(stage == ShaderStage::Vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::VULKAN, spirv, errorMessage); + return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::VULKAN, spirv, errorMessage); case ShaderLanguage::GLSL_1xx: - return GLSLtoSPV(stage == ShaderStage::Vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::GL140, spirv, errorMessage); + return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::GL140, spirv, errorMessage); case ShaderLanguage::GLSL_3xx: - return GLSLtoSPV(stage == ShaderStage::Vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::GLES300, spirv, errorMessage); + return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::GLES300, spirv, errorMessage); default: return false; } From 87171cef98ae46adee28bbac7908f10859995f01 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 12:12:46 -0700 Subject: [PATCH 266/326] GPU: Add geometry path for shader writer. Not yet used. --- Common/GPU/ShaderWriter.cpp | 79 ++++++++++++++++++++++- Common/GPU/ShaderWriter.h | 2 + Common/GPU/Vulkan/VulkanContext.cpp | 1 + Common/GPU/Vulkan/VulkanQueueRunner.h | 1 + Common/GPU/Vulkan/VulkanRenderManager.cpp | 7 +- GPU/Common/ShaderId.h | 7 ++ GPU/Vulkan/GPU_Vulkan.cpp | 4 +- GPU/Vulkan/PipelineManagerVulkan.h | 1 + 8 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp index 6d3e0c0f50..7ca6deaeb0 100644 --- a/Common/GPU/ShaderWriter.cpp +++ b/Common/GPU/ShaderWriter.cpp @@ -53,6 +53,30 @@ static const char * const vulkan_glsl_preamble_vs = "precision highp float;\n" "\n"; +static const char * const hlsl_preamble_gs = +"#define vec2 float2\n" +"#define vec3 float3\n" +"#define vec4 float4\n" +"#define ivec2 int2\n" +"#define ivec4 int4\n" +"#define mat2 float2x2\n" +"#define mat4 float4x4\n" +"#define mat3x4 float4x3\n" // note how the conventions are backwards +"#define splat3(x) vec3(x, x, x)\n" +"#define lowp\n" +"#define mediump\n" +"#define highp\n" +"\n"; + +static const char * const vulkan_glsl_preamble_gs = +"#version 450\n" +"#extension GL_ARB_separate_shader_objects : enable\n" +"#extension GL_ARB_shading_language_420pack : enable\n" +"#define mul(x, y) ((x) * (y))\n" +"#define splat3(x) vec3(x)\n" +"precision highp float;\n" +"\n"; + static const char * const hlsl_preamble_vs = "#define vec2 float2\n" "#define vec3 float3\n" @@ -99,6 +123,9 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions case ShaderStage::Fragment: W(vulkan_glsl_preamble_fs); break; + case ShaderStage::Geometry: + W(vulkan_glsl_preamble_gs); + break; default: break; } @@ -117,6 +144,9 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions W(hlsl_d3d11_preamble_fs); } break; + case ShaderStage::Geometry: + W(hlsl_preamble_gs); + break; default: break; } @@ -147,6 +177,11 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions } C("#define gl_VertexIndex gl_VertexID\n"); break; + case ShaderStage::Geometry: + if (lang_.gles) { + C("precision highp float;\n"); + } + break; default: break; } @@ -174,7 +209,7 @@ void ShaderWriter::BeginVSMain(Slice inputs, Slice uniform F(" vec4 pos : %s;\n", lang_.shaderLanguage == HLSL_D3D11 ? "SV_Position" : "POSITION"); C("};\n"); - C("VS_OUTPUT main( "); // 2 spaces for the D3D9 rewind + C("VS_OUTPUT main( "); // 2 spaces for the rewind if (lang_.shaderLanguage == HLSL_D3D11) { C("uint gl_VertexIndex : SV_VertexID, "); } @@ -310,6 +345,43 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var } } +void ShaderWriter::BeginGSMain(Slice varyings, Slice outVaryings) { + _assert_(this->stage_ == ShaderStage::Geometry); + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + // Untested, but should work. + C("\nstruct GS_OUTPUT {\n"); + for (auto &varying : outVaryings) { + F(" %s %s : %s;\n", varying.type, varying.name, semanticNames[varying.semantic]); + } + F(" vec4 pos : %s;\n", lang_.shaderLanguage == HLSL_D3D11 ? "SV_Position" : "POSITION"); + C("};\n"); + C("#define EmitVertex() emit.Append(gsout)\n"); + + C("void main("); + for (auto &varying : varyings) { + F(" in %s %s : %s, ", varying.type, varying.name, semanticNames[varying.semantic]); + } + C("inout TriangleStream emit) {\n"); + C(" GS_OUTPUT gsout;\n"); + break; + case GLSL_VULKAN: + for (auto &varying : varyings) { + F("layout(location = %d) %s in %s %s[]; // %s\n", varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, semanticNames[varying.semantic]); + } + for (auto &varying : outVaryings) { + F("layout(location = %d) %s out %s %s; // %s\n", varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, semanticNames[varying.semantic]); + } + C("\nvoid main() {\n"); + break; + case GLSL_3xx: + C("\nvoid main() {\n"); + break; + default: + break; + } +} + void ShaderWriter::EndVSMain(Slice varyings) { _assert_(this->stage_ == ShaderStage::Vertex); switch (lang_.shaderLanguage) { @@ -351,6 +423,11 @@ void ShaderWriter::EndFSMain(const char *vec4_color_variable, FSFlags flags) { C("}\n"); } +void ShaderWriter::EndGSMain() { + _assert_(this->stage_ == ShaderStage::Geometry); + C("}\n"); +} + void ShaderWriter::HighPrecisionFloat() { if ((ShaderLanguageIsOpenGL(lang_.shaderLanguage) && lang_.gles) || lang_.shaderLanguage == GLSL_VULKAN) { C("precision highp float;\n"); diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h index f6a04c828b..50f3bb1f0d 100644 --- a/Common/GPU/ShaderWriter.h +++ b/Common/GPU/ShaderWriter.h @@ -85,10 +85,12 @@ public: // Simple shaders with no special tricks. void BeginVSMain(Slice inputs, Slice uniforms, Slice varyings); void BeginFSMain(Slice uniforms, Slice varyings, FSFlags flags); + void BeginGSMain(Slice varyings, Slice outVaryings); // For simple shaders that output a single color, we can deal with this generically. void EndVSMain(Slice varyings); void EndFSMain(const char *vec4_color_variable, FSFlags flags); + void EndGSMain(); const ShaderLanguageDesc &Lang() const { return lang_; diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 3b3b5124b9..1379cb9f21 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -602,6 +602,7 @@ void VulkanContext::ChooseDevice(int physical_device) { deviceFeatures_.enabled.shaderCullDistance = deviceFeatures_.available.shaderCullDistance; // For easy wireframe mode, someday. deviceFeatures_.enabled.fillModeNonSolid = deviceFeatures_.available.fillModeNonSolid; + deviceFeatures_.enabled.geometryShader = deviceFeatures_.available.geometryShader; GetDeviceLayerExtensionList(nullptr, device_extension_properties_); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index 54430cd891..7036029149 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -46,6 +46,7 @@ enum class PipelineFlags { USES_BLEND_CONSTANT = (1 << 1), USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers. USES_INPUT_ATTACHMENT = (1 << 3), + USES_GEOMETRY_SHADER = (1 << 4), }; ENUM_CLASS_BITOPS(PipelineFlags); diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 47d4d1896e..4e047bf64e 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -37,23 +37,22 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR return false; } - VkPipelineShaderStageCreateInfo ss[2]{}; + uint32_t stageCount = 2; + VkPipelineShaderStageCreateInfo ss[3]{}; ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; ss[0].stage = VK_SHADER_STAGE_VERTEX_BIT; ss[0].pSpecializationInfo = nullptr; ss[0].module = vs; ss[0].pName = "main"; - ss[0].flags = 0; ss[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; ss[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; ss[1].pSpecializationInfo = nullptr; ss[1].module = fs; ss[1].pName = "main"; - ss[1].flags = 0; VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; pipe.pStages = ss; - pipe.stageCount = 2; + pipe.stageCount = stageCount; pipe.renderPass = compatibleRenderPass; pipe.basePipelineIndex = 0; pipe.pColorBlendState = &desc->cbs; diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index b63f93739e..20a6cfda48 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -118,6 +118,13 @@ struct ShaderID { d[i] = 0xFFFFFFFF; } } + bool is_invalid() const { + for (size_t i = 0; i < ARRAY_SIZE(d); i++) { + if (d[i] != 0xFFFFFFFF) + return false; + } + return true; + } uint32_t d[2]; bool operator < (const ShaderID &other) const { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index b38201c8c5..5bf6b0a2ac 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -566,7 +566,7 @@ std::vector GPU_Vulkan::DebugGetShaderIDs(DebugShaderType type) { 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) { + } else if (type == SHADER_TYPE_VERTEX || type == SHADER_TYPE_FRAGMENT || type == SHADER_TYPE_GEOMETRY) { return shaderManagerVulkan_->DebugGetShaderIDs(type); } else if (type == SHADER_TYPE_SAMPLER) { return textureCacheVulkan_->DebugGetSamplerIDs(); @@ -584,7 +584,7 @@ std::string GPU_Vulkan::DebugGetShaderString(std::string id, DebugShaderType typ return textureCache_->GetTextureShaderCache()->DebugGetShaderString(id, type, stringType); } else if (type == SHADER_TYPE_SAMPLER) { return textureCacheVulkan_->DebugGetSamplerString(id, stringType); - } else if (type == SHADER_TYPE_VERTEX || type == SHADER_TYPE_FRAGMENT) { + } else if (type == SHADER_TYPE_VERTEX || type == SHADER_TYPE_FRAGMENT || type == SHADER_TYPE_GEOMETRY) { return shaderManagerVulkan_->DebugGetShaderString(id, type, stringType); } else { return std::string(); diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index a7dac0c30f..e72f53a800 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -60,6 +60,7 @@ struct VulkanPipeline { bool UsesBlendConstant() const { return (pipelineFlags & PipelineFlags::USES_BLEND_CONSTANT) != 0; } bool UsesDepthStencil() const { return (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) != 0; } bool UsesInputAttachment() const { return (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) != 0; } + bool UsesGeometryShader() const { return (pipelineFlags & PipelineFlags::USES_GEOMETRY_SHADER) != 0; } u32 GetVariantsBitmask() const; }; From ab08db6fcaa82f5b7dbfb9c599a546db43ec5d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 1 Oct 2022 23:53:13 +0200 Subject: [PATCH 267/326] Tighten up some color format checks with displays and copies Now that we allow multiple color format buffers to overlap, and don't just take one and change its format during copy for example, we could use some additional checking. Additionally, do a simple heuristic to reject "obviously" wrong copies copies to framebuffers. Fixes #15959, should also help #16124 --- GPU/Common/FramebufferManagerCommon.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index a9487dd6f7..edd7cbb9fa 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1358,6 +1358,12 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { for (auto v : vfbs_) { const u32 v_addr = v->fb_address & 0x3FFFFFFF; const u32 v_size = ColorBufferByteSize(v); + + if (v->fb_format != displayFormat_ || v->fb_stride != displayStride_) { + // Displaying a buffer of the wrong format or stride is nonsense, ignore it. + continue; + } + if (addr >= v_addr && addr < v_addr + v_size) { const u32 dstBpp = BufferFormatBytesPerPixel(v->fb_format); const u32 v_offsetX = ((addr - v_addr) / dstBpp) % v->fb_stride; @@ -1623,7 +1629,9 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, dst &= 0x3FFFFFFF; src &= 0x3FFFFFFF; - // TODO: Merge the below into FindTransferFramebuffer + // TODO: Merge the below into FindTransferFramebuffer. + // Or at least this should be like the other ones, gathering possible candidates + // with the ability to list them out for debugging. VirtualFramebuffer *dstBuffer = 0; VirtualFramebuffer *srcBuffer = 0; @@ -1643,6 +1651,14 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, const u32 vfb_byteStride = vfb->fb_stride * vfb_bpp; const int vfb_byteWidth = vfb->width * vfb_bpp; + // Heuristic to try to prevent potential glitches with video playback. + if (vfb_address == dst && (size == 0x44000 && vfb_size == 0x88000 || size == 0x88000 && vfb_size == 0x44000)) { + // Not likely to be a correct color format copy for this buffer. Ignore it, there will either be RAM + // that can be displayed from, or another matching buffer with the right format if rendering is going on. + WARN_LOG_N_TIMES(notify_copy_2x, 5, G3D, "Framebuffer size %08x conspicuously not matching copy size %08x in NotifyFramebufferCopy. Ignoring.", size, vfb_size); + continue; + } + if (dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) { const u32 offset = dst - vfb_address; const u32 yOffset = offset / vfb_byteStride; From 80cccd7abb57f34f2a2f7963ebbacebf64d9209c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 16:13:25 -0700 Subject: [PATCH 268/326] Build: Fix debug build on Windows 32-bit. --- GPU/Software/TransformUnit.h | 1 + 1 file changed, 1 insertion(+) diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index b3d2e9b06d..3c022523d2 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -180,6 +180,7 @@ public: TransformUnit transformUnit; #if PPSSPP_ARCH(32BIT) +#undef new void *operator new(size_t s) { return AllocateAlignedMemory(s, 16); } From a268b9d1c93a42e4c7456ca12d14f9a34f41483d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 17:14:12 -0700 Subject: [PATCH 269/326] Build: Ignore no symbols link warning. This is just because we ifdef some files out for certain platforms. --- Common/Common.vcxproj | 8 ++++++++ Core/Core.vcxproj | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index d232c01534..901006a0d9 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -153,6 +153,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -179,6 +180,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -205,6 +207,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -232,6 +235,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -264,6 +268,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -298,6 +303,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -332,6 +338,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) @@ -366,6 +373,7 @@ ws2_32.lib + /ignore:4221 %(AdditionalOptions) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 25194a1741..1978f3bcac 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -158,6 +158,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -183,6 +186,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -208,6 +214,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -234,6 +243,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -260,6 +272,7 @@ %(AdditionalDependencies) + /ignore:4221 %(AdditionalOptions) ../Windows/git-version-gen.cmd @@ -298,6 +311,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -330,6 +346,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + @@ -362,6 +381,9 @@ ../git-version.cpp;%(Outputs) Updating git-version.cpp + + /ignore:4221 %(AdditionalOptions) + From 753ac953079bcdedb4830e9f3d0e467b7f822bc4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 18:13:22 -0700 Subject: [PATCH 270/326] Debugger: Fix game.reset by adding a reboot status. We don't want EmuScreen to auto-exit, and we don't want to double-lock the debugger lifecycle lock. Let's just handle reboot specifically. --- Core/Debugger/WebSocket.cpp | 4 ++++ Core/Debugger/WebSocket/GameBroadcaster.cpp | 2 +- Core/Debugger/WebSocket/GameSubscriber.cpp | 3 +-- Core/System.cpp | 24 +++++++++++++++++++-- Core/System.h | 2 ++ UI/EmuScreen.cpp | 2 +- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index b71db06877..23333d5a19 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -97,6 +97,10 @@ static void UpdateConnected(int delta) { } static void WebSocketNotifyLifecycle(CoreLifecycle stage) { + // We'll likely already be locked during the reboot. + if (PSP_IsRebooting()) + return; + switch (stage) { case CoreLifecycle::STARTING: case CoreLifecycle::STOPPING: diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp index 3045e70fc4..c8b4039769 100644 --- a/Core/Debugger/WebSocket/GameBroadcaster.cpp +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -86,7 +86,7 @@ void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { } else if (state == UISTATE_INGAME && PSP_IsInited()) { ws->Send(GameStatusEvent{"game.start"}); prevState_ = state; - } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting()) { + } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting() && !PSP_IsRebooting()) { ws->Send(GameStatusEvent{"game.quit"}); prevState_ = state; } diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 8cb9fcfc69..c48f396bb7 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -49,9 +49,8 @@ void WebSocketGameReset(DebuggerRequest &req) { if (needBreak) PSP_CoreParameter().startBreak = true; - PSP_Shutdown(); std::string resetError; - if (!PSP_Init(PSP_CoreParameter(), &resetError)) { + if (!PSP_Reboot(&resetError)) { ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str()); return req.Fail("Could not reset"); } diff --git a/Core/System.cpp b/Core/System.cpp index 9c7519edd6..9388d92820 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -114,6 +114,7 @@ static std::string gpuBackendDevice; static volatile bool pspIsInited = false; static volatile bool pspIsIniting = false; static volatile bool pspIsQuitting = false; +static volatile bool pspIsRebooting = false; void ResetUIState() { globalUIState = UISTATE_MENU; @@ -448,6 +449,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { bool success = !g_CoreParameter.fileToStart.empty(); if (!success) { Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE); + pspIsRebooting = false; // In this case, we must call shutdown since the caller won't know to. // It must've partially started since CPU_Init returned true. PSP_Shutdown(); @@ -475,6 +477,7 @@ bool PSP_InitUpdate(std::string *error_string) { } } if (!success) { + pspIsRebooting = false; PSP_Shutdown(); return true; } @@ -483,6 +486,7 @@ bool PSP_InitUpdate(std::string *error_string) { pspIsIniting = !pspIsInited; if (pspIsInited) { Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE); + pspIsRebooting = false; } return pspIsInited; } @@ -501,7 +505,11 @@ bool PSP_IsIniting() { } bool PSP_IsInited() { - return pspIsInited && !pspIsQuitting; + return pspIsInited && !pspIsQuitting && !pspIsRebooting; +} + +bool PSP_IsRebooting() { + return pspIsRebooting; } bool PSP_IsQuitting() { @@ -515,7 +523,7 @@ void PSP_Shutdown() { } // Make sure things know right away that PSP memory, etc. is going away. - pspIsQuitting = true; + pspIsQuitting = !pspIsRebooting; if (coreState == CORE_RUNNING) Core_Stop(); @@ -540,6 +548,18 @@ void PSP_Shutdown() { Core_NotifyLifecycle(CoreLifecycle::STOPPED); } +bool PSP_Reboot(std::string *error_string) { + if (!pspIsInited || pspIsQuitting) + return false; + + pspIsRebooting = true; + Core_Stop(); + Core_WaitInactive(); + PSP_Shutdown(); + std::string resetError; + return PSP_Init(PSP_CoreParameter(), error_string); +} + void PSP_BeginHostFrame() { // Reapply the graphics state of the PSP if (gpu) { diff --git a/Core/System.h b/Core/System.h index 599836e259..9a801596ab 100644 --- a/Core/System.h +++ b/Core/System.h @@ -75,8 +75,10 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string); bool PSP_InitUpdate(std::string *error_string); bool PSP_IsIniting(); bool PSP_IsInited(); +bool PSP_IsRebooting(); bool PSP_IsQuitting(); void PSP_Shutdown(); +bool PSP_Reboot(std::string *error_string); void PSP_BeginHostFrame(); void PSP_EndHostFrame(); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 14ee1ced6c..6d60ba7d5a 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1097,7 +1097,7 @@ void EmuScreen::update() { } void EmuScreen::checkPowerDown() { - if (coreState == CORE_POWERDOWN && !PSP_IsIniting()) { + if (coreState == CORE_POWERDOWN && !PSP_IsIniting() && !PSP_IsRebooting()) { if (PSP_IsInited()) { PSP_Shutdown(); } From 24999e792aa8a963250d3d61c4070023c963076b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 23:18:42 -0700 Subject: [PATCH 271/326] Ge: Report and save Edram translation value. See #16126 for some details on its usage and effects. --- Core/HLE/sceGe.cpp | 18 +++++++----------- GPU/Common/GPUDebugInterface.h | 3 +++ GPU/GPUCommon.cpp | 14 +++++++++++++- GPU/GPUCommon.h | 4 ++++ GPU/GPUInterface.h | 1 + 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 5afe38e02f..c4feedd6c6 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -570,21 +570,17 @@ static int sceGeGetStack(int index, u32 stackPtr) { return gpu->GetStack(index, stackPtr); } -static u32 sceGeEdramSetAddrTranslation(int new_size) { +static u32 sceGeEdramSetAddrTranslation(u32 new_size) { bool outsideRange = new_size != 0 && (new_size < 0x200 || new_size > 0x1000); bool notPowerOfTwo = (new_size & (new_size - 1)) != 0; if (outsideRange || notPowerOfTwo) { - WARN_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i): invalid value", new_size); - return SCE_KERNEL_ERROR_INVALID_VALUE; + return hleLogWarning(SCEGE, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid value"); + } + if (!gpu) { + return hleLogError(SCEGE, -1, "GPUInterface not available"); } - DEBUG_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i)", new_size); - - // TODO: This isn't safe. EDRamWidth should be global and saved. - static int EDRamWidth = 0x400; - int last = EDRamWidth; - EDRamWidth = new_size; - return last; + return hleReportDebug(SCEGE, gpu->SetAddrTranslation(new_size)); } const HLEFunction sceGe_user[] = { @@ -599,7 +595,7 @@ const HLEFunction sceGe_user[] = { {0XA4FC06A4, &WrapU_U, "sceGeSetCallback", 'i', "p" }, {0X05DB22CE, &WrapI_U, "sceGeUnsetCallback", 'i', "x" }, {0X1F6752AD, &WrapU_V, "sceGeEdramGetSize", 'x', "" }, - {0XB77905EA, &WrapU_I, "sceGeEdramSetAddrTranslation", 'x', "i" }, + {0XB77905EA, &WrapU_U, "sceGeEdramSetAddrTranslation", 'x', "x" }, {0XDC93CFEF, &WrapU_I, "sceGeGetCmd", 'x', "i" }, {0X57C8945B, &WrapI_IU, "sceGeGetMtx", 'i', "ip" }, {0X438A385A, &WrapU_U, "sceGeSaveContext", 'x', "x" }, diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index 09254ddf53..f584dfd576 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -213,6 +213,9 @@ public: virtual void SetCmdValue(u32 op) = 0; virtual void DispatchFlush() = 0; + virtual uint32_t SetAddrTranslation(uint32_t value) = 0; + virtual uint32_t GetAddrTranslation() = 0; + virtual bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) { return false; } diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 1ff4b30b11..2ebdf3ab05 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1280,6 +1280,15 @@ void GPUCommon::ReapplyGfxState() { // Let's just skip the transfer size stuff, it's just values. } +uint32_t GPUCommon::SetAddrTranslation(uint32_t value) { + std::swap(edramTranslation_, value); + return value; +} + +uint32_t GPUCommon::GetAddrTranslation() { + return edramTranslation_; +} + inline void GPUCommon::UpdateState(GPURunState state) { gpuState = state; if (state != GPUSTATE_RUNNING) @@ -2706,7 +2715,7 @@ struct DisplayList_v2 { }; void GPUCommon::DoState(PointerWrap &p) { - auto s = p.Section("GPUCommon", 1, 5); + auto s = p.Section("GPUCommon", 1, 6); if (!s) return; @@ -2782,6 +2791,9 @@ void GPUCommon::DoState(PointerWrap &p) { if (s >= 5) { Do(p, matrixVisible.all); } + if (s >= 6) { + Do(p, edramTranslation_); + } } void GPUCommon::InterruptStart(int listid) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index bd03692fda..1b4eb0494d 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -118,6 +118,8 @@ public: u32 Continue() override; u32 Break(int mode) override; void ReapplyGfxState() override; + 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; @@ -367,6 +369,8 @@ protected: uint32_t immFlags_ = 0; bool immFirstSent_ = false; + uint32_t edramTranslation_ = 0x400; + // Whe 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. diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 2e1bc406d4..e0c5931326 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -200,6 +200,7 @@ public: virtual int GetStack(int index, u32 stackPtr) = 0; virtual bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) = 0; virtual void ResetMatrices() = 0; + virtual uint32_t SetAddrTranslation(uint32_t value) = 0; virtual void InterruptStart(int listid) = 0; virtual void InterruptEnd(int listid) = 0; From 978fd9fc60270f0a13c9ac0523ccf6a14c9bf124 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 23:48:06 -0700 Subject: [PATCH 272/326] GE Debugger: Record the Edram translation value. --- GPU/Debugger/Playback.cpp | 24 ++++++++++++++++++++++++ GPU/Debugger/Record.cpp | 31 +++++++++++++++++++++++++++++++ GPU/Debugger/RecordFormat.h | 1 + 3 files changed, 56 insertions(+) diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index da55c9238d..0410a61c76 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -308,6 +308,7 @@ private: void Texture(int level, u32 ptr, u32 sz); void Framebuf(int level, u32 ptr, u32 sz); void Display(u32 ptr, u32 sz); + void EdramTrans(u32 ptr, u32 sz); u32 execMemcpyDest = 0; u32 execClutAddr = 0; @@ -328,6 +329,10 @@ private: }; void DumpExecute::SyncStall() { + if (execListBuf == 0) { + return; + } + gpu->UpdateStall(execListID, execListPos); s64 listTicks = gpu->GetListTicks(execListID); if (listTicks != -1) { @@ -620,6 +625,17 @@ void DumpExecute::Display(u32 ptr, u32 sz) { __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0); } +void DumpExecute::EdramTrans(u32 ptr, u32 sz) { + uint32_t value; + memcpy(&value, pushbuf_.data() + ptr, 4); + + // Sync up drawing. + SyncStall(); + + if (gpu) + gpu->SetAddrTranslation(value); +} + DumpExecute::~DumpExecute() { execMemcpyDest = 0; if (execListBuf) { @@ -631,6 +647,10 @@ DumpExecute::~DumpExecute() { } bool DumpExecute::Run() { + // Start with the default value. + if (gpu) + gpu->SetAddrTranslation(0x400); + for (const Command &cmd : commands_) { switch (cmd.type) { case CommandType::INIT: @@ -673,6 +693,10 @@ bool DumpExecute::Run() { Memcpy(cmd.ptr, cmd.sz); break; + case CommandType::EDRAMTRANS: + EdramTrans(cmd.ptr, cmd.sz); + break; + case CommandType::TEXTURE0: case CommandType::TEXTURE1: case CommandType::TEXTURE2: diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index cc73992c1b..69c0941ced 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -52,6 +52,7 @@ static bool active = false; static bool nextFrame = false; static int flipLastAction = -1; static int flipFinishAt = -1; +static uint32_t lastEdramTrans = 0x400; static std::function writeCallback; static std::vector pushbuf; @@ -561,17 +562,35 @@ static void FinishRecording() { active = false; flipLastAction = gpuStats.numFlips; flipFinishAt = -1; + lastEdramTrans = 0x400; if (writeCallback) writeCallback(filename); writeCallback = nullptr; } +static void CheckEdramTrans() { + if (!gpuDebug) + return; + + uint32_t value = gpuDebug->GetAddrTranslation(); + if (value == lastEdramTrans) + return; + lastEdramTrans = value; + + FlushRegisters(); + Command cmd{CommandType::EDRAMTRANS, sizeof(value), (u32)pushbuf.size()}; + pushbuf.resize(pushbuf.size() + sizeof(value)); + memcpy(pushbuf.data() + cmd.ptr, &value, sizeof(value)); + commands.push_back(cmd); +} + void NotifyCommand(u32 pc) { if (!active) { return; } + CheckEdramTrans(); const u32 op = Memory::Read_U32(pc); const GECommand cmd = GECommand(op >> 24); @@ -624,6 +643,8 @@ void NotifyMemcpy(u32 dest, u32 src, u32 sz) { if (!active) { return; } + + CheckEdramTrans(); if (Memory::IsVRAMAddress(dest)) { FlushRegisters(); Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()}; @@ -642,6 +663,8 @@ void NotifyMemset(u32 dest, int v, u32 sz) { if (!active) { return; } + + CheckEdramTrans(); struct MemsetCommand { u32 dest; int value; @@ -664,6 +687,8 @@ void NotifyUpload(u32 dest, u32 sz) { if (!active) { return; } + + CheckEdramTrans(); NotifyMemcpy(dest, dest, sz); if (Memory::IsVRAMAddress(dest)) DirtyVRAM(dest, sz, DirtyVRAMFlag::DIRTY); @@ -701,6 +726,7 @@ void NotifyDisplay(u32 framebuf, int stride, int fmt) { return; } + CheckEdramTrans(); struct DisplayBufData { PSPPointer topaddr; int linesize, pixelFormat; @@ -728,6 +754,7 @@ void NotifyBeginFrame() { if (active && HasDrawCommands() && (noDisplayAction || gpuStats.numFlips == flipFinishAt)) { NOTICE_LOG(SYSTEM, "Recording complete on frame"); + CheckEdramTrans(); struct DisplayBufData { PSPPointer topaddr; u32 linesize, pixelFormat; @@ -755,6 +782,10 @@ void NotifyBeginFrame() { } void NotifyCPU() { + if (!active) { + return; + } + DirtyAllVRAM(DirtyVRAMFlag::DIRTY); } diff --git a/GPU/Debugger/RecordFormat.h b/GPU/Debugger/RecordFormat.h index dc7abe3ec1..295273eb0f 100644 --- a/GPU/Debugger/RecordFormat.h +++ b/GPU/Debugger/RecordFormat.h @@ -50,6 +50,7 @@ enum class CommandType : u8 { MEMCPYDATA = 8, DISPLAY = 9, CLUTADDR = 10, + EDRAMTRANS = 11, TEXTURE0 = 0x10, TEXTURE1 = 0x11, From fcc877a0f35a4cbf533710e639bdcd2a05173447 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 23:48:23 -0700 Subject: [PATCH 273/326] GE Debugger: Fix memcpy/memset recording. Uhh, oops. I'm surprised I didn't notice these were broken for so long. --- GPU/Debugger/Record.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 69c0941ced..56cc164c63 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -650,6 +650,7 @@ void NotifyMemcpy(u32 dest, u32 src, u32 sz) { Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()}; pushbuf.resize(pushbuf.size() + sizeof(dest)); memcpy(pushbuf.data() + cmd.ptr, &dest, sizeof(dest)); + commands.push_back(cmd); sz = Memory::ValidSize(dest, sz); if (sz != 0) { @@ -679,6 +680,7 @@ void NotifyMemset(u32 dest, int v, u32 sz) { Command cmd{CommandType::MEMSET, sizeof(data), (u32)pushbuf.size()}; pushbuf.resize(pushbuf.size() + sizeof(data)); memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data)); + commands.push_back(cmd); DirtyVRAM(dest, sz, DirtyVRAMFlag::DIRTY); } } From 4a64a863e726f6596ae343c5e775dda8437d5fdf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 23:51:30 -0700 Subject: [PATCH 274/326] headless: Update tests. --- pspautotests | 2 +- test.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pspautotests b/pspautotests index d7a55d5487..dd16bcad59 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit d7a55d5487b1fa1a688178206abaad4aea5be18a +Subproject commit dd16bcad59dbd098a66b87048184e70debb22d6f diff --git a/test.py b/test.py index e2967f06a2..0a8896b2b7 100755 --- a/test.py +++ b/test.py @@ -414,6 +414,10 @@ tests_next = [ "gpu/signals/jumps", "gpu/signals/simple", "gpu/simple/simple", + "gpu/texmtx/normals", + "gpu/texmtx/prims", + "gpu/texmtx/source", + "gpu/texmtx/uvs", "gpu/textures/size", "gpu/triangle/triangle", "gpu/vertices/texcoords", From 5caa43d05a2b1d02becdd92a4031e49db19a4582 Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Sun, 2 Oct 2022 15:19:47 +0200 Subject: [PATCH 275/326] assets/lang: translate some key phrases in pl_PL.ini I've switched into polish language for a while and noticed that some parts of settings are not translated... It was a bit... harmful ;-) --- assets/lang/pl_PL.ini | 114 +++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 4493f6db83..e963535aee 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -1,20 +1,20 @@ [Audio] Alternate speed volume = Alternate speed volume Audio backend = Sterownik dźwięku (wymagany restart) -Audio Error = Audio Error +Audio Error = Błąd Audio AudioBufferingForBluetooth = Bufory dźwięku dost. do Bluetooth (wolniejsze) Auto = Automatyczny -Device = Device -Disabled = Disabled +Device = Urządzenie +Disabled = Wyłączone DSound (compatible) = DSound (kompatybilny) Enable Sound = Włącz dźwięk Global volume = Głośność globalna -Microphone = Microphone -Microphone Device = Microphone device -Mute = Mute -Reverb volume = Reverb volume -Use new audio devices automatically = Use new audio devices automatically -Use global volume = Use global volume +Microphone = Mikrofon +Microphone Device = Mikrofon +Mute = Wycisz +Reverb volume = Pogłos +Use new audio devices automatically = Automatycznie używaj nowego urządzenia audio +Use global volume = Używaj głośności globalnej WASAPI (fast) = WASAPI (szybki) [Controls] @@ -27,44 +27,44 @@ AnalogLimiter Tip = Jeśli "ogranicznik analoga" jest aktywny Auto = Automatyczny Auto-centering analog stick = Autocentrowanie gałki analogowej Auto-hide buttons after seconds = Automatyczne ukrywanie przycisków po: -Auto-rotation speed = Auto-rotation speed +Auto-rotation speed = Prędkość automatycznego obracania Auto-switch = Auto-switch Base tilt position = Base tilt position Binds = Binds Button Binding = Button Binding Button Opacity = Przeźroczystość przycisków Button style = Styl przycisków -Calibrate Analog Stick = Calibrate Analog Stick +Calibrate Analog Stick = Kalibracja Gałki Analogowej Calibrate D-Pad = Kalibracja krzyżaka -Calibrated = Calibrated +Calibrated = Skalibrowany Calibration = Kalibracja Circular stick input = Circular stick input Classic = Klasyczny Confine Mouse = Zablokuj myszkę w oknie Control Mapping = Mapowanie przycisków -Custom Key Setting = Custom Key Setting -Customize = Customize +Custom Key Setting = Niestandardowe Ustawienia Klawiszy +Customize = Ustawienia Niestandardowe Customize Touch Controls = Edycja układu przycisków ekranowych... Customize tilt = Zmodyfikuj przechył... D-PAD = Krzyżak Deadzone radius = Promień martwej strefy Disable D-Pad diagonals (4-way touch) = Wył. przekątne krzyżaka (pozostaw 4 kierunki) -Disable diagonal input = Disable diagonal input -Double tap = Double tap -Enable gesture control = Enable gesture control -Enable standard shortcut keys = Enable standard shortcut keys -Gesture = Gesture -Gesture mapping = Gesture mapping +Disable diagonal input = Wyłącz wejście cyfrowe +Double tap = Dwukrotne dotknięcie +Enable gesture control = Włącz kontrolę kestami +Enable standard shortcut keys = Włącz standardowe klawisze skrótów +Gesture = Gesty +Gesture mapping = Konfiguracja gestów Glowing borders = Świecące krawędzie HapticFeedback = Wibracje Hide touch analog stick background circle = Hide touch analog stick background circle -Icon = Icon +Icon = Ikona Ignore gamepads when not focused = Ignoruj przyciski pada gdy okno nieaktywne Ignore Windows Key = Ignoruj klawisz Windows Invert Axes = Odwrócone osie Invert Tilt along X axis = Odwróć przechył wzdłuż osi X Invert Tilt along Y axis = Odwróć przechył wzdłuż osi Y -Keep this button pressed when right analog is pressed = Keep this button pressed when right analog is pressed +Keep this button pressed when right analog is pressed = Kontynuuj naciskanie tego klawisza gdy prawa Gałka Analogowa wciśnięta Keyboard = Ustawienia klawiatury L/R Trigger Buttons = Przyciski L/R Landscape = Poziomo @@ -83,12 +83,12 @@ Portrait Reversed = Odwrócone pionowo PSP Action Buttons = Klawisze akcji PSP Raw input = Raw input Repeat mode = Repeat mode -Reset to defaults = Reset to defaults +Reset to defaults = Domyślne Screen aligned to ground = Screen aligned to ground Screen at right angle to ground = Screen at right angle to ground Screen Rotation = Obrót ekranu seconds, 0 : off = sekundy, 0 = wył. -Sensitivity (scale) = Sensitivity (scale) +Sensitivity (scale) = Czułość (dostosuj) Sensitivity = Czułość Shape = Shape Show Touch Pause Menu Button = Pokaż przycisk Menu Pauzy @@ -101,7 +101,7 @@ Tilt Input Type = Typ przechyłu Tilt Sensitivity along X axis = Czułość przechyłu wzdłuż osi X Tilt Sensitivity along Y axis = Czułość przechyłu wzdłuż osi Y To Calibrate = Aby skalibrować, połóż urządzenie na płaskiej powierzchni i wciśnij "Kalibracja". -Toggle mode = Toggle mode +Toggle mode = Zmień tryb Touch Control Visibility = Kontroler dotykowy (widoczny) Use custom right analog = Use custom right analog Use Mouse Control = Włącz kontrolę myszką @@ -175,11 +175,11 @@ More Settings... = Więcej ustawień... Nearest = Najbliższe Non-Buffered Rendering = &Nie renderuj efektów z bufora (niebuforowane, szybsze) Off = Wyłącz -Open Chat = Open Chat +Open Chat = Otwórz Chat Open Directory... = Otwórz katalog... Open from MS:/PSP/GAME... = Otwórz z MS:/PSP/GAME... Open Memory Stick = Otwórz Kartę Pamięci -Open New Instance = Open new instance +Open New Instance = Otwórz nową instancję OpenGL = &OpenGL Pause = Pauza Pause When Not Focused = Pauzuj, gdy okno nieaktywne @@ -234,15 +234,15 @@ Disabled JIT functionality = Wyłącz funkcje JIT Draw Frametimes Graph = Draw frametimes graph Dump Decrypted Eboot = Zachowuj odszyfrowany EBOOT.BIN przy starcie Dump Frame GPU Commands = Zrzuć klatkę komend GPU -Enable driver bug workarounds = Enable driver bug workarounds +Enable driver bug workarounds = Uruchom obejścia błędów sterownika Enable Logging = Włącz dziennik zdarzeń debugera Enter address = Wprowadź adres FPU = FPU Framedump tests = Framedump tests Frame Profiler = Profiler klatki GPU Driver Test = Test sterownika GPU -GPU Log Profiler = GPU Log Profiler -GPU Profile = GPU profile +GPU Log Profiler = Profilowanie logów GPU +GPU Profile = Profil GPU Jit Compare = Porównywanie JIT JIT debug tools = Narzędzia debugowania JIT Language = Język @@ -260,7 +260,7 @@ Reset = Reset Reset limited logging = Reset limited logging RestoreDefaultSettings = Czy na pewno chcesz przywrócić domyślne ustawienia?\nUstawienia sterowania pozostaną niezmienione.\n\n\nNie można tego cofnąć.\nZrestartuj PPSSPP, aby zastosować zmiany. RestoreGameDefaultSettings = Czy na pewno chcesz przywrócić ustawienia specyficzne dla danych gier\npowrót do ustawień domyślnych PPSSPP? -Resume = Resume +Resume = Wznów Run CPU Tests = Uruchom testy CPU Save language ini = Zapisz plik ini języka Save new textures = Zapisz nową teksturę @@ -269,11 +269,11 @@ Show Developer Menu = Pokaż przycisk menu dewelopera Show on-screen messages = Show on-screen messages Stats = Statystyki System Information = Informacje o systemie -Texture ini file created = Texture ini file created +Texture ini file created = Stworzono plik ini Tekstury Texture Replacement = Podmiana tekstur Toggle Audio Debug = Wł./Wył. debugowanie audio Toggle Freeze = Wł./Wył. zamrożenie -Touchscreen Test = Touchscreen test +Touchscreen Test = Test ekranu dotykowego VFPU = VFPU [Dialog] @@ -283,16 +283,16 @@ Back = Powrót Cancel = Anuluj Center = Wyśrodkuj ChangingGPUBackends = Zmiana sterownika grafiki wymaga restartu PPSSPP. Zrestartować teraz? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? -Channel: = Channel: +ChangingInflightFrames = Zmiana komend buffera grafiki wymaka restartu PPSSPP. Zrestartować teraz? +Channel: = Kanał: Choose PPSSPP save folder = Wybierz folder do zapisu stanów PPSSPP Confirm Overwrite = Czy chcesz nadpisać te dane? Confirm Save = Czy chcesz zapisać te dane? ConfirmLoad = Wczytać te dane? -ConnectingAP = Connecting to the access point.\nPlease wait... -ConnectingPleaseWait = Connecting.\nPlease wait... -ConnectionName = Connection name -Corrupted Data = Corrupted data +ConnectingAP = Łączenie do punktu dostępowego.\nProszę czekać... +ConnectingPleaseWait = Łączenie.\nProszę czekać... +ConnectionName = Nazwa Połączenia +Corrupted Data = Uszkodzone dane Delete = Usuń Delete all = Usuń wszystko Delete completed = Usuwanie zakończone. @@ -304,15 +304,15 @@ DeleteFailed = Nie można usunąć danych. Deleting = Usuwanie\nProszę czekać... Disable All = Wyłącz wszystko Dumps = Dumps -Edit = Edit +Edit = Edytuj Enable All = Włącz wszystko Enter = Dalej -Filter = Filter +Filter = Filtruj Finish = Zakończ GE Frame Dumps = GE Frame Dumps -Grid = Grid +Grid = Siatka Inactive = Nieaktywny -InternalError = An internal error has occurred. +InternalError = Występił wewnętrzny błąd aplikacji Load = Wczytaj Load completed = Ładowanie zakończone. Loading = Ładowanie\nProszę czekać... @@ -321,7 +321,7 @@ Move = Przenieś Network Connection = Połączenie sieciowe NEW DATA = NOWE DANE No = Nie -ObtainingIP = Obtaining IP address.\nPlease wait... +ObtainingIP = Uzyskiwanie adresu IP.\nProszę czekać... OK = OK Old savedata detected = Wykryto stary zapis gry Options = Opcje @@ -332,10 +332,10 @@ Save = Zapisz Save completed = Zapisywanie zakończone. Saving = Zapisywanie\nProszę czekać... SavingFailed = Nie można zapisać danych. -Search = Search +Search = Szukaj Select = Wybierz Shift = Shift -Skip = Skip +Skip = Pomiń Snap = Snap Space = Spacja SSID = SSID @@ -366,7 +366,7 @@ Error loading file = Wystąpił błąd: Error reading file = Błąd podczas odczytywania pliku. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: -File corrupt = File corrupt +File corrupt = Uszkodzony plik Game disc read error - ISO corrupt = Nie można odczytać danych z dysku - plik ISO uszkodzony. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: @@ -579,11 +579,11 @@ xBRZ = xBRZ Delete ZIP file = Usuń plik ZIP Install = Zainstaluj Install game from ZIP file? = Zainstalować grę z pliku ZIP? -Install textures from ZIP file? = Install textures from ZIP file? +Install textures from ZIP file? = Czy zainstalować teksturę z archiwum ZIP? Installed! = Zainstalowano! Texture pack doesn't support install = Texture pack doesn't support install Zip archive corrupt = ZIP archive corrupt -Zip file does not contain PSP software = ZIP file does not contain PSP software +Zip file does not contain PSP software = Archiwum ZIP nie zawiera aplikacji dla PSP [KeyMapping] Autoconfigure = Autokonfiguracja @@ -725,7 +725,7 @@ DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP DataWillStay = Data will stay even if you uninstall PPSSPP. Done! = Done! EasyUSBAccess = Easy USB access -Failed to move some files! = Failed to move some files! +Failed to move some files! = Nie udało się przenieść niektórych plików! Failed to save config = Failed to save config Free space = Free space Manually specify PSP folder = Manually specify PSP folder @@ -737,8 +737,8 @@ PPSSPP will restart after the change = PPSSPP will restart after the change Skip for now = Skip for now Starting move... = Starting move... That folder doesn't work as a memstick folder. = That folder doesn't work as a memstick folder. -USBAccessThrough = USB access through Android/data/org.ppsspp.ppsspp/files -USBAccessThroughGold = USB access through Android/data/org.ppsspp.ppssppgold/files +USBAccessThrough = Dostęp do USB poprzez Android/data/org.ppsspp.ppsspp/files +USBAccessThroughGold = Dostęþ do USB poprzez Android/data/org.ppsspp.ppssppgold/files Use App Private Data = Use App Private Data Use PSP folder at root of storage = Use PSP folder at root of storage Welcome to PPSSPP! = Welcome to PPSSPP! @@ -968,7 +968,7 @@ View Feedback = Zobacz opinie [Savedata] Date = Data -Filename = Nazwa +Filename = Nazwa pliku No screenshot = Brak obrazka None yet. Things will appear here after you save. = Na razie nic tu nie ma. Wpisy pojawią się, gdy zapiszesz grę. Nothing matching '%1' was found. = Nothing matching '%1' was found. @@ -1100,7 +1100,7 @@ Error: load undo state is from a different game = Error: load undo state is from Failed to load state for load undo. Error in the file system. = Failed to load state for load undo. Error in the file system. Floating symbols = Floating symbols Memory Stick folder = Zmień folder Karty Pamięci -Memory Stick size = Memory Stick size +Memory Stick size = Rozmiar Karty Pamięci Change Nickname = Zmień pseudonim ChangingMemstickPath = Zapisy gier, zapisy stanu i inne dane nie zostaną skopiowane do tego folderu.\n\nZmienić ścieżkę do folderu Karty Pamięci? ChangingMemstickPathInvalid = Ta ścieżka nie może zostać użyta do zapisu plików Karty Pamięci. @@ -1177,10 +1177,10 @@ Slot 4 = Slot 4 Slot 5 = Slot 5 Storage full = Dysk zapełniony Sustained performance mode = Tryb wysokiej wydajności -Theme = Theme +Theme = Motyw Time Format = Format czasu UI = Interfejs użytkownika -UI background animation = UI background animation +UI background animation = Animacja tła UI Sound = UI sound undo %c = backup %c USB = USB @@ -1202,7 +1202,7 @@ Tex2xBRZ = Tex2xBRZ Tex4xBRZ = Tex4xBRZ [Themes] -Default = Default +Default = Domyślny [UI Elements] %1 button = %1 button From a8c12385c14bc91a045024cc7a0d95688fb95c81 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 07:25:15 -0700 Subject: [PATCH 276/326] UI: Prevent rendering during reboot. Can sometimes crash. --- UI/EmuScreen.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 6d60ba7d5a..baa795351f 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -224,6 +224,15 @@ bool EmuScreen::bootAllowStorage(const Path &filename) { } void EmuScreen::bootGame(const Path &filename) { + if (PSP_IsRebooting()) + return; + if (PSP_IsInited()) { + bootPending_ = false; + invalid_ = false; + bootComplete(); + return; + } + if (PSP_IsIniting()) { std::string error_string; bootPending_ = !PSP_InitUpdate(&error_string); @@ -1097,6 +1106,11 @@ void EmuScreen::update() { } void EmuScreen::checkPowerDown() { + if (PSP_IsRebooting()) { + bootPending_ = true; + invalid_ = true; + } + if (coreState == CORE_POWERDOWN && !PSP_IsIniting() && !PSP_IsRebooting()) { if (PSP_IsInited()) { PSP_Shutdown(); From 878a049f602d95fb287bd00586481b37f5c4cb3c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 19:22:16 -0700 Subject: [PATCH 277/326] GPU: Add dirtying for geo shader state. Not yet used, but dirtied at the right times. --- GPU/Common/ShaderCommon.h | 3 ++- GPU/GPUCommon.cpp | 20 ++++++++++---------- GPU/GPUCommon.h | 4 ++-- GPU/Vulkan/DrawEngineVulkan.cpp | 4 ++-- GPU/Vulkan/ShaderManagerVulkan.cpp | 8 ++++---- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index d6dfbbc3f8..63e3116014 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -109,10 +109,11 @@ enum : uint64_t { DIRTY_VIEWPORTSCISSOR_STATE = 1ULL << 46, DIRTY_VERTEXSHADER_STATE = 1ULL << 47, DIRTY_FRAGMENTSHADER_STATE = 1ULL << 48, + DIRTY_GEOMETRYSHADER_STATE = 1ULL << 49, // Everything that's not uniforms. Use this after using thin3d. // TODO: Should we also add DIRTY_FRAMEBUF here? It kinda generally takes care of itself. - DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS, + DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS, // Note that the top 8 bits (54-63) cannot be dirtied through the commonCommandTable due to packing of other flags. diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 2ebdf3ab05..a44fb47572 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -87,8 +87,8 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEF }, // 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 }, - { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { 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_FRAGMENTSHADER_STATE }, { GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, @@ -102,7 +102,7 @@ const CommonCommandTableEntry commonCommandTable[] = { // 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 }, + { 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 }, @@ -114,7 +114,7 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, // These change both shaders so need flushing. - { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE }, { GE_CMD_TEXFILTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS }, { GE_CMD_TEXWRAP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE }, @@ -1683,7 +1683,7 @@ void GPUCommon::Execute_VertexType(u32 op, u32 diff) { if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) { gstate_c.Dirty(DIRTY_UVSCALEOFFSET); if (diff & GE_VTYPE_THROUGH_MASK) - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_CULLRANGE); + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE); } } @@ -1710,7 +1710,7 @@ void GPUCommon::Execute_VertexTypeSkinning(u32 op, u32 diff) { 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_CULLRANGE); + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE); } void GPUCommon::CheckDepthUsage(VirtualFramebuffer *vfb) { @@ -2044,8 +2044,8 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) { SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType)); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); gstate_c.submitType = SubmitType::HW_BEZIER; if (gstate_c.spline_num_points_u != surface.num_points_u) { gstate_c.Dirty(DIRTY_BEZIERSPLINE); @@ -2059,7 +2059,7 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) { UpdateUVScaleOffset(); drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "bezier"); - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); gstate_c.submitType = SubmitType::DRAW; // After drawing, we advance pointers - see SubmitPrim which does the same. @@ -2119,8 +2119,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType)); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) { - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); gstate_c.submitType = SubmitType::HW_SPLINE; if (gstate_c.spline_num_points_u != surface.num_points_u) { gstate_c.Dirty(DIRTY_BEZIERSPLINE); @@ -2134,7 +2134,7 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { UpdateUVScaleOffset(); drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "spline"); - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); gstate_c.submitType = SubmitType::DRAW; // After drawing, we advance pointers - see SubmitPrim which does the same. diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 1b4eb0494d..78f7b15cef 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -269,14 +269,14 @@ protected: void SetDrawType(DrawType type, GEPrimitiveType prim) { if (type != lastDraw_) { // We always flush when drawing splines/beziers so no need to do so here - gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE); + gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); lastDraw_ = type; } // Prim == RECTANGLES can cause CanUseHardwareTransform to flip, so we need to dirty. // Also, culling may be affected so dirty the raster state. if (IsTrianglePrim(prim) != IsTrianglePrim(lastPrim_)) { Flush(); - gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE); + gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); lastPrim_ = prim; } } diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index d086b60921..703ada14b4 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -770,7 +770,7 @@ void DrawEngineVulkan::DoFlush() { sampler = nullSampler_; } - if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) { + if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE) || prim != lastPrim_) { if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } @@ -901,7 +901,7 @@ void DrawEngineVulkan::DoFlush() { if (sampler == VK_NULL_HANDLE) sampler = nullSampler_; } - if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) { + if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE) || prim != lastPrim_) { if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 6c39280a82..7633159154 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -128,7 +128,7 @@ std::string VulkanFragmentShader::GetShaderString(DebugShaderStringType type) co VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, const char *code, bool useHWTransform) : vulkan_(vulkan), useHWTransform_(useHWTransform), id_(id) { source_ = code; - module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_VERTEX_BIT, source_.c_str(), new std::string(VertexShaderDesc(id).c_str())); + module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_VERTEX_BIT, source_.c_str(), new std::string(VertexShaderDesc(id))); if (!module_) { failed_ = true; } else { @@ -195,13 +195,13 @@ void ShaderManagerVulkan::Clear() { vsCache_.Clear(); lastFSID_.set_invalid(); lastVSID_.set_invalid(); - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); } void ShaderManagerVulkan::ClearShaders() { Clear(); DirtyShader(); - gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); } void ShaderManagerVulkan::DirtyShader() { @@ -214,7 +214,7 @@ void ShaderManagerVulkan::DirtyShader() { void ShaderManagerVulkan::DirtyLastShader() { lastVShader_ = nullptr; lastFShader_ = nullptr; - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); } uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) { From 38e16324f0014549f148fd13102ff9f7d0d64ffb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 14:57:00 -0700 Subject: [PATCH 278/326] Vulkan: Clean up shader module tag. --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 2 +- GPU/Vulkan/ShaderManagerVulkan.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 35d620be3c..413057b288 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -177,9 +177,9 @@ VkShaderStageFlagBits StageToVulkan(ShaderStage stage) { case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT; case ShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; case ShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; - default: case ShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; } + return VK_SHADER_STAGE_FRAGMENT_BIT; } // Not registering this as a resource holder, instead the pipeline is registered. It will diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 7633159154..bfb5547b6a 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -73,14 +73,22 @@ static Promise *CompileShaderModuleAsync(VulkanContext *vulkan, VkShaderModule shaderModule = VK_NULL_HANDLE; if (success) { - success = vulkan->CreateShaderModule(spirv, &shaderModule, stage == VK_SHADER_STAGE_VERTEX_BIT ? "game_vertex" : "game_fragment"); + const char *createTag = tag ? tag->c_str() : nullptr; + if (!createTag) { + switch (stage) { + case VK_SHADER_STAGE_VERTEX_BIT: createTag = "game_vertex"; break; + case VK_SHADER_STAGE_FRAGMENT_BIT: createTag = "game_fragment"; break; + case VK_SHADER_STAGE_GEOMETRY_BIT: createTag = "game_geometry"; break; + case VK_SHADER_STAGE_COMPUTE_BIT: createTag = "game_compute"; break; + } + } + + success = vulkan->CreateShaderModule(spirv, &shaderModule, createTag); #ifdef SHADERLOG OutputDebugStringA("OK"); #endif - if (tag) { - vulkan->SetDebugName(shaderModule, VK_OBJECT_TYPE_SHADER_MODULE, tag->c_str()); + if (tag) delete tag; - } } return shaderModule; From d16caa71af444ee089a9ac74ce76fa3176eb3867 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 20:01:23 -0700 Subject: [PATCH 279/326] Vulkan: Add geometry shader ID tracking. We're still not generating them, yet. But this tracks the objects and IDs through the pipeline. --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 11 ++- Common/GPU/Vulkan/VulkanRenderManager.h | 1 + GPU/Common/ShaderId.cpp | 39 ++++++++++ GPU/Common/ShaderId.h | 39 ++++++++++ GPU/GPUState.h | 3 +- GPU/Vulkan/DrawEngineVulkan.cpp | 13 ++-- GPU/Vulkan/DrawEngineVulkan.h | 4 +- GPU/Vulkan/GPU_Vulkan.cpp | 1 + GPU/Vulkan/PipelineManagerVulkan.cpp | 21 +++-- GPU/Vulkan/PipelineManagerVulkan.h | 4 +- GPU/Vulkan/ShaderManagerVulkan.cpp | 95 +++++++++++++++++++++-- GPU/Vulkan/ShaderManagerVulkan.h | 32 +++++++- UI/DevScreens.cpp | 2 +- 13 files changed, 241 insertions(+), 24 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 4e047bf64e..344b059bbc 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -30,8 +30,9 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR // Fill in the last part of the desc since now it's time to block. VkShaderModule vs = desc->vertexShader->BlockUntilReady(); VkShaderModule fs = desc->fragmentShader->BlockUntilReady(); + VkShaderModule gs = desc->geometryShader ? desc->geometryShader->BlockUntilReady() : VK_NULL_HANDLE; - if (!vs || !fs) { + if (!vs || !fs || (!gs && desc->geometryShader)) { ERROR_LOG(G3D, "Failed creating graphics pipeline - missing shader modules"); // We're kinda screwed here? return false; @@ -49,6 +50,14 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR ss[1].pSpecializationInfo = nullptr; ss[1].module = fs; ss[1].pName = "main"; + if (gs) { + stageCount++; + ss[2].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + ss[2].stage = VK_SHADER_STAGE_GEOMETRY_BIT; + ss[2].pSpecializationInfo = nullptr; + ss[2].module = gs; + ss[2].pName = "main"; + } VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; pipe.pStages = ss; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 66692d99a7..7fd45e2246 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -128,6 +128,7 @@ struct VKRGraphicsPipelineDesc { // Replaced the ShaderStageInfo with promises here so we can wait for compiles to finish. Promise *vertexShader = nullptr; Promise *fragmentShader = nullptr; + Promise *geometryShader = nullptr; VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; VkVertexInputAttributeDescription attrs[8]{}; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index bb8dd54237..eccd5d158f 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -366,3 +366,42 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip *id_out = id; } + +std::string GeometryShaderDesc(const GShaderID &id) { + std::stringstream desc; + desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]); + if (id.Bit(GS_BIT_ENABLED)) desc << "ENABLED "; + if (id.Bit(GS_BIT_DO_TEXTURE)) desc << "TEX "; + if (id.Bit(GS_BIT_LMODE)) desc << "LMODE "; + return desc.str(); +} + +void ComputeGeometryShaderID(GShaderID *id_out, const Draw::Bugs &bugs, int prim) { + GShaderID id; + + bool vertexRangeCulling = + !gstate.isModeThrough() && gstate_c.submitType == SubmitType::DRAW; // neither hw nor sw spline/bezier. See #11692 + + // If we're not using GS culling, return a zero ID. + // Also, only use this for triangle primitives. + if (!vertexRangeCulling || !gstate_c.Supports(GPU_SUPPORTS_GS_CULLING) || (prim != GE_PRIM_TRIANGLES && prim != GE_PRIM_TRIANGLE_FAN && prim != GE_PRIM_TRIANGLE_STRIP)) { + *id_out = id; + return; + } + + id.SetBit(GS_BIT_ENABLED, true); + + if (gstate.isModeClear()) { + // No attribute bits. + } else { + bool isModeThrough = gstate.isModeThrough(); + bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough; + + id.SetBit(GS_BIT_LMODE, lmode); + if (gstate.isTextureMapEnabled()) { + id.SetBit(GS_BIT_DO_TEXTURE); + } + } + + *id_out = id; +} diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 20a6cfda48..ec25abb4d3 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -104,6 +104,17 @@ static inline FShaderBit operator +(FShaderBit bit, int i) { return FShaderBit((int)bit + i); } +// Some of these bits are straight from FShaderBit, since they essentially enable attributes directly. +enum GShaderBit : uint8_t { + GS_BIT_ENABLED = 0, // If not set, we don't use a geo shader. + GS_BIT_DO_TEXTURE = 1, // presence of texcoords + GS_BIT_LMODE = 2, // presence of specular color (regular color always present) +}; + +static inline GShaderBit operator +(GShaderBit bit, int i) { + return GShaderBit((int)bit + i); +} + struct ShaderID { ShaderID() { clear(); @@ -232,6 +243,31 @@ struct FShaderID : ShaderID { } }; +struct GShaderID : ShaderID { + GShaderID() : ShaderID() { + } + + explicit GShaderID(ShaderID &src) { + memcpy(d, src.d, sizeof(d)); + } + + bool Bit(GShaderBit bit) const { + return ShaderID::Bit((int)bit); + } + + int Bits(GShaderBit bit, int count) const { + return ShaderID::Bits((int)bit, count); + } + + void SetBit(GShaderBit bit, bool value = true) { + ShaderID::SetBit((int)bit, value); + } + + void SetBits(GShaderBit bit, int count, int value) { + ShaderID::SetBits((int)bit, count, value); + } +}; + namespace Draw { class Bugs; } @@ -244,3 +280,6 @@ std::string VertexShaderDesc(const VShaderID &id); struct ComputedPipelineState; void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs); std::string FragmentShaderDesc(const FShaderID &id); + +void ComputeGeometryShaderID(GShaderID *id, const Draw::Bugs &bugs, int prim); +std::string GeometryShaderDesc(const GShaderID &id); diff --git a/GPU/GPUState.h b/GPU/GPUState.h index a10cfe790e..e5d5662845 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -486,7 +486,8 @@ enum { // Free bit: 15 GPU_SUPPORTS_DEPTH_TEXTURE = FLAG_BIT(16), GPU_SUPPORTS_ACCURATE_DEPTH = FLAG_BIT(17), - // Free bits: 18-19 + GPU_SUPPORTS_GS_CULLING = FLAG_BIT(18), // Geometry shader + // Free bit: 19 GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20), GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT = FLAG_BIT(21), GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22), diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 703ada14b4..2c50795472 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -89,8 +89,6 @@ DrawEngineVulkan::DrawEngineVulkan(Draw::DrawContext *draw) decIndex = (u16 *)AllocateMemoryPages(DECODED_INDEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE); indexGen.Setup(decIndex); - - InitDeviceObjects(); } void DrawEngineVulkan::InitDeviceObjects() { @@ -114,6 +112,8 @@ void DrawEngineVulkan::InitDeviceObjects() { bindings[3].descriptorCount = 1; bindings[3].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; bindings[3].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + if (gstate_c.Supports(GPU_SUPPORTS_GS_CULLING)) + bindings[3].stageFlags |= VK_SHADER_STAGE_GEOMETRY_BIT; bindings[3].binding = DRAW_BINDING_DYNUBO_BASE; bindings[4].descriptorCount = 1; bindings[4].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; @@ -581,6 +581,7 @@ void DrawEngineVulkan::DoFlush() { VulkanVertexShader *vshader = nullptr; VulkanFragmentShader *fshader = nullptr; + VulkanGeometryShader *gshader = nullptr; uint32_t ibOffset; uint32_t vbOffset; @@ -775,14 +776,14 @@ void DrawEngineVulkan::DoFlush() { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, pipelineState_, true, useHWTessellation_, decOptions_.expandAllWeightsToFloat); // usehwtransform + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, &gshader, pipelineState_, true, useHWTessellation_, decOptions_.expandAllWeightsToFloat); // usehwtransform if (!vshader) { // We're screwed. return; } _dbg_assert_msg_(vshader->UseHWTransform(), "Bad vshader"); - VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, true, 0); + VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, gshader, true, 0); if (!pipeline || !pipeline->pipeline) { // Already logged, let's bail out. return; @@ -905,9 +906,9 @@ void DrawEngineVulkan::DoFlush() { if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat); // usehwtransform + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, &gshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat); // usehwtransform _dbg_assert_msg_(!vshader->UseHWTransform(), "Bad vshader"); - VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, false, 0); + VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, gshader, false, 0); if (!pipeline || !pipeline->pipeline) { // Already logged, let's bail out. decodedVerts_ = 0; diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 50df67b136..ebf50e33b5 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -127,6 +127,9 @@ public: DrawEngineVulkan(Draw::DrawContext *draw); virtual ~DrawEngineVulkan(); + // We reference feature flags, so this is called after construction. + void InitDeviceObjects(); + void SetShaderManager(ShaderManagerVulkan *shaderManager) { shaderManager_ = shaderManager; } @@ -196,7 +199,6 @@ private: void ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManager, ShaderManagerVulkan *shaderManager, int prim, VulkanPipelineRasterStateKey &key, VulkanDynamicState &dynState); void BindShaderBlendTex(); - void InitDeviceObjects(); void DestroyDeviceObjects(); void DecodeVertsToPushBuffer(VulkanPushBuffer *push, uint32_t *bindOffset, VkBuffer *vkbuf); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 5bf6b0a2ac..aa31c93be4 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -53,6 +53,7 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommon(gfxCtx, draw), drawEngine_(draw) { gstate_c.featureFlags = CheckGPUFeatures(); + drawEngine_.InitDeviceObjects(); VulkanContext *vulkan = (VulkanContext *)gfxCtx->GetAPIContext(); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 2ea530d25a..f25b0cb269 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -171,7 +171,7 @@ static std::string CutFromMain(std::string str) { static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache, VkPipelineLayout layout, PipelineFlags pipelineFlags, const VulkanPipelineRasterStateKey &key, - const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask) { + const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantBitmask) { VulkanPipeline *vulkanPipeline = new VulkanPipeline(); VKRGraphicsPipelineDesc *desc = &vulkanPipeline->desc; desc->pipelineCache = pipelineCache; @@ -254,6 +254,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, desc->fragmentShader = fs->GetModule(); desc->vertexShader = vs->GetModule(); + desc->geometryShader = gs ? gs->GetModule() : nullptr; VkPipelineInputAssemblyStateCreateInfo &inputAssembly = desc->inputAssembly; inputAssembly.flags = 0; @@ -301,6 +302,9 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, if (useBlendConstant) { pipelineFlags |= PipelineFlags::USES_BLEND_CONSTANT; } + if (gs) { + pipelineFlags |= PipelineFlags::USES_GEOMETRY_SHADER; + } if (dss.depthTestEnable || dss.stencilTestEnable) { pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL; } @@ -308,7 +312,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, return vulkanPipeline; } -VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask) { +VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantBitmask) { if (!pipelineCache_) { VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO }; VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_); @@ -321,6 +325,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager * key.useHWTransform = useHwTransform; key.vShader = vs->GetModule(); key.fShader = fs->GetModule(); + key.gShader = gs ? gs->GetModule() : VK_NULL_HANDLE; key.vtxFmtId = useHwTransform ? decFmt->id : 0; auto iter = pipelines_.Get(key); @@ -334,7 +339,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager * VulkanPipeline *pipeline = CreateVulkanPipeline( renderManager, pipelineCache_, layout, pipelineFlags, - rasterKey, decFmt, vs, fs, useHwTransform, variantBitmask); + rasterKey, decFmt, vs, fs, gs, useHwTransform, variantBitmask); pipelines_.Insert(key, pipeline); // Don't return placeholder null pipelines. @@ -589,7 +594,13 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha return; VulkanVertexShader *vshader = shaderManager->GetVertexShaderFromModule(pkey.vShader->BlockUntilReady()); VulkanFragmentShader *fshader = shaderManager->GetFragmentShaderFromModule(pkey.fShader->BlockUntilReady()); - if (!vshader || !fshader) { + VulkanGeometryShader *gshader = nullptr; + if (pkey.gShader) { + gshader = shaderManager->GetGeometryShaderFromModule(pkey.gShader->BlockUntilReady()); + if (!gshader) + failed = true; + } + if (!vshader || !fshader || failed) { failed = true; return; } @@ -710,7 +721,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha DecVtxFormat fmt; fmt.InitializeFromID(key.vtxFmtId); - VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, key.useHWTransform, key.variants); + VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, nullptr, key.useHWTransform, key.variants); if (!pipeline) { pipelineCreateFailCount += 1; } diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index e72f53a800..32e45c3faf 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -38,6 +38,7 @@ struct VulkanPipelineKey { VKRRenderPass *renderPass; Promise *vShader; Promise *fShader; + Promise *gShader; uint32_t vtxFmtId; bool useHWTransform; @@ -68,6 +69,7 @@ struct VulkanPipeline { class VulkanContext; class VulkanVertexShader; class VulkanFragmentShader; +class VulkanGeometryShader; class ShaderManagerVulkan; class DrawEngineCommon; @@ -77,7 +79,7 @@ public: ~PipelineManagerVulkan(); // variantMask is only used when loading pipelines from cache. - VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantMask); + VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantMask); int GetNumPipelines() const { return (int)pipelines_.size(); } void Clear(); diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index bfb5547b6a..769c258993 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -163,8 +163,38 @@ std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) cons } } +VulkanGeometryShader::VulkanGeometryShader(VulkanContext *vulkan, GShaderID id, const char *code) + : vulkan_(vulkan), id_(id) { + source_ = code; + module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_GEOMETRY_BIT, source_.c_str(), new std::string(GeometryShaderDesc(id).c_str())); + if (!module_) { + failed_ = true; + } else { + VERBOSE_LOG(G3D, "Compiled geometry shader:\n%s\n", (const char *)code); + } +} + +VulkanGeometryShader::~VulkanGeometryShader() { + if (module_) { + VkShaderModule shaderModule = module_->BlockUntilReady(); + vulkan_->Delete().QueueDeleteShaderModule(shaderModule); + delete module_; + } +} + +std::string VulkanGeometryShader::GetShaderString(DebugShaderStringType type) const { + switch (type) { + case SHADER_STRING_SOURCE_CODE: + return source_; + case SHADER_STRING_SHORT_DESC: + return GeometryShaderDesc(id_); + default: + return "N/A"; + } +} + ShaderManagerVulkan::ShaderManagerVulkan(Draw::DrawContext *draw) - : ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16) { + : ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16), gsCache_(16) { codeBuffer_ = new char[16384]; VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); uboAlignment_ = vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment; @@ -199,10 +229,15 @@ void ShaderManagerVulkan::Clear() { vsCache_.Iterate([&](const VShaderID &key, VulkanVertexShader *shader) { delete shader; }); + gsCache_.Iterate([&](const GShaderID &key, VulkanGeometryShader *shader) { + delete shader; + }); fsCache_.Clear(); vsCache_.Clear(); + gsCache_.Clear(); lastFSID_.set_invalid(); lastVSID_.set_invalid(); + lastGSID_.set_invalid(); gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); } @@ -216,12 +251,14 @@ void ShaderManagerVulkan::DirtyShader() { // Forget the last shader ID lastFSID_.set_invalid(); lastVSID_.set_invalid(); + lastGSID_.set_invalid(); DirtyLastShader(); } void ShaderManagerVulkan::DirtyLastShader() { lastVShader_ = nullptr; lastFShader_ = nullptr; + lastGShader_ = nullptr; gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE); } @@ -239,7 +276,7 @@ uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) { return dirty; } -void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat) { +void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat) { VShaderID VSID; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); @@ -256,14 +293,23 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader FSID = lastFSID_; } + GShaderID GSID; + if (gstate_c.IsDirty(DIRTY_GEOMETRYSHADER_STATE)) { + gstate_c.Clean(DIRTY_GEOMETRYSHADER_STATE); + ComputeGeometryShaderID(&GSID, draw_->GetBugs(), prim); + } else { + GSID = lastGSID_; + } + _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); _dbg_assert_(FSID.Bit(FS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE)); _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); // Just update uniforms if this is the same shader as last time. - if (lastVShader_ != nullptr && lastFShader_ != nullptr && VSID == lastVSID_ && FSID == lastFSID_) { + if (lastVShader_ != nullptr && lastFShader_ != nullptr && VSID == lastVSID_ && FSID == lastFSID_ && GSID == lastGSID_) { *vshader = lastVShader_; *fshader = lastFShader_; + *gshader = lastGShader_; _dbg_assert_msg_((*vshader)->UseHWTransform() == useHWTransform, "Bad vshader was cached"); // Already all set, no need to look up in shader maps. return; @@ -281,11 +327,9 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader vs = new VulkanVertexShader(vulkan, VSID, codeBuffer_, useHWTransform); vsCache_.Insert(VSID, vs); } - lastVSID_ = VSID; VulkanFragmentShader *fs = fsCache_.Get(FSID); if (!fs) { - // uint32_t vendorID = vulkan->GetPhysicalDeviceProperties().properties.vendorID; // Fragment shader not in cache. Let's compile it. std::string genErrorString; uint64_t uniformMask = 0; // Not used @@ -296,13 +340,28 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader fsCache_.Insert(FSID, fs); } + VulkanGeometryShader *gs; + if (GSID.Bit(GS_BIT_ENABLED)) { + gs = gsCache_.Get(GSID); + if (!gs) { + // Geometry shader not in cache. Let's compile it. + // TODO + } + } else { + gs = nullptr; + } + + lastVSID_ = VSID; lastFSID_ = FSID; + lastGSID_ = GSID; lastVShader_ = vs; lastFShader_ = fs; + lastGShader_ = gs; *vshader = vs; *fshader = fs; + *gshader = gs; _dbg_assert_msg_((*vshader)->UseHWTransform() == useHWTransform, "Bad vshader was computed"); } @@ -327,6 +386,15 @@ std::vector ShaderManagerVulkan::DebugGetShaderIDs(DebugShaderType }); break; } + case SHADER_TYPE_GEOMETRY: + { + gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *shader) { + std::string idstr; + id.ToString(&idstr); + ids.push_back(idstr); + }); + break; + } default: break; } @@ -342,12 +410,16 @@ std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShade VulkanVertexShader *vs = vsCache_.Get(VShaderID(shaderId)); return vs ? vs->GetShaderString(stringType) : ""; } - case SHADER_TYPE_FRAGMENT: { VulkanFragmentShader *fs = fsCache_.Get(FShaderID(shaderId)); return fs ? fs->GetShaderString(stringType) : ""; } + case SHADER_TYPE_GEOMETRY: + { + VulkanGeometryShader *gs = gsCache_.Get(GShaderID(shaderId)); + return gs ? gs->GetShaderString(stringType) : ""; + } default: return "N/A"; } @@ -375,6 +447,17 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM return fs; } +VulkanGeometryShader *ShaderManagerVulkan::GetGeometryShaderFromModule(VkShaderModule module) { + VulkanGeometryShader *gs = nullptr; + gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *shader) { + Promise *p = shader->GetModule(); + VkShaderModule m = p->BlockUntilReady(); + if (m == module) + gs = shader; + }); + return gs; +} + // Shader cache. // // We simply store the IDs of the shaders used during gameplay. On next startup of diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index 6593a366f4..4c89f91ef5 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -83,7 +83,27 @@ protected: VShaderID id_; }; -class VulkanPushBuffer; +class VulkanGeometryShader { +public: + VulkanGeometryShader(VulkanContext *vulkan, GShaderID id, const char *code); + ~VulkanGeometryShader(); + + const std::string &source() const { return source_; } + + bool Failed() const { return failed_; } + + std::string GetShaderString(DebugShaderStringType type) const; + Promise *GetModule() const { return module_; } + const GShaderID &GetID() { return id_; } + +protected: + Promise *module_ = nullptr; + + VulkanContext *vulkan_; + std::string source_; + bool failed_ = false; + GShaderID id_; +}; class ShaderManagerVulkan : public ShaderManagerCommon { public: @@ -93,19 +113,22 @@ public: void DeviceLost(); void DeviceRestore(Draw::DrawContext *draw); - void GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat); + void GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat); void ClearShaders(); void DirtyShader(); void DirtyLastShader() override; int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); } + int GetNumGeometryShaders() const { return (int)gsCache_.size(); } // Used for saving/loading the cache. Don't need to be particularly fast. VulkanVertexShader *GetVertexShaderFromID(VShaderID id) { return vsCache_.Get(id); } VulkanFragmentShader *GetFragmentShaderFromID(FShaderID id) { return fsCache_.Get(id); } + VulkanGeometryShader *GetGeometryShaderFromID(GShaderID id) { return gsCache_.Get(id); } VulkanVertexShader *GetVertexShaderFromModule(VkShaderModule module); VulkanFragmentShader *GetFragmentShaderFromModule(VkShaderModule module); + VulkanGeometryShader *GetGeometryShaderFromModule(VkShaderModule module); std::vector DebugGetShaderIDs(DebugShaderType type); std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); @@ -143,6 +166,9 @@ private: typedef DenseHashMap VSCache; VSCache vsCache_; + typedef DenseHashMap GSCache; + GSCache gsCache_; + char *codeBuffer_; uint64_t uboAlignment_; @@ -153,7 +179,9 @@ private: VulkanFragmentShader *lastFShader_ = nullptr; VulkanVertexShader *lastVShader_ = nullptr; + VulkanGeometryShader *lastGShader_ = nullptr; FShaderID lastFSID_; VShaderID lastVSID_; + GShaderID lastGSID_; }; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index e85f1d3adc..5dea5e0921 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -1156,7 +1156,7 @@ int ShaderListScreen::ListShaders(DebugShaderType shaderType, UI::LinearLayout * struct { DebugShaderType type; const char *name; } shaderTypes[] = { { SHADER_TYPE_VERTEX, "Vertex" }, { SHADER_TYPE_FRAGMENT, "Fragment" }, - // { SHADER_TYPE_GEOMETRY, "Geometry" }, + { SHADER_TYPE_GEOMETRY, "Geometry" }, { SHADER_TYPE_VERTEXLOADER, "VertexLoader" }, { SHADER_TYPE_PIPELINE, "Pipeline" }, { SHADER_TYPE_TEXTURE, "Texture" }, From fbdb2781688a997f630cd33bb7d1d5f70613dfc3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 20:13:30 -0700 Subject: [PATCH 280/326] Vulkan: Update shader cache format for geo shaders. --- GPU/Vulkan/PipelineManagerVulkan.cpp | 5 ++++- GPU/Vulkan/ShaderManagerVulkan.cpp | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index f25b0cb269..4b9ff1e4b3 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -544,6 +544,7 @@ struct StoredVulkanPipelineKey { VulkanPipelineRasterStateKey raster; VShaderID vShaderID; FShaderID fShaderID; + GShaderID gShaderID; uint32_t vtxFmtId; uint32_t variants; bool useHWTransform; // TODO: Still needed? @@ -609,6 +610,7 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha key.useHWTransform = pkey.useHWTransform; key.fShaderID = fshader->GetID(); key.vShaderID = vshader->GetID(); + key.gShaderID = gshader ? gshader->GetID() : GShaderID(); key.variants = value->GetVariantsBitmask(); if (key.useHWTransform) { // NOTE: This is not a vtype, but a decoded vertex format. @@ -713,6 +715,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha } VulkanVertexShader *vs = shaderManager->GetVertexShaderFromID(key.vShaderID); VulkanFragmentShader *fs = shaderManager->GetFragmentShaderFromID(key.fShaderID); + VulkanGeometryShader *gs = shaderManager->GetGeometryShaderFromID(key.gShaderID); if (!vs || !fs) { failed = true; ERROR_LOG(G3D, "Failed to find vs or fs in of pipeline %d in cache", (int)i); @@ -721,7 +724,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha DecVtxFormat fmt; fmt.InitializeFromID(key.vtxFmtId); - VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, nullptr, key.useHWTransform, key.variants); + VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, gs, key.useHWTransform, key.variants); if (!pipeline) { pipelineCreateFailCount += 1; } diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 769c258993..07d9603322 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -467,7 +467,7 @@ VulkanGeometryShader *ShaderManagerVulkan::GetGeometryShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 28 +#define CACHE_VERSION 29 struct VulkanCacheHeader { uint32_t magic; uint32_t version; @@ -475,6 +475,7 @@ struct VulkanCacheHeader { uint32_t reserved; int numVertexShaders; int numFragmentShaders; + int numGeometryShaders; }; bool ShaderManagerVulkan::LoadCache(FILE *f) { @@ -522,6 +523,15 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { fsCache_.Insert(id, fs); } + for (int i = 0; i < header.numGeometryShaders; i++) { + GShaderID id; + if (fread(&id, sizeof(id), 1, f) != 1) { + ERROR_LOG(G3D, "Vulkan shader cache truncated"); + break; + } + // TODO: Actually generate geometry shaders. + } + NOTICE_LOG(G3D, "Loaded %d vertex and %d fragment shaders", header.numVertexShaders, header.numFragmentShaders); return true; } @@ -534,6 +544,7 @@ void ShaderManagerVulkan::SaveCache(FILE *f) { header.reserved = 0; header.numVertexShaders = (int)vsCache_.size(); header.numFragmentShaders = (int)fsCache_.size(); + header.numGeometryShaders = (int)gsCache_.size(); bool writeFailed = fwrite(&header, sizeof(header), 1, f) != 1; vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) { writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1; @@ -541,6 +552,9 @@ void ShaderManagerVulkan::SaveCache(FILE *f) { fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *fs) { writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1; }); + gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *gs) { + writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1; + }); if (writeFailed) { ERROR_LOG(G3D, "Failed to write Vulkan shader cache, disk full?"); } else { From cdee10fe86ddeffbfc643715586c12cec0d571e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 26 Oct 2021 09:56:14 +0200 Subject: [PATCH 281/326] Vulkan: Basic geoshader code generation. --- CMakeLists.txt | 2 + Common/GPU/Vulkan/VulkanContext.cpp | 2 - GPU/Common/GeometryShaderGenerator.cpp | 79 ++++++++++++++++++++++ GPU/Common/GeometryShaderGenerator.h | 5 ++ GPU/GPU.vcxproj | 2 + GPU/GPU.vcxproj.filters | 6 ++ GPU/Vulkan/GPU_Vulkan.cpp | 8 +++ GPU/Vulkan/PipelineManagerVulkan.cpp | 2 +- GPU/Vulkan/ShaderManagerVulkan.cpp | 19 +++++- UWP/GPU_UWP/GPU_UWP.vcxproj | 2 + UWP/GPU_UWP/GPU_UWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + unittest/TestShaderGenerators.cpp | 91 ++++++++++++++++++++++++++ 14 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 GPU/Common/GeometryShaderGenerator.cpp create mode 100644 GPU/Common/GeometryShaderGenerator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cec0f281d..52fb7e40c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1562,6 +1562,8 @@ set(GPU_SOURCES GPU/Common/FragmentShaderGenerator.h GPU/Common/VertexShaderGenerator.cpp GPU/Common/VertexShaderGenerator.h + GPU/Common/GeometryShaderGenerator.cpp + GPU/Common/GeometryShaderGenerator.h GPU/Common/FramebufferManagerCommon.cpp GPU/Common/FramebufferManagerCommon.h GPU/Common/GPUDebugInterface.cpp diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 1379cb9f21..c2140c5663 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -600,8 +600,6 @@ void VulkanContext::ChooseDevice(int physical_device) { deviceFeatures_.enabled.samplerAnisotropy = deviceFeatures_.available.samplerAnisotropy; deviceFeatures_.enabled.shaderClipDistance = deviceFeatures_.available.shaderClipDistance; deviceFeatures_.enabled.shaderCullDistance = deviceFeatures_.available.shaderCullDistance; - // For easy wireframe mode, someday. - deviceFeatures_.enabled.fillModeNonSolid = deviceFeatures_.available.fillModeNonSolid; deviceFeatures_.enabled.geometryShader = deviceFeatures_.available.geometryShader; GetDeviceLayerExtensionList(nullptr, device_extension_properties_); diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp new file mode 100644 index 0000000000..fdd64c8077 --- /dev/null +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2012- 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 +#include +#include + +#include "Common/StringUtils.h" +#include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/GPU/ShaderWriter.h" +#include "Common/GPU/thin3d.h" +#include "Core/Config.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUState.h" +#include "GPU/Common/ShaderId.h" +#include "GPU/Common/ShaderUniforms.h" +#include "GPU/Common/GeometryShaderGenerator.h" + +#undef WRITE + +#define WRITE(p, ...) p.F(__VA_ARGS__) + +bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString) { + std::vector gl_exts; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (gl_extensions.EXT_gpu_shader4) { + gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable"); + } + } + + ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size()); + p.C("layout(triangles) in;\n"); + p.C("layout(triangle_strip, max_vertices = 3) out;\n"); + + std::vector varyings, outVaryings; + + if (id.Bit(GS_BIT_DO_TEXTURE)) { + varyings.push_back(VaryingDef{ "vec3", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" }); + outVaryings.push_back(VaryingDef{ "vec3", "v_texcoordOut", Draw::SEM_TEXCOORD0, 0, "highp" }); + } + varyings.push_back(VaryingDef{ "vec4", "v_color0", Draw::SEM_COLOR0, 1, "lowp" }); + outVaryings.push_back(VaryingDef{ "vec4", "v_color0Out", Draw::SEM_COLOR0, 1, "lowp" }); + if (id.Bit(GS_BIT_LMODE)) { + varyings.push_back(VaryingDef{ "vec3", "v_color1", Draw::SEM_COLOR1, 2, "lowp" }); + outVaryings.push_back(VaryingDef{ "vec3", "v_color1Out", Draw::SEM_COLOR1, 2, "lowp" }); + } + varyings.push_back(VaryingDef{ "float", "v_fogdepth", Draw::SEM_TEXCOORD1, 3, "highp" }); + outVaryings.push_back(VaryingDef{ "float", "v_fogdepthOut", Draw::SEM_TEXCOORD1, 3, "highp" }); + + p.BeginGSMain(varyings, outVaryings); + + p.C(" for (int i = 0; i < gl_in.length(); i++) {\n"); + p.C(" gl_Position = gl_in[i].gl_Position;\n"); // copy attributes + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" }\n"); + + p.EndGSMain(); + + return true; +} diff --git a/GPU/Common/GeometryShaderGenerator.h b/GPU/Common/GeometryShaderGenerator.h new file mode 100644 index 0000000000..9848ffde18 --- /dev/null +++ b/GPU/Common/GeometryShaderGenerator.h @@ -0,0 +1,5 @@ +#pragma once + +#include "GPU/Common/ShaderId.h" + +bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 0480b490d7..808ce53635 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -340,6 +340,7 @@ + @@ -455,6 +456,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index bd28a45e97..e110c20b8b 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -261,6 +261,9 @@ Debugger + + Common + @@ -512,6 +515,9 @@ Debugger + + Common + diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index aa31c93be4..79a2db00d6 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -232,6 +232,14 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { features |= GPU_SUPPORTS_DEPTH_CLAMP; } + if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { + const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; + // Fall back to geometry shader culling. + if (enabledFeatures.geometryShader && !disabled && (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0) { + features |= GPU_SUPPORTS_GS_CULLING; + } + } + // These are VULKAN_4444_FORMAT and friends. uint32_t fmt4444 = draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16); uint32_t fmt1555 = draw_->GetDataFormatSupport(Draw::DataFormat::A1R5G5B5_UNORM_PACK16); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 4b9ff1e4b3..3d7745b452 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -716,7 +716,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha VulkanVertexShader *vs = shaderManager->GetVertexShaderFromID(key.vShaderID); VulkanFragmentShader *fs = shaderManager->GetFragmentShaderFromID(key.fShaderID); VulkanGeometryShader *gs = shaderManager->GetGeometryShaderFromID(key.gShaderID); - if (!vs || !fs) { + if (!vs || !fs || (!gs && key.gShaderID.Bit(GS_BIT_ENABLED))) { failed = true; ERROR_LOG(G3D, "Failed to find vs or fs in of pipeline %d in cache", (int)i); continue; diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 07d9603322..00649a6be1 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -38,6 +38,7 @@ #include "GPU/ge_constants.h" #include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/GeometryShaderGenerator.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #include "GPU/Vulkan/FramebufferManagerVulkan.h" @@ -305,6 +306,11 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader _dbg_assert_(FSID.Bit(FS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE)); _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); + if (GSID.Bit(GS_BIT_ENABLED)) { + _dbg_assert_(GSID.Bit(GS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); + _dbg_assert_(GSID.Bit(GS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE)); + } + // Just update uniforms if this is the same shader as last time. if (lastVShader_ != nullptr && lastFShader_ != nullptr && VSID == lastVSID_ && FSID == lastFSID_ && GSID == lastGSID_) { *vshader = lastVShader_; @@ -345,7 +351,11 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader gs = gsCache_.Get(GSID); if (!gs) { // Geometry shader not in cache. Let's compile it. - // TODO + std::string genErrorString; + bool success = GenerateGeometryShader(GSID, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString); + _assert_msg_(success, "GS gen error: %s", genErrorString.c_str()); + gs = new VulkanGeometryShader(vulkan, GSID, codeBuffer_); + gsCache_.Insert(GSID, gs); } } else { gs = nullptr; @@ -529,7 +539,12 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { ERROR_LOG(G3D, "Vulkan shader cache truncated"); break; } - // TODO: Actually generate geometry shaders. + std::string genErrorString; + if (!GenerateGeometryShader(id, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString)) { + return false; + } + VulkanGeometryShader *gs = new VulkanGeometryShader(vulkan, id, codeBuffer_); + gsCache_.Insert(id, gs); } NOTICE_LOG(G3D, "Loaded %d vertex and %d fragment shaders", header.numVertexShaders, header.numFragmentShaders); diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 87aac4d38b..05b7839736 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -385,6 +385,7 @@ + @@ -446,6 +447,7 @@ + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index dbce632981..2296b16a16 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -55,6 +55,7 @@ + @@ -116,6 +117,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2f86711855..42cae5cb28 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -350,6 +350,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Common/PostShader.cpp \ $(SRC)/GPU/Common/ShaderUniforms.cpp \ $(SRC)/GPU/Common/VertexShaderGenerator.cpp \ + $(SRC)/GPU/Common/GeometryShaderGenerator.cpp \ $(SRC)/GPU/Debugger/Breakpoints.cpp \ $(SRC)/GPU/Debugger/Debugger.cpp \ $(SRC)/GPU/Debugger/GECommandTable.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index e7dd40be35..8eed277932 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -342,6 +342,7 @@ SOURCES_CXX += \ $(GPUDIR)/Debugger/Stepping.cpp \ $(GPUDIR)/Common/FragmentShaderGenerator.cpp \ $(GPUDIR)/Common/VertexShaderGenerator.cpp \ + $(GPUDIR)/Common/GeometryShaderGenerator.cpp \ $(GPUDIR)/Common/TextureCacheCommon.cpp \ $(GPUDIR)/Common/TextureScalerCommon.cpp \ $(GPUDIR)/Common/SoftwareTransformCommon.cpp \ diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index 810de85b9e..346fadf643 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -12,6 +12,7 @@ #include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/GeometryShaderGenerator.h" #include "GPU/Common/ReinterpretFramebuffer.h" #include "GPU/Common/StencilCommon.h" #include "GPU/Common/DepalettizeShaderCommon.h" @@ -91,6 +92,32 @@ bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs } } +bool GenerateGShader(GShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) { + errorString->clear(); + + switch (lang) { + case ShaderLanguage::GLSL_VULKAN: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN); + return GenerateGeometryShader(id, buffer, compat, bugs, errorString); + } + /* + case ShaderLanguage::GLSL_3xx: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx); + return GenerateGeometryShader(id, buffer, compat, bugs, errorString); + } + case ShaderLanguage::HLSL_D3D11: + { + ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11); + return GenerateGeometryShader(id, buffer, compat, bugs, errorString); + } + */ + default: + return false; + } +} + static VkShaderStageFlagBits StageToVulkan(ShaderStage stage) { switch (stage) { case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT; @@ -369,6 +396,9 @@ bool TestVertexShaders() { if (!id.Bit(VS_BIT_USE_HW_TRANSFORM)) { id.SetBit(VS_BIT_ENABLE_BONES, 0); } + if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) { + continue; + } bool generateSuccess[numLanguages]{}; std::string genErrorString[numLanguages]; @@ -463,6 +493,63 @@ bool TestFragmentShaders() { return true; } + +bool TestGeometryShaders() { + char *buffer[numLanguages]; + + for (int i = 0; i < numLanguages; i++) { + buffer[i] = new char[65536]; + } + GMRng rng; + int successes = 0; + int count = 30; + + Draw::Bugs bugs; + + // Generate a bunch of random fragment shader IDs, try to generate shader source. + // Then compile it and check that it's ok. + for (int i = 0; i < count; i++) { + uint32_t bottom = i << 1; + GShaderID id; + id.d[0] = bottom; + id.d[1] = 0; + + id.SetBit(GS_BIT_ENABLED, true); + + bool generateSuccess[numLanguages]{}; + std::string genErrorString[numLanguages]; + + for (int j = 0; j < numLanguages; j++) { + generateSuccess[j] = GenerateGShader(id, buffer[j], languages[j], bugs, &genErrorString[j]); + if (!genErrorString[j].empty()) { + printf("%s\n", genErrorString[j].c_str()); + } + // We ignore the contents of the error string here, not even gonna try to compile if it errors. + } + + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), + // let's try to compile them. + for (int j = 0; j < numLanguages; j++) { + if (generateSuccess[j]) { + std::string errorMessage; + if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Geometry, &errorMessage)) { + printf("Error compiling geometry shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + return false; + } + successes++; + } + } + } + + printf("%d/%d geometry shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages); + + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return true; +} + + bool TestShaderGenerators() { #if PPSSPP_PLATFORM(WINDOWS) LoadD3D11(); @@ -476,6 +563,10 @@ bool TestShaderGenerators() { return false; } + if (!TestGeometryShaders()) { + return false; + } + if (!TestReinterpretShaders()) { return false; } From ac248338be64161eb47ac88ce3892e9538731112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 27 Oct 2021 09:28:07 +0200 Subject: [PATCH 282/326] Vulkan: Cull in geoshader, hack to on for now. --- GPU/Common/GeometryShaderGenerator.cpp | 54 +++++++++++++++++++++++++- GPU/Common/VertexShaderGenerator.cpp | 2 +- GPU/Vulkan/GPU_Vulkan.cpp | 6 +++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index fdd64c8077..5ec7875e5c 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -34,6 +34,10 @@ #define WRITE(p, ...) p.F(__VA_ARGS__) +// TODO: Could support VK_NV_geometry_shader_passthrough, though the hardware that supports +// it is already pretty fast at geometry shaders.. + + bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString) { std::vector gl_exts; if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { @@ -46,6 +50,13 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C("layout(triangles) in;\n"); p.C("layout(triangle_strip, max_vertices = 3) out;\n"); + if (compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, "\n"); + WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseVars {\n%s};\n", ub_baseStr); + } else if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "cbuffer base : register(b0) {\n%s};\n", ub_baseStr); + } + std::vector varyings, outVaryings; if (id.Bit(GS_BIT_DO_TEXTURE)) { @@ -63,13 +74,52 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.BeginGSMain(varyings, outVaryings); - p.C(" for (int i = 0; i < gl_in.length(); i++) {\n"); - p.C(" gl_Position = gl_in[i].gl_Position;\n"); // copy attributes + // Apply culling + p.C(" bool anyInside = false;\n"); // TODO: 3 or gl_in.length()? which will be faster? + + p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? + p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); + p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); + p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); + // Vertex range culling doesn't happen when Z clips, note sign of w is important. + p.C(" if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); + const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; + const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; + p.F(" if ((%s) || (%s)) {\n", outMin, outMax); + p.C(" return;\n"); // Cull! + p.C(" }\n"); + p.C(" }\n"); + p.C(" if (u_cullRangeMin.w <= 0.0) {\n"); + p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); + p.C(" return;\n"); // Cull! + p.C(" }\n"); + p.C(" } else {\n"); + p.C(" if (projPos.z >= u_cullRangeMin.z && projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); + p.C(" }\n"); + p.C(" } // for\n"); + + // Cull any triangle fully outside in the same direction when depth clamp enabled. + // Basically simulate cull distances. + p.C(" if (!anyInside) { return; }\n"); + + const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; + + p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? + p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); + p.C(" gl_Position = outPos;\n"); + p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); + p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); + // TODO: Not rectangles... + // TODO: Check feature flag. + //p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clip0); + for (size_t i = 0; i < varyings.size(); i++) { VaryingDef &in = varyings[i]; VaryingDef &out = outVaryings[i]; p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); } + // Debug - null the red channel + p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); p.C(" EmitVertex();\n"); p.C(" }\n"); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 797a6e0e50..d6f356c6d4 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1299,7 +1299,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; - WRITE(p, " if (%s || %s) {\n", outMin, outMax); + WRITE(p, " if ((%s) || (%s)) {\n", outMin, outMax); WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); WRITE(p, " }\n"); WRITE(p, " }\n"); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 79a2db00d6..67ed9ae299 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -232,6 +232,11 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { features |= GPU_SUPPORTS_DEPTH_CLAMP; } + // Force geo shader culling for debugging. +#if 1 + features |= GPU_SUPPORTS_GS_CULLING; + features &= ~GPU_SUPPORTS_VS_RANGE_CULLING; +#else if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; // Fall back to geometry shader culling. @@ -239,6 +244,7 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { features |= GPU_SUPPORTS_GS_CULLING; } } +#endif // These are VULKAN_4444_FORMAT and friends. uint32_t fmt4444 = draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16); From bfaa304461e99b1b088db08ecec4f78d10f6adc2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Oct 2021 21:22:45 -0700 Subject: [PATCH 283/326] Vulkan: Correct geometry shader culling. --- GPU/Common/GeometryShaderGenerator.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 5ec7875e5c..2c2906d100 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -75,7 +75,7 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.BeginGSMain(varyings, outVaryings); // Apply culling - p.C(" bool anyInside = false;\n"); // TODO: 3 or gl_in.length()? which will be faster? + p.C(" bool anyInside = false;\n"); p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); @@ -91,16 +91,20 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" }\n"); p.C(" if (u_cullRangeMin.w <= 0.0) {\n"); p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); - p.C(" return;\n"); // Cull! + // When not clamping depth, cull the triangle of Z is outside the valid range (not based on clip Z.) + p.C(" return;\n"); p.C(" }\n"); p.C(" } else {\n"); - p.C(" if (projPos.z >= u_cullRangeMin.z && projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); + p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n"); + p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); p.C(" }\n"); p.C(" } // for\n"); // Cull any triangle fully outside in the same direction when depth clamp enabled. // Basically simulate cull distances. - p.C(" if (!anyInside) { return; }\n"); + p.C(" if (u_cullRangeMin.w > 0.0 && !anyInside) {\n"); + p.C(" return;\n"); + p.C(" }\n"); const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; @@ -119,7 +123,7 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); } // Debug - null the red channel - p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); + //p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); p.C(" EmitVertex();\n"); p.C(" }\n"); From 2ce0cda333c945de63ad32179026b42fc9307dc8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 20:39:22 -0700 Subject: [PATCH 284/326] Vulkan: Enable geo shader for culling. The compat setting was really for some previously buggy cases that couldn't work without cull. --- GPU/Vulkan/GPU_Vulkan.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 67ed9ae299..6376fc0620 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -232,19 +232,15 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { features |= GPU_SUPPORTS_DEPTH_CLAMP; } - // Force geo shader culling for debugging. -#if 1 - features |= GPU_SUPPORTS_GS_CULLING; - features &= ~GPU_SUPPORTS_VS_RANGE_CULLING; -#else - if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; - // Fall back to geometry shader culling. - if (enabledFeatures.geometryShader && !disabled && (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0) { + // Fall back to geometry shader culling if we can't do vertex range culling. + if (enabledFeatures.geometryShader) { + const bool vertexSupported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; + if (!vertexSupported || (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0) { + // Switch to culling via the geometry shader if not fully supported in vertex. features |= GPU_SUPPORTS_GS_CULLING; + features &= ~GPU_SUPPORTS_VS_RANGE_CULLING; } } -#endif // These are VULKAN_4444_FORMAT and friends. uint32_t fmt4444 = draw_->GetDataFormatSupport(Draw::DataFormat::B4G4R4A4_UNORM_PACK16); From 36eb0d9ad5994fe4e4ad9f02e4732efc397fc631 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 1 Oct 2022 20:42:00 -0700 Subject: [PATCH 285/326] Vulkan: Use geo clip distance only where supported. It might be supported without cull or GS. Otherwise we may need to clip the triangles manually. --- GPU/Common/GeometryShaderGenerator.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 2c2906d100..0121d7748a 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -114,8 +114,9 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); // TODO: Not rectangles... - // TODO: Check feature flag. - //p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clip0); + if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clip0); + } for (size_t i = 0; i < varyings.size(); i++) { VaryingDef &in = varyings[i]; From 8df956b03639acbaf0e14b483295cdb7c23cac05 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 07:32:31 -0700 Subject: [PATCH 286/326] Vulkan: Block geometry shaders on older Mali. They're too slow to be usable. --- Common/GPU/Vulkan/thin3d_vulkan.cpp | 5 +++++ Common/GPU/thin3d.h | 3 +++ GPU/Vulkan/GPU_Vulkan.cpp | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 413057b288..06e026d35c 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -846,6 +846,11 @@ VKContext::VKContext(VulkanContext *vulkan) if (majorVersion >= 32) { bugs_.Infest(Bugs::MALI_CONSTANT_LOAD_BUG); // See issue #15661 } + + // Older ARM devices have very slow geometry shaders, not worth using. At least before 15. + if (majorVersion <= 15) { + bugs_.Infest(Bugs::GEOMETRY_SHADERS_SLOW); + } } // Limited, through input attachments and self-dependencies. diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index f0c562b2f3..c04b7119cd 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -333,11 +333,14 @@ public: RASPBERRY_SHADER_COMP_HANG = 8, MALI_CONSTANT_LOAD_BUG = 9, SUBPASS_FEEDBACK_BROKEN = 10, + GEOMETRY_SHADERS_SLOW = 11, MAX_BUG, }; protected: uint32_t flags_ = 0; + + static_assert(sizeof(flags_) * 8 > MAX_BUG, "Ran out of space for bugs."); }; class RefCountedObject { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 6376fc0620..6d91a3b20e 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -233,7 +233,7 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { } // Fall back to geometry shader culling if we can't do vertex range culling. - if (enabledFeatures.geometryShader) { + if (enabledFeatures.geometryShader && !draw_->GetBugs().Has(Draw::Bugs::GEOMETRY_SHADERS_SLOW)) { const bool vertexSupported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; if (!vertexSupported || (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0) { // Switch to culling via the geometry shader if not fully supported in vertex. From 2832edcc3791f05227e4fa534aaab7e69021d14c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 07:41:36 -0700 Subject: [PATCH 287/326] Vulkan: Allow configuring geometry shaders on/off. --- Core/Config.cpp | 1 + Core/Config.h | 1 + GPU/Vulkan/GPU_Vulkan.cpp | 5 +++-- UI/GameSettingsScreen.cpp | 9 +++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 45e396c9b7..dee26c3793 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -874,6 +874,7 @@ static ConfigSetting graphicsSettings[] = { #endif ConfigSetting("CameraDevice", &g_Config.sCameraDevice, "", true, false), ConfigSetting("VendorBugChecksEnabled", &g_Config.bVendorBugChecksEnabled, true, false, false), + ConfigSetting("UseGeometryShader", &g_Config.bUseGeometryShader, true, true, true), ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, 1, true, true), ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true), ConfigSetting("SoftwareRendererJit", &g_Config.bSoftwareRenderingJit, true, true, true), diff --git a/Core/Config.h b/Core/Config.h index 8a7f519621..65eaf74099 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -164,6 +164,7 @@ public: bool bHardwareTransform; // only used in the GLES backend bool bSoftwareSkinning; // may speed up some games bool bVendorBugChecksEnabled; + bool bUseGeometryShader; int iRenderingMode; // 0 = non-buffered rendering 1 = buffered rendering int iTexFiltering; // 1 = auto , 2 = nearest , 3 = linear , 4 = auto max quality diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 6d91a3b20e..f285af82d0 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -233,9 +233,10 @@ u32 GPU_Vulkan::CheckGPUFeatures() const { } // Fall back to geometry shader culling if we can't do vertex range culling. - if (enabledFeatures.geometryShader && !draw_->GetBugs().Has(Draw::Bugs::GEOMETRY_SHADERS_SLOW)) { + if (enabledFeatures.geometryShader) { + const bool useGeometry = g_Config.bUseGeometryShader && !draw_->GetBugs().Has(Draw::Bugs::GEOMETRY_SHADERS_SLOW); const bool vertexSupported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; - if (!vertexSupported || (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0) { + if (useGeometry && (!vertexSupported || (features & GPU_SUPPORTS_VS_RANGE_CULLING) == 0)) { // Switch to culling via the geometry shader if not fully supported in vertex. features |= GPU_SUPPORTS_GS_CULLING; features &= ~GPU_SUPPORTS_VS_RANGE_CULLING; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 0c1c0d009e..876db2773e 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -473,6 +473,15 @@ void GameSettingsScreen::CreateViews() { inflightChoice->OnChoice.Handle(this, &GameSettingsScreen::OnInflightFramesChoice); } + if (GetGPUBackend() == GPUBackend::VULKAN) { + const bool usable = !draw->GetBugs().Has(Draw::Bugs::GEOMETRY_SHADERS_SLOW); + const bool vertexSupported = draw->GetDeviceCaps().clipDistanceSupported && draw->GetDeviceCaps().cullDistanceSupported; + if (usable && !vertexSupported) { + CheckBox *geometryCulling = graphicsSettings->Add(new CheckBox(&g_Config.bUseGeometryShader, gr->T("Geometry shader culling"))); + geometryCulling->SetDisabledPtr(&g_Config.bSoftwareRendering); + } + } + if (deviceType != DEVICE_TYPE_VR) { CheckBox *hwTransform = graphicsSettings->Add(new CheckBox(&g_Config.bHardwareTransform, gr->T("Hardware Transform"))); hwTransform->SetDisabledPtr(&g_Config.bSoftwareRendering); From 4df7a8f3578a77f029879e25960686fdeaad96b3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 07:43:35 -0700 Subject: [PATCH 288/326] Vulkan: Cleanup unused geometry shader vars. Without clipping, these aren't used (but could be in the future with manual clipping.) --- GPU/Common/GeometryShaderGenerator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 0121d7748a..b701bb547c 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -111,10 +111,10 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); p.C(" gl_Position = outPos;\n"); - p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); - p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); // TODO: Not rectangles... if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); + p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clip0); } From b9b59f7806d1b90b19f3d2b8192077ea97808cb2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 20:44:35 -0700 Subject: [PATCH 289/326] GPU: Mask away unused bits in framebuf/zbuf ptr. Lower 4 bits are ignored during rendering, and mirrors (even even the 8 bit at the top) are ignored. --- GPU/D3D11/DrawEngineD3D11.cpp | 2 +- GPU/D3D11/StateMappingD3D11.cpp | 2 +- GPU/Directx9/DrawEngineDX9.cpp | 2 +- GPU/GLES/DrawEngineGLES.cpp | 2 +- GPU/GPUState.h | 5 +++-- GPU/Vulkan/DrawEngineVulkan.cpp | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index bc3c67da1d..eff16b543b 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -340,7 +340,7 @@ void DrawEngineD3D11::DoFlush() { textureCache_->SetTexture(); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); textureNeedsApply = true; - } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) { + } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. (#10957) gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index f4ce888f7b..2f86aa9914 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -433,7 +433,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) { textureCache_->SetTexture(); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); - } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) { + } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index e05df6c98c..b8d4bfa0fb 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -327,7 +327,7 @@ void DrawEngineDX9::DoFlush() { textureCache_->SetTexture(); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); textureNeedsApply = true; - } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) { + } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. (#10957) gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 47b10722ef..2102e46095 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -259,7 +259,7 @@ void DrawEngineGLES::DoFlush() { textureCache_->SetTexture(); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); textureNeedsApply = true; - } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) { + } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. (#10957) gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } diff --git a/GPU/GPUState.h b/GPU/GPUState.h index e5d5662845..2cc899c11e 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -206,12 +206,13 @@ struct GPUgstate { float boneMatrix[12 * 8]; // Eight 4x3 bone matrices. // We ignore the high bits of the framebuffer in fbwidth - even 0x08000000 renders to vRAM. - u32 getFrameBufRawAddress() const { return (fbptr & 0xFFFFFF); } + // The top bits of mirroring are also not respected, so we mask them away. + u32 getFrameBufRawAddress() const { return fbptr & 0x1FFFF0; } // 0x44000000 is uncached VRAM. u32 getFrameBufAddress() const { return 0x44000000 | getFrameBufRawAddress(); } GEBufferFormat FrameBufFormat() const { return static_cast(framebufpixformat & 3); } int FrameBufStride() const { return fbwidth&0x7FC; } - u32 getDepthBufRawAddress() const { return (zbptr & 0xFFFFFF); } + u32 getDepthBufRawAddress() const { return zbptr & 0x1FFFF0; } u32 getDepthBufAddress() const { return 0x44000000 | getDepthBufRawAddress(); } int DepthBufStride() const { return zbwidth&0x7FC; } diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 2c50795472..b0e88aa0f2 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -569,7 +569,7 @@ void DrawEngineVulkan::DoFlush() { textureCache_->SetTexture(); gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS); textureNeedsApply = true; - } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) { + } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) { // This catches the case of clearing a texture. gstate_c.Dirty(DIRTY_TEXTURE_IMAGE); } From 4a17ab8070d948b5639a55dfcf0f2321d68d0027 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 20:47:12 -0700 Subject: [PATCH 290/326] GE Debugger: Correct mask in target breakpoints. --- GPU/Debugger/Breakpoints.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp index 97fbca57ee..a512980c6a 100644 --- a/GPU/Debugger/Breakpoints.cpp +++ b/GPU/Debugger/Breakpoints.cpp @@ -122,7 +122,7 @@ u32 GetAdjustedRenderTargetAddress(u32 op) { switch (cmd) { case GE_CMD_FRAMEBUFPTR: case GE_CMD_ZBUFPTR: - return op & 0x003FFFF0; + return op & 0x001FFFF0; } return (u32)-1; @@ -278,7 +278,7 @@ bool IsRenderTargetBreakpoint(u32 addr, bool &temp) { return false; } - addr &= 0x003FFFF0; + addr &= 0x001FFFF0; std::lock_guard guard(breaksLock); temp = breakRenderTargetsTemp.find(addr) != breakRenderTargetsTemp.end(); @@ -290,7 +290,7 @@ bool IsRenderTargetBreakpoint(u32 addr) { return false; } - addr &= 0x003FFFF0; + addr &= 0x001FFFF0; std::lock_guard guard(breaksLock); return breakRenderTargets.find(addr) != breakRenderTargets.end(); @@ -385,7 +385,7 @@ void AddTextureBreakpoint(u32 addr, bool temp) { void AddRenderTargetBreakpoint(u32 addr, bool temp) { std::lock_guard guard(breaksLock); - addr &= 0x003FFFF0; + addr &= 0x001FFFF0; if (temp) { if (breakRenderTargets.find(addr) == breakRenderTargets.end()) { @@ -444,7 +444,7 @@ void RemoveTextureBreakpoint(u32 addr) { void RemoveRenderTargetBreakpoint(u32 addr) { std::lock_guard guard(breaksLock); - addr &= 0x003FFFF0; + addr &= 0x001FFFF0; breakRenderTargetsTemp.erase(addr); breakRenderTargets.erase(addr); From 73040ebb8fb97f441a8a81f70e4c318071716ba5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 20:48:28 -0700 Subject: [PATCH 291/326] GE Debugger: Ignore mirrors for target in record. --- GPU/Debugger/Record.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 56cc164c63..9947519b5e 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -322,6 +322,7 @@ static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 alig } static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) { + addr &= 0x041FFFFF; const bool isTarget = lastRenderTargets.find(addr) != lastRenderTargets.end(); bool isDirtyVRAM = false; From 58a43769988b8b02fee49716a8584039be708176 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 21:28:53 -0700 Subject: [PATCH 292/326] GPU: Normalize framebuf addresses. In VRAM, always store without mirror. In RAM, always store without cache/kernel bits. --- Core/Debugger/MemBlockInfo.cpp | 14 +++++--- GPU/Common/FramebufferManagerCommon.cpp | 44 ++++++++++++++++++------- GPU/Common/FramebufferManagerCommon.h | 4 ++- GPU/Common/TextureCacheCommon.cpp | 20 +++++------ GPU/Directx9/FramebufferManagerDX9.cpp | 2 +- GPU/GPUState.h | 2 +- GPU/Software/BinManager.cpp | 6 ++-- 7 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Core/Debugger/MemBlockInfo.cpp b/Core/Debugger/MemBlockInfo.cpp index e38b6861b7..e361b63014 100644 --- a/Core/Debugger/MemBlockInfo.cpp +++ b/Core/Debugger/MemBlockInfo.cpp @@ -399,12 +399,18 @@ void FlushPendingMemInfo() { pendingNotifyMaxAddr2 = 0; } +static inline uint32_t NormalizeAddress(uint32_t addr) { + if ((addr & 0x3F000000) == 0x04000000) + return addr & 0x041FFFFF; + return addr & 0x3FFFFFFF; +} + void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_t pc, const char *tagStr, size_t strLength) { if (size == 0) { return; } // Clear the uncached and kernel bits. - start &= ~0xC0000000; + start = NormalizeAddress(start); bool needFlush = false; // When the setting is off, we skip smaller info to keep things fast. @@ -450,7 +456,7 @@ void NotifyMemInfo(MemBlockFlags flags, uint32_t start, uint32_t size, const cha } std::vector FindMemInfo(uint32_t start, uint32_t size) { - start &= ~0xC0000000; + start = NormalizeAddress(start); if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start) FlushPendingMemInfo(); @@ -466,7 +472,7 @@ std::vector FindMemInfo(uint32_t start, uint32_t size) { } std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) { - start &= ~0xC0000000; + start = NormalizeAddress(start); if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start) FlushPendingMemInfo(); @@ -486,7 +492,7 @@ std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, } static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) { - start &= ~0xC0000000; + start = NormalizeAddress(start); if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start) FlushPendingMemInfo(); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index edd7cbb9fa..f31d8c79d1 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -112,7 +112,9 @@ void FramebufferManagerCommon::BeginFrame() { } void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { - displayFramebufPtr_ = framebuf; + displayFramebufPtr_ = framebuf & 0x3FFFFFFF; + if (Memory::IsVRAMAddress(displayFramebufPtr_)) + displayFramebufPtr_ = framebuf & 0x041FFFFF; displayStride_ = stride; displayFormat_ = format; GPUDebug::NotifyDisplay(framebuf, stride, format); @@ -121,6 +123,8 @@ void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, G VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const { addr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(addr)) + addr &= 0x041FFFFF; VirtualFramebuffer *match = nullptr; for (auto vfb : vfbs_) { if (vfb->fb_address == addr) { @@ -134,6 +138,9 @@ VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const { } VirtualFramebuffer *FramebufferManagerCommon::GetExactVFB(u32 addr, int stride, GEBufferFormat format) const { + addr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(addr)) + addr &= 0x041FFFFF; VirtualFramebuffer *newest = nullptr; for (auto vfb : vfbs_) { if (vfb->fb_address == addr && vfb->fb_stride == stride && vfb->fb_format == format) { @@ -150,6 +157,9 @@ VirtualFramebuffer *FramebufferManagerCommon::GetExactVFB(u32 addr, int stride, } VirtualFramebuffer *FramebufferManagerCommon::ResolveVFB(u32 addr, int stride, GEBufferFormat format) { + addr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(addr)) + addr &= 0x041FFFFF; // Find the newest one matching addr and stride. VirtualFramebuffer *newest = nullptr; for (auto vfb : vfbs_) { @@ -235,7 +245,7 @@ void FramebufferManagerCommon::EstimateDrawingSize(u32 fb_address, int fb_stride // The majority of the time, these are equal. If not, let's check what we know. u32 nearest_address = 0xFFFFFFFF; for (auto vfb : vfbs_) { - const u32 other_address = vfb->fb_address & 0x3FFFFFFF; + const u32 other_address = vfb->fb_address; if (other_address > fb_address && other_address < nearest_address) { nearest_address = other_address; } @@ -296,10 +306,11 @@ void FramebufferManagerCommon::EstimateDrawingSize(u32 fb_address, int fb_stride } void GetFramebufferHeuristicInputs(FramebufferHeuristicParams *params, const GPUgstate &gstate) { - params->fb_address = (gstate.getFrameBufRawAddress() & 0x3FFFFFFF) | 0x04000000; // GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM. + // GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM. + params->fb_address = gstate.getFrameBufRawAddress() | 0x04000000; params->fb_stride = gstate.FrameBufStride(); - params->z_address = (gstate.getDepthBufRawAddress() & 0x3FFFFFFF) | 0x04000000; + params->z_address = gstate.getDepthBufRawAddress() | 0x04000000; params->z_stride = gstate.DepthBufStride(); if (params->z_address == params->fb_address) { @@ -555,7 +566,7 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { bool newlyUsingDepth = (currentRenderVfb_->usageFlags & FB_USAGE_RENDER_DEPTH) == 0; currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; - uint32_t boundDepthBuffer = gstate.getDepthBufAddress() & 0x3FFFFFFF; + uint32_t boundDepthBuffer = gstate.getDepthBufRawAddress(); if (currentRenderVfb_->z_address != boundDepthBuffer) { WARN_LOG_N_TIMES(z_reassign, 5, G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.", currentRenderVfb_->fb_address, currentRenderVfb_->fb_stride, currentRenderVfb_->z_address, boundDepthBuffer); @@ -1034,6 +1045,8 @@ void FramebufferManagerCommon::NotifyVideoUpload(u32 addr, int size, int stride, void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { // Take off the uncached flag from the address. Not to be confused with the start of VRAM. addr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(addr)) + addr &= 0x041FFFFF; // TODO: Could go through all FBOs, but probably not important? // TODO: Could also check for inner changes, but video is most important. // TODO: This shouldn't care if it's a display framebuf or not, should work exactly the same. @@ -1354,9 +1367,9 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { // Let's search for a framebuf within this range. Note that we also look for // "framebuffers" sitting in RAM (created from block transfer or similar) so we only take off the kernel // and uncached bits of the address when comparing. - const u32 addr = fbaddr & 0x3FFFFFFF; + const u32 addr = fbaddr; for (auto v : vfbs_) { - const u32 v_addr = v->fb_address & 0x3FFFFFFF; + const u32 v_addr = v->fb_address; const u32 v_size = ColorBufferByteSize(v); if (v->fb_format != displayFormat_ || v->fb_stride != displayStride_) { @@ -1628,6 +1641,10 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, dst &= 0x3FFFFFFF; src &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(dst)) + dst &= 0x041FFFFF; + if (Memory::IsVRAMAddress(src)) + src &= 0x041FFFFF; // TODO: Merge the below into FindTransferFramebuffer. // Or at least this should be like the other ones, gathering possible candidates @@ -1645,7 +1662,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, } // We only remove the kernel and uncached bits when comparing. - const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF; + const u32 vfb_address = vfb->fb_address; const u32 vfb_size = ColorBufferByteSize(vfb); const u32 vfb_bpp = BufferFormatBytesPerPixel(vfb->fb_format); const u32 vfb_byteStride = vfb->fb_stride * vfb_bpp; @@ -1758,6 +1775,8 @@ std::string BlockTransferRect::ToString() const { // for depth data yet. bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_pixels, int x_pixels, int y, int w_pixels, int h, int bpp, bool destination, BlockTransferRect *rect) { basePtr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(basePtr)) + basePtr &= 0x041FFFFF; rect->vfb = nullptr; if (!stride_pixels) { @@ -1781,11 +1800,11 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p // Check for easily detected depth copies for logging purposes. // Depth copies are not that useful though because you manually need to account for swizzle, so // not sure if games will use them. - if ((vfb->z_address & 0x3FFFFFFF) == basePtr) { + if (vfb->z_address == basePtr) { WARN_LOG_N_TIMES(z_xfer, 5, G3D, "FindTransferFramebuffer: found matching depth buffer, %08x (dest=%d, bpp=%d)", basePtr, (int)destination, bpp); } - const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF; + const u32 vfb_address = vfb->fb_address; const u32 vfb_size = ColorBufferByteSize(vfb); if (basePtr < vfb_address || basePtr >= vfb_address + vfb_size) { @@ -1898,7 +1917,8 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd // create a new one each frame. VirtualFramebuffer *vfb = new VirtualFramebuffer{}; vfb->fbo = nullptr; - vfb->fb_address = fbAddress; // NOTE - not necessarily in VRAM! + uint32_t mask = Memory::IsVRAMAddress(fbAddress) ? 0x041FFFFF : 0x3FFFFFFF; + vfb->fb_address = fbAddress & mask; // NOTE - not necessarily in VRAM! vfb->fb_stride = stride; vfb->z_address = 0; // marks that if anyone tries to render to this framebuffer, it should be dropped and recreated. vfb->z_stride = 0; @@ -2543,7 +2563,7 @@ void FramebufferManagerCommon::PackFramebufferSync(VirtualFramebuffer *vfb, int return; } - const u32 fb_address = vfb->fb_address & 0x3FFFFFFF; + const u32 fb_address = vfb->fb_address; Draw::DataFormat destFormat = channel == RASTER_COLOR ? GEFormatToThin3D(vfb->fb_format) : GEFormatToThin3D(GE_FORMAT_DEPTH16); const int dstBpp = (int)DataFormatSizeInBytes(destFormat); diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 20fd15f87a..d4bd905f0d 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -355,7 +355,9 @@ public: bool MayIntersectFramebuffer(u32 start) const { // Clear the cache/kernel bits. - start = start & 0x3FFFFFFF; + start &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(start)) + start &= 0x041FFFFF; // Most games only have two framebuffers at the start. if (start >= framebufRangeEnd_ || start < PSP_GetVidMemBase()) { return false; diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index d55f890f64..f0c0d4145c 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -688,7 +688,7 @@ bool TextureCacheCommon::GetBestFramebufferCandidate(const TextureDefinition &en // Should avoid problems when pingponging two nearby buffers, like in Wipeout Pure in #15927. if (candidate.channel == RASTER_COLOR && (candidate.match.yOffset != 0 || candidate.match.xOffset != 0) && - (candidate.fb->fb_address & 0x1FFFFF) == (gstate.getFrameBufAddress() & 0x1FFFFF)) { + candidate.fb->fb_address == (gstate.getFrameBufRawAddress() | 0x04000000)) { relevancy -= 2; } @@ -845,10 +845,8 @@ void TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const c } void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, FramebufferNotification msg) { - const u32 mirrorMask = 0x00600000; const u32 fb_addr = framebuffer->fb_address; - - const u32 z_addr = framebuffer->z_address & ~mirrorMask; // Probably unnecessary. + const u32 z_addr = framebuffer->z_address; const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); const u32 z_bpp = 2; // No other format exists. @@ -931,7 +929,7 @@ bool TextureCacheCommon::MatchFramebuffer( uint32_t fb_stride_in_bytes = fb_stride * BufferFormatBytesPerPixel(fb_format); uint32_t tex_stride_in_bytes = entry.bufw * textureBitsPerPixel[entry.format] / 8; // Note, we're looking up bits here so need to divide by 8. - u32 addr = fb_address & 0x3FFFFFFF; + u32 addr = fb_address; u32 texaddr = entry.addr + texaddrOffset; bool texInVRAM = Memory::IsVRAMAddress(texaddr); @@ -943,10 +941,10 @@ bool TextureCacheCommon::MatchFramebuffer( } if (texInVRAM) { - const u32 mirrorMask = 0x00600000; + const u32 mirrorMask = 0x041FFFFF; - addr &= ~mirrorMask; - texaddr &= ~mirrorMask; + addr &= mirrorMask; + texaddr &= mirrorMask; } const bool noOffset = texaddr == addr; @@ -1205,8 +1203,8 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { if (Memory::IsValidAddress(clutAddr)) { if (Memory::IsVRAMAddress(clutAddr)) { - // Clear the uncached bit, etc. to match framebuffers. - const u32 clutLoadAddr = clutAddr & 0x3FFFFFFF; + // Clear the uncached and mirror bits, etc. to match framebuffers. + const u32 clutLoadAddr = clutAddr & 0x041FFFFF; const u32 clutLoadEnd = clutLoadAddr + loadBytes; static const u32 MAX_CLUT_OFFSET = 4096; @@ -1217,7 +1215,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { VirtualFramebuffer *chosenFramebuffer = nullptr; for (VirtualFramebuffer *framebuffer : framebuffers) { - const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF; + const u32 fb_address = framebuffer->fb_address; const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); int offset = clutLoadAddr - fb_address; diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index c3d9489b93..f0281c400e 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -140,7 +140,7 @@ return; } - const u32 fb_address = vfb->fb_address & 0x3FFFFFFF; + const u32 fb_address = vfb->fb_address; const int dstBpp = vfb->fb_format == GE_FORMAT_8888 ? 4 : 2; // We always need to convert from the framebuffer native format. diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 2cc899c11e..fa9d4b575c 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -213,7 +213,7 @@ struct GPUgstate { GEBufferFormat FrameBufFormat() const { return static_cast(framebufpixformat & 3); } int FrameBufStride() const { return fbwidth&0x7FC; } u32 getDepthBufRawAddress() const { return zbptr & 0x1FFFF0; } - u32 getDepthBufAddress() const { return 0x44000000 | getDepthBufRawAddress(); } + u32 getDepthBufAddress() const { return 0x44600000 | getDepthBufRawAddress(); } int DepthBufStride() const { return zbwidth&0x7FC; } // Pixel Pipeline diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index d36c8137b3..9a008e6658 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -286,7 +286,7 @@ void BinManager::MarkPendingWrites(const Rasterizer::RasterizerState &state) { DrawingCoords scissorTL(gstate.getScissorX1(), gstate.getScissorY1()); DrawingCoords scissorBR(std::min(gstate.getScissorX2(), gstate.getRegionX2()), std::min(gstate.getScissorY2(), gstate.getRegionY2())); - constexpr uint32_t mirrorMask = 0x0FFFFFFF & ~0x00600000; + constexpr uint32_t mirrorMask = 0x041FFFFF; const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2; pendingWrites_[0].Expand(gstate.getFrameBufAddress() & mirrorMask, bpp, gstate.FrameBufStride(), scissorTL, scissorBR); if (state.pixelID.depthWrite) @@ -538,7 +538,7 @@ bool BinManager::HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, ui if (!Memory::IsVRAMAddress(start)) return false; // Ignore mirrors for overlap detection. - start &= 0x0FFFFFFF & ~0x00600000; + start &= 0x041FFFFF; uint32_t size = stride * (h - 1) + w; for (const auto &range : pendingWrites_) { @@ -569,7 +569,7 @@ bool BinManager::HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, ui bool BinManager::HasPendingRead(uint32_t start, uint32_t stride, uint32_t w, uint32_t h) { if (Memory::IsVRAMAddress(start)) { // Ignore VRAM mirrors. - start &= 0x0FFFFFFF & ~0x00600000; + start &= 0x041FFFFF; } else { // Ignore only regular RAM mirrors. start &= 0x3FFFFFFF; From 0be891c7ff830f1bdc68af5b99e9baa916f27c5a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 2 Oct 2022 21:31:07 -0700 Subject: [PATCH 293/326] softgpu: Minor opt, ignore unused z_stride. --- GPU/Software/FuncId.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GPU/Software/FuncId.cpp b/GPU/Software/FuncId.cpp index 90a51778ae..e1520b8d72 100644 --- a/GPU/Software/FuncId.cpp +++ b/GPU/Software/FuncId.cpp @@ -56,7 +56,7 @@ void ComputePixelFuncID(PixelFuncID *id) { // Dither happens even in clear mode. id->dithering = gstate.isDitherEnabled(); id->fbFormat = gstate.FrameBufFormat(); - id->useStandardStride = gstate.FrameBufStride() == 512 && gstate.DepthBufStride() == 512; + id->useStandardStride = gstate.FrameBufStride() == 512; id->applyColorWriteMask = gstate.getColorMask() != 0; id->clearMode = gstate.isModeClear(); @@ -187,6 +187,9 @@ void ComputePixelFuncID(PixelFuncID *id) { } } + if (id->useStandardStride && (id->depthTestFunc != GE_COMP_ALWAYS || id->depthWrite)) + id->useStandardStride = gstate.DepthBufStride() == 512; + // Cache some values for later convenience. if (id->dithering) { for (int y = 0; y < 4; ++y) { From 09f0c0c8fd04584a565c3a7ded92416583a6a2fa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 3 Oct 2022 10:47:55 +0300 Subject: [PATCH 294/326] Fixed syntax accessing method reviewed by Unknown W. Brackets Signed-off-by: lainon --- Common/GPU/D3D9/D3D9ShaderCompiler.cpp | 2 +- Common/GPU/ShaderTranslation.cpp | 2 +- Core/Debugger/WebSocket/WebSocketUtils.cpp | 2 +- Core/Loaders.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp index 5a647196fe..d3821066aa 100644 --- a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp +++ b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp @@ -47,7 +47,7 @@ LPD3DBLOB CompileShaderToByteCodeD3D9(const char *code, const char *target, std: pShaderCode = nullptr; } } else { - (*errorMessage).clear(); + errorMessage->clear(); } return pShaderCode; diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp index 84cd69afe5..42e540a40f 100644 --- a/Common/GPU/ShaderTranslation.cpp +++ b/Common/GPU/ShaderTranslation.cpp @@ -233,7 +233,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan return false; #endif - (*errorMessage).clear(); + errorMessage->clear(); glslang::TProgram program; const char *shaderStrings[1]{}; diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index be1a1371a9..3ba5984f4b 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -262,7 +262,7 @@ bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerPa return true; } else if (tag == JSON_NULL) { if (required) { - (*out).clear(); + out->clear(); } return true; } else if (tag == JSON_NUMBER) { diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 02625a16c5..37722fe0cf 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -60,7 +60,7 @@ FileLoader *ConstructFileLoader(const Path &filename) { // TODO : improve, look in the file more IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorString) { - (*errorString).clear(); + errorString->clear(); if (fileLoader == nullptr) { *errorString = "Invalid fileLoader"; return IdentifiedFileType::ERROR_IDENTIFYING; From ba78854759f96bebfc9c5875146e19af26f304a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 3 Oct 2022 11:20:36 +0200 Subject: [PATCH 295/326] Improve Vulkan profiler (show framebuffer tag for render passes) --- Common/GPU/Vulkan/VulkanQueueRunner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 428d66e2cc..9ac19c7051 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -889,7 +889,7 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_DEPTH_INPUT"; break; default: renderCmd = "N/A"; } - snprintf(buffer, sizeof(buffer), "%s %s (draws: %d, %dx%d/%dx%d, fb: %p, )", renderCmd, step.tag, step.render.numDraws, actual_w, actual_h, w, h, step.render.framebuffer); + snprintf(buffer, sizeof(buffer), "%s %s %s (draws: %d, %dx%d/%dx%d)", renderCmd, step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", step.render.numDraws, actual_w, actual_h, w, h); break; } case VKRStepType::COPY: @@ -899,7 +899,7 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { snprintf(buffer, sizeof(buffer), "BLIT '%s' %s -> %s (%dx%d->%dx%d, %s)", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.blit.srcRect.extent.width, step.blit.srcRect.extent.height, step.blit.dstRect.extent.width, step.blit.dstRect.extent.height, AspectToString(step.blit.aspectMask)); break; case VKRStepType::READBACK: - snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)", step.tag, step.readback.src->Tag(), step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, AspectToString(step.readback.aspectMask)); + snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)", step.tag, step.readback.src ? step.readback.src->Tag() : "(backbuffer)", step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, AspectToString(step.readback.aspectMask)); break; case VKRStepType::READBACK_IMAGE: snprintf(buffer, sizeof(buffer), "READBACK_IMAGE '%s' (%dx%d)", step.tag, step.readback_image.srcRect.extent.width, step.readback_image.srcRect.extent.height); From 2207c1271a0f8f37df1dbf353514494ce0d0d5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 3 Oct 2022 12:13:11 +0200 Subject: [PATCH 296/326] Fix validation error with the GPU profiler --- Common/GPU/Vulkan/VulkanFrameData.cpp | 5 +++++ Common/GPU/Vulkan/VulkanFrameData.h | 2 ++ Common/GPU/Vulkan/VulkanRenderManager.cpp | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index 447630e0af..ab74478396 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -110,6 +110,11 @@ VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) { if (res != VK_SUCCESS) { return VK_NULL_HANDLE; } + + // Good spot to reset the query pool. + vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES); + vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, profile.queryPool, 0); + hasInitCommands = true; } return initCmd; diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 049f8d765c..88d4c185d2 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -80,6 +80,8 @@ struct FrameData { void AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared); VkResult QueuePresent(VulkanContext *vulkan, FrameDataShared &shared); + + // Generally called from the main thread, unlike most of the rest. VkCommandBuffer GetInitCmd(VulkanContext *vulkan); // This will only submit if we are actually recording init commands. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 344b059bbc..3343bd889f 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -609,8 +609,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile frameData.profile.timestampDescriptions.push_back("initCmd Begin"); frameData.profile.timestampDescriptions.push_back("initCmd"); VkCommandBuffer initCmd = GetInitCmd(); - vkCmdResetQueryPool(initCmd, frameData.profile.queryPool, 0, MAX_TIMESTAMP_QUERIES); - vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, frameData.profile.queryPool, 0); } } From e025629d4bd7d690db1441518ee00d2f7e027960 Mon Sep 17 00:00:00 2001 From: Kingcom Date: Mon, 3 Oct 2022 15:50:33 +0200 Subject: [PATCH 297/326] Attempt to fix libretro build --- .gitlab-ci.yml | 2 +- libretro/Makefile | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8648726fd7..32ee69b692 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ # Core definitions variables: - GIT_SUBMODULE_STRATEGY: normal + GIT_SUBMODULE_STRATEGY: recursive .core-defs: variables: diff --git a/libretro/Makefile b/libretro/Makefile index 1fd4f48632..088ad22267 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -332,7 +332,7 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) endif CFLAGS += $(MSVC2017CompileFlags) -nologo - CXXFLAGS += $(MSVC2017CompileFlags) -nologo -EHsc + CXXFLAGS += $(MSVC2017CompileFlags) -nologo -EHsc -DARMIPS_USE_STD_FILESYSTEM TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) @@ -443,7 +443,7 @@ else ifneq (,$(findstring windows_msvc2019,$(platform))) endif CFLAGS += $(MSVC2019CompileFlags) -nologo - CXXFLAGS += $(MSVC2019CompileFlags) -nologo -EHsc + CXXFLAGS += $(MSVC2019CompileFlags) -nologo -EHsc -DARMIPS_USE_STD_FILESYSTEM TargetArchMoniker = $(subst $(WinPartition)_,,$(PlatformSuffix)) @@ -537,7 +537,7 @@ else ifneq (,$(findstring windows_msvc2019,$(platform))) else ifneq (,$(findstring win,$(platform))) TARGET := $(TARGET_NAME)_libretro.dll CFLAGS += -D_UNICODE -DUNICODE - CXXFLAGS += -fpermissive -Wno-multichar -D_UNICODE -DUNICODE + CXXFLAGS += -fpermissive -Wno-multichar -D_UNICODE -DUNICODE -DARMIPS_USE_STD_FILESYSTEM LDFLAGS += -shared -Wl,--no-undefined -static-libgcc -static-libstdc++ -Wl,--version-script=link.T -lwinmm -lgdi32 -lwsock32 -lws2_32 -ld3d9 -ld3dx9 GL_LIB := -lopengl32 PLATFORM_EXT = win32 @@ -578,6 +578,8 @@ endif ifeq (,$(findstring msvc,$(platform))) CXXFLAGS += -std=c++17 +else + CXXFLAGS += -std:c++17 endif ### Finalize ### From 49097d359b8964adf476ad3425d6efe40619b290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 3 Oct 2022 16:36:03 +0200 Subject: [PATCH 298/326] More libretro buildfix, thanks stuken --- libretro/Makefile.common | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 8eed277932..56c748b52a 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -210,6 +210,9 @@ SOURCES_CXX += \ SOURCES_CXX += $(EXTDIR)/cityhash/city.cpp +SOURCES_CXX += \ + $(EXTDIR)/armips/Core/Types.cpp + SOURCES_CXX += \ $(COMMONDIR)/Crypto/md5.cpp \ $(COMMONDIR)/Crypto/sha1.cpp \ From 973d0435c1e38b44430bd5a0c54bb5ee4f3a5f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 3 Oct 2022 19:02:16 +0200 Subject: [PATCH 299/326] Fix another crash with non-buffered rendering --- GPU/Common/FramebufferManagerCommon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index aa2bafe54a..109dcfe675 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -574,9 +574,12 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // Technically, here we should copy away the depth buffer to another framebuffer that uses that z_address, or maybe // even write it back to RAM. However, this is rare. Silent Hill is one example, see #16126. currentRenderVfb_->z_address = boundDepthBuffer; - char tag[128]; - FormatFramebufferName(currentRenderVfb_, tag, sizeof(tag)); - currentRenderVfb_->fbo->UpdateTag(tag); + + if (currentRenderVfb_->fbo) { + char tag[128]; + FormatFramebufferName(currentRenderVfb_, tag, sizeof(tag)); + currentRenderVfb_->fbo->UpdateTag(tag); + } } // If this first draw call is anything other than a clear, "resolve" the depth buffer, From 1469a32a9da3ee94f0cb6cb14fb96410918287ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 3 Oct 2022 19:06:02 +0200 Subject: [PATCH 300/326] Vertex decoder: Add fallback for non-SSE4.1 See #16157 --- GPU/Common/VertexDecoderX86.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index 13aabe2df3..3618071c76 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -1391,7 +1391,13 @@ void VertexDecoderJitCache::Jit_PosFloatThrough() { CVTTPS2DQ(fpScratchReg, R(fpScratchReg)); // Use pack to saturate to 0,65535. - PACKUSDW(fpScratchReg, R(fpScratchReg)); + if (cpu_info.bSSE4_1) { + PACKUSDW(fpScratchReg, R(fpScratchReg)); + } else { + PSLLD(fpScratchReg, 16); + PSRAD(fpScratchReg, 16); + PACKSSDW(fpScratchReg, R(fpScratchReg)); + } PUNPCKLWD(fpScratchReg, R(fpScratchReg2)); CVTDQ2PS(fpScratchReg, R(fpScratchReg)); From 95e6eaa6019ec530e0399ebb3670a23c1b91ece4 Mon Sep 17 00:00:00 2001 From: sum2012 Date: Tue, 4 Oct 2022 08:29:23 +0800 Subject: [PATCH 301/326] Add crc calculation in loading module --- Core/HLE/sceKernelModule.cpp | 6 +++++- UI/GameScreen.cpp | 11 +++++------ UI/GameScreen.h | 2 ++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index a32604e928..5029e22ade 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -67,6 +67,7 @@ #include "GPU/GPU.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" +#include "UI/GameScreen.h" enum { PSP_THREAD_ATTR_KERNEL = 0x00001000, @@ -1144,6 +1145,9 @@ static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) { } static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) { + u32 crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, ptr, elfSize); + const std::string crcstring = int2hexstr(crc); PSPModule *module = new PSPModule(); kernelObjects.Create(module); loadedModules.insert(module->GetUID()); @@ -1170,7 +1174,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load if (IsHLEVersionedModule(head->modname)) { int ver = (head->module_ver_hi << 8) | head->module_ver_lo; - INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", head->modname, ver, head->devkitversion); + INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x, crc %s", head->modname, ver, head->devkitversion, crcstring.c_str()); reportedModule = true; if (!strcmp(head->modname, "sceMpeg_library")) { diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index edf519f46b..114b6e7a35 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -44,6 +44,7 @@ #include "UI/MainScreen.h" #include "UI/BackgroundAudio.h" #include "Core/Reporting.h" +#include GameScreen::GameScreen(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) { g_BackgroundAudio.SetGame(gamePath); @@ -55,12 +56,10 @@ GameScreen::~GameScreen() { } } -template std::string int2hexstr(I w, size_t hex_len = sizeof(I) << 1) { - static const char* digits = "0123456789ABCDEF"; - std::string rc(hex_len, '0'); - for (size_t i = 0, j = (hex_len - 1) * 4; i < hex_len; ++i, j -= 4) - rc[i] = digits[(w >> j) & 0x0f]; - return rc; +std::string int2hexstr(const int a) { + std::stringstream stream; + stream << std::hex << a; + return stream.str(); } void GameScreen::update() { diff --git a/UI/GameScreen.h b/UI/GameScreen.h index cff0c48366..1018acbf10 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -29,6 +29,8 @@ // Uses GameInfoCache heavily to implement the functionality. // Should possibly merge this with the PauseScreen. +std::string int2hexstr(const int a); + class GameScreen : public UIDialogScreenWithGameBackground { public: GameScreen(const Path &gamePath); From bbe9c410989b37059b6cba8a72886bb890472a2f Mon Sep 17 00:00:00 2001 From: sum2012 Date: Tue, 4 Oct 2022 09:21:11 +0800 Subject: [PATCH 302/326] Add crc record into mpeg module --- Core/HLE/sceKernelModule.cpp | 4 ++-- Core/HLE/sceMpeg.cpp | 17 +++++++++++++---- Core/HLE/sceMpeg.h | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 5029e22ade..c53811c752 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1178,7 +1178,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load reportedModule = true; if (!strcmp(head->modname, "sceMpeg_library")) { - __MpegLoadModule(ver); + __MpegLoadModule(ver, crcstring); } if (!strcmp(head->modname, "scePsmfP_library") || !strcmp(head->modname, "scePsmfPlayer")) { __PsmfPlayerLoadModule(head->devkitversion); @@ -1614,7 +1614,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion); if (!strcmp(modinfo->name, "sceMpeg_library")) { - __MpegLoadModule(modinfo->moduleVersion); + __MpegLoadModule(modinfo->moduleVersion, crcstring); } if (!strcmp(modinfo->name, "scePsmfP_library") || !strcmp(modinfo->name, "scePsmfPlayer")) { __PsmfPlayerLoadModule(devkitVersion); diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 0d7a81b758..cb9fd9aba1 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -258,7 +258,8 @@ struct MpegContext { }; static bool isMpegInit; -static int mpegLibVersion; +static int mpegLibVersion = 0; +static std::string mpegLibCrcstring = "null"; static u32 streamIdGen; static int actionPostPut; static std::map mpegMap; @@ -403,7 +404,7 @@ void __MpegInit() { } void __MpegDoState(PointerWrap &p) { - auto s = p.Section("sceMpeg", 1, 3); + auto s = p.Section("sceMpeg", 1, 4); if (!s) return; @@ -421,7 +422,14 @@ void __MpegDoState(PointerWrap &p) { ringbufferPutPacketsAdded = 0; } else { Do(p, ringbufferPutPacketsAdded); + } + if (s < 4) { + mpegLibCrcstring = "null"; } + else { + Do(p, mpegLibCrcstring); + } + Do(p, streamIdGen); Do(p, mpegLibVersion); } @@ -440,8 +448,9 @@ void __MpegShutdown() { mpegMap.clear(); } -void __MpegLoadModule(int version) { +void __MpegLoadModule(int version,std::string crcstr) { mpegLibVersion = version; + mpegLibCrcstring = crcstr; } static u32 sceMpegInit() { @@ -450,7 +459,7 @@ static u32 sceMpegInit() { // TODO: Need to properly hook module load/unload for this to work right. //return ERROR_MPEG_ALREADY_INIT; } else { - INFO_LOG(ME, "sceMpegInit()"); + INFO_LOG(ME, "sceMpegInit(), mpegLibVersion 0x%0x, mpegLibcrc %s", mpegLibVersion, mpegLibCrcstring.c_str()); } isMpegInit = true; return hleDelayResult(0, "mpeg init", 750); diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h index aded1238cc..538c60c9a8 100644 --- a/Core/HLE/sceMpeg.h +++ b/Core/HLE/sceMpeg.h @@ -79,7 +79,7 @@ void __MpegInit(); void __MpegDoState(PointerWrap &p); void __MpegShutdown(); -void __MpegLoadModule(int version); +void __MpegLoadModule(int version,std::string crcstr); void Register_sceMpeg(); From af93a7185bb02a677deca4496df83c7e98542c9b Mon Sep 17 00:00:00 2001 From: sum2012 Date: Tue, 4 Oct 2022 09:50:33 +0800 Subject: [PATCH 303/326] Add crc record to psmfplayer --- Core/HLE/sceKernelModule.cpp | 4 ++-- Core/HLE/scePsmf.cpp | 14 +++++++++++--- Core/HLE/scePsmf.h | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index c53811c752..f542354805 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1181,7 +1181,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load __MpegLoadModule(ver, crcstring); } if (!strcmp(head->modname, "scePsmfP_library") || !strcmp(head->modname, "scePsmfPlayer")) { - __PsmfPlayerLoadModule(head->devkitversion); + __PsmfPlayerLoadModule(head->devkitversion, crcstring); } } @@ -1617,7 +1617,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load __MpegLoadModule(modinfo->moduleVersion, crcstring); } if (!strcmp(modinfo->name, "scePsmfP_library") || !strcmp(modinfo->name, "scePsmfPlayer")) { - __PsmfPlayerLoadModule(devkitVersion); + __PsmfPlayerLoadModule(devkitVersion, crcstring); } } diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index 393469f029..9b81e83a96 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -59,6 +59,7 @@ static const int audioSamplesBytes = audioSamples * 4; static int videoPixelMode = GE_CMODE_32BIT_ABGR8888; static int videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP; static int psmfPlayerLibVersion = 0; +static std::string psmfPlayerLibcrcstring = "null"; int eventPsmfPlayerStatusChange = -1; @@ -696,8 +697,9 @@ void __PsmfInit() { eventPsmfPlayerStatusChange = CoreTiming::RegisterEvent("PsmfPlayerStatusChange", &__PsmfPlayerStatusChange); } -void __PsmfPlayerLoadModule(int devkitVersion) { +void __PsmfPlayerLoadModule(int devkitVersion, std::string crcstr) { psmfPlayerLibVersion = devkitVersion; + psmfPlayerLibcrcstring = crcstr; } void __PsmfDoState(PointerWrap &p) { @@ -722,6 +724,11 @@ void __PsmfPlayerDoState(PointerWrap &p) { Do(p, eventPsmfPlayerStatusChange); } CoreTiming::RestoreRegisterEvent(eventPsmfPlayerStatusChange, "PsmfPlayerStatusChangeEvent", &__PsmfPlayerStatusChange); + if (s < 3) { + psmfPlayerLibcrcstring = "null"; + } else { + Do(p, psmfPlayerLibcrcstring); + } if (s < 2) { // Assume the latest, which is what we were emulating before. psmfPlayerLibVersion = 0x06060010; @@ -1157,7 +1164,8 @@ static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) { int delayUs = 20000; DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_INIT, delayUs); - return hleLogSuccessInfoI(ME, hleDelayResult(0, "player create", delayUs)); + INFO_LOG(ME, "psmfplayer create, psmfPlayerLibVersion 0x%0x, psmfPlayerLibcrcstring %s", psmfPlayerLibVersion, psmfPlayerLibcrcstring.c_str()); + return hleDelayResult(0, "player create", delayUs); } static int scePsmfPlayerStop(u32 psmfPlayer) { @@ -1427,7 +1435,7 @@ static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts) psmfplayer->playMode = playerData->playMode; psmfplayer->playSpeed = playerData->playSpeed; - WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d,(mode %d, speed %d)", psmfPlayer, psmfPlayerData, initPts, playerData->playMode, playerData->playSpeed); + WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d (mode %d, speed %d)", psmfPlayer, psmfPlayerData, initPts, playerData->playMode, playerData->playSpeed); // Does not alter current pts, it just catches up when Update()/etc. get there. diff --git a/Core/HLE/scePsmf.h b/Core/HLE/scePsmf.h index 3248962470..8e201adade 100644 --- a/Core/HLE/scePsmf.h +++ b/Core/HLE/scePsmf.h @@ -21,7 +21,7 @@ void Register_scePsmf(); void Register_scePsmfPlayer(); void __PsmfInit(); -void __PsmfPlayerLoadModule(int devkitVersion); +void __PsmfPlayerLoadModule(int devkitVersion,std::string crcstr); void __PsmfDoState(PointerWrap &p); void __PsmfPlayerDoState(PointerWrap &p); void __PsmfShutdown(); From a1efed31b9dee4a477506b45cf5ccced06a0b12d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 3 Oct 2022 20:17:25 -0700 Subject: [PATCH 304/326] GPU: Use flags to fix triggered upload/download. No longer using mirror hacks. --- GPU/Common/FramebufferManagerCommon.cpp | 8 ++++---- GPU/Common/FramebufferManagerCommon.h | 2 +- GPU/Debugger/Record.cpp | 7 ++++--- GPU/GPUCommon.cpp | 17 +++++++---------- GPU/GPUCommon.h | 2 +- GPU/GPUInterface.h | 12 +++++++++++- GPU/Software/SoftGpu.cpp | 6 +++--- GPU/Software/SoftGpu.h | 2 +- 8 files changed, 32 insertions(+), 24 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index f31d8c79d1..878b677466 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1634,7 +1634,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, // This is called from detected memcopies and framebuffer initialization from VRAM. Not block transfers. // MotoGP goes this path so we need to catch those copies here. -bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, bool isMemset, u32 skipDrawReason) { +bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, GPUCopyFlag flags, u32 skipDrawReason) { if (size == 0) { return false; } @@ -1728,7 +1728,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, dstBuffer->last_frame_used = gpuStats.numFlips; } - if (dstBuffer && srcBuffer && !isMemset) { + if (dstBuffer && srcBuffer && !(flags & GPUCopyFlag::MEMSET) && !(flags & GPUCopyFlag::FORCE_SRC_MEM) && !(flags & GPUCopyFlag::FORCE_DST_MEM)) { if (srcBuffer == dstBuffer) { WARN_LOG_ONCE(dstsrccpy, G3D, "Intra-buffer memcpy (not supported) %08x -> %08x (size: %x)", src, dst, size); } else { @@ -1739,8 +1739,8 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, RebindFramebuffer("RebindFramebuffer - Inter-buffer memcpy"); } return false; - } else if (dstBuffer) { - if (isMemset) { + } else if (dstBuffer && !(flags & GPUCopyFlag::FORCE_DST_MEM)) { + if (flags & GPUCopyFlag::MEMSET) { gpuStats.numClears++; } WARN_LOG_ONCE(btucpy, G3D, "Memcpy fbo upload %08x -> %08x (size: %x)", src, dst, size); diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index d4bd905f0d..b3bd21d7c9 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -306,7 +306,7 @@ public: void CopyDisplayToOutput(bool reallyDirty); - bool NotifyFramebufferCopy(u32 src, u32 dest, int size, bool isMemset, u32 skipDrawReason); + bool NotifyFramebufferCopy(u32 src, u32 dest, int size, GPUCopyFlag flags, u32 skipDrawReason); void NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt); void UpdateFromMemory(u32 addr, int size); void ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor); diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 9947519b5e..6058546b0f 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -691,10 +691,11 @@ void NotifyUpload(u32 dest, u32 sz) { return; } - CheckEdramTrans(); - NotifyMemcpy(dest, dest, sz); - if (Memory::IsVRAMAddress(dest)) + if (Memory::IsVRAMAddress(dest)) { + // This also checks the edram translation value. + NotifyMemcpy(dest, dest, sz); DirtyVRAM(dest, sz, DirtyVRAMFlag::DIRTY); + } } static bool HasDrawCommands() { diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index a44fb47572..4c43b0a992 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3046,10 +3046,10 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) { cyclesExecuted += ((height * width * bpp) * 16) / 10; } -bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) { +bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) { // Track stray copies of a framebuffer in RAM. MotoGP does this. if (framebufferManager_->MayIntersectFramebuffer(src) || framebufferManager_->MayIntersectFramebuffer(dest)) { - if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, false, gstate_c.skipDrawReason)) { + if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, flags, gstate_c.skipDrawReason)) { // We use a little hack for PerformMemoryDownload/PerformMemoryUpload using a VRAM mirror. // Since they're identical we don't need to copy. if (!Memory::IsVRAMAddress(dest) || (dest ^ 0x00400000) != src) { @@ -3071,7 +3071,8 @@ bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) { NotifyMemInfo(MemBlockFlags::WRITE, dest, size, tag.c_str(), tag.size()); } InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - GPURecord::NotifyMemcpy(dest, src, size); + if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED)) + GPURecord::NotifyMemcpy(dest, src, size); return false; } @@ -3079,7 +3080,7 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) { // This may indicate a memset, usually to 0, of a framebuffer. if (framebufferManager_->MayIntersectFramebuffer(dest)) { Memory::Memset(dest, v, size, "GPUMemset"); - if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, true, gstate_c.skipDrawReason)) { + if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, GPUCopyFlag::MEMSET, gstate_c.skipDrawReason)) { InvalidateCache(dest, size, GPU_INVALIDATE_HINT); } return true; @@ -3093,20 +3094,16 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) { } bool GPUCommon::PerformMemoryDownload(u32 dest, int size) { - // Cheat a bit to force a download of the framebuffer. - // VRAM + 0x00400000 is simply a VRAM mirror. if (Memory::IsVRAMAddress(dest)) { - return PerformMemoryCopy(dest ^ 0x00400000, dest, size); + return PerformMemoryCopy(dest, dest, size, GPUCopyFlag::FORCE_DST_MEM); } return false; } bool GPUCommon::PerformMemoryUpload(u32 dest, int size) { - // Cheat a bit to force an upload of the framebuffer. - // VRAM + 0x00400000 is simply a VRAM mirror. if (Memory::IsVRAMAddress(dest)) { GPURecord::NotifyUpload(dest, size); - return PerformMemoryCopy(dest, dest ^ 0x00400000, size); + return PerformMemoryCopy(dest, dest, size, GPUCopyFlag::FORCE_SRC_MEM | GPUCopyFlag::DEBUG_NOTIFIED); } return false; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 78f7b15cef..3c478e2cec 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -124,7 +124,7 @@ 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) override; + bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; bool PerformMemoryDownload(u32 dest, int size) override; bool PerformMemoryUpload(u32 dest, int size) override; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index e0c5931326..e7cf2f9af6 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -114,6 +114,16 @@ enum class StencilUpload { }; ENUM_CLASS_BITOPS(StencilUpload); +enum class GPUCopyFlag { + NONE = 0, + FORCE_SRC_MEM = 1, + FORCE_DST_MEM = 2, + // Note: implies src == dst and FORCE_SRC_MEM. + MEMSET = 4, + DEBUG_NOTIFIED = 8, +}; +ENUM_CLASS_BITOPS(GPUCopyFlag); + // Used for debug struct FramebufferInfo { u32 fb_address; @@ -222,7 +232,7 @@ public: virtual void InvalidateCache(u32 addr, int size, GPUInvalidationType type) = 0; virtual void NotifyVideoUpload(u32 addr, int size, int width, int format) = 0; // Update either RAM from VRAM, or VRAM from RAM... or even VRAM from VRAM. - virtual bool PerformMemoryCopy(u32 dest, u32 src, int size) = 0; + virtual bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) = 0; virtual bool PerformMemorySet(u32 dest, u8 v, int size) = 0; virtual bool PerformMemoryDownload(u32 dest, int size) = 0; virtual bool PerformMemoryUpload(u32 dest, int size) = 0; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index cddfa557d9..28fe4041fb 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1265,11 +1265,11 @@ void SoftGPU::NotifyVideoUpload(u32 addr, int size, int width, int format) // Ignore. } -bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size) -{ +bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - GPURecord::NotifyMemcpy(dest, src, size); + if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED)) + GPURecord::NotifyMemcpy(dest, src, size); // Let's just be safe. MarkDirty(dest, size, SoftGPUVRAMDirty::DIRTY | SoftGPUVRAMDirty::REALLY_DIRTY); return false; diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 29ce1e5934..84732eb43a 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -139,7 +139,7 @@ public: void GetStats(char *buffer, size_t bufsize) override; void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; void NotifyVideoUpload(u32 addr, int size, int width, int format) override; - bool PerformMemoryCopy(u32 dest, u32 src, int size) override; + bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; bool PerformMemoryDownload(u32 dest, int size) override; bool PerformMemoryUpload(u32 dest, int size) override; From 9ac4523fd2238d910d6756d80217e8088ad39d0b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 3 Oct 2022 20:22:27 -0700 Subject: [PATCH 305/326] GPU: Skip matching a framebuf for RAM. --- GPU/Common/FramebufferManagerCommon.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 878b677466..b688355706 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1650,8 +1650,11 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, // Or at least this should be like the other ones, gathering possible candidates // with the ability to list them out for debugging. - VirtualFramebuffer *dstBuffer = 0; - VirtualFramebuffer *srcBuffer = 0; + VirtualFramebuffer *dstBuffer = nullptr; + VirtualFramebuffer *srcBuffer = nullptr; + bool ignoreDstBuffer = flags & GPUCopyFlag::FORCE_DST_MEM; + bool ignoreSrcBuffer = flags & (GPUCopyFlag::FORCE_SRC_MEM | GPUCopyFlag::MEMSET); + u32 dstY = (u32)-1; u32 dstH = 0; u32 srcY = (u32)-1; @@ -1669,14 +1672,14 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, const int vfb_byteWidth = vfb->width * vfb_bpp; // Heuristic to try to prevent potential glitches with video playback. - if (vfb_address == dst && (size == 0x44000 && vfb_size == 0x88000 || size == 0x88000 && vfb_size == 0x44000)) { + if (!ignoreDstBuffer && vfb_address == dst && (size == 0x44000 && vfb_size == 0x88000 || size == 0x88000 && vfb_size == 0x44000)) { // Not likely to be a correct color format copy for this buffer. Ignore it, there will either be RAM // that can be displayed from, or another matching buffer with the right format if rendering is going on. WARN_LOG_N_TIMES(notify_copy_2x, 5, G3D, "Framebuffer size %08x conspicuously not matching copy size %08x in NotifyFramebufferCopy. Ignoring.", size, vfb_size); continue; } - if (dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) { + if (!ignoreDstBuffer && dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) { const u32 offset = dst - vfb_address; const u32 yOffset = offset / vfb_byteStride; if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < dstY) { @@ -1686,7 +1689,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, } } - if (src >= vfb_address && (src + size <= vfb_address + vfb_size || src == vfb_address)) { + if (!ignoreSrcBuffer && src >= vfb_address && (src + size <= vfb_address + vfb_size || src == vfb_address)) { const u32 offset = src - vfb_address; const u32 yOffset = offset / vfb_byteStride; if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < srcY) { @@ -1728,7 +1731,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, dstBuffer->last_frame_used = gpuStats.numFlips; } - if (dstBuffer && srcBuffer && !(flags & GPUCopyFlag::MEMSET) && !(flags & GPUCopyFlag::FORCE_SRC_MEM) && !(flags & GPUCopyFlag::FORCE_DST_MEM)) { + if (dstBuffer && srcBuffer) { if (srcBuffer == dstBuffer) { WARN_LOG_ONCE(dstsrccpy, G3D, "Intra-buffer memcpy (not supported) %08x -> %08x (size: %x)", src, dst, size); } else { @@ -1739,7 +1742,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, RebindFramebuffer("RebindFramebuffer - Inter-buffer memcpy"); } return false; - } else if (dstBuffer && !(flags & GPUCopyFlag::FORCE_DST_MEM)) { + } else if (dstBuffer) { if (flags & GPUCopyFlag::MEMSET) { gpuStats.numClears++; } From 362391b9d88ad819fc862f96ff6640087acf7765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 4 Oct 2022 20:56:41 +0200 Subject: [PATCH 306/326] Fix Kurohyou again. See #9576 --- GPU/Common/FramebufferManagerCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index a72b1ca2bf..df0342724a 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -566,7 +566,7 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { bool newlyUsingDepth = (currentRenderVfb_->usageFlags & FB_USAGE_RENDER_DEPTH) == 0; currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; - uint32_t boundDepthBuffer = gstate.getDepthBufRawAddress(); + uint32_t boundDepthBuffer = gstate.getDepthBufRawAddress() | 0x04000000; if (currentRenderVfb_->z_address != boundDepthBuffer) { WARN_LOG_N_TIMES(z_reassign, 5, G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.", currentRenderVfb_->fb_address, currentRenderVfb_->fb_stride, currentRenderVfb_->z_address, boundDepthBuffer); From 09e9488de8a3343aeec4361d8cdc18e6659747fd Mon Sep 17 00:00:00 2001 From: sum2012 Date: Wed, 5 Oct 2022 06:22:40 +0800 Subject: [PATCH 307/326] Try to build fix --- UI/GameScreen.cpp | 5 +++-- UI/GameScreen.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 114b6e7a35..89490193df 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -56,10 +56,11 @@ GameScreen::~GameScreen() { } } -std::string int2hexstr(const int a) { +const char* int2hexstr(const int a) { std::stringstream stream; stream << std::hex << a; - return stream.str(); + auto s = stream.str(); + return s.c_str(); } void GameScreen::update() { diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 1018acbf10..43671ec272 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -29,7 +29,7 @@ // Uses GameInfoCache heavily to implement the functionality. // Should possibly merge this with the PauseScreen. -std::string int2hexstr(const int a); +const char* int2hexstr(const int a); class GameScreen : public UIDialogScreenWithGameBackground { public: From 32f5a7e95fe98e7976c4ac3efac64a86ea965dce Mon Sep 17 00:00:00 2001 From: GITNE Date: Fri, 30 Sep 2022 23:59:59 +0000 Subject: [PATCH 308/326] Improve Freedesktop integration * Move scalable icon install from `${prefix}/share/pixmaps` (`pixmaps` has been deprecated long time ago) to `${prefix}/share/icons/hicolor/scalable/apps` * Add `Keywords`, `Categories`, and `MimeType` keys to `.desktop` file * Add shared MIME-info on [`application/x-compressed-iso`](https://en.wikipedia.org/wiki/.CSO "Compressed ISO") MIME type --- CMakeLists.txt | 10 ++++++++-- assets/mime/ppsspp.xml | 14 ++++++++++++++ ppsspp.desktop.in | 4 +++- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 assets/mime/ppsspp.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 52fb7e40c9..6cb38fb017 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2513,6 +2513,8 @@ if(UNIX AND NOT ANDROID AND NOT APPLE) DIRECTORY "${CMAKE_BINARY_DIR}/assets" DESTINATION "${CMAKE_INSTALL_DATADIR}/ppsspp" PATTERN ".git*" EXCLUDE + PATTERN "mime" EXCLUDE + PATTERN "lang/README.md" EXCLUDE ) install( FILES "${CMAKE_BINARY_DIR}/ppsspp.desktop" @@ -2525,7 +2527,11 @@ if(UNIX AND NOT ANDROID AND NOT APPLE) ) install( FILES "${CMAKE_SOURCE_DIR}/icons/icon-512.svg" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pixmaps" - RENAME ppsspp.svg + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps" + RENAME "ppsspp.svg" + ) + install( + FILES "${CMAKE_SOURCE_DIR}/assets/mime/ppsspp.xml" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/mime/packages" ) endif() diff --git a/assets/mime/ppsspp.xml b/assets/mime/ppsspp.xml new file mode 100644 index 0000000000..26b62e56e9 --- /dev/null +++ b/assets/mime/ppsspp.xml @@ -0,0 +1,14 @@ + + + + + + + + Compressed ISO Image + Komprimiertes ISO‑Abbild + Skomprymowany obraz ISO + CSO + + + diff --git a/ppsspp.desktop.in b/ppsspp.desktop.in index 05fd1e7348..fd8ffc35b8 100644 --- a/ppsspp.desktop.in +++ b/ppsspp.desktop.in @@ -4,4 +4,6 @@ Exec=@TargetBin@ Icon=ppsspp Type=Application Comment=ppsspp (fast and portable PSP emulator) -Categories=Game;Emulator +Keywords=Sony;PlayStation;Portable;PSP;handheld;console; +Categories=Game;Emulator; +MimeType=application/x-cd-image;application/x-iso9660-image;application/x-compressed-iso;application/zip; From 8025def8d25e8df32719ecfbf6d93069685ab182 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 4 Oct 2022 22:10:24 -0700 Subject: [PATCH 309/326] Vulkan: Clip to neg z in the geometry shader. This is only used when clip distance is unsupported, such as on Mali. --- GPU/Common/GeometryShaderGenerator.cpp | 150 ++++++++++++++++++++++--- GPU/Common/VertexShaderGenerator.cpp | 2 +- 2 files changed, 134 insertions(+), 18 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index b701bb547c..786fd25734 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -48,7 +48,7 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size()); p.C("layout(triangles) in;\n"); - p.C("layout(triangle_strip, max_vertices = 3) out;\n"); + p.C("layout(triangle_strip, max_vertices = 6) out;\n"); if (compat.shaderLanguage == GLSL_VULKAN) { WRITE(p, "\n"); @@ -76,6 +76,10 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu // Apply culling p.C(" bool anyInside = false;\n"); + // And apply manual clipping if necessary. + if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + p.C(" float clip0[3];\n"); + } p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); @@ -98,6 +102,13 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n"); p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); p.C(" }\n"); + + if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + // This is basically the same value as gl_ClipDistance would take, z + w. + // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z. + p.F(" clip0[i] = projZ * outPos.w + outPos.w;\n"); + } + p.C(" } // for\n"); // Cull any triangle fully outside in the same direction when depth clamp enabled. @@ -106,27 +117,132 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" return;\n"); p.C(" }\n"); - const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; + if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + // Clipping against one half-space cuts a triangle (17/27), culls (7/27), or creates two triangles (3/27). + p.C(" int emitted = 0;\n"); + p.C(" for (int i = 0; i < 3; i++) {\n"); + // First, emit this vertex if it doesn't need clipping + p.C(" if (clip0[i] >= 0.0) {\n"); - p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? - p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); - p.C(" gl_Position = outPos;\n"); - // TODO: Not rectangles... - if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + // But before we emit that, is this the second triangle? We'll need extra verts. + p.C(" if (emitted == 3) {\n"); + p.C(" EndPrimitive();\n"); + // In this case, it can only be +/-/+, we must emit vert 0 and then mix(1, 2) again first. + p.C(" gl_Position = gl_in[0].gl_Position;\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[0];\n", outVaryings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + // Next, it can only be the interpolated between 1 and 2 (before this one, direct 2.) + p.C(" float t = clip0[1] / (clip0[1] - clip0[2]);\n"); + p.C(" gl_Position = mix(gl_in[1].gl_Position, gl_in[2].gl_Position, t);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[1], %s[2], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + p.C(" }\n"); + + // Now emit the regular vertex itself. + p.C(" gl_Position = gl_in[i].gl_Position;\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + p.C(" }\n"); + + // Next, we generate an interpolated vertex if signs differ. + p.C(" int inext = i == 2 ? 0 : i + 1;\n"); + p.C(" if (clip0[i] * clip0[inext] < 0.0) {\n"); + + // There are two cases here: +/+/- and -/+/+. + p.C(" if (emitted == 3 && clip0[i] < 0.0) {\n"); + p.C(" EndPrimitive();\n"); + // In this case, it can only be +/-/+, we must emit vert 0 and then mix(1, 2) again first. + p.C(" gl_Position = gl_in[0].gl_Position;\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[0];\n", outVaryings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + // Next, it can only be the interpolated between 1 and 2 (before this one, direct 2.) + p.C(" float t = 1.0 - (clip0[2] / (clip0[2] - clip0[1]));\n"); + p.C(" gl_Position = mix(gl_in[1].gl_Position, gl_in[2].gl_Position, t);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[1], %s[2], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + p.C(" } else if (emitted == 3) {\n"); + p.C(" EndPrimitive();\n"); + // Now we emit mix(0, 1) first, then vert 2 again. + p.C(" float t = clip0[0] / (clip0[0] - clip0[1]);\n"); + p.C(" gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, t);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[0], %s[1], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + // Then here's vert 2. + p.C(" gl_Position = gl_in[2].gl_Position;\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[2];\n", outVaryings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + p.C(" }\n"); + + // Finally, the actual interpolated vertex. + p.C(" float t = clip0[i] < 0.0 ? clip0[i] / (clip0[i] - clip0[inext]) : 1.0 - (clip0[inext] / (clip0[inext] - clip0[i]));\n"); + p.C(" gl_Position = mix(gl_in[i].gl_Position, gl_in[inext].gl_Position, t);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[i], %s[inext], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" emitted++;\n"); + p.C(" }\n"); + p.C(" }\n"); + } else { + const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; + + p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? + p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); - p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clip0); - } + // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z. + p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0); + p.C(" gl_Position = outPos;\n"); + if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { + } - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); + } + // Debug - null the red channel + //p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); + p.C(" EmitVertex();\n"); + p.C(" }\n"); } - // Debug - null the red channel - //p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); - p.C(" EmitVertex();\n"); - p.C(" }\n"); p.EndGSMain(); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index d6f356c6d4..928aa3f6dd 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1312,7 +1312,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag const char *cull0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; const char *cull1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { - // TODO: Not rectangles... + // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z. WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", compat.vsOutPrefix, vertexRangeClipSuffix); } if (gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) { From f24edbe8a81dd4d708a23a49f7dd0d00d9d8db37 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 4 Oct 2022 22:19:40 -0700 Subject: [PATCH 310/326] Compat: Remove DisableRangeCulling. This hack was used because culling previously incorrectly handled Z, which was fixed in #14833. --- Core/Compatibility.cpp | 1 - Core/Compatibility.h | 1 - GPU/GPUCommon.cpp | 6 +-- assets/compat.ini | 83 ------------------------------------------ 4 files changed, 1 insertion(+), 90 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 94b4279a39..861e926429 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -97,7 +97,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DateLimited", &flags_.DateLimited); CheckSetting(iniFile, gameID, "ShaderColorBitmask", &flags_.ShaderColorBitmask); CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback); - CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp); CheckSetting(iniFile, gameID, "BlueToAlpha", &flags_.BlueToAlpha); CheckSetting(iniFile, gameID, "CenteredLines", &flags_.CenteredLines); diff --git a/Core/Compatibility.h b/Core/Compatibility.h index bd824b916d..8bbdc52ebd 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -77,7 +77,6 @@ struct CompatFlags { bool DateLimited; bool ShaderColorBitmask; bool DisableFirstFrameReadback; - bool DisableRangeCulling; bool MpegAvcWarmUp; bool BlueToAlpha; bool CenteredLines; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index a44fb47572..9445ab894a 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3309,11 +3309,7 @@ u32 GPUCommon::CheckGPUFeatures() const { } if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - // Ignore the compat setting if clip and cull are both enabled. - // When supported, we can do the depth side of range culling more correctly. - const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported; - const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling; - if (supported || !disabled) { + if (draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported) { features |= GPU_SUPPORTS_VS_RANGE_CULLING; } } diff --git a/assets/compat.ini b/assets/compat.ini index 6df16cff48..2fbe053168 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -156,89 +156,6 @@ NPJH90062 = true # Phantasy Star Portable Infinity Demo NPJH90157 = true # Infinity demo -[DisableRangeCulling] -# Phantasy Star games also have range culling issues. -# Phantasy Star Portable 2 and Infinity -ULJM05309 = true -ULUS10410 = true -ULES01218 = true -ULJM08023 = true -ULES01218 = true -# Phantasy Star Portable 1 Demo -NPUH90023 = true -# Phantasy Star Portable 2 -ULES01439 = true -ULUS10529 = true -ULJM05493 = true -NPJH50043 = true -ULJM08030 = true -NPUH90023 = true -ULJM91014 = true -NPJH90002 = true -ULJM05732 = true -NPJH50332 = true -# Phantasy Star Portable 2 JP Demo -ULJM91018 = true -NPJH90062 = true -# Phantasy Star Portable Infinity Demo -NPJH90157 = true # Infinity demo - -# Test Drive Unlimited -ULET00386 = true -ULES00637 = true -ULKS46126 = true -ULUS10249 = true - -# Metal Gear Solid : Peace Walker (see issue #12348) -ULUS10509 = true -ULES01372 = true -ULJM08038 = true -NPJH50045 = true -ULJM05630 = true -NPJH90082 = true -NPJH90063 = true - -# Metal Gear Solid : Peace Walker Demo Ops (see issue #12348) -NPUH90066 = true -NPHH00145 = true -NPEH90023 = true - -# Metal Gear Solid: Portable Ops (see issue #12348) -ULES00645 = true -ULJM05193 = true -ULJM05227 = true -ULUS10202 = true -ULES01003 = true -ULJM05261 = true -ULUS10290 = true -ULED90040 = true -ULJM05284 = true -ULES00645 = true - -# Splinter Cell Essentials (#13035) -ULES00281 = true -ULUS10070 = true - -# Asphalt 2 (#14299) -ULES00719 = true -# No US release, it seems - -# Star Wars: Lethal Alliance (#11551) -ULES00599 = true -ULUS10188 = true - -# Digimon World Re:Digitize -ULJS00496 = true -NPJH50588 = true -ULAS42319 = true - -# NOVA: Near Orbit Vanguard Alliance -NPUZ00179 = true -NPEZ00222 = true - -# Street Riders (only region found. See #14746) -ULES00276 = true - [ClearToRAM] # SOCOM Navy Seals games require this. See issue #8973. # Navy Seals : Tactical Strike From 5d88e50201aa40c356766329713dba899391af3d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 4 Oct 2022 23:04:25 -0700 Subject: [PATCH 311/326] Vulkan: Generate indices in clipping. --- GPU/Common/GeometryShaderGenerator.cpp | 158 +++++++++++-------------- 1 file changed, 70 insertions(+), 88 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 786fd25734..6d1b7bdcd9 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -119,106 +119,88 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { // Clipping against one half-space cuts a triangle (17/27), culls (7/27), or creates two triangles (3/27). - p.C(" int emitted = 0;\n"); + p.C(" int indices[4];\n"); + p.C(" float factors[4];\n"); + p.C(" int ind = 0;\n"); + + // Pass 1 - clip against first half-space. p.C(" for (int i = 0; i < 3; i++) {\n"); - // First, emit this vertex if it doesn't need clipping + // First, use this vertex if it doesn't need clipping. p.C(" if (clip0[i] >= 0.0) {\n"); - - // But before we emit that, is this the second triangle? We'll need extra verts. - p.C(" if (emitted == 3) {\n"); - p.C(" EndPrimitive();\n"); - // In this case, it can only be +/-/+, we must emit vert 0 and then mix(1, 2) again first. - p.C(" gl_Position = gl_in[0].gl_Position;\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[0];\n", outVaryings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - // Next, it can only be the interpolated between 1 and 2 (before this one, direct 2.) - p.C(" float t = clip0[1] / (clip0[1] - clip0[2]);\n"); - p.C(" gl_Position = mix(gl_in[1].gl_Position, gl_in[2].gl_Position, t);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[1], %s[2], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - p.C(" }\n"); - - // Now emit the regular vertex itself. - p.C(" gl_Position = gl_in[i].gl_Position;\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); + p.C(" indices[ind] = i;\n"); + p.C(" factors[ind] = 0.0;\n"); + p.C(" ind++;\n"); p.C(" }\n"); // Next, we generate an interpolated vertex if signs differ. p.C(" int inext = i == 2 ? 0 : i + 1;\n"); p.C(" if (clip0[i] * clip0[inext] < 0.0) {\n"); - - // There are two cases here: +/+/- and -/+/+. - p.C(" if (emitted == 3 && clip0[i] < 0.0) {\n"); - p.C(" EndPrimitive();\n"); - // In this case, it can only be +/-/+, we must emit vert 0 and then mix(1, 2) again first. - p.C(" gl_Position = gl_in[0].gl_Position;\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[0];\n", outVaryings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - // Next, it can only be the interpolated between 1 and 2 (before this one, direct 2.) - p.C(" float t = 1.0 - (clip0[2] / (clip0[2] - clip0[1]));\n"); - p.C(" gl_Position = mix(gl_in[1].gl_Position, gl_in[2].gl_Position, t);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[1], %s[2], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - p.C(" } else if (emitted == 3) {\n"); - p.C(" EndPrimitive();\n"); - // Now we emit mix(0, 1) first, then vert 2 again. - p.C(" float t = clip0[0] / (clip0[0] - clip0[1]);\n"); - p.C(" gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, t);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[0], %s[1], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - // Then here's vert 2. - p.C(" gl_Position = gl_in[2].gl_Position;\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[2];\n", outVaryings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - p.C(" }\n"); - - // Finally, the actual interpolated vertex. p.C(" float t = clip0[i] < 0.0 ? clip0[i] / (clip0[i] - clip0[inext]) : 1.0 - (clip0[inext] / (clip0[inext] - clip0[i]));\n"); - p.C(" gl_Position = mix(gl_in[i].gl_Position, gl_in[inext].gl_Position, t);\n"); + p.C(" indices[ind] = i;\n"); + p.C(" factors[ind] = t;\n"); + p.C(" ind++;\n"); + p.C(" }\n"); + + p.C(" }\n"); + + p.C(" if (ind < 3) {\n"); + p.C(" return;\n"); + p.C(" }\n"); + + // Alright, time to actually emit the first triangle. + p.C(" for (int i = 0; i < 3; i++) {\n"); + p.C(" int idx = indices[i];\n"); + p.C(" float factor = factors[i];\n"); + p.C(" int next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); for (size_t i = 0; i < varyings.size(); i++) { VaryingDef &in = varyings[i]; VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[i], %s[inext], t);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); } - p.C(" EmitVertex();\n"); - p.C(" emitted++;\n"); - p.C(" }\n"); + p.C(" EmitVertex();\n"); + p.C(" }\n"); + + // Did we end up with additional triangles? We'll do three points each for the rest. + p.C(" for (int i = 3; i < ind; i++) {\n"); + p.C(" EndPrimitive();\n"); + + // Point one, always index zero. + p.C(" int idx = indices[0];\n"); + p.C(" float factor = factors[0];\n"); + p.C(" int next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + + // After that, one less than i (basically a triangle fan.) + p.C(" idx = indices[i - 1];\n"); + p.C(" factor = factors[i - 1];\n"); + p.C(" next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + + // And the new vertex itself. + p.C(" idx = indices[i];\n"); + p.C(" factor = factors[i];\n"); + p.C(" next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + VaryingDef &in = varyings[i]; + VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); + } + p.C(" EmitVertex();\n"); + p.C(" }\n"); } else { const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; From c33ff58980c4e6cf7f7a69534bba4d8266d2768c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 4 Oct 2022 23:46:25 -0700 Subject: [PATCH 312/326] Reporting: Avoid accessing ticks before start. --- Core/Reporting.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index f344e1bac9..4f15062e3c 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -434,7 +434,8 @@ namespace Reporting void AddGameplayInfo(UrlEncoder &postdata) { // Just to get an idea of how long they played. - postdata.Add("ticks", (const uint64_t)CoreTiming::GetTicks()); + if (PSP_IsInited()) + postdata.Add("ticks", (const uint64_t)CoreTiming::GetTicks()); float vps, fps; __DisplayGetAveragedFPS(&vps, &fps); From c735d10021db49d5f2bf63bb2fe04124bd263363 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 4 Oct 2022 23:48:57 -0700 Subject: [PATCH 313/326] Reporting: Always report hleReportDebug. Even if debug logging is not enabled. --- Core/HLE/HLE.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h index d2a74f9d43..0331fb7b9b 100644 --- a/Core/HLE/HLE.h +++ b/Core/HLE/HLE.h @@ -189,7 +189,7 @@ T hleDoLog(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, T res, const char * template T hleDoLog(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, T res, const char *file, int line, const char *reportTag, char retmask) { - if (level > MAX_LOGLEVEL || !GenericLogEnabled(level, t)) { + if ((level > MAX_LOGLEVEL || !GenericLogEnabled(level, t)) && !reportTag) { return res; } From de6d747cf0423de74ebb41084584e35ac6e32a0c Mon Sep 17 00:00:00 2001 From: sum2012 Date: Wed, 5 Oct 2022 20:36:01 +0800 Subject: [PATCH 314/326] Store crc change to u32 Should build fixed --- Core/HLE/sceKernelModule.cpp | 11 +++++------ Core/HLE/sceMpeg.cpp | 12 ++++++------ Core/HLE/sceMpeg.h | 2 +- Core/HLE/scePsmf.cpp | 12 ++++++------ Core/HLE/scePsmf.h | 2 +- UI/GameScreen.cpp | 12 ++++++------ UI/GameScreen.h | 2 -- 7 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index f542354805..f74a78e0d0 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1147,7 +1147,6 @@ static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) { static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) { u32 crc = crc32(0, Z_NULL, 0); crc = crc32(crc, ptr, elfSize); - const std::string crcstring = int2hexstr(crc); PSPModule *module = new PSPModule(); kernelObjects.Create(module); loadedModules.insert(module->GetUID()); @@ -1174,14 +1173,14 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load if (IsHLEVersionedModule(head->modname)) { int ver = (head->module_ver_hi << 8) | head->module_ver_lo; - INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x, crc %s", head->modname, ver, head->devkitversion, crcstring.c_str()); + INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, crc); reportedModule = true; if (!strcmp(head->modname, "sceMpeg_library")) { - __MpegLoadModule(ver, crcstring); + __MpegLoadModule(ver, crc); } if (!strcmp(head->modname, "scePsmfP_library") || !strcmp(head->modname, "scePsmfPlayer")) { - __PsmfPlayerLoadModule(head->devkitversion, crcstring); + __PsmfPlayerLoadModule(head->devkitversion, crc); } } @@ -1614,10 +1613,10 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion); if (!strcmp(modinfo->name, "sceMpeg_library")) { - __MpegLoadModule(modinfo->moduleVersion, crcstring); + __MpegLoadModule(modinfo->moduleVersion, crc); } if (!strcmp(modinfo->name, "scePsmfP_library") || !strcmp(modinfo->name, "scePsmfPlayer")) { - __PsmfPlayerLoadModule(devkitVersion, crcstring); + __PsmfPlayerLoadModule(devkitVersion, crc); } } diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index cb9fd9aba1..929e782d0c 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -259,7 +259,7 @@ struct MpegContext { static bool isMpegInit; static int mpegLibVersion = 0; -static std::string mpegLibCrcstring = "null"; +static u32 mpegLibCrc = 0; static u32 streamIdGen; static int actionPostPut; static std::map mpegMap; @@ -424,10 +424,10 @@ void __MpegDoState(PointerWrap &p) { Do(p, ringbufferPutPacketsAdded); } if (s < 4) { - mpegLibCrcstring = "null"; + mpegLibCrc = 0; } else { - Do(p, mpegLibCrcstring); + Do(p, mpegLibCrc); } Do(p, streamIdGen); @@ -448,9 +448,9 @@ void __MpegShutdown() { mpegMap.clear(); } -void __MpegLoadModule(int version,std::string crcstr) { +void __MpegLoadModule(int version,u32 crc) { mpegLibVersion = version; - mpegLibCrcstring = crcstr; + mpegLibCrc = crc; } static u32 sceMpegInit() { @@ -459,7 +459,7 @@ static u32 sceMpegInit() { // TODO: Need to properly hook module load/unload for this to work right. //return ERROR_MPEG_ALREADY_INIT; } else { - INFO_LOG(ME, "sceMpegInit(), mpegLibVersion 0x%0x, mpegLibcrc %s", mpegLibVersion, mpegLibCrcstring.c_str()); + INFO_LOG(ME, "sceMpegInit(), mpegLibVersion 0x%0x, mpegLibcrc %x", mpegLibVersion, mpegLibCrc); } isMpegInit = true; return hleDelayResult(0, "mpeg init", 750); diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h index 538c60c9a8..925a2b005b 100644 --- a/Core/HLE/sceMpeg.h +++ b/Core/HLE/sceMpeg.h @@ -79,7 +79,7 @@ void __MpegInit(); void __MpegDoState(PointerWrap &p); void __MpegShutdown(); -void __MpegLoadModule(int version,std::string crcstr); +void __MpegLoadModule(int version,u32 crc); void Register_sceMpeg(); diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index 9b81e83a96..cf01260f2c 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -59,7 +59,7 @@ static const int audioSamplesBytes = audioSamples * 4; static int videoPixelMode = GE_CMODE_32BIT_ABGR8888; static int videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP; static int psmfPlayerLibVersion = 0; -static std::string psmfPlayerLibcrcstring = "null"; +static u32 psmfPlayerLibcrc = 0; int eventPsmfPlayerStatusChange = -1; @@ -697,9 +697,9 @@ void __PsmfInit() { eventPsmfPlayerStatusChange = CoreTiming::RegisterEvent("PsmfPlayerStatusChange", &__PsmfPlayerStatusChange); } -void __PsmfPlayerLoadModule(int devkitVersion, std::string crcstr) { +void __PsmfPlayerLoadModule(int devkitVersion, u32 crc) { psmfPlayerLibVersion = devkitVersion; - psmfPlayerLibcrcstring = crcstr; + psmfPlayerLibcrc = crc; } void __PsmfDoState(PointerWrap &p) { @@ -725,9 +725,9 @@ void __PsmfPlayerDoState(PointerWrap &p) { } CoreTiming::RestoreRegisterEvent(eventPsmfPlayerStatusChange, "PsmfPlayerStatusChangeEvent", &__PsmfPlayerStatusChange); if (s < 3) { - psmfPlayerLibcrcstring = "null"; + psmfPlayerLibcrc = 0; } else { - Do(p, psmfPlayerLibcrcstring); + Do(p, psmfPlayerLibcrc); } if (s < 2) { // Assume the latest, which is what we were emulating before. @@ -1164,7 +1164,7 @@ static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) { int delayUs = 20000; DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_INIT, delayUs); - INFO_LOG(ME, "psmfplayer create, psmfPlayerLibVersion 0x%0x, psmfPlayerLibcrcstring %s", psmfPlayerLibVersion, psmfPlayerLibcrcstring.c_str()); + INFO_LOG(ME, "psmfplayer create, psmfPlayerLibVersion 0x%0x, psmfPlayerLibcrc %x", psmfPlayerLibVersion, psmfPlayerLibcrc); return hleDelayResult(0, "player create", delayUs); } diff --git a/Core/HLE/scePsmf.h b/Core/HLE/scePsmf.h index 8e201adade..ae143fce04 100644 --- a/Core/HLE/scePsmf.h +++ b/Core/HLE/scePsmf.h @@ -21,7 +21,7 @@ void Register_scePsmf(); void Register_scePsmfPlayer(); void __PsmfInit(); -void __PsmfPlayerLoadModule(int devkitVersion,std::string crcstr); +void __PsmfPlayerLoadModule(int devkitVersion,u32 crc); void __PsmfDoState(PointerWrap &p); void __PsmfPlayerDoState(PointerWrap &p); void __PsmfShutdown(); diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 89490193df..edf519f46b 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -44,7 +44,6 @@ #include "UI/MainScreen.h" #include "UI/BackgroundAudio.h" #include "Core/Reporting.h" -#include GameScreen::GameScreen(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) { g_BackgroundAudio.SetGame(gamePath); @@ -56,11 +55,12 @@ GameScreen::~GameScreen() { } } -const char* int2hexstr(const int a) { - std::stringstream stream; - stream << std::hex << a; - auto s = stream.str(); - return s.c_str(); +template std::string int2hexstr(I w, size_t hex_len = sizeof(I) << 1) { + static const char* digits = "0123456789ABCDEF"; + std::string rc(hex_len, '0'); + for (size_t i = 0, j = (hex_len - 1) * 4; i < hex_len; ++i, j -= 4) + rc[i] = digits[(w >> j) & 0x0f]; + return rc; } void GameScreen::update() { diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 43671ec272..cff0c48366 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -29,8 +29,6 @@ // Uses GameInfoCache heavily to implement the functionality. // Should possibly merge this with the PauseScreen. -const char* int2hexstr(const int a); - class GameScreen : public UIDialogScreenWithGameBackground { public: GameScreen(const Path &gamePath); From 776e72715fedf37de6fd166cbdf1be97086bbca0 Mon Sep 17 00:00:00 2001 From: sum2012 Date: Wed, 5 Oct 2022 20:52:47 +0800 Subject: [PATCH 315/326] Forget remove the include --- Core/HLE/sceKernelModule.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index f74a78e0d0..5cd5dedbf7 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -67,7 +67,6 @@ #include "GPU/GPU.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" -#include "UI/GameScreen.h" enum { PSP_THREAD_ATTR_KERNEL = 0x00001000, From 3e5c09d432a9135711031bf264b4d95c1ffd1bde Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 5 Oct 2022 19:41:59 -0700 Subject: [PATCH 316/326] Vulkan: Clip clamped depth in geometry shader. This corrects deformed geometry on Mali devices which don't support user-space clipping but do support depth clamp. --- GPU/Common/GeometryShaderGenerator.cpp | 211 ++++++++++++++++--------- GPU/Common/ShaderId.cpp | 13 +- GPU/Common/ShaderId.h | 1 + 3 files changed, 149 insertions(+), 76 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 6d1b7bdcd9..525dc1f0f5 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -48,7 +48,7 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size()); p.C("layout(triangles) in;\n"); - p.C("layout(triangle_strip, max_vertices = 6) out;\n"); + p.C("layout(triangle_strip, max_vertices = 12) out;\n"); if (compat.shaderLanguage == GLSL_VULKAN) { WRITE(p, "\n"); @@ -58,6 +58,8 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu } std::vector varyings, outVaryings; + bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE); + bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); if (id.Bit(GS_BIT_DO_TEXTURE)) { varyings.push_back(VaryingDef{ "vec3", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" }); @@ -74,53 +76,87 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.BeginGSMain(varyings, outVaryings); - // Apply culling - p.C(" bool anyInside = false;\n"); + // Apply culling. + if (vertexRangeCulling) { + p.C(" bool anyInside = false;\n"); + } // And apply manual clipping if necessary. if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { p.C(" float clip0[3];\n"); + if (clipClampedDepth) { + p.C(" float clip1[3];\n"); + } } p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); - p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); - // Vertex range culling doesn't happen when Z clips, note sign of w is important. - p.C(" if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); - const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; - const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; - p.F(" if ((%s) || (%s)) {\n", outMin, outMax); - p.C(" return;\n"); // Cull! - p.C(" }\n"); - p.C(" }\n"); - p.C(" if (u_cullRangeMin.w <= 0.0) {\n"); - p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); - // When not clamping depth, cull the triangle of Z is outside the valid range (not based on clip Z.) - p.C(" return;\n"); - p.C(" }\n"); - p.C(" } else {\n"); - p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n"); - p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); - p.C(" }\n"); + + if (vertexRangeCulling) { + p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); + // Vertex range culling doesn't happen when Z clips, note sign of w is important. + p.C(" if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); + const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; + const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; + p.F(" if ((%s) || (%s)) {\n", outMin, outMax); + p.C(" return;\n"); // Cull! + p.C(" }\n"); + p.C(" }\n"); + p.C(" if (u_cullRangeMin.w <= 0.0) {\n"); + p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); + // When not clamping depth, cull the triangle of Z is outside the valid range (not based on clip Z.) + p.C(" return;\n"); + p.C(" }\n"); + p.C(" } else {\n"); + p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n"); + p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n"); + p.C(" }\n"); + } if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { // This is basically the same value as gl_ClipDistance would take, z + w. - // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z. - p.F(" clip0[i] = projZ * outPos.w + outPos.w;\n"); + if (vertexRangeCulling) { + p.C(" clip0[i] = projZ * outPos.w + outPos.w;\n"); + } else { + // Let's not complicate the code overly for this case. We'll clipClampedDepth. + p.C(" clip0[i] = 0.0;\n"); + } + + // This one does happen for rectangles. + if (clipClampedDepth) { + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + // On OpenGL/GLES, these values account for the -1 -> 1 range. + p.C(" if (u_depthRange.y - u_depthRange.x >= 1.0) {\n"); + p.C(" clip1[i] = outPos.w + outPos.z;\n"); + } else { + // Everywhere else, it's 0 -> 1, simpler. + p.C(" if (u_depthRange.y >= 1.0) {\n"); + p.C(" clip1[i] = outPos.z;\n"); + } + // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here. + p.C(" } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); + p.C(" clip1[i] = outPos.w - outPos.z;\n"); + p.C(" } else {\n"); + p.C(" clip1[i] = 0.0;\n"); + p.C(" }\n"); + } } p.C(" } // for\n"); // Cull any triangle fully outside in the same direction when depth clamp enabled. // Basically simulate cull distances. - p.C(" if (u_cullRangeMin.w > 0.0 && !anyInside) {\n"); - p.C(" return;\n"); - p.C(" }\n"); + if (vertexRangeCulling) { + p.C(" if (u_cullRangeMin.w > 0.0 && !anyInside) {\n"); + p.C(" return;\n"); + p.C(" }\n"); + } if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { // Clipping against one half-space cuts a triangle (17/27), culls (7/27), or creates two triangles (3/27). - p.C(" int indices[4];\n"); - p.C(" float factors[4];\n"); + // We clip against two, so we can generate up to 4 triangles, a polygon with 6 points. + p.C(" int indices[6];\n"); + p.C(" float factors[6];\n"); p.C(" int ind = 0;\n"); // Pass 1 - clip against first half-space. @@ -143,22 +179,80 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" }\n"); + // Pass 2 - further clip against clamped Z. + if (clipClampedDepth) { + p.C(" int count0 = ind;\n"); + p.C(" int indices1[6];\n"); + p.C(" float factors1[6];\n"); + p.C(" ind = 0;\n"); + + // Let's start by interpolating the clip values. + p.C(" float clip1after[4];\n"); + p.C(" for (int i = 0; i < count0; i++) {\n"); + p.C(" int idx = indices[i];\n"); + p.C(" float factor = factors[i];\n"); + p.C(" int next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" clip1after[i] = mix(clip1[idx], clip1[next], factor);\n"); + p.C(" }\n"); + + // Alright, now time to clip, again. + p.C(" for (int i = 0; i < count0; i++) {\n"); + // First, use this vertex if it doesn't need clipping. + p.C(" if (clip1after[i] >= 0.0) {\n"); + p.C(" indices1[ind] = i;\n"); + p.C(" factors1[ind] = 0.0;\n"); + p.C(" ind++;\n"); + p.C(" }\n"); + + // Next, we generate an interpolated vertex if signs differ. + p.C(" int inext = i == count0 - 1 ? 0 : i + 1;\n"); + p.C(" if (clip1after[i] * clip1after[inext] < 0.0) {\n"); + p.C(" float t = clip1after[i] < 0.0 ? clip1after[i] / (clip1after[i] - clip1after[inext]) : 1.0 - (clip1after[inext] / (clip1after[inext] - clip1after[i]));\n"); + p.C(" indices1[ind] = i;\n"); + p.C(" factors1[ind] = t;\n"); + p.C(" ind++;\n"); + p.C(" }\n"); + + p.C(" }\n"); + } + p.C(" if (ind < 3) {\n"); p.C(" return;\n"); p.C(" }\n"); + p.C(" int idx;\n"); + p.C(" int next;\n"); + p.C(" float factor;\n"); + + auto emitIndex = [&](const char *which) { + if (clipClampedDepth) { + // We have to interpolate between four vertices. + p.F(" idx = indices1[%s];\n", which); + p.F(" factor = factors1[%s];\n", which); + p.C(" next = idx == count0 - 1 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(mix(gl_in[indices[idx]].gl_Position, gl_in[(indices[idx] + 1) % 3].gl_Position, factors[idx]), mix(gl_in[indices[next]].gl_Position, gl_in[(indices[next] + 1) % 3].gl_Position, factors[next]), factor);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + const VaryingDef &in = varyings[i]; + const VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(mix(%s[indices[idx]], %s[(indices[idx] + 1) % 3], factors[idx]), mix(%s[indices[next]], %s[(indices[next] + 1) % 3], factors[next]), factor);\n", out.name, in.name, in.name, in.name, in.name); + } + } else { + p.F(" idx = indices[%s];\n", which); + p.F(" factor = factors[%s];\n", which); + p.C(" next = idx == 2 ? 0 : idx + 1;\n"); + p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); + for (size_t i = 0; i < varyings.size(); i++) { + const VaryingDef &in = varyings[i]; + const VaryingDef &out = outVaryings[i]; + p.F(" %s = mix(%s[idx], %s[next], factor);\n", out.name, in.name, in.name); + } + } + p.C(" EmitVertex();\n"); + }; + // Alright, time to actually emit the first triangle. p.C(" for (int i = 0; i < 3; i++) {\n"); - p.C(" int idx = indices[i];\n"); - p.C(" float factor = factors[i];\n"); - p.C(" int next = idx == 2 ? 0 : idx + 1;\n"); - p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); + emitIndex("i"); p.C(" }\n"); // Did we end up with additional triangles? We'll do three points each for the rest. @@ -166,40 +260,13 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" EndPrimitive();\n"); // Point one, always index zero. - p.C(" int idx = indices[0];\n"); - p.C(" float factor = factors[0];\n"); - p.C(" int next = idx == 2 ? 0 : idx + 1;\n"); - p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); + emitIndex("0"); // After that, one less than i (basically a triangle fan.) - p.C(" idx = indices[i - 1];\n"); - p.C(" factor = factors[i - 1];\n"); - p.C(" next = idx == 2 ? 0 : idx + 1;\n"); - p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); + emitIndex("(i - 1)"); // And the new vertex itself. - p.C(" idx = indices[i];\n"); - p.C(" factor = factors[i];\n"); - p.C(" next = idx == 2 ? 0 : idx + 1;\n"); - p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n"); - for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = mix(%s[idx], %s[next], factor);\n", outVaryings[i].name, varyings[i].name, varyings[i].name); - } - p.C(" EmitVertex();\n"); + emitIndex("i"); p.C(" }\n"); } else { @@ -209,16 +276,16 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); - // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z. + // We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that. p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0); p.C(" gl_Position = outPos;\n"); if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { } for (size_t i = 0; i < varyings.size(); i++) { - VaryingDef &in = varyings[i]; - VaryingDef &out = outVaryings[i]; - p.F(" %s = %s[i];\n", outVaryings[i].name, varyings[i].name); + const VaryingDef &in = varyings[i]; + const VaryingDef &out = outVaryings[i]; + p.F(" %s = %s[i];\n", out.name, in.name); } // Debug - null the red channel //p.C(" if (i == 0) v_color0Out.x = 0.0;\n"); diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 9c8cb3905b..94ade2fd26 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -379,22 +379,27 @@ std::string GeometryShaderDesc(const GShaderID &id) { void ComputeGeometryShaderID(GShaderID *id_out, const Draw::Bugs &bugs, int prim) { GShaderID id; - bool vertexRangeCulling = - !gstate.isModeThrough() && gstate_c.submitType == SubmitType::DRAW; // neither hw nor sw spline/bezier. See #11692 + bool isModeThrough = gstate.isModeThrough(); + bool isCurve = gstate_c.submitType != SubmitType::DRAW; + bool isTriangle = prim == GE_PRIM_TRIANGLES || prim == GE_PRIM_TRIANGLE_FAN || prim == GE_PRIM_TRIANGLE_STRIP; + + bool vertexRangeCulling = !isCurve; + bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); // If we're not using GS culling, return a zero ID. // Also, only use this for triangle primitives. - if (!vertexRangeCulling || !gstate_c.Supports(GPU_SUPPORTS_GS_CULLING) || (prim != GE_PRIM_TRIANGLES && prim != GE_PRIM_TRIANGLE_FAN && prim != GE_PRIM_TRIANGLE_STRIP)) { + if ((!vertexRangeCulling && !clipClampedDepth) || isModeThrough || !isTriangle || !gstate_c.Supports(GPU_SUPPORTS_GS_CULLING)) { *id_out = id; return; } id.SetBit(GS_BIT_ENABLED, true); + // Vertex range culling doesn't seem tno happen for spline/bezier, see #11692. + id.SetBit(GS_BIT_CURVE, isCurve); if (gstate.isModeClear()) { // No attribute bits. } else { - bool isModeThrough = gstate.isModeThrough(); bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough; id.SetBit(GS_BIT_LMODE, lmode); diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index ec25abb4d3..560b8a873a 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -109,6 +109,7 @@ enum GShaderBit : uint8_t { GS_BIT_ENABLED = 0, // If not set, we don't use a geo shader. GS_BIT_DO_TEXTURE = 1, // presence of texcoords GS_BIT_LMODE = 2, // presence of specular color (regular color always present) + GS_BIT_CURVE = 3, // curve, which means don't do range culling. }; static inline GShaderBit operator +(GShaderBit bit, int i) { From 8663541403cb89ca113e744d2d359eb5573d4994 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 5 Oct 2022 20:11:10 -0700 Subject: [PATCH 317/326] Vulkan: Avoid max_vertices=12 if unnecessary. --- GPU/Common/GeometryShaderGenerator.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index 525dc1f0f5..d9e8c8c22a 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -45,10 +45,16 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable"); } } + bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE); + bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size()); p.C("layout(triangles) in;\n"); - p.C("layout(triangle_strip, max_vertices = 12) out;\n"); + if (clipClampedDepth && vertexRangeCulling) { + p.C("layout(triangle_strip, max_vertices = 12) out;\n"); + } else { + p.C("layout(triangle_strip, max_vertices = 6) out;\n"); + } if (compat.shaderLanguage == GLSL_VULKAN) { WRITE(p, "\n"); @@ -58,8 +64,6 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu } std::vector varyings, outVaryings; - bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE); - bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); if (id.Bit(GS_BIT_DO_TEXTURE)) { varyings.push_back(VaryingDef{ "vec3", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" }); From 14bf9d1923fe3edbfa8c5f93731e9c9be8dd1803 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 5 Oct 2022 20:23:10 -0700 Subject: [PATCH 318/326] Vulkan: Correct clamped Z clip when clipping neg Z. In the geometry shader, if used, we need to output the clip distance from the clamped Z clip or it gets lost. --- GPU/Common/GeometryShaderGenerator.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index d9e8c8c22a..f6bc7fae5d 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -46,11 +46,11 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu } } bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE); - bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); + bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP); ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size()); p.C("layout(triangles) in;\n"); - if (clipClampedDepth && vertexRangeCulling) { + if (clipClampedDepth && vertexRangeCulling && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { p.C("layout(triangle_strip, max_vertices = 12) out;\n"); } else { p.C("layout(triangle_strip, max_vertices = 6) out;\n"); @@ -274,14 +274,23 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu p.C(" }\n"); } else { - const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]"; + const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; + const char *clipSuffix1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; + const char *clipSuffix2 = compat.shaderLanguage == HLSL_D3D11 ? ".z" : "[2]"; p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); p.C(" vec3 projPos = outPos.xyz / outPos.w;\n"); p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); - // We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that. - p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0); + if (clipClampedDepth) { + // Copy the clip distance from the vertex shader. + p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix0, clipSuffix0); + p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix1, clipSuffix1); + p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix2); + } else { + // We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that. + p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0); + } p.C(" gl_Position = outPos;\n"); if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { } From bc3d3cf9fb8ebecca7e7ab34c6630b45ee4eebbe Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 5 Oct 2022 21:17:17 -0700 Subject: [PATCH 319/326] GPU: Optimize clip distances needed. We only need to write one clip distance to clip clamped depth, since we don't clamp when it needs clipping on both sides. --- GPU/Common/GeometryShaderGenerator.cpp | 4 +--- GPU/Common/VertexShaderGenerator.cpp | 30 ++++++++++---------------- GPU/GLES/ShaderManagerGLES.cpp | 3 +-- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp index f6bc7fae5d..a814a345da 100644 --- a/GPU/Common/GeometryShaderGenerator.cpp +++ b/GPU/Common/GeometryShaderGenerator.cpp @@ -276,7 +276,6 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu } else { const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; const char *clipSuffix1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; - const char *clipSuffix2 = compat.shaderLanguage == HLSL_D3D11 ? ".z" : "[2]"; p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster? p.C(" vec4 outPos = gl_in[i].gl_Position;\n"); @@ -285,8 +284,7 @@ bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLangu if (clipClampedDepth) { // Copy the clip distance from the vertex shader. p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix0, clipSuffix0); - p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix1, clipSuffix1); - p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix2); + p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix1); } else { // We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that. p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 928aa3f6dd..658cbb27f6 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -231,9 +231,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool vertexRangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING) && !isModeThrough; bool clipClampedDepth = !isModeThrough && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); - const char *vertexRangeClipSuffix = "[0]"; - if (vertexRangeCulling && clipClampedDepth) - vertexRangeClipSuffix = "[2]"; + const char *clipClampedDepthSuffix = "[0]"; + const char *vertexRangeClipSuffix = clipClampedDepth ? "[1]" : "[0]"; if (compat.shaderLanguage == GLSL_VULKAN) { WRITE(p, "\n"); @@ -419,12 +418,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " vec4 gl_Position : SV_Position;\n"); bool clipRange = vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE); if (clipClampedDepth && clipRange) { - WRITE(p, " float3 gl_ClipDistance : SV_ClipDistance;\n"); - vertexRangeClipSuffix = ".z"; - } else if (clipClampedDepth) { WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n"); - } else if (clipRange) { + clipClampedDepthSuffix = ".x"; + vertexRangeClipSuffix = ".y"; + } else if (clipClampedDepth || clipRange) { WRITE(p, " float gl_ClipDistance : SV_ClipDistance;\n"); + clipClampedDepthSuffix = ""; vertexRangeClipSuffix = ""; } if (vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) { @@ -1267,28 +1266,21 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } if (clipClampedDepth) { - const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; - const char *clip1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; - // This should clip against minz, but only when it's above zero. if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { // On OpenGL/GLES, these values account for the -1 -> 1 range. WRITE(p, " if (u_depthRange.y - u_depthRange.x >= 1.0) {\n"); - WRITE(p, " %sgl_ClipDistance%s = outPos.w + outPos.z;\n", compat.vsOutPrefix, clip0); + WRITE(p, " %sgl_ClipDistance%s = outPos.w + outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix); } else { // Everywhere else, it's 0 -> 1, simpler. WRITE(p, " if (u_depthRange.y >= 1.0) {\n"); - WRITE(p, " %sgl_ClipDistance%s = outPos.z;\n", compat.vsOutPrefix, clip0); + WRITE(p, " %sgl_ClipDistance%s = outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix); } - WRITE(p, " } else {\n"); - WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip0); - WRITE(p, " }\n"); - // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here. - WRITE(p, " if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); - WRITE(p, " %sgl_ClipDistance%s = outPos.w - outPos.z;\n", compat.vsOutPrefix, clip1); + WRITE(p, " } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n"); + WRITE(p, " %sgl_ClipDistance%s = outPos.w - outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix); WRITE(p, " } else {\n"); - WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clip1); + WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clipClampedDepthSuffix); WRITE(p, " }\n"); } diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 6868b5d988..50cba0328e 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -199,9 +199,8 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, flags.supportDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0; if (!VSID.Bit(VS_BIT_IS_THROUGH) && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP)) { flags.useClipDistance0 = true; - flags.useClipDistance1 = true; if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) - flags.useClipDistance2 = true; + flags.useClipDistance1 = true; } else if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) { flags.useClipDistance0 = true; } From aee2ad46a2cba6409c582e503e69004d4bcf2a55 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 5 Oct 2022 21:41:09 -0700 Subject: [PATCH 320/326] GPU: Verify generated shader buffer length. Hardware tessellation + uberlighting + clamp was exceeding the buffer, causing memory corruption. Let's try to catch it, but also increase buffers to be safe. --- GPU/Common/Draw2D.cpp | 6 ++- GPU/Common/StencilCommon.cpp | 7 ++- GPU/Common/TextureShaderCommon.cpp | 2 - GPU/D3D11/ShaderManagerD3D11.cpp | 6 ++- GPU/GLES/ShaderManagerGLES.cpp | 6 ++- GPU/Vulkan/ShaderManagerVulkan.cpp | 10 ++++- unittest/TestShaderGenerators.cpp | 69 ++++++++++++++++++++++++++---- 7 files changed, 89 insertions(+), 17 deletions(-) diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index 83df3ee34d..05bead52e4 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -178,9 +178,10 @@ void Draw2D::Ensure2DResources() { const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc(); if (!draw2DVs_) { - char *vsCode = new char[4000]; + char *vsCode = new char[8192]; ShaderWriter writer(vsCode, shaderLanguageDesc, ShaderStage::Vertex); GenerateDraw2DVS(writer); + _assert_msg_(strlen(vsCode) < 8192, "Draw2D VS length error: %d", (int)strlen(vsCode)); draw2DVs_ = draw_->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)vsCode, strlen(vsCode), "draw2d_vs"); _assert_(draw2DVs_); delete[] vsCode; @@ -215,9 +216,10 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::functionGetShaderLanguageDesc(); - char *fsCode = new char[4000]; + char *fsCode = new char[8192]; ShaderWriter writer(fsCode, shaderLanguageDesc, ShaderStage::Fragment); Draw2DPipelineInfo info = generate(writer); + _assert_msg_(strlen(fsCode) < 8192, "Draw2D FS length error: %d", (int)strlen(fsCode)); ShaderModule *fs = draw_->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)fsCode, strlen(fsCode), info.tag); diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp index 86a5508b80..d30327df93 100644 --- a/GPU/Common/StencilCommon.cpp +++ b/GPU/Common/StencilCommon.cpp @@ -199,11 +199,14 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU if (!stencilUploadPipeline_) { const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc(); - char *fsCode = new char[4000]; - char *vsCode = new char[4000]; + char *fsCode = new char[8192]; + char *vsCode = new char[8192]; GenerateStencilFs(fsCode, shaderLanguageDesc, draw_->GetBugs()); GenerateStencilVs(vsCode, shaderLanguageDesc); + _assert_msg_(strlen(fsCode) < 8192, "StenFS length error: %d", (int)strlen(fsCode)); + _assert_msg_(strlen(vsCode) < 8192, "StenVS length error: %d", (int)strlen(vsCode)); + ShaderModule *stencilUploadFs = draw_->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)fsCode, strlen(fsCode), "stencil_fs"); ShaderModule *stencilUploadVs = draw_->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)vsCode, strlen(vsCode), "stencil_vs"); diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp index 1c67818a85..2860389fc2 100644 --- a/GPU/Common/TextureShaderCommon.cpp +++ b/GPU/Common/TextureShaderCommon.cpp @@ -209,7 +209,6 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe config.smoothedDepal = smoothedDepal; config.depthUpperBits = depthUpperBits; - char *buffer = new char[4096]; Draw2DPipeline *ts = draw2D_->Create2DPipeline([=](ShaderWriter &writer) -> Draw2DPipelineInfo { GenerateDepalFs(writer, config); return Draw2DPipelineInfo{ @@ -219,7 +218,6 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe samplers }; }); - delete[] buffer; depalCache_[id] = ts; diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp index a8537c8545..0560b52941 100644 --- a/GPU/D3D11/ShaderManagerD3D11.cpp +++ b/GPU/D3D11/ShaderManagerD3D11.cpp @@ -88,9 +88,11 @@ std::string D3D11VertexShader::GetShaderString(DebugShaderStringType type) const } } +static constexpr size_t CODE_BUFFER_SIZE = 32768; + ShaderManagerD3D11::ShaderManagerD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context, D3D_FEATURE_LEVEL featureLevel) : ShaderManagerCommon(draw), device_(device), context_(context), featureLevel_(featureLevel) { - codeBuffer_ = new char[16384]; + codeBuffer_ = new char[CODE_BUFFER_SIZE]; memset(&ub_base, 0, sizeof(ub_base)); memset(&ub_lights, 0, sizeof(ub_lights)); memset(&ub_bones, 0, sizeof(ub_bones)); @@ -211,6 +213,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader ** uint32_t attrMask; uint64_t uniformMask; GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &attrMask, &uniformMask, &genErrorString); + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_)); vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, vertType, useHWTransform); vsCache_[VSID] = vs; } else { @@ -225,6 +228,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader ** std::string genErrorString; uint64_t uniformMask; GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, nullptr, &genErrorString); + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform); fsCache_[FSID] = fs; } else { diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 6868b5d988..6e2bcd3c63 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -662,10 +662,12 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu } } +static constexpr size_t CODE_BUFFER_SIZE = 32768; + ShaderManagerGLES::ShaderManagerGLES(Draw::DrawContext *draw) : ShaderManagerCommon(draw), fsCache_(16), vsCache_(16) { render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - codeBuffer_ = new char[16384]; + codeBuffer_ = new char[CODE_BUFFER_SIZE]; lastFSID_.set_invalid(); lastVSID_.set_invalid(); } @@ -728,6 +730,7 @@ Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) { ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str()); return nullptr; } + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); std::string desc = FragmentShaderDesc(FSID); ShaderDescGLES params{ GL_FRAGMENT_SHADER, 0, uniformMask }; return new Shader(render_, codeBuffer_, desc, params); @@ -742,6 +745,7 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) { ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str()); return nullptr; } + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_)); std::string desc = VertexShaderDesc(VSID); ShaderDescGLES params{ GL_VERTEX_SHADER, attrMask, uniformMask }; params.useHWTransform = useHWTransform; diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 00649a6be1..c7724da670 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -194,9 +194,11 @@ std::string VulkanGeometryShader::GetShaderString(DebugShaderStringType type) co } } +static constexpr size_t CODE_BUFFER_SIZE = 32768; + ShaderManagerVulkan::ShaderManagerVulkan(Draw::DrawContext *draw) : ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16), gsCache_(16) { - codeBuffer_ = new char[16384]; + codeBuffer_ = new char[CODE_BUFFER_SIZE]; VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); uboAlignment_ = vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment; memset(&ub_base, 0, sizeof(ub_base)); @@ -330,6 +332,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader uint32_t attributeMask = 0; // Not used bool success = GenerateVertexShader(VSID, codeBuffer_, compat_, draw_->GetBugs(), &attributeMask, &uniformMask, &genErrorString); _assert_msg_(success, "VS gen error: %s", genErrorString.c_str()); + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_)); vs = new VulkanVertexShader(vulkan, VSID, codeBuffer_, useHWTransform); vsCache_.Insert(VSID, vs); } @@ -342,6 +345,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader FragmentShaderFlags flags; bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString); _assert_msg_(success, "FS gen error: %s", genErrorString.c_str()); + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); fs = new VulkanFragmentShader(vulkan, FSID, flags, codeBuffer_); fsCache_.Insert(FSID, fs); } @@ -354,6 +358,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader std::string genErrorString; bool success = GenerateGeometryShader(GSID, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString); _assert_msg_(success, "GS gen error: %s", genErrorString.c_str()); + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "GS length error: %d", (int)strlen(codeBuffer_)); gs = new VulkanGeometryShader(vulkan, GSID, codeBuffer_); gsCache_.Insert(GSID, gs); } @@ -512,6 +517,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { if (!GenerateVertexShader(id, codeBuffer_, compat_, draw_->GetBugs(), &attributeMask, &uniformMask, &genErrorString)) { return false; } + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_)); VulkanVertexShader *vs = new VulkanVertexShader(vulkan, id, codeBuffer_, useHWTransform); vsCache_.Insert(id, vs); } @@ -529,6 +535,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { if (!GenerateFragmentShader(id, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString)) { return false; } + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan, id, flags, codeBuffer_); fsCache_.Insert(id, fs); } @@ -543,6 +550,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { if (!GenerateGeometryShader(id, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString)) { return false; } + _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "GS length error: %d", (int)strlen(codeBuffer_)); VulkanGeometryShader *gs = new VulkanGeometryShader(vulkan, id, codeBuffer_); gsCache_.Insert(id, gs); } diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index 346fadf643..fa5fd1eb7c 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -25,6 +25,8 @@ #include "GPU/D3D9/D3D9ShaderCompiler.h" #endif +static constexpr size_t CODE_BUFFER_SIZE = 32768; + bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) { uint64_t uniformMask; switch (lang) { @@ -243,10 +245,13 @@ bool TestReinterpretShaders() { continue; // useless shader! ShaderWriter writer(buffer, desc, ShaderStage::Fragment, nullptr, 0); GenerateReinterpretFragmentShader(writer, fmts[i], fmts[j]); + if (strlen(buffer) >= 8192) { + printf("Reinterpret fragment shader %d exceeded buffer:\n\n%s\n", (int)j, LineNumberString(buffer).c_str()); + failed = true; + } if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) { - printf("Error compiling fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str()); + printf("Error compiling reinterpret fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str()); failed = true; - return false; } else { printf("===\n%s\n===\n", buffer); } @@ -282,19 +287,25 @@ bool TestStencilShaders() { // Generate all despite failures - it's only 6. GenerateStencilFs(buffer, desc, bugs); + if (strlen(buffer) >= 8192) { + printf("Stencil fragment shader exceeded buffer:\n\n%s\n", LineNumberString(buffer).c_str()); + failed = true; + } if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) { printf("Error compiling stencil shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str()); failed = true; - return false; } else { printf("===\n%s\n===\n", buffer); } GenerateStencilVs(buffer, desc); + if (strlen(buffer) >= 8192) { + printf("Stencil vertex shader exceeded buffer:\n\n%s\n", LineNumberString(buffer).c_str()); + failed = true; + } if (!TestCompileShader(buffer, languages[k], ShaderStage::Vertex, &errorMessage)) { printf("Error compiling stencil shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str()); failed = true; - return false; } else { printf("===\n%s\n===\n", buffer); } @@ -319,8 +330,6 @@ bool TestDepalShaders() { char *buffer = new char[65536]; - bool failed = false; - for (int k = 0; k < ARRAY_SIZE(languages); k++) { printf("=== %s ===\n\n", ShaderLanguageToString(languages[k])); @@ -340,9 +349,14 @@ bool TestDepalShaders() { ShaderWriter writer(buffer, desc, ShaderStage::Fragment); GenerateDepalFs(writer, config); + if (strlen(buffer) >= 8192) { + printf("Depal shader exceeded buffer:\n\n%s\n", LineNumberString(buffer).c_str()); + delete[] buffer; + return false; + } if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) { printf("Error compiling depal shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str()); - failed = true; + delete[] buffer; return false; } else { printf("===\n%s\n===\n", buffer); @@ -350,7 +364,7 @@ bool TestDepalShaders() { } delete[] buffer; - return !failed; + return true; } const ShaderLanguage languages[] = { @@ -410,6 +424,16 @@ bool TestVertexShaders() { } } + for (int j = 0; j < numLanguages; j++) { + if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) { + printf("Vertex shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return false; + } + } + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), // let's try to compile them. for (int j = 0; j < numLanguages; j++) { @@ -417,6 +441,9 @@ bool TestVertexShaders() { std::string errorMessage; if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Vertex, &errorMessage)) { printf("Error compiling vertex shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } return false; } successes++; @@ -471,6 +498,16 @@ bool TestFragmentShaders() { // We ignore the contents of the error string here, not even gonna try to compile if it errors. } + for (int j = 0; j < numLanguages; j++) { + if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) { + printf("Fragment shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return false; + } + } + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), // let's try to compile them. for (int j = 0; j < numLanguages; j++) { @@ -478,6 +515,9 @@ bool TestFragmentShaders() { std::string errorMessage; if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Fragment, &errorMessage)) { printf("Error compiling fragment shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } return false; } successes++; @@ -527,6 +567,16 @@ bool TestGeometryShaders() { // We ignore the contents of the error string here, not even gonna try to compile if it errors. } + for (int j = 0; j < numLanguages; j++) { + if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) { + printf("Geoemtry shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return false; + } + } + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), // let's try to compile them. for (int j = 0; j < numLanguages; j++) { @@ -534,6 +584,9 @@ bool TestGeometryShaders() { std::string errorMessage; if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Geometry, &errorMessage)) { printf("Error compiling geometry shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } return false; } successes++; From 3aa863ec4141955eb5d0b1bffe64dfe00b127cde Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 6 Oct 2022 00:34:02 -0700 Subject: [PATCH 321/326] GPU: Clip against neg Z even w/o cull support. This should fix rendering issues on Apple devices. --- GPU/Common/VertexShaderGenerator.cpp | 29 +++++++++++++++------------- GPU/GPUCommon.cpp | 9 +++++---- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 658cbb27f6..24f118603f 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1287,19 +1287,22 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (vertexRangeCulling && !IsVRBuild()) { WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n"); - // Vertex range culling doesn't happen when Z clips, note sign of w is important. - WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); - const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; - const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; - WRITE(p, " if ((%s) || (%s)) {\n", outMin, outMax); - WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); - WRITE(p, " }\n"); - WRITE(p, " }\n"); - WRITE(p, " if (u_cullRangeMin.w <= 0.0) {\n"); - WRITE(p, " if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); - WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); - WRITE(p, " }\n"); - WRITE(p, " }\n"); + + if (!bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { + // Vertex range culling doesn't happen when Z clips, note sign of w is important. + WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n"); + const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y"; + const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y"; + WRITE(p, " if ((%s) || (%s)) {\n", outMin, outMax); + WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); + WRITE(p, " }\n"); + WRITE(p, " }\n"); + WRITE(p, " if (u_cullRangeMin.w <= 0.0) {\n"); + WRITE(p, " if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n"); + WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); + WRITE(p, " }\n"); + WRITE(p, " }\n"); + } const char *cull0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]"; const char *cull1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]"; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 7c6e7620eb..9830984739 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -3305,10 +3305,11 @@ u32 GPUCommon::CheckGPUFeatures() const { features |= GPU_SUPPORTS_DEPTH_TEXTURE; } - if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) { - if (draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported) { - features |= GPU_SUPPORTS_VS_RANGE_CULLING; - } + 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_SUPPORTS_VS_RANGE_CULLING; } if (draw_->GetDeviceCaps().framebufferFetchSupported) { From bf25f4b283fd04c24f54f3d055caca9c981b5ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 6 Oct 2022 10:40:58 +0200 Subject: [PATCH 322/326] Shader uniforms (VK/D3D11): Fix issue where we could overwrite the fourth component padding. --- Common/Data/Convert/SmallDataConvert.h | 14 ++++++++++++++ GPU/Common/ShaderUniforms.cpp | 10 ++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Common/Data/Convert/SmallDataConvert.h b/Common/Data/Convert/SmallDataConvert.h index a877295918..e130596a3f 100644 --- a/Common/Data/Convert/SmallDataConvert.h +++ b/Common/Data/Convert/SmallDataConvert.h @@ -82,6 +82,20 @@ inline void Uint8x3ToFloat4(float f[4], uint32_t u) { #endif } +inline void Uint8x3ToFloat3(float f[4], uint32_t u) { +#if defined(_M_SSE) || PPSSPP_ARCH(ARM_NEON) + float temp[3]; + Uint8x4ToFloat4(temp, u & 0xFFFFFF); + f[0] = temp[0]; + f[1] = temp[1]; + f[2] = temp[2]; +#else + f[0] = ((u >> 0) & 0xFF) * (1.0f / 255.0f); + f[1] = ((u >> 8) & 0xFF) * (1.0f / 255.0f); + f[2] = ((u >> 16) & 0xFF) * (1.0f / 255.0f); +#endif +} + inline void Uint8x3ToInt4(int i[4], uint32_t u) { i[0] = ((u >> 0) & 0xFF); i[1] = ((u >> 8) & 0xFF); diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 05683e5040..b276f7f094 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -74,7 +74,7 @@ void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bo void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport, bool useBufferedRendering) { if (dirtyUniforms & DIRTY_TEXENV) { - Uint8x3ToFloat4(ub->texEnvColor, gstate.texenvcolor); + Uint8x3ToFloat3(ub->texEnvColor, gstate.texenvcolor); } if (dirtyUniforms & DIRTY_ALPHACOLORREF) { Uint8x3ToInt4_Alpha(ub->alphaColorRef, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask()); @@ -86,8 +86,8 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView Uint8x3ToFloat4(ub->fogColor, gstate.fogcolor); } if (dirtyUniforms & DIRTY_SHADERBLEND) { - Uint8x3ToFloat4(ub->blendFixA, gstate.getFixA()); - Uint8x3ToFloat4(ub->blendFixB, gstate.getFixB()); + Uint8x3ToFloat3(ub->blendFixA, gstate.getFixA()); + Uint8x3ToFloat3(ub->blendFixB, gstate.getFixB()); } if (dirtyUniforms & DIRTY_TEXCLAMP) { const float invW = 1.0f / (float)gstate_c.curTextureWidth; @@ -303,9 +303,7 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) { } if (dirtyUniforms & DIRTY_MATEMISSIVE) { // We're not touching the fourth f32 here, because we store an u32 of control bits in it. - float temp[4]; - Uint8x3ToFloat4(temp, gstate.materialemissive); - memcpy(ub->materialEmissive, temp, 12); + Uint8x3ToFloat3(ub->materialEmissive, gstate.materialemissive); } if (dirtyUniforms & DIRTY_LIGHT_CONTROL) { ub->lightControl = PackLightControlBits(); From 131c1ca79e33a0ab169b0c20aa24a6b0c288fd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 6 Oct 2022 20:46:57 +0200 Subject: [PATCH 323/326] Typo fix, thanks iota97 --- Common/Data/Convert/SmallDataConvert.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Data/Convert/SmallDataConvert.h b/Common/Data/Convert/SmallDataConvert.h index e130596a3f..95558a17df 100644 --- a/Common/Data/Convert/SmallDataConvert.h +++ b/Common/Data/Convert/SmallDataConvert.h @@ -84,7 +84,7 @@ inline void Uint8x3ToFloat4(float f[4], uint32_t u) { inline void Uint8x3ToFloat3(float f[4], uint32_t u) { #if defined(_M_SSE) || PPSSPP_ARCH(ARM_NEON) - float temp[3]; + float temp[4]; Uint8x4ToFloat4(temp, u & 0xFFFFFF); f[0] = temp[0]; f[1] = temp[1]; From d716b74480c020a9452e18cf1dc897934ee2a91f Mon Sep 17 00:00:00 2001 From: sum2012 Date: Fri, 7 Oct 2022 06:01:53 +0800 Subject: [PATCH 324/326] Fix as @hrydgard suggest --- Core/HLE/sceKernelModule.cpp | 3 +-- Core/HLE/sceMpeg.h | 2 +- Core/HLE/scePsmf.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 5cd5dedbf7..cb1e9d091d 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1144,8 +1144,7 @@ static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) { } static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) { - u32 crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, ptr, elfSize); + u32 crc = crc32(0, ptr, elfSize); PSPModule *module = new PSPModule(); kernelObjects.Create(module); loadedModules.insert(module->GetUID()); diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h index 925a2b005b..e8bde2da34 100644 --- a/Core/HLE/sceMpeg.h +++ b/Core/HLE/sceMpeg.h @@ -79,7 +79,7 @@ void __MpegInit(); void __MpegDoState(PointerWrap &p); void __MpegShutdown(); -void __MpegLoadModule(int version,u32 crc); +void __MpegLoadModule(int version, u32 crc); void Register_sceMpeg(); diff --git a/Core/HLE/scePsmf.h b/Core/HLE/scePsmf.h index ae143fce04..bb0e53c411 100644 --- a/Core/HLE/scePsmf.h +++ b/Core/HLE/scePsmf.h @@ -21,7 +21,7 @@ void Register_scePsmf(); void Register_scePsmfPlayer(); void __PsmfInit(); -void __PsmfPlayerLoadModule(int devkitVersion,u32 crc); +void __PsmfPlayerLoadModule(int devkitVersion, u32 crc); void __PsmfDoState(PointerWrap &p); void __PsmfPlayerDoState(PointerWrap &p); void __PsmfShutdown(); From 3af8a667e12e25d814f401f4af08ad774022f95f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 7 Oct 2022 07:03:29 -0700 Subject: [PATCH 325/326] Psmf: Fix save states. --- Core/HLE/sceKernelModule.cpp | 2 +- Core/HLE/scePsmf.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index cb1e9d091d..4f3d586672 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1144,7 +1144,7 @@ static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) { } static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) { - u32 crc = crc32(0, ptr, elfSize); + u32 crc = crc32(0, ptr, (uInt)elfSize); PSPModule *module = new PSPModule(); kernelObjects.Create(module); loadedModules.insert(module->GetUID()); diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index cf01260f2c..06fc172c53 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -711,7 +711,7 @@ void __PsmfDoState(PointerWrap &p) { } void __PsmfPlayerDoState(PointerWrap &p) { - auto s = p.Section("scePsmfPlayer", 1, 3); + auto s = p.Section("scePsmfPlayer", 1, 4); if (!s) return; @@ -724,7 +724,7 @@ void __PsmfPlayerDoState(PointerWrap &p) { Do(p, eventPsmfPlayerStatusChange); } CoreTiming::RestoreRegisterEvent(eventPsmfPlayerStatusChange, "PsmfPlayerStatusChangeEvent", &__PsmfPlayerStatusChange); - if (s < 3) { + if (s < 4) { psmfPlayerLibcrc = 0; } else { Do(p, psmfPlayerLibcrc); From bf14ca84d3180ce8b2bbf6c2345e4145027393aa Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 8 Oct 2022 14:38:49 +0200 Subject: [PATCH 326/326] OpenXR - Revision of VR rendering --- Common/GPU/OpenGL/GLRenderManager.cpp | 16 ---- Common/GPU/OpenGL/GLRenderManager.h | 1 - Common/VR/VRRenderer.cpp | 110 +++++++++++++------------- Common/VR/VRRenderer.h | 9 ++- android/jni/app-android.cpp | 8 +- 5 files changed, 68 insertions(+), 76 deletions(-) diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index 01a94fb8b4..01cccb8874 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -236,28 +236,12 @@ bool GLRenderManager::ThreadFrame() { firstFrame = false; } - // Start of an OpenXR frame. This updates user's head pose and VR timestamps. - // For fluent rendering, delay between StartVRRender and FinishVRRender must be very short. - if (IsVRBuild() && !vrRenderStarted) { - if (StartVRRender()) { - vrRenderStarted = true; - } else { - return false; - } - } - // Render the scene. Run(threadFrame_); VLOG("PULL: Finished frame %d", threadFrame_); } while (!nextFrame); - // Post OpenXR frame on a screen. - if (IsVRBuild() && vrRenderStarted) { - FinishVRRender(); - vrRenderStarted = false; - } - return true; } diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 089c588aeb..51d7e1e9eb 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -1051,7 +1051,6 @@ private: bool nextFrame = false; bool firstFrame = true; - bool vrRenderStarted = false; GLDeleter deleter_; bool skipGLCalls_ = false; diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index 2ad0014c10..22668fb3f3 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -16,6 +16,7 @@ XrFrameState frameState = {}; GLboolean initialized = GL_FALSE; GLboolean stageSupported = GL_FALSE; int vrConfig[VR_CONFIG_MAX] = {}; +ovrMatrix4f vrMatrix[VR_MATRIX_COUNT]; XrVector3f hmdorientation; XrVector3f hmdposition; @@ -310,6 +311,61 @@ bool VR_InitFrame( engine_t* engine ) { engine->appState.LayerCount = 0; memset(engine->appState.Layers, 0, sizeof(ovrCompositorLayer_Union) * ovrMaxLayerCount); + + // Update matrices + for (int matrix = 0; matrix < VR_MATRIX_COUNT; matrix++) { + if ((matrix == VR_PROJECTION_MATRIX_LEFT_EYE) || (matrix == VR_PROJECTION_MATRIX_RIGHT_EYE)) { + float near = (float)vrConfig[VR_CONFIG_FOV_SCALE] / 200.0f; + vrMatrix[matrix] = ovrMatrix4f_CreateProjectionFov(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown, near, 0.0f ); + } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { + XrPosef invView = invViewTransform[0]; + + // get axis mirroring configuration + float mx = vrConfig[VR_CONFIG_MIRROR_PITCH] ? -1 : 1; + float my = vrConfig[VR_CONFIG_MIRROR_YAW] ? -1 : 1; + float mz = vrConfig[VR_CONFIG_MIRROR_ROLL] ? -1 : 1; + + // ensure there is maximally one axis to mirror rotation + if (mx + my + mz < 0) { + mx *= -1.0f; + my *= -1.0f; + mz *= -1.0f; + } else { + invView = XrPosef_Inverse(invView); + } + + // create updated quaternion + if (mx + my + mz < 3 - EPSILON) { + XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation); + XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mx * ToRadians(rotation.x)); + XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, my * ToRadians(rotation.y)); + XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mz * ToRadians(rotation.z)); + invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw)); + } + + vrMatrix[matrix] = ovrMatrix4f_CreateFromQuaternion(&invView.orientation); + float scale = (float)VR_GetConfig(VR_CONFIG_6DOF_SCALE) * 0.000001f; + if (vrConfig[VR_CONFIG_6DOF_ENABLED]) { + vrMatrix[matrix].M[0][3] -= hmdposition.x * (vrConfig[VR_CONFIG_MIRROR_AXIS_X] ? -1.0f : 1.0f) * scale; + vrMatrix[matrix].M[1][3] -= hmdposition.y * (vrConfig[VR_CONFIG_MIRROR_AXIS_Y] ? -1.0f : 1.0f) * scale; + vrMatrix[matrix].M[2][3] -= hmdposition.z * (vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f) * scale; + } + if (vrConfig[VR_CONFIG_6DOF_PRECISE] && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { + float dx = fabs(invViewTransform[1].position.x - invViewTransform[0].position.x); + float dy = fabs(invViewTransform[1].position.y - invViewTransform[0].position.y); + float dz = fabs(invViewTransform[1].position.z - invViewTransform[0].position.z); + float ipd = sqrt(dx * dx + dy * dy + dz * dz); + XrVector3f separation = {ipd * scale, 0.0f, 0.0f}; + separation = XrQuaternionf_Rotate(invView.orientation, separation); + separation = XrVector3f_ScalarMultiply(separation, vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f); + vrMatrix[matrix].M[0][3] -= separation.x; + vrMatrix[matrix].M[1][3] -= separation.y; + vrMatrix[matrix].M[2][3] -= separation.z; + } + } else { + assert(false); + } + } return true; } @@ -468,57 +524,5 @@ void VR_BindFramebuffer(engine_t *engine) { } ovrMatrix4f VR_GetMatrix( VRMatrix matrix ) { - ovrMatrix4f output; - if ((matrix == VR_PROJECTION_MATRIX_LEFT_EYE) || (matrix == VR_PROJECTION_MATRIX_RIGHT_EYE)) { - float near = (float)vrConfig[VR_CONFIG_FOV_SCALE] / 200.0f; - output = ovrMatrix4f_CreateProjectionFov(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown, near, 0.0f ); - } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { - XrPosef invView = invViewTransform[0]; - - // get axis mirroring configuration - float mx = vrConfig[VR_CONFIG_MIRROR_PITCH] ? -1 : 1; - float my = vrConfig[VR_CONFIG_MIRROR_YAW] ? -1 : 1; - float mz = vrConfig[VR_CONFIG_MIRROR_ROLL] ? -1 : 1; - - // ensure there is maximally one axis to mirror rotation - if (mx + my + mz < 0) { - mx *= -1.0f; - my *= -1.0f; - mz *= -1.0f; - } else { - invView = XrPosef_Inverse(invView); - } - - // create updated quaternion - if (mx + my + mz < 3 - EPSILON) { - XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation); - XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mx * ToRadians(rotation.x)); - XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, my * ToRadians(rotation.y)); - XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mz * ToRadians(rotation.z)); - invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw)); - } - - output = ovrMatrix4f_CreateFromQuaternion(&invView.orientation); - float scale = (float)VR_GetConfig(VR_CONFIG_6DOF_SCALE) * 0.000001f; - if (vrConfig[VR_CONFIG_6DOF_ENABLED]) { - output.M[0][3] -= hmdposition.x * (vrConfig[VR_CONFIG_MIRROR_AXIS_X] ? -1.0f : 1.0f) * scale; - output.M[1][3] -= hmdposition.y * (vrConfig[VR_CONFIG_MIRROR_AXIS_Y] ? -1.0f : 1.0f) * scale; - output.M[2][3] -= hmdposition.z * (vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f) * scale; - } - if (vrConfig[VR_CONFIG_6DOF_PRECISE] && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) { - float dx = fabs(invViewTransform[1].position.x - invViewTransform[0].position.x); - float dy = fabs(invViewTransform[1].position.y - invViewTransform[0].position.y); - float dz = fabs(invViewTransform[1].position.z - invViewTransform[0].position.z); - float ipd = sqrt(dx * dx + dy * dy + dz * dz); - XrVector3f separation = {ipd * scale, 0.0f, 0.0f}; - separation = XrQuaternionf_Rotate(invView.orientation, separation); - separation = XrVector3f_ScalarMultiply(separation, vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f); - output.M[0][3] -= separation.x; - output.M[1][3] -= separation.y; - output.M[2][3] -= separation.z; - } - } else { - assert(false); - } - return output; + return vrMatrix[matrix]; } diff --git a/Common/VR/VRRenderer.h b/Common/VR/VRRenderer.h index 9375370cad..e19a0e82a5 100644 --- a/Common/VR/VRRenderer.h +++ b/Common/VR/VRRenderer.h @@ -26,10 +26,11 @@ enum VRConfig { }; enum VRMatrix { - VR_PROJECTION_MATRIX_LEFT_EYE = 0, - VR_PROJECTION_MATRIX_RIGHT_EYE = 1, - VR_VIEW_MATRIX_LEFT_EYE = 2, - VR_VIEW_MATRIX_RIGHT_EYE = 3 + VR_PROJECTION_MATRIX_LEFT_EYE, + VR_PROJECTION_MATRIX_RIGHT_EYE, + VR_VIEW_MATRIX_LEFT_EYE, + VR_VIEW_MATRIX_RIGHT_EYE, + VR_MATRIX_COUNT }; enum VRMode { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 482b864ea5..98e1ca6052 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -1057,16 +1057,20 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, SetCurrentThreadName("AndroidRender"); } + if (IsVRBuild() && !StartVRRender()) + return; + if (useCPUThread) { // This is the "GPU thread". - if (graphicsContext) - graphicsContext->ThreadFrame(); + if (!graphicsContext || !graphicsContext->ThreadFrame()) + return; } else { UpdateRunLoopAndroid(env); } if (IsVRBuild()) { UpdateVRInput(NativeKey, NativeTouch, g_Config.bHapticFeedback, dp_xscale, dp_yscale); + FinishVRRender(); } }