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/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..530b7b1a5c 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1273,6 +1273,21 @@ 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 (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; + 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)) { 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