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
This commit is contained in:
Henrik Rydgård 2022-04-24 20:53:09 +02:00
parent 7be86264d0
commit 462972f7ea
12 changed files with 106 additions and 17 deletions

View file

@ -79,6 +79,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback); CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback);
CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling);
CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp); 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) { void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) {

View file

@ -44,6 +44,7 @@
// //
// We already have the Action Replay-based cheat system for such use cases. // 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 { struct CompatFlags {
bool VertexDepthRounding; bool VertexDepthRounding;
bool PixelDepthRounding; bool PixelDepthRounding;
@ -77,6 +78,7 @@ struct CompatFlags {
bool DisableFirstFrameReadback; bool DisableFirstFrameReadback;
bool DisableRangeCulling; bool DisableRangeCulling;
bool MpegAvcWarmUp; bool MpegAvcWarmUp;
bool BlueToAlpha;
}; };
class IniFile; class IniFile;

View file

@ -95,6 +95,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
ReplaceBlendType replaceBlend = static_cast<ReplaceBlendType>(id.Bits(FS_BIT_REPLACE_BLEND, 3)); ReplaceBlendType replaceBlend = static_cast<ReplaceBlendType>(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); GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4);
GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4);
GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); 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); 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)) { if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) {
const double scale = DepthSliceFactor() * 65535.0; const double scale = DepthSliceFactor() * 65535.0;

View file

@ -1642,8 +1642,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram
// Create a new fbo if none was found for the size // Create a new fbo if none was found for the size
if (!nvfb) { if (!nvfb) {
nvfb = new VirtualFramebuffer(); nvfb = new VirtualFramebuffer{};
memset(nvfb, 0, sizeof(VirtualFramebuffer));
nvfb->fbo = nullptr; nvfb->fbo = nullptr;
nvfb->fb_address = vfb->fb_address; nvfb->fb_address = vfb->fb_address;
nvfb->fb_stride = vfb->fb_stride; nvfb->fb_stride = vfb->fb_stride;

View file

@ -100,6 +100,8 @@ struct VirtualFramebuffer {
bool dirtyAfterDisplay; bool dirtyAfterDisplay;
bool reallyDirtyAfterDisplay; // takes frame skipping into account bool reallyDirtyAfterDisplay; // takes frame skipping into account
bool blueToAlphaUsed;
int last_frame_used; int last_frame_used;
int last_frame_attached; int last_frame_attached;
int last_frame_render; int last_frame_render;

View file

@ -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; return REPLACE_ALPHA_YES;
} }
@ -254,6 +258,10 @@ StencilValueType ReplaceAlphaWithStencilType() {
} }
ReplaceBlendType ReplaceBlendWithShader(bool allowFramebufferRead, GEBufferFormat bufferFormat) { ReplaceBlendType ReplaceBlendWithShader(bool allowFramebufferRead, GEBufferFormat bufferFormat) {
if (gstate_c.blueToAlpha) {
return REPLACE_BLEND_BLUE_TO_ALPHA;
}
if (!gstate.isAlphaBlendEnabled() || gstate.isModeClear()) { if (!gstate.isAlphaBlendEnabled() || gstate.isModeClear()) {
return REPLACE_BLEND_NO; return REPLACE_BLEND_NO;
} }
@ -976,6 +984,11 @@ bool IsColorWriteMaskComplex(bool allowFramebufferRead) {
return false; return false;
} }
if (gstate_c.blueToAlpha) {
// We'll generate a simple ___A mask.
return false;
}
uint32_t colorMask = (gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24); uint32_t colorMask = (gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24);
for (int i = 0; i < 4; i++) { 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, // 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). // we read from the framebuffer (or a copy of it).
void ConvertMaskState(GenericMaskState &maskState, bool allowFramebufferRead) { 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. // 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)); uint32_t colorMask = ~((gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24));
@ -1056,6 +1078,8 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(replaceBlend); ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(replaceBlend);
bool usePreSrc = false; bool usePreSrc = false;
bool blueToAlpha = false;
switch (replaceBlend) { switch (replaceBlend) {
case REPLACE_BLEND_NO: case REPLACE_BLEND_NO:
blendState.resetFramebufferRead = true; blendState.resetFramebufferRead = true;
@ -1063,6 +1087,10 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState); ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState);
return; return;
case REPLACE_BLEND_BLUE_TO_ALPHA:
blueToAlpha = true;
break;
case REPLACE_BLEND_COPY_FBO: case REPLACE_BLEND_COPY_FBO:
blendState.applyFramebufferRead = true; blendState.applyFramebufferRead = true;
blendState.resetFramebufferRead = false; blendState.resetFramebufferRead = false;
@ -1303,6 +1331,10 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
alphaEq = BlendEq::REVERSE_SUBTRACT; alphaEq = BlendEq::REVERSE_SUBTRACT;
break; break;
} }
} else if (blueToAlpha) {
blendState.setFactors(BlendFactor::ZERO, BlendFactor::ZERO, glBlendFuncA, glBlendFuncB);
blendState.setEquation(BlendEq::ADD, colorEq);
return;
} else { } else {
// Retain the existing value when stencil testing is off. // Retain the existing value when stencil testing is off.
blendState.setFactors(glBlendFuncA, glBlendFuncB, BlendFactor::ZERO, BlendFactor::ONE); blendState.setFactors(glBlendFuncA, glBlendFuncB, BlendFactor::ZERO, BlendFactor::ONE);

View file

@ -39,6 +39,9 @@ enum ReplaceBlendType {
// Full blend equation runs in shader. // Full blend equation runs in shader.
// We might have to make a copy of the framebuffer target to read from. // We might have to make a copy of the framebuffer target to read from.
REPLACE_BLEND_COPY_FBO, REPLACE_BLEND_COPY_FBO,
// Color blend mode and color gets copied to alpha blend mode.
REPLACE_BLEND_BLUE_TO_ALPHA,
}; };
enum LogicOpReplaceType { enum LogicOpReplaceType {

View file

@ -186,8 +186,19 @@ std::string FragmentShaderDesc(const FShaderID &id) {
if (id.Bit(FS_BIT_CLAMP_T)) desc << "T"; if (id.Bit(FS_BIT_CLAMP_T)) desc << "T";
desc << " "; desc << " ";
} }
if (id.Bits(FS_BIT_REPLACE_BLEND, 3)) { int blendBits = 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) << " "; 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)) { 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()); 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 (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. // 3 bits.
id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend); id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend);
// 11 bits total. // 11 bits total.

View file

@ -915,7 +915,7 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer(
} }
// NOTE: This check is okay because the first texture formats are the same as the buffer formats. // NOTE: This check is okay because the first texture formats are the same as the buffer formats.
if (IsTextureFormatBufferCompatible(entry.format)) { if (IsTextureFormatBufferCompatible(entry.format)) {
if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format)) { if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format) || framebuffer->blueToAlphaUsed) {
return FramebufferMatchInfo{ FramebufferMatch::VALID }; return FramebufferMatchInfo{ FramebufferMatch::VALID };
} else if (IsTextureFormat16Bit(entry.format) && IsBufferFormat16Bit(framebuffer->format)) { } 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)); WARN_LOG_ONCE(diffFormat1, G3D, "Texturing from framebuffer with reinterpretable format: %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format));

View file

@ -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. // We store it in the cache so it can be modified for blue-to-alpha, next.
gstate_c.framebufFormat = gstate.FrameBufFormat(); 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. // 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)) { if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) {
// Rough estimate, not sure what's correct. // Rough estimate, not sure what's correct.

View file

@ -594,6 +594,11 @@ struct GPUStateCache {
KnownVertexBounds vertBounds; KnownVertexBounds vertBounds;
GEBufferFormat framebufFormat; 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. // TODO: These should be accessed from the current VFB object directly.
u32 curRTWidth; u32 curRTWidth;

View file

@ -990,6 +990,16 @@ ULES01367 = true
NPEH00029 = true NPEH00029 = true
ULUS10455 = 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] [DateLimited]
# Car Jack Streets - issue #12698 # Car Jack Streets - issue #12698
NPUZ00043 = true NPUZ00043 = true
@ -1025,11 +1035,11 @@ ULES01441 = true
ULJM05600 = true ULJM05600 = true
ULJM05775 = true ULJM05775 = true
# Split/Second # Split/Second now uses BlueToAlpha instead.
ULES01402 = true # ULES01402 = true
ULUS10513 = true # ULUS10513 = true
ULJM05812 = true # ULJM05812 = true
NPJH50371 = true # NPJH50371 = true
[ShaderColorBitmask] [ShaderColorBitmask]
# Outrun 2006: Coast to Coast - issue #11358 # Outrun 2006: Coast to Coast - issue #11358
@ -1043,11 +1053,11 @@ ULJM05533 = true
NPJH50006 = true NPJH50006 = true
ULES01301 = true ULES01301 = true
# Split/Second # Split/Second now uses BlueToAlpha instead.
ULES01402 = true #ULES01402 = true
ULUS10513 = true #ULUS10513 = true
ULJM05812 = true #ULJM05812 = true
NPJH50371 = true #NPJH50371 = true
[DisableFirstFrameReadback] [DisableFirstFrameReadback]
# Wipeout Pure: Temporary workaround for lens flare flicker. See #13344 # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344