From 8dc3e8ed14b43c0bddf6f33aebd372f9ed813b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 14 May 2026 22:13:04 +0200 Subject: [PATCH 1/9] Add depthMinusOneToOne to specify GL's ridiculous behavior --- Common/GPU/Shader.cpp | 4 ++++ Common/GPU/Shader.h | 1 + Common/GPU/ShaderWriter.cpp | 3 +++ 3 files changed, 8 insertions(+) diff --git a/Common/GPU/Shader.cpp b/Common/GPU/Shader.cpp index 2fe810bb7333..19becbd320b7 100644 --- a/Common/GPU/Shader.cpp +++ b/Common/GPU/Shader.cpp @@ -50,6 +50,7 @@ void ShaderLanguageDesc::Init(ShaderLanguage lang) { lastFragData = nullptr; gles = false; forceMatrix4x4 = true; + depthMinusOneToOne = true; break; case GLSL_3xx: // Just used in the shader test. @@ -67,6 +68,7 @@ void ShaderLanguageDesc::Init(ShaderLanguage lang) { gles = true; forceMatrix4x4 = true; glslES30 = true; + depthMinusOneToOne = true; break; case GLSL_VULKAN: fragColor0 = "fragColor0"; @@ -86,6 +88,7 @@ void ShaderLanguageDesc::Init(ShaderLanguage lang) { forceMatrix4x4 = false; coefsFromBuffers = true; vertexIndex = true; + depthMinusOneToOne = false; break; case HLSL_D3D11: fragColor0 = "outfragment.target"; @@ -107,6 +110,7 @@ void ShaderLanguageDesc::Init(ShaderLanguage lang) { coefsFromBuffers = true; vsOutPrefix = "Out."; viewportYSign = "-"; + depthMinusOneToOne = false; break; } } diff --git a/Common/GPU/Shader.h b/Common/GPU/Shader.h index 5dbb6a549090..45e54cab5464 100644 --- a/Common/GPU/Shader.h +++ b/Common/GPU/Shader.h @@ -57,6 +57,7 @@ struct ShaderLanguageDesc { const char *vsOutPrefix = ""; const char *viewportYSign = ""; + bool depthMinusOneToOne = false; bool vertexIndex = false; bool glslES30 = false; // really glslES30Features. TODO: Clean this up. bool bitwiseOps = false; diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp index ecdee31eb107..1bcfa95b6835 100644 --- a/Common/GPU/ShaderWriter.cpp +++ b/Common/GPU/ShaderWriter.cpp @@ -361,6 +361,9 @@ void ShaderWriter::EndVSMain(Slice varyings) { if (strlen(lang_.viewportYSign)) { F(" gl_Position.y *= %s1.0;\n", lang_.viewportYSign); } + if (lang_.depthMinusOneToOne) { + F(" gl_Position.z = gl_Position.z * 2.0 - gl_Position.w;\n"); // homogenous math... looks confusing. + } C(" vs_out.pos = gl_Position;\n"); for (auto &varying : varyings) { F(" vs_out.%s = %s;\n", varying.name, varying.name); From 85de7fdc50e5ea754f16aada03e32bd9cddacb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 14 May 2026 22:20:35 +0200 Subject: [PATCH 2/9] Add new flag that will simplify the GL path --- Common/Math/lin/matrix4x4.cpp | 2 +- Common/Math/lin/matrix4x4.h | 2 +- Common/System/Display.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 4 ++-- GPU/GLES/GPU_GLES.cpp | 5 +++++ GPU/GPUState.h | 1 + Windows/GEDebugger/SimpleGLWindow.cpp | 4 ++-- Windows/GEDebugger/VertexPreview.cpp | 4 ++-- 8 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Common/Math/lin/matrix4x4.cpp b/Common/Math/lin/matrix4x4.cpp index e341210cee74..44a689ff7c3e 100644 --- a/Common/Math/lin/matrix4x4.cpp +++ b/Common/Math/lin/matrix4x4.cpp @@ -43,7 +43,7 @@ void Matrix4x4::setViewFrame(const Vec3 &pos, const Vec3 &vRight, const Vec3 &vV ww = 1.0f; } -void Matrix4x4::setOrtho(float left, float right, float bottom, float top, float near, float far) { +void Matrix4x4::setOrthoGL(float left, float right, float bottom, float top, float near, float far) { empty(); xx = 2.0f / (right - left); yy = 2.0f / (top - bottom); diff --git a/Common/Math/lin/matrix4x4.h b/Common/Math/lin/matrix4x4.h index 575b6a61d431..eec421258336 100644 --- a/Common/Math/lin/matrix4x4.h +++ b/Common/Math/lin/matrix4x4.h @@ -87,7 +87,7 @@ class Matrix4x4 { ww = 1.0f; } - void setOrtho(float left, float right, float bottom, float top, float near, float far); + void setOrthoGL(float left, float right, float bottom, float top, float near, float far); void setOrthoD3D(float left, float right, float bottom, float top, float near, float far); void setOrthoVulkan(float left, float right, float top, float bottom, float near, float far); diff --git a/Common/System/Display.cpp b/Common/System/Display.cpp index aae1776a57c7..0356a1cba116 100644 --- a/Common/System/Display.cpp +++ b/Common/System/Display.cpp @@ -138,7 +138,7 @@ Lin::Matrix4x4 ComputeOrthoMatrix(float xres, float yres, CoordConvention coordC break; case CoordConvention::OpenGL: default: - ortho.setOrtho(0.0f, xres, yres, 0.0f, -1.0f, 1.0f); + ortho.setOrthoGL(0.0f, xres, yres, 0.0f, -1.0f, 1.0f); break; } // Compensate for rotated display if needed. diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 18bc85c09215..dc4c41fc9468 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -3185,9 +3185,9 @@ bool GetOutputFramebuffer(Draw::DrawContext *draw, GPUDebugBuffer &buffer) { if (fmt != Draw::DataFormat::B8G8R8A8_UNORM) fmt = Draw::DataFormat::R8G8B8A8_UNORM; - bool flipped = g_Config.iGPUBackend == (int)GPUBackend::OPENGL; + bool flipY = g_Config.iGPUBackend == (int)GPUBackend::OPENGL; - buffer.Allocate(w, h, fmt == Draw::DataFormat::R8G8B8A8_UNORM ? GPU_DBG_FORMAT_8888 : GPU_DBG_FORMAT_8888_BGRA, flipped); + buffer.Allocate(w, h, fmt == Draw::DataFormat::R8G8B8A8_UNORM ? GPU_DBG_FORMAT_8888 : GPU_DBG_FORMAT_8888_BGRA, flipY); buffer.SetIsBackbuffer(true); return draw->CopyFramebufferToMemory(nullptr, Draw::Aspect::COLOR_BIT, 0, 0, w, h, fmt, buffer.GetData(), w, Draw::ReadbackMode::BLOCK, "GetOutputFramebuffer"); } diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 307ad5e1222e..65bd18c70622 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -190,6 +190,11 @@ u32 GPU_GLES::CheckGPUFeatures() const { features |= GPU_ROUND_DEPTH_TO_16BIT; } } + + if (!g_Config.bSkipBufferEffects) { + features |= GPU_USE_BUFFERED_FLIP; + } + return features; } diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 627a4c05c804..fabc602d5a1f 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -480,6 +480,7 @@ enum { GPU_USE_CLIP_DISTANCE = FLAG_BIT(24), GPU_USE_CULL_DISTANCE = FLAG_BIT(25), GPU_USE_SHADER_BLENDING = FLAG_BIT(26), // This is set to false when skip buffer effects is enabled and GPU_USE_FRAMEBUFFER_FETCH is not. + GPU_USE_BUFFERED_FLIP = FLAG_BIT(27), // We use a flip hack to pretend the coordinate system is right-side-up, except if buffered rendering is off. // VR flags (reserved or in-use) GPU_USE_VIRTUAL_REALITY = FLAG_BIT(29), diff --git a/Windows/GEDebugger/SimpleGLWindow.cpp b/Windows/GEDebugger/SimpleGLWindow.cpp index 08060967cee3..a1bc6178cab8 100644 --- a/Windows/GEDebugger/SimpleGLWindow.cpp +++ b/Windows/GEDebugger/SimpleGLWindow.cpp @@ -237,7 +237,7 @@ void SimpleGLWindow::DrawChecker() { const GLubyte indices[4] = {0,1,3,2}; Matrix4x4 ortho; - ortho.setOrtho(0, (float)w_, (float)h_, 0, -1, 1); + ortho.setOrthoGL(0, (float)w_, (float)h_, 0, -1, 1); glUniformMatrix4fv(drawProgram_->u_viewproj, 1, GL_FALSE, ortho.getReadPtr()); if (vao_) { glBufferData(GL_ARRAY_BUFFER, sizeof(pos) + sizeof(texCoords), nullptr, GL_DYNAMIC_DRAW); @@ -429,7 +429,7 @@ void SimpleGLWindow::Redraw(bool andSwap) { const float *texCoords = tflipped_ ? texCoordsFlipped : texCoordsNormal; Matrix4x4 ortho; - ortho.setOrtho(0, (float)w_, (float)h_, 0, -1, 1); + ortho.setOrthoGL(0, (float)w_, (float)h_, 0, -1, 1); glUniformMatrix4fv(drawProgram_->u_viewproj, 1, GL_FALSE, ortho.getReadPtr()); if (vao_) { glBufferData(GL_ARRAY_BUFFER, sizeof(pos) + sizeof(texCoordsNormal), nullptr, GL_DYNAMIC_DRAW); diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index eaf4fdc503b6..87304666c3a2 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -153,7 +153,7 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { }; Lin::Matrix4x4 ortho; - ortho.setOrtho(-(float)gstate_c.curRTOffsetX, (primaryWindow->TexWidth() - (int)gstate_c.curRTOffsetX) * scale[0], primaryWindow->TexHeight() * scale[1], 0, -1, 1); + ortho.setOrthoGL(-(float)gstate_c.curRTOffsetX, (primaryWindow->TexWidth() - (int)gstate_c.curRTOffsetX) * scale[0], primaryWindow->TexHeight() * scale[1], 0, -1, 1); glUniformMatrix4fv(previewProgram->u_viewproj, 1, GL_FALSE, ortho.getReadPtr()); if (previewVao != 0) { glBindVertexArray(previewVao); @@ -216,7 +216,7 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { } Lin::Matrix4x4 ortho; - ortho.setOrtho(0.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, 0.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, -1.0f, 1.0f); + ortho.setOrthoGL(0.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureXOffset * invRealTexWidth, 1.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, 0.0f - (float)gstate_c.curTextureYOffset * invRealTexHeight, -1.0f, 1.0f); glUniformMatrix4fv(texPreviewProgram->u_viewproj, 1, GL_FALSE, ortho.getReadPtr()); if (texPreviewVao != 0) { glBindVertexArray(texPreviewVao); From afb764e6ae0d2092747b762c7e47f13551228c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 14 May 2026 23:21:14 +0200 Subject: [PATCH 3/9] Rendering: Unify the Y-flipping state across backends (now only applied in-shader, not through matrices). --- GPU/Common/ShaderUniforms.cpp | 53 +++++++++++--------------- GPU/Common/SoftwareTransformCommon.cpp | 28 +++----------- GPU/Common/VertexShaderGenerator.cpp | 8 ++++ GPU/D3D11/DrawEngineD3D11.cpp | 4 +- GPU/GLES/DrawEngineGLES.cpp | 3 +- GPU/GLES/GPU_GLES.cpp | 4 +- GPU/GLES/ShaderManagerGLES.cpp | 28 +++++--------- GPU/GPUState.h | 2 +- UI/GameSettingsScreen.cpp | 1 + 9 files changed, 52 insertions(+), 79 deletions(-) diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 35985587caa3..48dfa97db114 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -14,18 +14,12 @@ using namespace Lin; -static void ConvertProjMatrixToVulkan(Matrix4x4 &in) { +static void ConvertProjMatrixToZeroToOneDepth(Matrix4x4 &in) { const Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f); const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); in.translateAndScale(trans, scale); } -static void ConvertProjMatrixToD3D11(Matrix4x4 &in) { - const Vec3 trans(gstate_c.vpXOffset, -gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f); - const Vec3 scale(gstate_c.vpWidthScale, -gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); - in.translateAndScale(trans, scale); -} - void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bool hasNegZ) { // Account for the projection viewport adjustment when viewport is too large. auto reverseViewportX = [](float x) { @@ -72,6 +66,24 @@ void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bo maxValues[3] = NAN; } +// TODO: This will be removed later. +static inline void FlipProjMatrix(Matrix4x4 &in) { + const bool invertedY = gstate_c.vpHeight < 0; + if (invertedY) { + in[1] = -in[1]; + in[5] = -in[5]; + in[9] = -in[9]; + in[13] = -in[13]; + } + const bool invertedX = gstate_c.vpWidth < 0; + if (invertedX) { + in[0] = -in[0]; + in[4] = -in[4]; + in[8] = -in[8]; + in[12] = -in[12]; + } +} + void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport, bool useBufferedRendering) { if (dirtyUniforms & DIRTY_TEXENV) { Uint8x3ToFloat3(ub->texEnvColor, gstate.texenvcolor); @@ -115,26 +127,9 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView Matrix4x4 flippedMatrix; memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float)); - const bool invertedY = gstate_c.vpHeight < 0; - if (invertedY) { - flippedMatrix[1] = -flippedMatrix[1]; - flippedMatrix[5] = -flippedMatrix[5]; - flippedMatrix[9] = -flippedMatrix[9]; - flippedMatrix[13] = -flippedMatrix[13]; - } - const bool invertedX = gstate_c.vpWidth < 0; - if (invertedX) { - flippedMatrix[0] = -flippedMatrix[0]; - flippedMatrix[4] = -flippedMatrix[4]; - flippedMatrix[8] = -flippedMatrix[8]; - flippedMatrix[12] = -flippedMatrix[12]; - } - if (flipViewport) { - ConvertProjMatrixToD3D11(flippedMatrix); - } else { - ConvertProjMatrixToVulkan(flippedMatrix); - } + FlipProjMatrix(flippedMatrix); + ConvertProjMatrixToZeroToOneDepth(flippedMatrix); if (!useBufferedRendering && g_display.rotation != DisplayRotation::ROTATE_0) { flippedMatrix = flippedMatrix * g_display.rot_matrix; } @@ -145,11 +140,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView if (dirtyUniforms & DIRTY_PROJTHROUGHMATRIX) { Matrix4x4 proj_through; - if (flipViewport) { - proj_through.setOrthoD3D(0.0f, gstate_c.curRTWidth, gstate_c.curRTHeight, 0, 0, 1); - } else { - proj_through.setOrthoVulkan(0.0f, gstate_c.curRTWidth, 0, gstate_c.curRTHeight, 0, 1); - } + proj_through.setOrthoVulkan(0.0f, gstate_c.curRTWidth, 0, gstate_c.curRTHeight, 0, 1); if (!useBufferedRendering && g_display.rotation != DisplayRotation::ROTATE_0) { proj_through = proj_through * g_display.rot_matrix; } diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 6ef76f3fbfa5..5c29975a3df5 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -65,27 +65,15 @@ static void SwapUVs(TransformedVertex &a, TransformedVertex &b) { // Note: 0 is BR and 2 is TL. -static void RotateUV(TransformedVertex v[4], bool flippedY) { - // We use the transformed tl/br coordinates to figure out whether they're flipped or not. - float ySign = flippedY ? -1.0 : 1.0; - +static void RotateUV(TransformedVertex v[4]) { const float x1 = v[2].x; const float x2 = v[0].x; - const float y1 = v[2].y * ySign; - const float y2 = v[0].y * ySign; - - if ((x1 < x2 && y1 < y2) || (x1 > x2 && y1 > y2)) - SwapUVs(v[1], v[3]); -} + const float y1 = v[2].y; + const float y2 = v[0].y; -static void RotateUVThrough(TransformedVertex v[4]) { - float x1 = v[2].x; - float x2 = v[0].x; - float y1 = v[2].y; - float y2 = v[0].y; - - if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) + if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) { SwapUVs(v[1], v[3]); + } } // Clears on the PSP are best done by drawing a series of vertical strips @@ -658,11 +646,7 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts, trans[3].v = transVtxBR.v * vscale; // That's the four corners. Now process UV rotation. - if (throughmode) { - RotateUVThrough(trans); - } else { - RotateUV(trans, params_.flippedY); - } + RotateUV(trans); // Triangle: BR-TR-TL indsOut[0] = i * 2 + 0; diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index ced337597085..f3591c626fcd 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -1276,6 +1276,14 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } } + bool flipY = strlen(compat.viewportYSign) > 0; + if (gstate_c.Use(GPU_USE_NONBUFFERED_FLIP)) { + flipY = !flipY; + } + if (flipY) { + WRITE(p, " outPos.y *= -1.0;\n"); + } + // We've named the output gl_Position in HLSL as well. WRITE(p, " %sgl_Position = outPos;\n", compat.vsOutPrefix); diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index 5dc9da2646ef..cc14e712f3a3 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -436,8 +436,8 @@ void DrawEngineD3D11::Flush() { SoftwareTransform swTransform(params); - const Lin::Vec3 trans(gstate_c.vpXOffset, -gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f); - const Lin::Vec3 scale(gstate_c.vpWidthScale, -gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); + const Lin::Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f); + const Lin::Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, gstate_c.vpHeight < 0, trans, scale); swTransform.Transform(prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, &result); diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 7a5c760f9b4f..4728c57083dd 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -387,8 +387,7 @@ void DrawEngineGLES::Flush() { const Lin::Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset); const Lin::Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale); - const bool invertedY = gstate_c.vpHeight * (params.flippedY ? 1.0 : -1.0f) < 0; - swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, invertedY, trans, scale); + swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, gstate_c.vpHeight < 0, trans, scale); swTransform.Transform(prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, &result); // Non-zero depth clears are unusual, but some drivers don't match drawn depth values to cleared values. diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 65bd18c70622..c233d80e9740 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -191,8 +191,8 @@ u32 GPU_GLES::CheckGPUFeatures() const { } } - if (!g_Config.bSkipBufferEffects) { - features |= GPU_USE_BUFFERED_FLIP; + if (g_Config.bSkipBufferEffects) { + features |= GPU_USE_NONBUFFERED_FLIP; } return features; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index e02aeced0b7d..ec610ec6d496 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -309,20 +309,14 @@ static void SetMatrix4x3(GLRenderManager *render, GLint *uniform, const float *m render->SetUniformM4x4(uniform, m4x4); } -static inline void ScaleProjMatrix(Matrix4x4 &in, bool useBufferedRendering) { - float yOffset = gstate_c.vpYOffset; - if (!useBufferedRendering) { - // GL upside down is a pain as usual. - yOffset = -yOffset; - } - const Vec3 trans(gstate_c.vpXOffset, yOffset, gstate_c.vpZOffset); +static inline void ConvertProjMatrixToGL(Matrix4x4 &in) { + const Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset); const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale); in.translateAndScale(trans, scale); } -static inline void FlipProjMatrix(Matrix4x4 &in, bool useBufferedRendering) { - - const bool invertedY = useBufferedRendering ? (gstate_c.vpHeight < 0) : (gstate_c.vpHeight > 0); +static inline void FlipProjMatrix(Matrix4x4 &in) { + const bool invertedY = gstate_c.vpHeight < 0; if (invertedY) { in[1] = -in[1]; in[5] = -in[5]; @@ -435,8 +429,8 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin } UpdateVRParams(gstate.projMatrix); - FlipProjMatrix(vrProjection, useBufferedRendering); - ScaleProjMatrix(vrProjection, useBufferedRendering); + FlipProjMatrix(vrProjection); + ConvertProjMatrixToGL(vrProjection); render_->SetUniformM4x4(&u_proj_lens, vrProjection.m); } @@ -444,19 +438,15 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin Matrix4x4 flippedMatrix; memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float)); - FlipProjMatrix(flippedMatrix, useBufferedRendering); - ScaleProjMatrix(flippedMatrix, useBufferedRendering); + FlipProjMatrix(flippedMatrix); + ConvertProjMatrixToGL(flippedMatrix); render_->SetUniformM4x4(&u_proj, flippedMatrix.m); render_->SetUniformF1(&u_rotation, useBufferedRendering ? 0 : (float)g_display.rotation); } if (dirty & DIRTY_PROJTHROUGHMATRIX) { Matrix4x4 proj_through; - if (useBufferedRendering) { - proj_through.setOrtho(0.0f, gstate_c.curRTWidth, 0.0f, gstate_c.curRTHeight, 0.0f, 1.0f); - } else { - proj_through.setOrtho(0.0f, gstate_c.curRTWidth, gstate_c.curRTHeight, 0.0f, 0.0f, 1.0f); - } + proj_through.setOrthoGL(0.0f, gstate_c.curRTWidth, 0.0f, gstate_c.curRTHeight, 0.0f, 1.0f); render_->SetUniformM4x4(&u_proj_through, proj_through.getReadPtr()); } if (dirty & DIRTY_TEXENV) { diff --git a/GPU/GPUState.h b/GPU/GPUState.h index fabc602d5a1f..713680b64190 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -480,7 +480,7 @@ enum { GPU_USE_CLIP_DISTANCE = FLAG_BIT(24), GPU_USE_CULL_DISTANCE = FLAG_BIT(25), GPU_USE_SHADER_BLENDING = FLAG_BIT(26), // This is set to false when skip buffer effects is enabled and GPU_USE_FRAMEBUFFER_FETCH is not. - GPU_USE_BUFFERED_FLIP = FLAG_BIT(27), // We use a flip hack to pretend the coordinate system is right-side-up, except if buffered rendering is off. + GPU_USE_NONBUFFERED_FLIP = FLAG_BIT(27), // We use a flip hack to pretend the coordinate system is right-side-up, except if buffered rendering is off. // VR flags (reserved or in-use) GPU_USE_VIRTUAL_REALITY = FLAG_BIT(29), diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index cf9eaff5a18c..1d1891a169b5 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -488,6 +488,7 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) } System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED); + System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); }); skipBufferEffects->SetDisabledPtr(&g_Config.bSoftwareRendering); From 91b3741a572cc50c5f62fb701ceb77af35d5e1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 5 May 2026 18:38:50 +0200 Subject: [PATCH 4/9] Implement a simple cheat database download facility --- Common/Log.h | 1 + Common/Log/LogManager.cpp | 1 + Common/UI/Notice.h | 6 +- Common/UI/PopupScreens.cpp | 1 + UI/CwCheatScreen.cpp | 143 +++++++++++++++++++++++++++++++-- UI/CwCheatScreen.h | 5 ++ UI/OnScreenDisplay.cpp | 4 - UI/UIAtlas.cpp | 1 + Windows/PPSSPP.vcxproj | 1 + Windows/PPSSPP.vcxproj.filters | 3 + assets/cheats.json | 9 +++ assets/ui_images/images.svg | 17 ++-- 12 files changed, 169 insertions(+), 23 deletions(-) create mode 100644 assets/cheats.json diff --git a/Common/Log.h b/Common/Log.h index 1e672cec6020..268bfc35fb39 100644 --- a/Common/Log.h +++ b/Common/Log.h @@ -57,6 +57,7 @@ enum class Log { GeDebugger, UI, IAP, + CwCheats, sceAudio, sceCtrl, diff --git a/Common/Log/LogManager.cpp b/Common/Log/LogManager.cpp index 3c1a4b3bb6b0..dff5ecba86f6 100644 --- a/Common/Log/LogManager.cpp +++ b/Common/Log/LogManager.cpp @@ -96,6 +96,7 @@ static const char * const g_logTypeNames[] = { "GEDEBUGGER", "UI", "IAP", + "CWCHEATS", "SCEAUDIO", "SCECTRL", "SCEDISP", diff --git a/Common/UI/Notice.h b/Common/UI/Notice.h index e35710ae76d2..5962e7750200 100644 --- a/Common/UI/Notice.h +++ b/Common/UI/Notice.h @@ -34,9 +34,6 @@ class NoticeView : public UI::InertView { level_ = level; text_ = text; } - void SetSquishy(bool squishy) { - squishy_ = squishy; - } void SetWrapText(bool wrapText) { wrapText_ = wrapText; } @@ -53,8 +50,7 @@ class NoticeView : public UI::InertView { std::string iconName_; NoticeLevel level_; mutable float height1_ = 0.0f; - bool squishy_ = false; - bool wrapText_ = false; + bool wrapText_ = true; }; ImageID GetOSDIcon(NoticeLevel level); diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index 325677df6bc4..b78dc024df6a 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -816,6 +816,7 @@ void AskForInput(ScreenManager *screenManager, RequesterToken token, UI::View *s PopupTextInputChoice::PopupTextInputChoice(RequesterToken token, std::string *value, std::string_view title, std::string_view placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams) : AbstractChoiceWithValueDisplay(title, layoutParams), screenManager_(screenManager), value_(value), placeHolder_(placeholder), maxLen_(maxLen), token_(token), restriction_(StringRestriction::None) { + _dbg_assert_(value); OnClick.Handle(this, &PopupTextInputChoice::HandleClick); } diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 216c70d01ebe..4f949fe8f638 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -18,29 +18,132 @@ #include "ppsspp_config.h" #include "ext/xxhash.h" #include "Common/UI/UI.h" +#include "Common/UI/PopupScreens.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/File/FileUtil.h" +#include "Common/Net/HTTPClient.h" #include "Common/StringUtils.h" #include "Common/System/System.h" #include "Common/System/Request.h" -#include "Common/UI/PopupScreens.h" #include "Core/System.h" #include "Core/Config.h" #include "Core/CwCheat.h" #include "Core/MIPS/JitCommon/JitCommon.h" - +#undef new +#ifdef SYSTEM_RAPIDJSON +#include +#else +#include "ext/rapidjson/include/rapidjson/document.h" +#endif +#include "Common/DbgNew.h" #include "UI/GameInfoCache.h" #include "UI/CwCheatScreen.h" #include "UI/MiscViews.h" static const int FILE_CHECK_FRAME_INTERVAL = 53; +constexpr char g_cheatDBListUrl[] = "https://metadata.ppsspp.org/cheats.json"; + static Path GetGlobalCheatFilePath() { return GetSysDirectory(DIRECTORY_CHEATS) / "cheat.db"; } +struct CheatDatabaseDownloadInfo { + std::string name; + std::string maintainer; + std::string url; +}; + +// Use rapidjson to parse the cheat database list. +std::vector ParseJson(std::string_view json) { + rapidjson::Document d; + d.Parse(json.data(), json.size()); + + std::vector downloads; + + if (d.HasParseError()) { + ERROR_LOG(Log::CwCheats, "Failed to parse cheat database list: %.*s", STR_VIEW(json)); + return downloads; + } + if (!d.IsObject()) { + ERROR_LOG(Log::CwCheats, "1"); + return downloads; + } + if (!d.HasMember("databases") || !d["databases"].IsArray()) { + ERROR_LOG(Log::CwCheats, "databases is not an array"); + return downloads; + } + + const auto& dbs = d["databases"]; + for (auto& db : dbs.GetArray()) { + if (!db.IsObject()) + continue; + CheatDatabaseDownloadInfo info; + if (!db.HasMember("name") || !db["name"].IsString()) + continue; + info.name = db["name"].GetString(); + if (!db.HasMember("maintainer") || !db["maintainer"].IsString()) + continue; + info.maintainer = db["maintainer"].GetString(); + if (!db.HasMember("url") || !db["url"].IsString()) + continue; + info.url = db["url"].GetString(); + downloads.push_back(info); + } + return downloads; +} + +class CwCheatDownloadPopupScreen : public UI::PopupScreen { +public: + CwCheatDownloadPopupScreen(); + + void CreatePopupContents(UI::ViewGroup *parent) override; + void update() override; + + const char *tag() const override { return "CwCheatDownloadScreen"; } + + const std::string &ChosenUrl() const { return chosenUrl_; } + +private: + std::shared_ptr cheatsRequest_; + std::string chosenUrl_; + std::vector cheatDbs_; +}; + +CwCheatDownloadPopupScreen::CwCheatDownloadPopupScreen() : PopupScreen(T(I18NCat::DIALOG, "Back")) { + cheatsRequest_ = g_DownloadManager.StartDownload(g_cheatDBListUrl, Path(), http::RequestFlags::KeepInMemory, nullptr, "cheatdblist"); +} + +void CwCheatDownloadPopupScreen::update() { + UI::PopupScreen::update(); + if (cheatsRequest_ && cheatsRequest_->Done()) { + std::string data; + cheatsRequest_->buffer().TakeAll(&data); + cheatDbs_ = ParseJson(data); + cheatsRequest_.reset(); + RecreateViews(); + } +} + +void CwCheatDownloadPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { + using namespace UI; + auto cw = GetI18NCategory(I18NCat::CWCHEATS); + + if (File::Exists(GetGlobalCheatFilePath())) { + parent->Add(new NoticeView(NoticeLevel::WARN, cw->T("A cheat database already exists. Downloading a new one will overwrite it."), "")); + } + + for (auto &db : cheatDbs_) { + parent->Add(new Choice(db.name + " - " + db.maintainer, ImageID("I_DOWNLOAD")))->OnClick.Add([this, url = db.url](UI::EventParams &) { + INFO_LOG(Log::CwCheats, "Chosen cheat database URL: %s", url.c_str()); + chosenUrl_ = url; + TriggerFinish(DialogResult::DR_OK); + }); + } +} + CwCheatScreen::CwCheatScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::Default) { } @@ -61,8 +164,7 @@ bool CwCheatScreen::TryLoadCheatInfo() { return false; } gameID = info->GetParamSFO().GetValueString("DISC_ID"); - if ((info->id.empty() || !info->disc_total) - && gamePath_.FilePathContainsNoCase("PSP/GAME/")) { + if ((info->id.empty() || !info->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/")) { gameID = g_paramSFO.GenerateFakeID(gamePath_); } @@ -100,6 +202,19 @@ bool CwCheatScreen::key(const KeyInput &input) { return UITwoPaneBaseDialogScreen::key(input); } +void CwCheatScreen::dialogFinished(const Screen *dialog, DialogResult result) { + if (auto downloadScreen = dynamic_cast(dialog)) { + if (downloadRequest_) { + ERROR_LOG(Log::CwCheats, "Download already in progress, ignoring new download request."); + return; + } + INFO_LOG(Log::CwCheats, "Starting download of cheat database from %s", downloadScreen->ChosenUrl().c_str()); + downloadRequest_ = g_DownloadManager.StartDownload(downloadScreen->ChosenUrl(), GetGlobalCheatFilePath(), http::RequestFlags::ProgressBar, nullptr, "cheatdbdownload"); + } else { + UITwoPaneBaseDialogScreen::dialogFinished(dialog, result); + } +} + void CwCheatScreen::CreateSettingsViews(UI::ViewGroup *leftColumn) { using namespace UI; auto cw = GetI18NCategory(I18NCat::CWCHEATS); @@ -109,6 +224,10 @@ void CwCheatScreen::CreateSettingsViews(UI::ViewGroup *leftColumn) { //leftColumn->Add(new Choice(cw->T("Add Cheat")))->OnClick.Handle(this, &CwCheatScreen::OnAddCheat); leftColumn->Add(new ItemHeader(cw->T("Import Cheats"))); + leftColumn->Add(new Choice(cw->T("Download database")))->OnClick.Add([this](UI::EventParams &) { + screenManager()->push(new CwCheatDownloadPopupScreen()); + }); + Path cheatPath = GetGlobalCheatFilePath(); std::string root = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT).ToString(); @@ -222,6 +341,16 @@ void CwCheatScreen::update() { fileCheckCounter_ = 0; } + if (downloadRequest_ && downloadRequest_->Done()) { + if (!downloadRequest_->Failed()) { + INFO_LOG(Log::CwCheats, "Cheat database downloaded successfully to %s.", GetGlobalCheatFilePath().ToVisualString().c_str()); + RecreateViews(); + } else { + ERROR_LOG(Log::CwCheats, "Failed to download cheat database"); + } + downloadRequest_.reset(); + } + UIBaseDialogScreen::update(); } @@ -313,7 +442,7 @@ void CwCheatScreen::OnImportBrowse(UI::EventParams ¶ms) { return; } Path path(value); - INFO_LOG(Log::System, "Attempting to load cheats from: '%s'", path.ToVisualString().c_str()); + INFO_LOG(Log::CwCheats, "Attempting to load cheats from: '%s'", path.ToVisualString().c_str()); int cheatsFound = 0; ImportAndReport(path); }); @@ -327,12 +456,12 @@ void CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { bool CwCheatScreen::ImportCheats(const Path &cheatFile, int *cheatsFound) { FILE *in = File::OpenCFile(cheatFile, "rt"); if (!in) { - WARN_LOG(Log::Common, "Unable to open %s\n", cheatFile.c_str()); + WARN_LOG(Log::CwCheats, "Unable to open %s\n", cheatFile.c_str()); return false; } if (gameID_.length() != 9 || !engine_) { - WARN_LOG(Log::Common, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); + WARN_LOG(Log::CwCheats, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); return false; } diff --git a/UI/CwCheatScreen.h b/UI/CwCheatScreen.h index 6922aa26b61d..84e2525bdc43 100644 --- a/UI/CwCheatScreen.h +++ b/UI/CwCheatScreen.h @@ -26,6 +26,7 @@ #include "UI/BaseScreens.h" #include "UI/SimpleDialogScreen.h" #include "UI/MiscViews.h" +#include "Common/Net/HTTPClient.h" struct CheatFileInfo; class CWCheatEngine; @@ -58,6 +59,8 @@ class CwCheatScreen : public UITwoPaneBaseDialogScreen { void CreateContentViews(UI::ViewGroup *) override; std::string_view GetTitle() const override; + void dialogFinished(const Screen *dialog, DialogResult result) override; + private: void OnCheckBox(int index); bool ImportCheats(const Path &cheatFile, int *cheatsFound); @@ -80,4 +83,6 @@ class CwCheatScreen : public UITwoPaneBaseDialogScreen { UI::ViewGroup *cheatList_ = nullptr; ViewSearch search_; + + std::shared_ptr downloadRequest_; }; diff --git a/UI/OnScreenDisplay.cpp b/UI/OnScreenDisplay.cpp index 1cc1923ecaa3..271855fd7a3f 100644 --- a/UI/OnScreenDisplay.cpp +++ b/UI/OnScreenDisplay.cpp @@ -439,10 +439,6 @@ void NoticeView::GetContentDimensionsBySpec(const UIContext &dc, UI::MeasureSpec ApplyBoundBySpec(layoutWidth, horiz); const int align = wrapText_ ? FLAG_WRAP_TEXT : 0; MeasureNotice(dc, level_, text_, detailsText_, iconName_, align, layoutWidth, &w, &h, &height1_); - // Layout hack! Some weird problems with the layout that I can't figure out right now.. - if (squishy_) { - w = 50.0; - } } void NoticeView::Draw(UIContext &dc) { diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index 791c1d8fa697..c2ca1142d5cb 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -197,6 +197,7 @@ static const ImageMeta g_uiImageIDs[] = { {"I_ARCHIVE_ZIP", false}, {"I_ARCHIVE_7Z", false}, {"I_ARCHIVE_RAR", false}, + {"I_DOWNLOAD", false}, }; static std::string PNGNameFromID(std::string_view id) { diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index ed8a0029ef36..3f62b6e38319 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -1185,6 +1185,7 @@ true true + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 4bc460a74415..40ca4d963758 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -845,6 +845,9 @@ assets + + assets + diff --git a/assets/cheats.json b/assets/cheats.json new file mode 100644 index 000000000000..39711f8053f3 --- /dev/null +++ b/assets/cheats.json @@ -0,0 +1,9 @@ +{ + "databases": [ + { + "name": "CWCheat-Database-Plus", + "maintainer": "Saramagrean", + "url": "https://raw.githubusercontent.com/Saramagrean/CWCheat-Database-Plus-/refs/heads/master/cheat.db" + } + ] +} diff --git a/assets/ui_images/images.svg b/assets/ui_images/images.svg index 8b1ecbdcb3d8..13b8100a8d86 100644 --- a/assets/ui_images/images.svg +++ b/assets/ui_images/images.svg @@ -23,9 +23,9 @@ inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" - inkscape:zoom="4" - inkscape:cx="361.75" - inkscape:cy="499.625" + inkscape:zoom="5.6568542" + inkscape:cx="331.63308" + inkscape:cy="448.74764" inkscape:window-width="3840" inkscape:window-height="2071" inkscape:window-x="-9" @@ -125,7 +125,7 @@ transform="matrix(0.51483133,0,0,0.48865723,85.611849,129.89633)" aria-label="SD" />