mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-23 03:09:10 +02:00
Video textures were clamped to a 480x272 frame on both axes, so filtering can't pull in whatever lies next to the frame in memory. The VSH's UMD Video player converts 480 rows, into a 256 or 512-wide texture it uses as a ring that its strips wrap around: the rows past 272 smeared, and the wrapped strips were stretched. The clamp now covers the rows written at the texture, and the columns the strips written into its first row cover. An axis the frame fills keeps the game's wrap mode. A game's 480x272 frame is clamped as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
487 lines
20 KiB
C++
487 lines
20 KiB
C++
#include <string>
|
|
#include <vector>
|
|
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/Data/Text/StringWriter.h"
|
|
#include "Common/BitSet.h"
|
|
#include "Core/Config.h"
|
|
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/GPU.h"
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/Common/GPUStateUtils.h"
|
|
#include "GPU/Common/ShaderId.h"
|
|
#include "GPU/Common/ShaderUniforms.h"
|
|
#include "GPU/Common/VertexDecoderCommon.h"
|
|
#include "GPU/Common/DrawEngineCommon.h" // Just for ClipInfoFlags
|
|
|
|
std::string ShaderID::ToDebugString() const {
|
|
return StringFromFormat("%08x:%08x", d >> 32, d & 0xFFFFFFFF);
|
|
}
|
|
|
|
std::string VShaderID::Description(bool includeID) const {
|
|
char buffer[512];
|
|
StringWriter desc(buffer, sizeof(buffer));
|
|
|
|
if (includeID) {
|
|
desc.W(ToDebugString()).C(" ");
|
|
}
|
|
if (Bit(VS_BIT_IS_THROUGH)) desc.C("THR ");
|
|
if (Bit(VS_BIT_USE_HW_TRANSFORM)) desc.C("HWX "); else desc.C("SWX ");
|
|
if (Bit(VS_BIT_HAS_NORMAL)) desc.C("N ");
|
|
if (Bit(VS_BIT_HAS_TEXCOORD)) desc.C("T ");
|
|
if (Bit(VS_BIT_HAS_COLOR)) desc.C("C ");
|
|
if (Bit(VS_BIT_LMODE)) desc.C("LM ");
|
|
if (Bit(VS_BIT_NORM_REVERSE)) desc.C("RevN ");
|
|
if (Bit(VS_BIT_FLATSHADE)) desc.C("Flat ");
|
|
if (Bits(VS_BIT_MATERIAL_UPDATE, 3)) desc.C("MatUp:").F("%d", Bits(VS_BIT_MATERIAL_UPDATE, 3)).C(" ");
|
|
|
|
int uvgMode = Bits(VS_BIT_UVGEN_MODE, 2);
|
|
static constexpr std::string_view uvgModes[4] = {"UV ", "UVMtx ", "UVEnv ", "UVUnk "};
|
|
if (uvgMode) desc.W(uvgModes[uvgMode]);
|
|
if (uvgMode == GE_TEXMAP_TEXTURE_MATRIX) {
|
|
int uvprojMode = Bits(VS_BIT_UVPROJ_MODE, 2);
|
|
static constexpr std::string_view uvprojModes[4] = { "TexProjPos ", "TexProjUV ", "TexProjNNrm ", "TexProjNrm " };
|
|
desc.W(uvprojModes[uvprojMode]);
|
|
}
|
|
|
|
int ls0 = Bits(VS_BIT_LS0, 2);
|
|
int ls1 = Bits(VS_BIT_LS1, 2);
|
|
|
|
if (Bit(VS_BIT_FS_MINMAX_DISCARD)) desc.C("FSMinMax ");
|
|
if (Bit(VS_BIT_FS_DEPTH_CLAMP)) desc.C("FSDepthClamp ");
|
|
|
|
// Lights
|
|
if (Bit(VS_BIT_LIGHTING_ENABLE)) {
|
|
desc.C("Light: ");
|
|
}
|
|
if (Bit(VS_BIT_LIGHT_UBERSHADER)) {
|
|
desc.C("LightUberShader ");
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
bool enabled = Bit(VS_BIT_LIGHT0_ENABLE + i) && Bit(VS_BIT_LIGHTING_ENABLE);
|
|
if (enabled || (uvgMode == GE_TEXMAP_ENVIRONMENT_MAP && (ls0 == i || ls1 == i))) {
|
|
desc.F("%d: ", i);
|
|
desc.F("c:%d t:%d ", Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2), Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2));
|
|
}
|
|
}
|
|
|
|
if (Bit(VS_BIT_SIMPLE_STEREO)) desc.C("SimpleStereo ");
|
|
if (Bit(VS_BIT_VERTEX_RANGE_CULLING)) desc.C("RangeCull ");
|
|
|
|
return desc.as_string();
|
|
}
|
|
|
|
void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, ClipInfoFlags clipInfoFlags) {
|
|
const bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0;
|
|
bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear();
|
|
bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP);
|
|
bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT && !gstate.isModeClear();
|
|
|
|
bool vtypeHasColor = (vertType & GE_VTYPE_COL_MASK) != 0;
|
|
bool vtypeHasNormal = (vertType & GE_VTYPE_NRM_MASK) != 0;
|
|
bool vtypeHasTexcoord = (vertType & GE_VTYPE_TC_MASK) != 0;
|
|
|
|
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough && !gstate.isModeClear();
|
|
bool vertexRangeCulling = gstate_c.Use(GPU_USE_VS_RANGE_CULLING) &&
|
|
!isModeThrough && gstate_c.submitType == SubmitType::DRAW; // neither hw nor sw spline/bezier. See #11692
|
|
|
|
VShaderID id;
|
|
id.SetBit(VS_BIT_LMODE, lmode);
|
|
id.SetBit(VS_BIT_IS_THROUGH, isModeThrough);
|
|
id.SetBit(VS_BIT_VERTEX_RANGE_CULLING, vertexRangeCulling);
|
|
|
|
if (!isModeThrough && gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
|
|
id.SetBit(VS_BIT_SIMPLE_STEREO);
|
|
}
|
|
|
|
if (doTexture) {
|
|
// UV generation mode. doShadeMapping is implicitly stored here.
|
|
id.SetBits(VS_BIT_UVGEN_MODE, 2, gstate.getUVGenMode());
|
|
}
|
|
|
|
if (useHWTransform) {
|
|
_dbg_assert_(!isModeThrough);
|
|
id.SetBit(VS_BIT_USE_HW_TRANSFORM);
|
|
|
|
id.SetBit(VS_BIT_HAS_NORMAL, vtypeHasNormal);
|
|
id.SetBit(VS_BIT_HAS_COLOR, vtypeHasColor);
|
|
id.SetBit(VS_BIT_HAS_TEXCOORD, vtypeHasTexcoord);
|
|
|
|
// The next bits are used differently depending on UVgen mode
|
|
if (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX) {
|
|
id.SetBits(VS_BIT_UVPROJ_MODE, 2, gstate.getUVProjMode());
|
|
} else if (doShadeMapping) {
|
|
id.SetBits(VS_BIT_LS0, 2, gstate.getUVLS0());
|
|
id.SetBits(VS_BIT_LS1, 2, gstate.getUVLS1());
|
|
}
|
|
|
|
if (doShadeMapping) {
|
|
// Shade mapping depends on the type of its lights and whether they do specular, even when
|
|
// they're off. The ubershader reads that from u_lightControl instead.
|
|
if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) {
|
|
id.SetBit(VS_BIT_LIGHT_UBERSHADER);
|
|
} else {
|
|
const int shadeLights[2] = { gstate.getUVLS0(), gstate.getUVLS1() };
|
|
for (int l : shadeLights) {
|
|
id.SetBits(VS_BIT_LIGHT0_COMP + 4 * l, 2, gstate.getLightComputation(l));
|
|
id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * l, 2, gstate.getLightType(l));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gstate.isLightingEnabled()) {
|
|
// doShadeMapping is stored as UVGenMode.
|
|
id.SetBit(VS_BIT_LIGHTING_ENABLE);
|
|
if (gstate_c.Use(GPU_USE_LIGHT_UBERSHADER)) {
|
|
id.SetBit(VS_BIT_LIGHT_UBERSHADER);
|
|
} else {
|
|
id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate());
|
|
// Light bits
|
|
for (int i = 0; i < 4; i++) {
|
|
bool chanEnabled = gstate.isLightChanEnabled(i) != 0;
|
|
id.SetBit(VS_BIT_LIGHT0_ENABLE + i, chanEnabled);
|
|
if (chanEnabled) {
|
|
id.SetBits(VS_BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i));
|
|
id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
id.SetBit(VS_BIT_NORM_REVERSE, gstate.areNormalsReversed());
|
|
}
|
|
|
|
if (gstate_c.Use(GPU_USE_FULL_PRECISION_IN_FRAGMENT)) {
|
|
if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) {
|
|
id.SetBit(VS_BIT_FS_DEPTH_CLAMP);
|
|
}
|
|
if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) {
|
|
id.SetBit(VS_BIT_FS_MINMAX_DISCARD);
|
|
}
|
|
} else {
|
|
// TODO: We're gonna need to soft-clip... Ugh.
|
|
}
|
|
|
|
id.SetBit(VS_BIT_FLATSHADE, doFlatShading);
|
|
|
|
// These two bits cannot be combined, otherwise havoc occurs. We get reports that indicate this happened somehow... "ERROR: 0:14: 'u_proj' : undeclared identifier"
|
|
_dbg_assert_msg_(!id.Bit(VS_BIT_USE_HW_TRANSFORM) || !id.Bit(VS_BIT_IS_THROUGH), "Can't have both THROUGH and USE_HW_TRANSFORM together!");
|
|
|
|
*id_out = id;
|
|
}
|
|
|
|
static const char * const alphaTestFuncs[] = { "NEVER", "ALWAYS", "==", "!=", "<", "<=", ">", ">=" };
|
|
static_assert(ARRAY_SIZE(alphaTestFuncs) == 8);
|
|
static const char * const logicFuncs[] = {
|
|
"CLEAR", "AND", "AND_REV", "COPY", "AND_INV", "NOOP", "XOR", "OR",
|
|
"NOR", "EQUIV", "INVERTED", "OR_REV", "COPY_INV", "OR_INV", "NAND", "SET",
|
|
};
|
|
static_assert(ARRAY_SIZE(logicFuncs) == 16);
|
|
|
|
static bool MatrixNeedsProjection(const float m[12], GETexProjMapMode mode) {
|
|
// For GE_PROJMAP_UV, we can ignore m[8] since it multiplies to zero.
|
|
return m[2] != 0.0f || m[5] != 0.0f || (m[8] != 0.0f && mode != GE_PROJMAP_UV) || m[11] != 1.0f;
|
|
}
|
|
|
|
std::string FShaderID::Description(bool includeID) const {
|
|
char buffer[512];
|
|
StringWriter desc(buffer, sizeof(buffer));
|
|
if (includeID) {
|
|
desc.W(ToDebugString()).C(" ");
|
|
}
|
|
|
|
if (Bit(FS_BIT_CLEARMODE)) desc.C("Clear ");
|
|
if (Bit(FS_BIT_DO_TEXTURE)) {
|
|
desc.W(Bit(FS_BIT_3D_TEXTURE) ? "Tex3D" : "Tex");
|
|
switch (Bits(FS_BIT_TEXFUNC, 3)) {
|
|
case GE_TEXFUNC_ADD: desc.C("(TFuncAdd) "); break;
|
|
case GE_TEXFUNC_BLEND: desc.C("(TFuncBlend) "); break;
|
|
case GE_TEXFUNC_DECAL: desc.C("(TFuncDecal) "); break;
|
|
case GE_TEXFUNC_MODULATE: desc.C("(TFuncMod) "); break;
|
|
case GE_TEXFUNC_REPLACE: desc.C("(TFuncRepl) "); break;
|
|
default: desc.C("(TFuncUnk) "); break;
|
|
}
|
|
}
|
|
if (Bit(FS_BIT_LMODE)) desc.C("LM ");
|
|
if (Bit(FS_BIT_ENABLE_FOG)) desc.C("Fog ");
|
|
if (Bit(FS_BIT_FLATSHADE)) desc.C("Flat ");
|
|
if (Bit(FS_BIT_DEPTH_TEST_NEVER)) desc.C("DepthNever ");
|
|
if (Bit(FS_BIT_COLOR_WRITEMASK)) desc.C("WriteMask ");
|
|
if (Bit(FS_BIT_SHADER_TEX_CLAMP)) {
|
|
desc.C("TClamp");
|
|
if (Bit(FS_BIT_CLAMP_S)) desc.C("S");
|
|
if (Bit(FS_BIT_CLAMP_T)) desc.C("T");
|
|
desc.C(" ");
|
|
}
|
|
int blendBits = Bits(FS_BIT_REPLACE_BLEND, 3);
|
|
if (blendBits) {
|
|
switch (blendBits) {
|
|
case ReplaceBlendType::REPLACE_BLEND_BLUE_TO_ALPHA:
|
|
desc.C("BlueToAlpha_" "A:").F("%d ", Bits(FS_BIT_BLENDFUNC_A, 4));
|
|
break;
|
|
default:
|
|
desc.C("ReplaceBlend_").F("%d ", Bits(FS_BIT_REPLACE_BLEND, 3))
|
|
.C("A:").F("%d ", Bits(FS_BIT_BLENDFUNC_A, 4))
|
|
.C("_B:").F("%d ", Bits(FS_BIT_BLENDFUNC_B, 4))
|
|
.C("_Eq:").F("%d ", Bits(FS_BIT_BLENDEQ, 3));
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (Bits(FS_BIT_STENCIL_TO_ALPHA, 2)) {
|
|
case REPLACE_ALPHA_NO: break;
|
|
case REPLACE_ALPHA_YES: desc.C("StenToAlpha "); break;
|
|
case REPLACE_ALPHA_DUALSOURCE: desc.C("StenToAlphaDual "); break;
|
|
default: desc.C("StenToAlphaUnknown "); break; // bad
|
|
}
|
|
|
|
if (Bits(FS_BIT_STENCIL_TO_ALPHA, 2) != REPLACE_ALPHA_NO) {
|
|
switch (Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4)) {
|
|
case STENCIL_VALUE_UNIFORM: desc.C("StenUniform "); break;
|
|
case STENCIL_VALUE_ZERO: desc.C("Sten0 "); break;
|
|
case STENCIL_VALUE_ONE: desc.C("Sten1 "); break;
|
|
case STENCIL_VALUE_KEEP: desc.C("StenKeep "); break;
|
|
case STENCIL_VALUE_INVERT: desc.C("StenInv "); break;
|
|
case STENCIL_VALUE_INCR_4BIT: desc.C("StenIncr4 "); break;
|
|
case STENCIL_VALUE_INCR_8BIT: desc.C("StenIncr8 "); break;
|
|
case STENCIL_VALUE_DECR_4BIT: desc.C("StenDecr4 "); break;
|
|
case STENCIL_VALUE_DECR_8BIT: desc.C("StenDecr8 "); break;
|
|
default: desc.C("StenUnknown "); break;
|
|
}
|
|
} else if (Bit(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE)) {
|
|
desc.C("StenOff ");
|
|
}
|
|
|
|
if (Bit(FS_BIT_ALPHA_AGAINST_ZERO)) desc.C("AlphaTest0 ").W(alphaTestFuncs[Bits(FS_BIT_ALPHA_TEST_FUNC, 3)]).C(" ");
|
|
else if (Bit(FS_BIT_ALPHA_TEST)) desc.C("AlphaTest ").W(alphaTestFuncs[Bits(FS_BIT_ALPHA_TEST_FUNC, 3)]).C(" ");
|
|
if (Bit(FS_BIT_COLOR_AGAINST_ZERO)) desc.C("ColorTest0 ").W(alphaTestFuncs[Bits(FS_BIT_COLOR_TEST_FUNC, 2)]).C(" "); // first 4 match;
|
|
else if (Bit(FS_BIT_COLOR_TEST)) desc.C("ColorTest ").W(alphaTestFuncs[Bits(FS_BIT_COLOR_TEST_FUNC, 2)]).C(" "); // first 4 match
|
|
if (Bit(FS_BIT_TEST_DISCARD_TO_ZERO)) desc.C("TestDiscardToZero ");
|
|
if (Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL)) desc.C("StencilDiscardWorkaround ");
|
|
int logicMode = Bits(FS_BIT_REPLACE_LOGIC_OP, 4);
|
|
if ((logicMode != GE_LOGIC_COPY) && !Bit(FS_BIT_CLEARMODE)) desc.C("RLogic(").W(logicFuncs[logicMode]).C(")");
|
|
if (Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE)) desc.C("TexArray ");
|
|
if (Bit(FS_BIT_STEREO)) desc.C("Stereo ");
|
|
if (Bit(FS_BIT_USE_FRAMEBUFFER_FETCH)) desc.C("(fetch)");
|
|
if (Bit(FS_BIT_MINMAX_DISCARD)) desc.C("FragMinMaxDiscard ");
|
|
if (Bit(FS_BIT_DEPTH_CLAMP)) desc.C("FragDepthClamp ");
|
|
if (Bit(FS_BIT_NO_DEPTH_WRITE)) desc.C("NoDepthWrite ");
|
|
|
|
const ShaderDepalMode depalMode = (ShaderDepalMode)Bits(FS_BIT_SHADER_DEPAL_MODE, 2);
|
|
switch (depalMode) {
|
|
case ShaderDepalMode::OFF: break;
|
|
case ShaderDepalMode::NORMAL: desc.C("Depal(");
|
|
{
|
|
const GEBufferFormat shaderDepalFormat = (GEBufferFormat)Bits(FS_BIT_SHADER_DEPAL_FORMAT, 3);
|
|
desc.W(GeBufferFormatToString(shaderDepalFormat)).C(") ");
|
|
break;
|
|
}
|
|
case ShaderDepalMode::SMOOTHED: desc.C("SmoothDepal "); break;
|
|
case ShaderDepalMode::CLUT8: desc.C("CLUT8Depal(").W(GeBufferFormatToString((GEBufferFormat)Bits(FS_BIT_SHADER_DEPAL_FORMAT, 3))).C(") "); break;
|
|
}
|
|
|
|
return desc.as_string();
|
|
}
|
|
|
|
bool FragmentIdNeedsFramebufferRead(const FShaderID &id) {
|
|
return id.Bit(FS_BIT_COLOR_WRITEMASK) ||
|
|
id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4) != GE_LOGIC_COPY ||
|
|
(ReplaceBlendType)id.Bits(FS_BIT_REPLACE_BLEND, 3) == REPLACE_BLEND_READ_FRAMEBUFFER;
|
|
}
|
|
|
|
inline u32 SanitizeBlendMode(GEBlendMode mode) {
|
|
if (mode > GE_BLENDMODE_ABSDIFF)
|
|
return GE_BLENDMODE_MUL_AND_ADD; // Not sure what the undefined modes are.
|
|
else
|
|
return mode;
|
|
}
|
|
|
|
// Here we must take all the bits of the gstate that determine what the fragment shader will
|
|
// look like, and concatenate them together into an ID.
|
|
void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, ClipInfoFlags clipInfoFlags) {
|
|
FShaderID id;
|
|
bool isModeThrough = gstate.isModeThrough();
|
|
|
|
// NOTE: This check MUST be identical to the one in ComputeVertexShaderID, otherwise we might get mismatches between VS and FS and end up with no shader at all.
|
|
if (!isModeThrough) {
|
|
if (gstate_c.Use(GPU_USE_FULL_PRECISION_IN_FRAGMENT)) {
|
|
if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) {
|
|
id.SetBit(FS_BIT_DEPTH_CLAMP);
|
|
}
|
|
if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) {
|
|
id.SetBit(FS_BIT_MINMAX_DISCARD);
|
|
}
|
|
} else {
|
|
// TODO: We're gonna need to soft-clip... Ugh.
|
|
}
|
|
} else {
|
|
_dbg_assert_(0 == (clipInfoFlags & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)));
|
|
}
|
|
|
|
if (gstate.isModeClear()) {
|
|
// We only need one clear shader, so let's ignore the rest of the bits.
|
|
id.SetBit(FS_BIT_CLEARMODE);
|
|
} else {
|
|
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough;
|
|
bool enableFog = gstate.isFogEnabled() && !isModeThrough;
|
|
bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue();
|
|
bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue();
|
|
bool enableColorDouble = gstate.isColorDoublingEnabled();
|
|
bool doTextureProjection = (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX && MatrixNeedsProjection(gstate.tgenMatrix, gstate.getUVProjMode()));
|
|
bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT;
|
|
|
|
bool enableTexAlpha = gstate.isTextureAlphaUsed();
|
|
|
|
ShaderDepalMode shaderDepalMode = gstate_c.shaderDepalMode;
|
|
GEBufferFormat shaderDepalFormat = {};
|
|
if (shaderDepalMode == ShaderDepalMode::NORMAL || shaderDepalMode == ShaderDepalMode::CLUT8) {
|
|
shaderDepalFormat = gstate_c.depalTextureFormat;
|
|
}
|
|
|
|
bool colorWriteMask = pipelineState.maskState.applyFramebufferRead;
|
|
ReplaceBlendType replaceBlend = pipelineState.blendState.replaceBlend;
|
|
GELogicOp replaceLogicOpType = pipelineState.logicState.applyFramebufferRead ? pipelineState.logicState.logicOp : GE_LOGIC_COPY;
|
|
|
|
SimulateLogicOpType simulateLogicOpType = pipelineState.blendState.simulateLogicOpType;
|
|
ReplaceAlphaType stencilToAlpha = pipelineState.blendState.replaceAlphaWithStencil;
|
|
|
|
if (gstate.isTextureMapEnabled()) {
|
|
id.SetBit(FS_BIT_DO_TEXTURE);
|
|
id.SetBits(FS_BIT_TEXFUNC, 3, gstate.getTextureFunction());
|
|
if (gstate_c.needShaderTexClamp) {
|
|
// 4 bits total.
|
|
id.SetBit(FS_BIT_SHADER_TEX_CLAMP);
|
|
// Video is clamped where the frame is smaller than the texture (see CalcTexClamp), so it
|
|
// can't wrap around into the garbage outside the frame. Where the frame fills it, the
|
|
// texture wraps as the game says: the VSH's UMD Video player converts into a 256-wide
|
|
// texture as a ring, and its strips wrap around the right edge.
|
|
id.SetBit(FS_BIT_CLAMP_S, gstate.isTexCoordClampedS() || (gstate_c.textureIsVideo && VideoClampsS()));
|
|
id.SetBit(FS_BIT_CLAMP_T, gstate.isTexCoordClampedT() || (gstate_c.textureIsVideo && VideoClampsT()));
|
|
}
|
|
id.SetBits(FS_BIT_SHADER_DEPAL_MODE, 2, (int)shaderDepalMode);
|
|
id.SetBits(FS_BIT_SHADER_DEPAL_FORMAT, 3, (int)shaderDepalFormat);
|
|
id.SetBit(FS_BIT_3D_TEXTURE, gstate_c.curTextureIs3D);
|
|
// All framebuffers are array textures in Vulkan now.
|
|
if (gstate_c.textureIsArray && gstate_c.Use(GPU_USE_FRAMEBUFFER_ARRAYS)) {
|
|
id.SetBit(FS_BIT_SAMPLE_ARRAY_TEXTURE);
|
|
}
|
|
id.SetBit(FS_BIT_DO_TEXTURE_PROJ, doTextureProjection);
|
|
}
|
|
|
|
id.SetBit(FS_BIT_LMODE, lmode);
|
|
|
|
if (enableAlphaTest) {
|
|
// 5 bits total.
|
|
id.SetBit(FS_BIT_ALPHA_TEST);
|
|
id.SetBits(FS_BIT_ALPHA_TEST_FUNC, 3, gstate.getAlphaTestFunction());
|
|
id.SetBit(FS_BIT_ALPHA_AGAINST_ZERO, IsAlphaTestAgainstZero());
|
|
id.SetBit(FS_BIT_TEST_DISCARD_TO_ZERO, !NeedsTestDiscard());
|
|
}
|
|
if (enableColorTest) {
|
|
// 4 bits total.
|
|
id.SetBit(FS_BIT_COLOR_TEST);
|
|
id.SetBits(FS_BIT_COLOR_TEST_FUNC, 2, gstate.getColorTestFunction());
|
|
id.SetBit(FS_BIT_COLOR_AGAINST_ZERO, IsColorTestAgainstZero());
|
|
// This is also set in enableAlphaTest - color test is uncommon, but we can skip discard the same way.
|
|
id.SetBit(FS_BIT_TEST_DISCARD_TO_ZERO, !NeedsTestDiscard());
|
|
}
|
|
|
|
id.SetBit(FS_BIT_ENABLE_FOG, enableFog); // TODO: Will be moved back to the ubershader.
|
|
|
|
// 2 bits
|
|
id.SetBits(FS_BIT_STENCIL_TO_ALPHA, 2, stencilToAlpha);
|
|
|
|
if (stencilToAlpha != REPLACE_ALPHA_NO) {
|
|
// 4 bits
|
|
id.SetBits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4, ReplaceAlphaWithStencilType());
|
|
}
|
|
|
|
// 2 bits.
|
|
id.SetBits(FS_BIT_SIMULATE_LOGIC_OP_TYPE, 2, simulateLogicOpType);
|
|
|
|
// 4 bits. Set to GE_LOGIC_COPY if not used, which does nothing in the shader generator.
|
|
id.SetBits(FS_BIT_REPLACE_LOGIC_OP, 4, (int)replaceLogicOpType);
|
|
|
|
// If replaceBlend == REPLACE_BLEND_STANDARD (or REPLACE_BLEND_NO) nothing is done, so we kill these bits.
|
|
if (replaceBlend == REPLACE_BLEND_BLUE_TO_ALPHA) {
|
|
id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend);
|
|
id.SetBits(FS_BIT_BLENDFUNC_A, 4, gstate.getBlendFuncA());
|
|
} else if (replaceBlend > REPLACE_BLEND_STANDARD) {
|
|
// 3 bits.
|
|
id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend);
|
|
// 11 bits total.
|
|
id.SetBits(FS_BIT_BLENDEQ, 3, SanitizeBlendMode(gstate.getBlendEq()));
|
|
id.SetBits(FS_BIT_BLENDFUNC_A, 4, gstate.getBlendFuncA());
|
|
id.SetBits(FS_BIT_BLENDFUNC_B, 4, gstate.getBlendFuncB());
|
|
}
|
|
id.SetBit(FS_BIT_FLATSHADE, doFlatShading);
|
|
id.SetBit(FS_BIT_COLOR_WRITEMASK, colorWriteMask);
|
|
|
|
// Stereo support
|
|
if (gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
|
|
id.SetBit(FS_BIT_STEREO);
|
|
}
|
|
|
|
if (g_Config.bVendorBugChecksEnabled) {
|
|
if (bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL_ADRENO) || bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL_MALI)) {
|
|
// On Adreno, the workaround is safe, so we do simple checks.
|
|
bool stencilWithoutDepth = (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled()) && !IsStencilTestOutputDisabled();
|
|
if (stencilWithoutDepth) {
|
|
id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, stencilWithoutDepth);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Various conditions that require per-pixel depth manipulation (very expensive!)
|
|
bool needMinMaxClipping = gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !isModeThrough;
|
|
|
|
// Forcibly disable NEVER + depth-write on Mali.
|
|
// TODO: Take this from computed depth test instead of directly from the gstate.
|
|
// That will take more refactoring though.
|
|
if (bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL_MALI) &&
|
|
gstate.getDepthTestFunction() == GE_COMP_NEVER && gstate.isDepthTestEnabled()) {
|
|
id.SetBit(FS_BIT_DEPTH_TEST_NEVER);
|
|
}
|
|
|
|
// With the depth test effectively off, nothing writes depth, so don't write it in the shader either.
|
|
// That also avoids a driver bug in Apple's OpenGL, where discard stopped writing color but not stencil
|
|
// once the shader writes gl_FragDepth (Tales of Phantasia, which has PixelDepthRounding).
|
|
if ((gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) || id.Bit(FS_BIT_DEPTH_CLAMP)) && IsDepthTestEffectivelyDisabled()) {
|
|
id.SetBit(FS_BIT_NO_DEPTH_WRITE);
|
|
}
|
|
|
|
// In case the USE flag changes (for example, in multisampling we might disable input attachments),
|
|
// we don't want to accidentally use the wrong cached shader here. So moved it to a bit.
|
|
if (FragmentIdNeedsFramebufferRead(id)) {
|
|
if (gstate_c.Use(GPU_USE_FRAMEBUFFER_FETCH)) {
|
|
id.SetBit(FS_BIT_USE_FRAMEBUFFER_FETCH);
|
|
}
|
|
}
|
|
}
|
|
|
|
*id_out = id;
|
|
}
|
|
|
|
std::vector<std::string> ToSortedDebugShaderIdVec(std::vector<uint64_t> ids) {
|
|
// Reverse the bits so that the sort order matches the importance order.
|
|
for (auto &id : ids) {
|
|
id = ReverseBits64(id);
|
|
}
|
|
std::sort(ids.begin(), ids.end());
|
|
// Reverse the bits back to get the original IDs.
|
|
for (auto &id : ids) {
|
|
id = ReverseBits64(id);
|
|
}
|
|
std::vector<std::string> strIds;
|
|
for (auto &id : ids) {
|
|
ShaderID shaderId;
|
|
shaderId.FromUint64(id);
|
|
std::string idStr;
|
|
shaderId.ToString(&idStr);
|
|
strIds.push_back(idStr);
|
|
}
|
|
return strIds;
|
|
}
|