mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
The GE's blend terms are floor((2c+1)(2f+1)/1024), which truncates, while the hardware multiplies by f/255 and rounds, so every pass of a fixed color blend chain came out up to a level brighter. God Eater Burst's bloom is many such passes and ended up far too bright. For additive FIXA/FIXB blends that aren't just 0 and 0xFFFFFF, the shader now computes the src term exactly and lowers it by 0.5 - (2B+1)/1024, and the blend constant is the GE's dst factor (2B+1)/512, so the hardware's rounding of the sum lands on the GE's floor (except where the src term would go below zero). Blend constants are passed as floats to make that possible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
286 lines
8.0 KiB
C++
286 lines
8.0 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include "Common/CommonTypes.h"
|
|
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/GPUState.h"
|
|
|
|
// TODO: Replace enums and structs with same from thin3d.h, for convenient mapping.
|
|
|
|
enum StencilValueType {
|
|
STENCIL_VALUE_UNIFORM,
|
|
STENCIL_VALUE_ZERO,
|
|
STENCIL_VALUE_ONE,
|
|
STENCIL_VALUE_KEEP,
|
|
STENCIL_VALUE_INVERT,
|
|
STENCIL_VALUE_INCR_4BIT,
|
|
STENCIL_VALUE_INCR_8BIT,
|
|
STENCIL_VALUE_DECR_4BIT,
|
|
STENCIL_VALUE_DECR_8BIT,
|
|
};
|
|
|
|
enum ReplaceAlphaType {
|
|
REPLACE_ALPHA_NO = 0,
|
|
REPLACE_ALPHA_YES = 1,
|
|
REPLACE_ALPHA_DUALSOURCE = 2,
|
|
};
|
|
|
|
enum ReplaceBlendType {
|
|
REPLACE_BLEND_NO, // Blend function handled directly with blend states.
|
|
|
|
REPLACE_BLEND_STANDARD,
|
|
|
|
// SRC part of blend function handled in-shader.
|
|
REPLACE_BLEND_PRE_SRC,
|
|
REPLACE_BLEND_PRE_SRC_2X_ALPHA,
|
|
REPLACE_BLEND_2X_ALPHA,
|
|
REPLACE_BLEND_2X_SRC,
|
|
|
|
// Full blend equation runs in shader.
|
|
// We might have to make a copy of the framebuffer target to read from.
|
|
REPLACE_BLEND_READ_FRAMEBUFFER,
|
|
|
|
// Color blend mode and color gets copied to alpha blend mode.
|
|
REPLACE_BLEND_BLUE_TO_ALPHA,
|
|
};
|
|
|
|
enum SimulateLogicOpType {
|
|
LOGICOPTYPE_NORMAL,
|
|
LOGICOPTYPE_ONE,
|
|
LOGICOPTYPE_INVERT,
|
|
};
|
|
|
|
bool IsAlphaTestTriviallyTrue();
|
|
bool IsColorTestAgainstZero();
|
|
bool IsColorTestTriviallyTrue();
|
|
bool IsAlphaTestAgainstZero();
|
|
bool NeedsTestDiscard();
|
|
bool IsDepthTestEffectivelyDisabled();
|
|
bool IsStencilTestOutputDisabled();
|
|
|
|
inline bool CanForceBilinear(const GEState &gstate) {
|
|
return ((!gstate.isColorTestEnabled() || IsColorTestTriviallyTrue()) && (!gstate.isAlphaTestEnabled() || IsAlphaTestTriviallyTrue()));
|
|
}
|
|
|
|
StencilValueType ReplaceAlphaWithStencilType();
|
|
int ClearModeChannelMask();
|
|
ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend);
|
|
ReplaceBlendType ReplaceBlendWithShader(GEBufferFormat bufferFormat);
|
|
|
|
// Fixed color blends (with FIXA as the src factor) that REPLACE_BLEND_PRE_SRC computes like the GE.
|
|
inline bool IsGEExactFixedBlend(GEBlendDstFactor funcB, GEBlendMode eq) {
|
|
return funcB == GE_DSTBLEND_FIXB && eq == GE_BLENDMODE_MUL_AND_ADD;
|
|
}
|
|
|
|
// This is for the fallback path if real logic ops are not available.
|
|
SimulateLogicOpType SimulateLogicOpShaderTypeIfNeeded();
|
|
|
|
// Common representation, should be able to set this directly with any modern API.
|
|
struct ViewportAndScissor {
|
|
int scissorX;
|
|
int scissorY;
|
|
int scissorW;
|
|
int scissorH;
|
|
float viewportX;
|
|
float viewportY;
|
|
float viewportW;
|
|
float viewportH;
|
|
};
|
|
|
|
// config is only used for non-buffered rendering.
|
|
struct DisplayLayoutConfig;
|
|
void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBufferedRendering, float renderWidth, float renderHeight, int bufferWidth, int bufferHeight, ViewportAndScissor &out);
|
|
|
|
// These are common to all modern APIs and can be easily converted with a lookup table.
|
|
enum class BlendFactor : uint8_t {
|
|
ZERO,
|
|
ONE,
|
|
SRC_COLOR,
|
|
ONE_MINUS_SRC_COLOR,
|
|
DST_COLOR,
|
|
ONE_MINUS_DST_COLOR,
|
|
SRC_ALPHA,
|
|
ONE_MINUS_SRC_ALPHA,
|
|
DST_ALPHA,
|
|
ONE_MINUS_DST_ALPHA,
|
|
CONSTANT_COLOR,
|
|
ONE_MINUS_CONSTANT_COLOR,
|
|
CONSTANT_ALPHA,
|
|
ONE_MINUS_CONSTANT_ALPHA,
|
|
SRC1_COLOR,
|
|
ONE_MINUS_SRC1_COLOR,
|
|
SRC1_ALPHA,
|
|
ONE_MINUS_SRC1_ALPHA,
|
|
INVALID,
|
|
COUNT,
|
|
};
|
|
|
|
enum class BlendEq : uint8_t {
|
|
ADD,
|
|
SUBTRACT,
|
|
REVERSE_SUBTRACT,
|
|
MIN,
|
|
MAX,
|
|
COUNT
|
|
};
|
|
|
|
// Computed blend setup, including shader stuff.
|
|
struct GenericBlendState {
|
|
bool applyFramebufferRead;
|
|
bool dirtyShaderBlendFixValues;
|
|
|
|
// Shader generation state
|
|
ReplaceAlphaType replaceAlphaWithStencil;
|
|
ReplaceBlendType replaceBlend;
|
|
SimulateLogicOpType simulateLogicOpType;
|
|
|
|
// Resulting hardware blend state
|
|
bool blendEnabled;
|
|
|
|
BlendFactor srcColor;
|
|
BlendFactor dstColor;
|
|
BlendFactor srcAlpha;
|
|
BlendFactor dstAlpha;
|
|
|
|
BlendEq eqColor;
|
|
BlendEq eqAlpha;
|
|
|
|
bool useBlendColor;
|
|
u32 blendColor;
|
|
// The color channels are a GE fixed blend factor, applied as (2c+1)/512 like the GE does.
|
|
bool blendColorGE;
|
|
|
|
void setFactors(BlendFactor srcC, BlendFactor dstC, BlendFactor srcA, BlendFactor dstA) {
|
|
srcColor = srcC;
|
|
dstColor = dstC;
|
|
srcAlpha = srcA;
|
|
dstAlpha = dstA;
|
|
}
|
|
void setEquation(BlendEq eqC, BlendEq eqA) {
|
|
eqColor = eqC;
|
|
eqAlpha = eqA;
|
|
}
|
|
void setBlendColor(uint32_t color, uint8_t alpha) {
|
|
blendColor = color | ((uint32_t)alpha << 24);
|
|
useBlendColor = true;
|
|
blendColorGE = false;
|
|
}
|
|
void setBlendColorGE(uint32_t color, uint8_t alpha) {
|
|
setBlendColor(color, alpha);
|
|
blendColorGE = true;
|
|
}
|
|
void defaultBlendColor(uint8_t alpha) {
|
|
setBlendColor(0xFFFFFF, alpha);
|
|
}
|
|
void blendColorToFloat4(float out[4]) const;
|
|
|
|
void Log();
|
|
};
|
|
|
|
void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState);
|
|
|
|
struct GenericMaskState {
|
|
bool applyFramebufferRead;
|
|
uint32_t uniformMask; // For each bit, opposite to the PSP.
|
|
|
|
// The hardware channel masks, 1 bit per color component. From bit 0, order is RGBA like in all APIs!
|
|
uint8_t channelMask;
|
|
|
|
void ConvertToShaderBlend() {
|
|
// If we have to do it in the shader, we simply pass through all channels but mask only in the shader instead.
|
|
// Some GPUs have minor penalties for masks that are not all-channels-on or all-channels-off.
|
|
channelMask = 0xF;
|
|
applyFramebufferRead = true;
|
|
}
|
|
|
|
void Log();
|
|
};
|
|
|
|
struct GenericStencilFuncState {
|
|
bool enabled;
|
|
GEComparison testFunc;
|
|
u8 testRef;
|
|
u8 testMask;
|
|
u8 writeMask;
|
|
GEStencilOp sFail;
|
|
GEStencilOp zFail;
|
|
GEStencilOp zPass;
|
|
};
|
|
void ConvertStencilFuncState(GenericStencilFuncState &stencilFuncState);
|
|
|
|
struct GenericLogicState {
|
|
// If set, logic op is applied in the shader INSTEAD of in hardware.
|
|
// In this case, simulateLogicOpType and all that should be off.
|
|
bool applyFramebufferRead;
|
|
|
|
// Hardware
|
|
bool logicOpEnabled;
|
|
|
|
// Hardware and shader generation
|
|
GELogicOp logicOp;
|
|
|
|
void ApplyToBlendState(GenericBlendState &blendState);
|
|
void ConvertToShaderBlend() {
|
|
if (logicOp != GE_LOGIC_COPY) {
|
|
logicOpEnabled = false;
|
|
applyFramebufferRead = true;
|
|
// Same logicOp is kept.
|
|
}
|
|
}
|
|
};
|
|
|
|
struct ComputedPipelineState {
|
|
GenericBlendState blendState;
|
|
GenericMaskState maskState;
|
|
GenericLogicState logicState;
|
|
|
|
void Convert(bool shaderBitOpsSupported, bool fbReadAllowed);
|
|
|
|
bool FramebufferRead() const {
|
|
// If blending is off, its applyFramebufferRead can be false even after state propagation.
|
|
// So it's not enough to check just that one.
|
|
return blendState.applyFramebufferRead || maskState.applyFramebufferRead || logicState.applyFramebufferRead;
|
|
}
|
|
};
|
|
|
|
// See issue #15898
|
|
inline bool SpongebobDepthInverseConditions(const GenericStencilFuncState &stencilState) {
|
|
// Check that the depth/stencil state matches the conditions exactly.
|
|
// Always with a depth test that's not writing to the depth buffer (only stencil.)
|
|
if (!gstate.isDepthTestEnabled() || gstate.isDepthWriteEnabled())
|
|
return false;
|
|
// Always GREATER_EQUAL, which we flip to LESS.
|
|
if (gstate.getDepthTestFunction() != GE_COMP_GEQUAL)
|
|
return false;
|
|
|
|
// The whole purpose here is a depth fail that we need to write to alpha.
|
|
if (stencilState.zFail != GE_STENCILOP_ZERO || stencilState.sFail != GE_STENCILOP_KEEP || stencilState.zPass != GE_STENCILOP_KEEP)
|
|
return false;
|
|
if (stencilState.testFunc != GE_COMP_ALWAYS || stencilState.writeMask != 0xFF)
|
|
return false;
|
|
|
|
// Lastly, verify no color is written. Natural way is a mask, in case another game uses it.
|
|
// Note that the PSP masks are reversed compared to typical APIs.
|
|
if (gstate.getColorMask() == 0xFFFFFF00)
|
|
return true;
|
|
|
|
// These games specifically use simple alpha blending with a constant zero alpha.
|
|
if (!gstate.isAlphaBlendEnabled() || gstate.getBlendFuncA() != GE_SRCBLEND_SRCALPHA || gstate.getBlendFuncB() != GE_DSTBLEND_INVSRCALPHA)
|
|
return false;
|
|
|
|
// Also make sure there's no texture, in case its alpha gets involved.
|
|
if (gstate.isTextureMapEnabled())
|
|
return false;
|
|
|
|
// Spongebob uses material alpha.
|
|
if (gstate.getMaterialAmbientA() == 0x00 && gstate.getMaterialUpdate() == 0)
|
|
return true;
|
|
// MX vs ATV : Reflex uses vertex colors, should really check them...
|
|
if (gstate.getMaterialUpdate() == 1)
|
|
return true;
|
|
|
|
// Okay, color is most likely being used if we didn't hit the above.
|
|
return false;
|
|
}
|