From 732288c03aa450bfc1644143c2746cb6d2867903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 23:13:04 +0100 Subject: [PATCH 1/2] Brave Story: Hack to make the bloom effect run much more efficiently. Fixes #19820 Not ready for merge, it needs more checks and maybe a compat flag. --- Core/MemMap.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 11 +++++++++++ GPU/Common/GPUStateUtils.cpp | 13 +++++++++++++ GPU/GPUState.h | 3 +++ GPU/Vulkan/DrawEngineVulkan.cpp | 4 ++++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 33a173464d..a7b5caec05 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -149,7 +149,7 @@ static bool Memory_TryBase(MemMapSetupFlags flags) { } } - int i; // the final values is used in the next loop + int i; // the final value of i is used in the next loop for (i = 0; i < ARRAY_SIZE(views); i++) { const MemoryView &view = views[i]; if (view.size == 0) diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 3e29468ddf..e6ac55709c 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1273,6 +1273,17 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF return true; } + // There's a special case we can handle here, where the game is texturing from the same pixels being read, in order to + // implement a DST*DST blending function, which the PSP can't do. However on the PC we can absolutely do this! + // TODO: Add more checks here. + if (gstate.isAlphaBlendEnabled() && gstate.getBlendEq() == GE_BLENDMODE_MUL_AND_ADD && gstate.getBlendFuncA() == GE_SRCBLEND_DSTCOLOR && gstate.getBlendFuncB() == GE_DSTBLEND_FIXB && gstate.getFixB() == 0x0) { + // This is the DST*DST case. + // We can just override the blend mode. Let's set a state variable. + // We also just leave the last texture bound, ideally we should bind a placeholder here. + gstate_c.dstSquared = true; + return true; + } + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight); if (renderCopy) { VirtualFramebuffer copyInfo = *framebuffer; diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 02c39d27c5..0ec0ff919d 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -1154,6 +1154,19 @@ static void ConvertBlendState(GenericBlendState &blendState, FBReadSetting useFB // // If we can't apply blending, we make a copy of the framebuffer and do it manually. + if (gstate_c.dstSquared && useFBRead != FBReadSetting::Forced) { + blendState.blendEnabled = true; + blendState.applyFramebufferRead = false; + blendState.dirtyShaderBlendFixValues = false; + blendState.useBlendColor = false; + blendState.replaceBlend = REPLACE_BLEND_NO; + blendState.simulateLogicOpType = SimulateLogicOpShaderTypeIfNeeded(); + blendState.replaceAlphaWithStencil = REPLACE_ALPHA_NO; + blendState.setEquation(BlendEq::ADD, BlendEq::ADD); + blendState.setFactors(BlendFactor::ZERO, BlendFactor::DST_COLOR, BlendFactor::ZERO, BlendFactor::ONE); + return; + } + blendState.applyFramebufferRead = false; blendState.dirtyShaderBlendFixValues = false; blendState.useBlendColor = false; diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 1eb67262a3..9c14867cb0 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -656,6 +656,9 @@ public: // We detect this case and go into a special drawing mode. bool blueToAlpha; + // DST squared, used in Brave Story + bool dstSquared; + // U/V is 1:1 to pixels. Can influence texture sampling. bool pixelMapped; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 7793c3d316..3399cefa3a 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -470,6 +470,7 @@ void DrawEngineVulkan::Flush() { if (result.action == SW_DRAW_INDEXED) { if (textureNeedsApply) { gstate_c.pixelMapped = result.pixelMapped; + gstate_c.dstSquared = false; textureCache_->ApplyTexture(); gstate_c.pixelMapped = false; textureCache_->GetVulkanHandles(imageView, sampler); @@ -477,6 +478,9 @@ void DrawEngineVulkan::Flush() { imageView = (VkImageView)draw_->GetNativeObject(gstate_c.textureIsArray ? Draw::NativeObject::NULL_IMAGEVIEW_ARRAY : Draw::NativeObject::NULL_IMAGEVIEW); if (sampler == VK_NULL_HANDLE) sampler = nullSampler_; + if (gstate_c.dstSquared) { + gstate_c.Dirty(DIRTY_BLEND_STATE); + } } 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)) { From 60a065b2428f36a20e25ea6b710d376ee8873174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 23:29:08 +0100 Subject: [PATCH 2/2] Make the Brave Story hack safer, hide it behind a compat flag. --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 8 ++++++-- assets/compat.ini | 7 +++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 91a2f79b92..7ed69a9cd3 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -155,6 +155,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "IgnoreEnqueue", &flags_.IgnoreEnqueue); CheckSetting(iniFile, gameID, "MsgDialogAutoStatus", &flags_.MsgDialogAutoStatus); CheckSetting(iniFile, gameID, "NullPageValid", &flags_.NullPageValid); + CheckSetting(iniFile, gameID, "DetectDestBlendSquared", &flags_.DetectDestBlendSquared); } void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 4b02e14118..08dcd04c29 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -118,6 +118,7 @@ struct CompatFlags { bool IgnoreEnqueue; bool MsgDialogAutoStatus; bool NullPageValid; + bool DetectDestBlendSquared; }; struct VRCompat { diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index e6ac55709c..530b7b1a5c 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1276,8 +1276,12 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF // There's a special case we can handle here, where the game is texturing from the same pixels being read, in order to // implement a DST*DST blending function, which the PSP can't do. However on the PC we can absolutely do this! // TODO: Add more checks here. - if (gstate.isAlphaBlendEnabled() && gstate.getBlendEq() == GE_BLENDMODE_MUL_AND_ADD && gstate.getBlendFuncA() == GE_SRCBLEND_DSTCOLOR && gstate.getBlendFuncB() == GE_DSTBLEND_FIXB && gstate.getFixB() == 0x0) { - // This is the DST*DST case. + if (PSP_CoreParameter().compat.flags().DetectDestBlendSquared && + gstate.isAlphaBlendEnabled() && gstate.getBlendEq() == GE_BLENDMODE_MUL_AND_ADD && gstate.getBlendFuncA() == GE_SRCBLEND_DSTCOLOR && gstate.getBlendFuncB() == GE_DSTBLEND_FIXB && gstate.getFixB() == 0x0 && + gstate.getMaterialAmbientRGBA() == 0xFFFFFFFF) { + // This is the pure DST*DST case, the SRC color is ignored. + // Used by Brave Story - New Traveller. This assumes that texture coordinates are set to match the framebuffer pixels - and to + // be able to make that assumption reasonably safely we use a compat flag to restrict it to that game. // We can just override the blend mode. Let's set a state variable. // We also just leave the last texture bound, ideally we should bind a placeholder here. gstate_c.dstSquared = true; diff --git a/assets/compat.ini b/assets/compat.ini index d53f7cec4c..c95b899fa4 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2106,3 +2106,10 @@ UCES01473 = true NPJG90095 = true NPEG90035 = true NPUG70125 = true + +[DetectDestBlendSquared] +# Fixes the problem in Brave Story where we had to copy the framebuffer to blend it with itself. +# We can just do that blend natively on PC. See #19820 +ULUS10279 = true +UCJS10024 = true +UCAS40096 = true