Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BlueToAlpha compat.ini workaround, fixes Split/Second graphics #15500

Merged
merged 8 commits into from
May 1, 2022
24 changes: 0 additions & 24 deletions Common/Data/Convert/ColorConv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,6 @@
#endif
#endif

inline u16 RGBA8888toRGB565(u32 px) {
return ((px >> 3) & 0x001F) | ((px >> 5) & 0x07E0) | ((px >> 8) & 0xF800);
}

inline u16 RGBA8888toRGBA4444(u32 px) {
return ((px >> 4) & 0x000F) | ((px >> 8) & 0x00F0) | ((px >> 12) & 0x0F00) | ((px >> 16) & 0xF000);
}

inline u16 BGRA8888toRGB565(u32 px) {
return ((px >> 19) & 0x001F) | ((px >> 5) & 0x07E0) | ((px << 8) & 0xF800);
}

inline u16 BGRA8888toRGBA4444(u32 px) {
return ((px >> 20) & 0x000F) | ((px >> 8) & 0x00F0) | ((px << 4) & 0x0F00) | ((px >> 16) & 0xF000);
}

inline u16 BGRA8888toRGBA5551(u32 px) {
return ((px >> 19) & 0x001F) | ((px >> 6) & 0x03E0) | ((px << 7) & 0x7C00) | ((px >> 16) & 0x8000);
}

inline u16 RGBA8888toRGBA5551(u32 px) {
return ((px >> 3) & 0x001F) | ((px >> 6) & 0x03E0) | ((px >> 9) & 0x7C00) | ((px >> 16) & 0x8000);
}

// convert 4444 image to 8888, parallelizable
void convert4444_gl(u16* data, u32* out, int width, int l, int u) {
for (int y = l; y < u; ++y) {
Expand Down
24 changes: 24 additions & 0 deletions Common/Data/Convert/ColorConv.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ inline u8 Convert6To8(u8 v) {
return (v << 2) | (v >> 4);
}

inline u16 RGBA8888toRGB565(u32 px) {
return ((px >> 3) & 0x001F) | ((px >> 5) & 0x07E0) | ((px >> 8) & 0xF800);
}

inline u16 RGBA8888toRGBA4444(u32 px) {
return ((px >> 4) & 0x000F) | ((px >> 8) & 0x00F0) | ((px >> 12) & 0x0F00) | ((px >> 16) & 0xF000);
}

inline u16 BGRA8888toRGB565(u32 px) {
return ((px >> 19) & 0x001F) | ((px >> 5) & 0x07E0) | ((px << 8) & 0xF800);
}

inline u16 BGRA8888toRGBA4444(u32 px) {
return ((px >> 20) & 0x000F) | ((px >> 8) & 0x00F0) | ((px << 4) & 0x0F00) | ((px >> 16) & 0xF000);
}

inline u16 BGRA8888toRGBA5551(u32 px) {
return ((px >> 19) & 0x001F) | ((px >> 6) & 0x03E0) | ((px << 7) & 0x7C00) | ((px >> 16) & 0x8000);
}

inline u16 RGBA8888toRGBA5551(u32 px) {
return ((px >> 3) & 0x001F) | ((px >> 6) & 0x03E0) | ((px >> 9) & 0x7C00) | ((px >> 16) & 0x8000);
}

inline u32 RGBA4444ToRGBA8888(u16 src) {
const u32 r = (src & 0x000F) << 0;
const u32 g = (src & 0x00F0) << 4;
Expand Down
1 change: 1 addition & 0 deletions Core/Compatibility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions Core/Compatibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,6 +78,7 @@ struct CompatFlags {
bool DisableFirstFrameReadback;
bool DisableRangeCulling;
bool MpegAvcWarmUp;
bool BlueToAlpha;
};

class IniFile;
Expand Down
13 changes: 12 additions & 1 deletion GPU/Common/FragmentShaderGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -853,7 +858,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " v.rgb = v.rgb * 2.0;\n");
}

if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) {
// In some cases we need to replicate the first half of the blend equation here.
// In case of blue-to-alpha, it's since we overwrite alpha with blue before the actual blend equation runs.
if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA || replaceBlend == REPLACE_BLEND_BLUE_TO_ALPHA) {
const char *srcFactor = "ERROR";
switch (replaceBlendFuncA) {
case GE_SRCBLEND_DSTCOLOR: srcFactor = "ERROR"; break;
Expand Down Expand Up @@ -1025,6 +1032,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;

Expand Down
24 changes: 13 additions & 11 deletions GPU/Common/FramebufferManagerCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ void GetFramebufferHeuristicInputs(FramebufferHeuristicParams *params, const GPU
params->z_stride = 0;
}

params->fmt = gstate.FrameBufFormat();
params->fmt = gstate_c.framebufFormat;

params->isClearingDepth = gstate.isModeClear() && gstate.isClearModeDepthMask();
// Technically, it may write depth later, but we're trying to detect it only when it's really true.
Expand Down Expand Up @@ -589,7 +589,7 @@ void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, G

// Games that are marked as doing reinterpret just ignore this - better to keep the data than to clear.
// Fixes #13717.
if (!PSP_CoreParameter().compat.flags().ReinterpretFramebuffers) {
if (!PSP_CoreParameter().compat.flags().ReinterpretFramebuffers && !PSP_CoreParameter().compat.flags().BlueToAlpha) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "FakeReinterpret");
// Need to dirty anything that has command buffer dynamic state, in case we started a new pass above.
// Should find a way to feed that information back, maybe... Or simply correct the issue in the rendermanager.
Expand Down Expand Up @@ -670,9 +670,11 @@ void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, G
// Copy to a temp framebuffer.
Draw::Framebuffer *temp = GetTempFBO(TempFBO::REINTERPRET, vfb->renderWidth, vfb->renderHeight);

// Ideally on Vulkan this should be using the original framebuffer as an input attachment, allowing it to read from
// itself while writing.
draw_->InvalidateCachedState();
draw_->CopyFramebufferImage(vfb->fbo, 0, 0, 0, 0, temp, 0, 0, 0, 0, vfb->renderWidth, vfb->renderHeight, 1, Draw::FBChannel::FB_COLOR_BIT, "reinterpret_prep");
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, reinterpretStrings[(int)oldFormat][(int)newFormat]);
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, reinterpretStrings[(int)oldFormat][(int)newFormat]);
draw_->BindPipeline(pipeline);
draw_->BindFramebufferAsTexture(temp, 0, Draw::FBChannel::FB_COLOR_BIT, 0);
draw_->BindSamplerStates(0, 1, &reinterpretSampler_);
Expand All @@ -691,7 +693,7 @@ void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, G
shaderManager_->DirtyLastShader();
textureCache_->ForgetLastTexture();

gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE);
gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);

if (currentRenderVfb_ != vfb) {
// In case ReinterpretFramebuffer was called from the texture manager.
Expand Down Expand Up @@ -1642,8 +1644,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;
Expand Down Expand Up @@ -1688,20 +1689,21 @@ void FramebufferManagerCommon::ApplyClearToMemory(int x1, int y1, int x2, int y2
return;
}
}

if (!Memory::IsValidAddress(gstate.getFrameBufAddress())) {
return;
}

u8 *addr = Memory::GetPointerUnchecked(gstate.getFrameBufAddress());
const int bpp = gstate.FrameBufFormat() == GE_FORMAT_8888 ? 4 : 2;
const int bpp = gstate_c.framebufFormat == GE_FORMAT_8888 ? 4 : 2;

u32 clearBits = clearColor;
if (bpp == 2) {
u16 clear16 = 0;
switch (gstate.FrameBufFormat()) {
case GE_FORMAT_565: ConvertRGBA8888ToRGB565(&clear16, &clearColor, 1); break;
case GE_FORMAT_5551: ConvertRGBA8888ToRGBA5551(&clear16, &clearColor, 1); break;
case GE_FORMAT_4444: ConvertRGBA8888ToRGBA4444(&clear16, &clearColor, 1); break;
switch (gstate_c.framebufFormat) {
case GE_FORMAT_565: clear16 = RGBA8888toRGB565(clearColor); break;
case GE_FORMAT_5551: clear16 = RGBA8888toRGBA5551(clearColor); break;
case GE_FORMAT_4444: clear16 = RGBA8888toRGBA4444(clearColor); break;
default: _dbg_assert_(0); break;
}
clearBits = clear16 | (clear16 << 16);
Expand Down
1 change: 1 addition & 0 deletions GPU/Common/FramebufferManagerCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum {
FB_USAGE_CLUT = 8,
FB_USAGE_DOWNLOAD = 16,
FB_USAGE_DOWNLOAD_CLEAR = 32,
FB_USAGE_BLUE_TO_ALPHA = 64,
};

enum {
Expand Down
48 changes: 40 additions & 8 deletions GPU/Common/GPUStateUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
bool IsStencilTestOutputDisabled() {
// The mask applies on all stencil ops.
if (gstate.isStencilTestEnabled() && (gstate.pmska & 0xFF) != 0xFF) {
if (gstate.FrameBufFormat() == GE_FORMAT_565) {
if (gstate_c.framebufFormat == GE_FORMAT_565) {
return true;
}
return gstate.getStencilOpZPass() == GE_STENCILOP_KEEP && gstate.getStencilOpZFail() == GE_STENCILOP_KEEP && gstate.getStencilOpSFail() == GE_STENCILOP_KEEP;
Expand Down Expand Up @@ -191,11 +191,15 @@ ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend) {
}
}

if (replaceBlend == ReplaceBlendType::REPLACE_BLEND_BLUE_TO_ALPHA) {
return REPLACE_ALPHA_NO; // irrelevant
}

return REPLACE_ALPHA_YES;
}

StencilValueType ReplaceAlphaWithStencilType() {
switch (gstate.FrameBufFormat()) {
switch (gstate_c.framebufFormat) {
case GE_FORMAT_565:
// There's never a stencil value. Maybe the right alpha is 1?
return STENCIL_VALUE_ONE;
Expand Down Expand Up @@ -236,10 +240,10 @@ StencilValueType ReplaceAlphaWithStencilType() {
return STENCIL_VALUE_ZERO;

case GE_STENCILOP_DECR:
return gstate.FrameBufFormat() == GE_FORMAT_4444 ? STENCIL_VALUE_DECR_4 : STENCIL_VALUE_DECR_8;
return gstate_c.framebufFormat == GE_FORMAT_4444 ? STENCIL_VALUE_DECR_4 : STENCIL_VALUE_DECR_8;

case GE_STENCILOP_INCR:
return gstate.FrameBufFormat() == GE_FORMAT_4444 ? STENCIL_VALUE_INCR_4 : STENCIL_VALUE_INCR_8;
return gstate_c.framebufFormat == GE_FORMAT_4444 ? STENCIL_VALUE_INCR_4 : STENCIL_VALUE_INCR_8;

case GE_STENCILOP_INVERT:
return STENCIL_VALUE_INVERT;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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++) {
Expand All @@ -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));

Expand Down Expand Up @@ -1049,20 +1071,26 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
blendState.useBlendColor = false;
blendState.replaceAlphaWithStencil = REPLACE_ALPHA_NO;

ReplaceBlendType replaceBlend = ReplaceBlendWithShader(allowFramebufferRead, gstate.FrameBufFormat());
ReplaceBlendType replaceBlend = ReplaceBlendWithShader(allowFramebufferRead, gstate_c.framebufFormat);
if (forceReplaceBlend) {
replaceBlend = REPLACE_BLEND_COPY_FBO;
}
ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(replaceBlend);
bool usePreSrc = false;

bool blueToAlpha = false;

switch (replaceBlend) {
case REPLACE_BLEND_NO:
blendState.resetFramebufferRead = true;
// We may still want to do something about stencil -> alpha.
ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState);
return;

case REPLACE_BLEND_BLUE_TO_ALPHA:
blueToAlpha = true;
break;

case REPLACE_BLEND_COPY_FBO:
blendState.applyFramebufferRead = true;
blendState.resetFramebufferRead = false;
Expand Down Expand Up @@ -1130,7 +1158,7 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
bool approxFuncB = false;
BlendFactor glBlendFuncB = blendFuncB == GE_DSTBLEND_FIXB ? blendColor2Func(fixB, approxFuncB) : genericBLookup[blendFuncB];

if (gstate.FrameBufFormat() == GE_FORMAT_565) {
if (gstate_c.framebufFormat == GE_FORMAT_565) {
if (blendFuncA == GE_SRCBLEND_DSTALPHA || blendFuncA == GE_SRCBLEND_DOUBLEDSTALPHA) {
glBlendFuncA = BlendFactor::ZERO;
}
Expand Down Expand Up @@ -1303,6 +1331,10 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead,
alphaEq = BlendEq::REVERSE_SUBTRACT;
break;
}
} else if (blueToAlpha) {
blendState.setFactors(BlendFactor::ZERO, BlendFactor::ZERO, BlendFactor::ONE, 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);
Expand Down Expand Up @@ -1452,7 +1484,7 @@ void ConvertStencilFuncState(GenericStencilFuncState &state) {
state.writeMask = (~gstate.getStencilWriteMask()) & 0xFF;
state.enabled = gstate.isStencilTestEnabled();
if (!state.enabled) {
if (gstate.FrameBufFormat() == GE_FORMAT_5551)
if (gstate_c.framebufFormat == GE_FORMAT_5551)
ConvertStencilMask5551(state);
return;
}
Expand All @@ -1465,7 +1497,7 @@ void ConvertStencilFuncState(GenericStencilFuncState &state) {
state.testRef = gstate.getStencilTestRef();
state.testMask = gstate.getStencilTestMask();

switch (gstate.FrameBufFormat()) {
switch (gstate_c.framebufFormat) {
case GE_FORMAT_565:
state.writeMask = 0;
break;
Expand Down
6 changes: 3 additions & 3 deletions GPU/Common/GPUStateUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -54,9 +57,6 @@ bool IsAlphaTestAgainstZero();
bool NeedsTestDiscard();
bool IsStencilTestOutputDisabled();

// If not, we have to emulate it in the shader, similar to blend replace.
bool IsColorMaskSimple(uint32_t colorMask);

StencilValueType ReplaceAlphaWithStencilType();
ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend);
ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bufferFormat);
Expand Down
Loading