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, "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) {

View File

@ -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;

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));
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;

View File

@ -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;

View File

@ -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;

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;
}
@ -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);

View File

@ -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 {

View File

@ -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.

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.
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));

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.
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.

View File

@ -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;

View File

@ -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