From 462972f7ea4984c01d8b654b00f3b84145c8f629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 24 Apr 2022 20:53:09 +0200 Subject: [PATCH] Add option to redirect blue to alpha if 565 mode is rendered and mask is 0x0FFFFF. This is used by several games to render to the alpha channel of RGBA4444 images, which cannot normally be done directly on the PSP. Can be used as a far more efficient replacement for ReinterpretFramebuffers/ShaderColorBitmask --- Core/Compatibility.cpp | 1 + Core/Compatibility.h | 2 ++ GPU/Common/FragmentShaderGenerator.cpp | 9 +++++++ GPU/Common/FramebufferManagerCommon.cpp | 3 +-- GPU/Common/FramebufferManagerCommon.h | 2 ++ GPU/Common/GPUStateUtils.cpp | 32 +++++++++++++++++++++++++ GPU/Common/GPUStateUtils.h | 3 +++ GPU/Common/ShaderId.cpp | 19 ++++++++++++--- GPU/Common/TextureCacheCommon.cpp | 2 +- GPU/GPUCommon.cpp | 15 +++++++++++- GPU/GPUState.h | 5 ++++ assets/compat.ini | 30 +++++++++++++++-------- 12 files changed, 106 insertions(+), 17 deletions(-) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index b8de96d22b..7a27543ab4 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -79,6 +79,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback); CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp); + CheckSetting(iniFile, gameID, "BlueToAlpha", &flags_.BlueToAlpha); } 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 48eeddc197..fd3e76d743 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -44,6 +44,7 @@ // // We already have the Action Replay-based cheat system for such use cases. +// TODO: Turn into bitfield for smaller mem footprint. Though I think it still fits in a cacheline... struct CompatFlags { bool VertexDepthRounding; bool PixelDepthRounding; @@ -77,6 +78,7 @@ struct CompatFlags { bool DisableFirstFrameReadback; bool DisableRangeCulling; bool MpegAvcWarmUp; + bool BlueToAlpha; }; class IniFile; diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 3fc0e16ffc..896fb9f439 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -95,6 +95,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu ReplaceBlendType replaceBlend = static_cast(id.Bits(FS_BIT_REPLACE_BLEND, 3)); + bool blueToAlpha = false; + if (replaceBlend == ReplaceBlendType::REPLACE_BLEND_BLUE_TO_ALPHA) { + blueToAlpha = true; + } + GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); @@ -1025,6 +1030,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " %s = unpackUnorm4x8(v32);\n", compat.fragColor0); } + if (blueToAlpha) { + WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", compat.fragColor0, compat.fragColor0); + } + if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { const double scale = DepthSliceFactor() * 65535.0; diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index efd16b3966..8553243eaa 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1642,8 +1642,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram // Create a new fbo if none was found for the size if (!nvfb) { - nvfb = new VirtualFramebuffer(); - memset(nvfb, 0, sizeof(VirtualFramebuffer)); + nvfb = new VirtualFramebuffer{}; nvfb->fbo = nullptr; nvfb->fb_address = vfb->fb_address; nvfb->fb_stride = vfb->fb_stride; diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 1182c73e32..33d41992f9 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -100,6 +100,8 @@ struct VirtualFramebuffer { bool dirtyAfterDisplay; bool reallyDirtyAfterDisplay; // takes frame skipping into account + bool blueToAlphaUsed; + int last_frame_used; int last_frame_attached; int last_frame_render; diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 5e86033745..8a8b7c70ba 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -191,6 +191,10 @@ ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend) { } } + if (replaceBlend == ReplaceBlendType::REPLACE_BLEND_BLUE_TO_ALPHA) { + return REPLACE_ALPHA_NO; // irrelevant + } + return REPLACE_ALPHA_YES; } @@ -254,6 +258,10 @@ StencilValueType ReplaceAlphaWithStencilType() { } ReplaceBlendType ReplaceBlendWithShader(bool allowFramebufferRead, GEBufferFormat bufferFormat) { + if (gstate_c.blueToAlpha) { + return REPLACE_BLEND_BLUE_TO_ALPHA; + } + if (!gstate.isAlphaBlendEnabled() || gstate.isModeClear()) { return REPLACE_BLEND_NO; } @@ -976,6 +984,11 @@ bool IsColorWriteMaskComplex(bool allowFramebufferRead) { return false; } + if (gstate_c.blueToAlpha) { + // We'll generate a simple ___A mask. + return false; + } + uint32_t colorMask = (gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24); for (int i = 0; i < 4; i++) { @@ -996,6 +1009,15 @@ bool IsColorWriteMaskComplex(bool allowFramebufferRead) { // When that's not enough, we fall back on a technique similar to shader blending, // we read from the framebuffer (or a copy of it). void ConvertMaskState(GenericMaskState &maskState, bool allowFramebufferRead) { + if (gstate_c.blueToAlpha) { + maskState.applyFramebufferRead = false; + maskState.rgba[0] = false; + maskState.rgba[1] = false; + maskState.rgba[2] = false; + maskState.rgba[3] = true; + return; + } + // Invert to convert masks from the PSP's format where 1 is don't draw to PC where 1 is draw. uint32_t colorMask = ~((gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24)); @@ -1056,6 +1078,8 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead, ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(replaceBlend); bool usePreSrc = false; + bool blueToAlpha = false; + switch (replaceBlend) { case REPLACE_BLEND_NO: blendState.resetFramebufferRead = true; @@ -1063,6 +1087,10 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead, ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState); return; + case REPLACE_BLEND_BLUE_TO_ALPHA: + blueToAlpha = true; + break; + case REPLACE_BLEND_COPY_FBO: blendState.applyFramebufferRead = true; blendState.resetFramebufferRead = false; @@ -1303,6 +1331,10 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead, alphaEq = BlendEq::REVERSE_SUBTRACT; break; } + } else if (blueToAlpha) { + blendState.setFactors(BlendFactor::ZERO, BlendFactor::ZERO, glBlendFuncA, glBlendFuncB); + blendState.setEquation(BlendEq::ADD, colorEq); + return; } else { // Retain the existing value when stencil testing is off. blendState.setFactors(glBlendFuncA, glBlendFuncB, BlendFactor::ZERO, BlendFactor::ONE); diff --git a/GPU/Common/GPUStateUtils.h b/GPU/Common/GPUStateUtils.h index 037cc91da4..5f2f455e60 100644 --- a/GPU/Common/GPUStateUtils.h +++ b/GPU/Common/GPUStateUtils.h @@ -39,6 +39,9 @@ enum ReplaceBlendType { // Full blend equation runs in shader. // We might have to make a copy of the framebuffer target to read from. REPLACE_BLEND_COPY_FBO, + + // Color blend mode and color gets copied to alpha blend mode. + REPLACE_BLEND_BLUE_TO_ALPHA, }; enum LogicOpReplaceType { diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 653f171e66..dfaac9a675 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -186,8 +186,19 @@ std::string FragmentShaderDesc(const FShaderID &id) { if (id.Bit(FS_BIT_CLAMP_T)) desc << "T"; desc << " "; } - if (id.Bits(FS_BIT_REPLACE_BLEND, 3)) { - desc << "ReplaceBlend_" << id.Bits(FS_BIT_REPLACE_BLEND, 3) << "A:" << id.Bits(FS_BIT_BLENDFUNC_A, 4) << "_B:" << id.Bits(FS_BIT_BLENDFUNC_B, 4) << "_Eq:" << id.Bits(FS_BIT_BLENDEQ, 3) << " "; + int blendBits = id.Bits(FS_BIT_REPLACE_BLEND, 3); + if (blendBits) { + switch (blendBits) { + case ReplaceBlendType::REPLACE_BLEND_BLUE_TO_ALPHA: + desc << "BlueToAlpha"; + break; + default: + desc << "ReplaceBlend_" << id.Bits(FS_BIT_REPLACE_BLEND, 3) + << "A:" << id.Bits(FS_BIT_BLENDFUNC_A, 4) + << "_B:" << id.Bits(FS_BIT_BLENDFUNC_B, 4) + << "_Eq:" << id.Bits(FS_BIT_BLENDEQ, 3) << " "; + break; + } } switch (id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)) { @@ -312,7 +323,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const Draw::Bugs &bugs) { id.SetBits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2, ReplaceLogicOpType()); // If replaceBlend == REPLACE_BLEND_STANDARD (or REPLACE_BLEND_NO) nothing is done, so we kill these bits. - if (replaceBlend > REPLACE_BLEND_STANDARD) { + if (replaceBlend == REPLACE_BLEND_BLUE_TO_ALPHA) { + id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend); + } else if (replaceBlend > REPLACE_BLEND_STANDARD) { // 3 bits. id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend); // 11 bits total. diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 4a651746ed..8273ff9cb8 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -915,7 +915,7 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer( } // NOTE: This check is okay because the first texture formats are the same as the buffer formats. if (IsTextureFormatBufferCompatible(entry.format)) { - if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format)) { + if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format) || framebuffer->blueToAlphaUsed) { return FramebufferMatchInfo{ FramebufferMatch::VALID }; } else if (IsTextureFormat16Bit(entry.format) && IsBufferFormat16Bit(framebuffer->format)) { WARN_LOG_ONCE(diffFormat1, G3D, "Texturing from framebuffer with reinterpretable format: %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format)); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 307870a228..45427bab81 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1637,8 +1637,21 @@ void GPUCommon::Execute_Prim(u32 op, u32 diff) { // We store it in the cache so it can be modified for blue-to-alpha, next. gstate_c.framebufFormat = gstate.FrameBufFormat(); + // See the documentation for gstate_c.blueToAlpha. + bool blueToAlpha = false; + if (gstate_c.framebufFormat == GEBufferFormat::GE_FORMAT_565 && gstate.getColorMask() == 0x0FFFFF && PSP_CoreParameter().compat.flags().BlueToAlpha) { + blueToAlpha = true; + } + if (blueToAlpha != gstate_c.blueToAlpha) { + gstate_c.blueToAlpha = blueToAlpha; + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE | DIRTY_BLEND_STATE); + } + // This also makes skipping drawing very effective. - framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); + VirtualFramebuffer *vfb = framebufferManager_->SetRenderFrameBuffer(gstate_c.IsDirty(DIRTY_FRAMEBUF), gstate_c.skipDrawReason); + if (blueToAlpha) { + vfb->blueToAlphaUsed = true; + } if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) { // Rough estimate, not sure what's correct. diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 181f8129c1..a4e157448d 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -594,6 +594,11 @@ struct GPUStateCache { KnownVertexBounds vertBounds; GEBufferFormat framebufFormat; + // Some games use a very specific masking setup to draw into the alpha channel of a 4444 target using the blue channel of a 565 target. + // This is done because on PSP you can't write to destination alpha, other than stencil values, which can't be set from a texture. + // Examples of games that do this: Outrun, Split/Second. + // We detect this case and go into a special drawing mode. + bool blueToAlpha; // TODO: These should be accessed from the current VFB object directly. u32 curRTWidth; diff --git a/assets/compat.ini b/assets/compat.ini index c49b4c7490..214a470e8a 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -990,6 +990,16 @@ ULES01367 = true NPEH00029 = true ULUS10455 = true +[BlueToAlpha] +ULES01402 = true +ULUS10513 = true +ULJM05812 = true +NPJH50371 = true + +# Some games render first to RGB of a 4444 texture, then they switch to 565 and render masked to blue, +# just to be able to render to the alpha channel of the 4444. We can detect that and reroute rendering +# to avoid problems. + [DateLimited] # Car Jack Streets - issue #12698 NPUZ00043 = true @@ -1025,11 +1035,11 @@ ULES01441 = true ULJM05600 = true ULJM05775 = true -# Split/Second -ULES01402 = true -ULUS10513 = true -ULJM05812 = true -NPJH50371 = true +# Split/Second now uses BlueToAlpha instead. +# ULES01402 = true +# ULUS10513 = true +# ULJM05812 = true +# NPJH50371 = true [ShaderColorBitmask] # Outrun 2006: Coast to Coast - issue #11358 @@ -1043,11 +1053,11 @@ ULJM05533 = true NPJH50006 = true ULES01301 = true -# Split/Second -ULES01402 = true -ULUS10513 = true -ULJM05812 = true -NPJH50371 = true +# Split/Second now uses BlueToAlpha instead. +#ULES01402 = true +#ULUS10513 = true +#ULJM05812 = true +#NPJH50371 = true [DisableFirstFrameReadback] # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344