Implement fragment shader depth clamp and min/max discard. Slow on some hardware, but it's a fallback.

This commit is contained in:
Henrik Rydgård
2026-05-30 19:07:59 +02:00
parent 0b21731e7b
commit 0eede05f5f
13 changed files with 168 additions and 28 deletions
+1 -1
View File
@@ -421,7 +421,7 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
// Check for weird scaling that can make graphics extend beyond the viewport.
// NOTE: These checks are not bullet proof.
float mtx[16];
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || vpYScale < ((scissorY2 + 1) >> 1)) {
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || fabsf(vpYScale) < ((scissorY2 + 1) >> 1)) {
// Note that the PSP does not clip against the viewport.
const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());
// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior.
+34 -2
View File
@@ -58,6 +58,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
bool highpTexcoord = false;
bool enableFragmentTestCache = gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE);
const bool fsMinmaxDiscard = id.Bit(FS_BIT_MINMAX_DISCARD);
const bool fsDepthClamp = id.Bit(FS_BIT_DEPTH_CLAMP);
if (compat.gles) {
// PowerVR needs highp to do the fog in MHU correctly.
// Others don't, and some can't handle highp in the fragment shader.
@@ -183,7 +186,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
bool needFragCoord = readFramebufferTex || gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT);
bool writeDepth = gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) && !forceDepthWritesOff;
bool writeDepth = (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) && !forceDepthWritesOff) || fsDepthClamp;
// TODO: We could have a separate mechanism to support more ops using the shader blending mechanism,
// on hardware that can do proper bit math in fragment shaders.
@@ -225,6 +228,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (doTexture) {
WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n");
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, "layout (location = 4) in highp vec2 v_zw;\n");
}
if (enableAlphaTest && !alphaTestAgainstZero) {
WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n");
@@ -291,6 +297,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " vec4 pixelPos : SV_POSITION;\n");
}
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, " vec2 v_zw : TEXCOORD2;\n");
}
WRITE(p, "};\n");
if (compat.shaderLanguage == HLSL_D3D11) {
@@ -402,6 +411,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (doTexture) {
WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump");
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, "%s vec2 v_zw;\n", compat.varying_fs);
}
if (!enableFragmentTestCache) {
if (enableAlphaTest && !alphaTestAgainstZero) {
@@ -499,6 +511,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (doTexture) {
WRITE(p, " vec3 v_texcoord = In.v_texcoord;\n");
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, " vec2 v_zw = In.v_zw;\n");
}
}
// Two things read from the old framebuffer - shader replacement blending and bit-level masking.
@@ -1151,12 +1166,27 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", compat.fragColor0, compat.fragColor0);
}
if (fsDepthClamp || fsMinmaxDiscard) {
WRITE(p, " highp float projZ = v_zw.x / v_zw.y;\n");
}
if (fsMinmaxDiscard) {
// See the vertex shader generator for the explanation for this.
WRITE(p, " float clipZ = floor(projZ * 0.5 + 0.5) * 2.0;\n");
WRITE(p, " if (u_minZmaxZ.x > 0 && clipZ < u_minZmaxZ.x) DISCARD;\n");
WRITE(p, " if (u_minZmaxZ.y < 65535 && clipZ > u_minZmaxZ.y) DISCARD;\n");
}
if (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) {
DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());
const double scale = depthScale.ScaleU16();
WRITE(p, " highp float z = gl_FragCoord.z;\n");
if (fsDepthClamp) {
WRITE(p, " highp float z = clamp(projZ, 0.0, 65536.0) / 65536.0;\n");
} else {
WRITE(p, " highp float z = gl_FragCoord.z;\n");
}
// We center the depth with an offset, but only its fraction matters.
// When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0.
if (((int)(depthScale.Scale() - 1.0f) & 1) == 1) {
@@ -1165,6 +1195,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale);
}
WRITE(p, " gl_FragDepth = z;\n");
} else if (fsDepthClamp) {
WRITE(p, " gl_FragDepth = clamp(projZ, 0.0, 65536.0) / 65536.0;\n");
} else if (useDiscardStencilBugWorkaround) {
// Adreno and some Mali drivers apply early frag tests even with discard in the shader,
// when only stencil is used. The exact situation seems to vary by driver.
+48 -4
View File
@@ -13,12 +13,23 @@
#include "GPU/Common/ShaderId.h"
#include "GPU/Common/VertexDecoderCommon.h"
// Shared ID checks for when the vertex and fragment shaders need to coordinate.
// NOTE: Both of these assume non - through - mode.Don't check these if in through mode.
static bool needFragmentMinMaxClipping() {
return gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !gstate_c.Use(GPU_USE_CLIP_DISTANCE);
}
static bool needFragmentDepthClamp() {
// If gstate.isDepthClipEnabled is false, clamping does not happen, instead fragments are culled as normal.
return (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 0xFFFF) && gstate.isDepthClipEnabled() && !gstate_c.Use(GPU_USE_DEPTH_CLAMP);
}
std::string VertexShaderDesc(const VShaderID &id) {
std::stringstream desc;
desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]);
if (id.Bit(VS_BIT_IS_THROUGH)) desc << "THR ";
if (id.Bit(VS_BIT_CLIP_ENABLE)) desc << "Clip ";
if (id.Bit(VS_BIT_MINMAX_DISCARD)) desc << "ZClip ";
if (id.Bit(VS_BIT_USE_HW_TRANSFORM)) desc << "HWX ";
if (id.Bit(VS_BIT_HAS_COLOR)) desc << "C ";
if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T ";
@@ -37,6 +48,7 @@ std::string VertexShaderDesc(const VShaderID &id) {
if (uvgMode) desc << uvgModes[uvgMode];
if (id.Bit(VS_BIT_ENABLE_BONES)) desc << "Bones:" << (id.Bits(VS_BIT_BONES, 3) + 1) << " ";
// Lights
if (id.Bit(VS_BIT_LIGHTING_ENABLE)) {
desc << "Light: ";
@@ -64,12 +76,15 @@ std::string VertexShaderDesc(const VShaderID &id) {
if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) desc << "RangeCull ";
if (id.Bit(VS_BIT_SIMPLE_STEREO)) desc << "SimpleStereo ";
if (id.Bit(VS_BIT_FS_MINMAX_DISCARD)) desc << "FSMinMax ";
if (id.Bit(VS_BIT_FS_DEPTH_CLAMP)) desc << "FSDepthClamp ";
return desc.str();
}
void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) {
bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0;
const bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0;
const bool isSoftwareFallback = !isModeThrough && !useHWTransform && g_Config.bHardwareTransform;
bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear();
bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP);
bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT && !gstate.isModeClear();
@@ -106,6 +121,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
}
if (useHWTransform) {
_dbg_assert_(!isModeThrough);
id.SetBit(VS_BIT_USE_HW_TRANSFORM);
id.SetBit(VS_BIT_HAS_NORMAL, vtypeHasNormal);
@@ -160,6 +176,16 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
}
id.SetBit(VS_BIT_NORM_REVERSE_TESS, gstate.isPatchNormalsReversed());
}
} else { // !useHwTransform
// Various conditions that require per-pixel depth manipulation (very expensive!)
if (!isModeThrough) {
if (needFragmentDepthClamp()) {
id.SetBit(VS_BIT_FS_DEPTH_CLAMP);
}
if (needFragmentMinMaxClipping()) {
id.SetBit(VS_BIT_FS_MINMAX_DISCARD);
}
}
}
id.SetBit(VS_BIT_FLATSHADE, doFlatShading);
@@ -267,6 +293,8 @@ std::string FragmentShaderDesc(const FShaderID &id) {
if (id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE)) desc << "TexArray ";
if (id.Bit(FS_BIT_STEREO)) desc << "Stereo ";
if (id.Bit(FS_BIT_USE_FRAMEBUFFER_FETCH)) desc << "(fetch)";
if (id.Bit(FS_BIT_MINMAX_DISCARD)) desc << "FragMinMaxDiscard ";
if (id.Bit(FS_BIT_DEPTH_CLAMP)) desc << "FragDepthClamp ";
return desc.str();
}
@@ -285,13 +313,26 @@ inline u32 SanitizeBlendMode(GEBlendMode 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) {
void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform) {
FShaderID id;
bool isModeThrough = gstate.isModeThrough();
// We exclude hwTransform mode here, although we could absolutely do this in hwtransform as well, because we detect
// draws that needs this and use software transform as the fallback for them. That logic will have to change if we change that.
// 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 (!useHwTransform && !isModeThrough) {
if (needFragmentDepthClamp()) {
id.SetBit(FS_BIT_DEPTH_CLAMP);
}
if (needFragmentMinMaxClipping()) {
id.SetBit(FS_BIT_MINMAX_DISCARD);
}
}
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 isModeThrough = gstate.isModeThrough();
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough;
bool enableFog = gstate.isFogEnabled() && !isModeThrough;
bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue();
@@ -404,6 +445,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
}
}
// 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.
+6 -2
View File
@@ -16,7 +16,7 @@ enum VShaderBit : uint8_t {
VS_BIT_CLIP_ENABLE = 4,
VS_BIT_VERTEX_RANGE_CULLING = 5,
VS_BIT_SIMPLE_STEREO = 6,
VS_BIT_MINMAX_DISCARD = 7, // Do min/max in the fragment shader.
// bit 7 is free,
VS_BIT_USE_HW_TRANSFORM = 8,
VS_BIT_HAS_NORMAL = 9, // conditioned on hw transform
VS_BIT_NORM_REVERSE = 10,
@@ -54,6 +54,8 @@ enum VShaderBit : uint8_t {
VS_BIT_LIGHTING_ENABLE = 56,
VS_BIT_WEIGHT_FMTSCALE = 57, // only two bits
// 59 - 61 are free.
VS_BIT_FS_MINMAX_DISCARD = 59, // Do min/max and/or depth clamp in the fragment shader. It just means we need to forward Z and W to the fragment shader.
VS_BIT_FS_DEPTH_CLAMP = 60, // Do depth clamp in the fragment shader.
VS_BIT_FLATSHADE = 62, // 1 bit
VS_BIT_BEZIER = 63, // 1 bit
// No more free
@@ -103,6 +105,8 @@ enum FShaderBit : uint8_t {
FS_BIT_USE_FRAMEBUFFER_FETCH = 59,
FS_BIT_UBERSHADER = 60,
FS_BIT_DEPTH_TEST_NEVER = 61, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver.
FS_BIT_DEPTH_CLAMP = 62, // These both are connected to VS_BIT_MINMAX_DISCARD_OR_DEPTH_CLAMP in the vertex shader.
// Free bit: 63
};
static inline FShaderBit operator +(FShaderBit bit, int i) {
@@ -249,7 +253,7 @@ void ComputeVertexShaderID(VShaderID *id, u32 vertType, bool useHWTransform, boo
std::string VertexShaderDesc(const VShaderID &id);
struct ComputedPipelineState;
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs);
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform);
std::string FragmentShaderDesc(const FShaderID &id);
// For sanity checking.
+9 -1
View File
@@ -731,10 +731,18 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
// Now that we're done culling and generating clipped vertices if needed (not yet implemented), we go ahead and project.
ProjectVertices(transformed, numDecodedVerts);
#if 0
// NOTE! This code is effectively obsolete now that we have implemented depth clamp in the fragment shader,
// However, this can be an alternate partial solution for low-performance hardware in the future.
// Alright! Now, we can approximate Z-clamping, if the hardware lacks support for doing it for us.
// Now, this can only be done exactly if all vertices in a triangle are beyond the far plane.
// If not we technically need to cut it in two parts to clamp accurately.
// However, in most cases that matter (such as missing skies, etc), this is fine.
// We could be aggressive and clamp every individual vertex, but this takes the safer (but not 100% safe) route and only clamps vertices
// that are part of a triangle where all three are beyond the same plane.
const int maxZInt = gstate.getDepthRangeMax();
// float maxZ = maxZInt / 65535.0f;
const int minZInt = gstate.getDepthRangeMin();
@@ -742,7 +750,6 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
// We only need to clamp if minZ and maxZ aren't at the extreme in each direction, as otherwise
// minZ and maxZ will cut things off.
// If gstate_c.Use(GPU_USE_DEPTH_CLAMP), we can theoretically skip this. However, to keep behavior the same regardless of the flag, we won't.
if (gstate.isDepthClipEnabled() && (minZInt == 0 || maxZInt == 65535)) {
for (int i = 0; i < numTrans - 2; i += 3) {
TransformedVertex &v0 = transformed[newInds[i]];
@@ -779,6 +786,7 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
}
}
}
#endif
}
} else {
_dbg_assert_(false);
+36 -3
View File
@@ -222,6 +222,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS);
bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS);
// Should we do the min/max discard in the shader and/or or use depth clamping?
// In both cases we need to just forward
const bool fsMinmaxDiscard = id.Bit(VS_BIT_FS_MINMAX_DISCARD);
const bool fsDepthClamp = id.Bit(VS_BIT_FS_DEPTH_CLAMP);
const char *shading = "";
if (compat.glslES30 || compat.shaderLanguage == GLSL_VULKAN)
shading = doFlatShading ? "flat " : "";
@@ -298,6 +303,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n");
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, "layout (location = 4) out highp vec2 v_zw;\n");
}
WRITE(p, "invariant gl_Position;\n");
} else if (compat.shaderLanguage == HLSL_D3D11) {
// Note: These two share some code after this hellishly large if/else.
@@ -356,6 +365,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
WRITE(p, " float v_fogdepth : TEXCOORD1;\n");
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, " vec2 v_zw : TEXCOORD2;\n");
}
// gl_Position must be last for D3D11.
WRITE(p, " vec4 gl_Position : SV_Position;\n");
minZClipPlaneSuffix = ".x";
maxZClipPlaneSuffix = ".y";
@@ -387,7 +400,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (useHWTransform)
WRITE(p, "%s vec3 position;\n", compat.attribute);
else
WRITE(p, "%s vec4 position;\n", compat.attribute); // need to pass the fog coord in w
WRITE(p, "%s vec4 position;\n", compat.attribute); // XYZW clip space coordinate.
*attrMask |= 1 << ATTR_POSITION;
if (useHWTransform && hasNormal) {
@@ -523,6 +536,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
} else {
WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs);
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, "%s highp vec2 v_zw;\n", compat.varying_vs);
}
}
// Hardware tessellation
@@ -747,9 +764,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " vec4 outPos = position;\n");
WRITE(p, " outPos.z *= 65536.0;\n"); // TODO: This multiplication should be moved to the vertex decoders for through mode.
} else {
// The viewport has already been applied here.
// The viewport has already been applied here, along with the division.
WRITE(p, " vec4 outPos = position;\n");
}
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
}
} else {
// Step 1: World Transform / Skinning
if (!enableBones) {
@@ -836,6 +857,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// We're in clip space, so here we check for clipping. We check if the actual hardware will clip.
// If so, we skip the "range culling" (x and y out-of-bounds checks) since they wouldn't have happened, most likely.
// NOTE: There are some games that depend on clipping already having been done when checking the range culling.
// We can't handle that here, we use the software transform pipeline for that.
WRITE(p, " if (outPos.z < -outPos.w) {\n");
WRITE(p, " zClipped = true;\n");
WRITE(p, " }\n");
@@ -846,7 +869,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (depthCullEnable) {
// Before the viewport, discard any primitives that are fully outside the clipping volume in Z.
// NOTE: This volume is very slightly too small. It's unclear to me how to allow hex 0x3F8000XX (up to 1.0000304) where XX are arbitrary.
// NOTE: We add a small offset to the clip distance to allow hex 0x3F8000XX (up to 1.0000304) where XX are arbitrary.
WRITE(p, " %sgl_CullDistance%s = outPos.z + outPos.w + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceNearSuffix);
WRITE(p, " %sgl_CullDistance%s = outPos.w - outPos.z + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceFarSuffix);
}
@@ -855,6 +878,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// In software transform mode, this is performed in on the CPU.
WRITE(p, " outPos.xyz = (outPos.xyz / outPos.w) * u_vpScale.xyz + u_vpOffset.xyz;\n");
if (fsMinmaxDiscard || fsDepthClamp) {
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
}
// TODO: Declare variables for dots for shade mapping if needed.
const char *srcCol = "color0";
@@ -1286,6 +1313,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " %sgl_Position.y *= u_scaleY;\n", compat.vsOutPrefix);
}
if (fsDepthClamp) {
// Overwrite Z with a value that will not be clipped.
// Then we will overwrite the Z in the fragment shader with the per-pixel value computed from the interpolated v_zw.
WRITE(p, " %sgl_Position.z = (u_minZmaxZ.x + u_minZmaxZ.y) * 0.5 * (1.0 / 65536.0);\n", compat.vsOutPrefix, compat.vsOutPrefix);
}
if (compat.depthMinusOneToOne) {
// Convert from 0->1 to -1->1 depth range.
WRITE(p, " %sgl_Position.z = %sgl_Position.z * 2.0 - %sgl_Position.w;\n", compat.vsOutPrefix, compat.vsOutPrefix, compat.vsOutPrefix);
+8 -1
View File
@@ -199,7 +199,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs());
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform);
} else {
FSID = lastFSID_;
}
@@ -222,6 +222,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
VertexShaderFlags flags;
GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &attrMask, &uniformMask, &flags, &genErrorString);
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
// OutputDebugStringA(codeBuffer_);
vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, useHWTransform);
vsCache_[VSID] = vs;
} else {
@@ -238,6 +239,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
FragmentShaderFlags flags;
GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &flags, &genErrorString);
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
// OutputDebugStringA(codeBuffer_);
fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform);
fsCache_[FSID] = fs;
} else {
@@ -246,6 +248,11 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
lastFSID_ = FSID;
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
lastVShader_ = vs;
lastFShader_ = fs;
+5 -3
View File
@@ -274,12 +274,14 @@ void DrawEngineGLES::Flush() {
Shader *vshader = shaderManager_->ApplyVertexShader(useHWTransform, useHWTessellation_, dec_->VertexType(), decOptions_.expandAllWeightsToFloat, applySkinInDecode_ || !useHWTransform, &vsid);
useHWTransform = vshader->UseHWTransform(); // In case shader compilation failed and it fell back. However, this can no longer really happen... Need to fix this.
GLRBuffer *vertexBuffer = nullptr;
GLRBuffer *indexBuffer = nullptr;
uint32_t vertexBufferOffset = 0;
uint32_t indexBufferOffset = 0;
if (vshader->UseHWTransform()) {
if (useHWTransform) {
if (applySkinInDecode_ && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
// If software skinning, we're predecoding into "decoded". So make sure we're done, then push that content.
DecodeVerts(dec_, decoded_);
@@ -322,7 +324,7 @@ void DrawEngineGLES::Flush() {
ApplyDrawState(prim);
ApplyDrawStateLate(false, 0);
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_);
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, true);
GLRInputLayout *inputLayout = SetupDecFmtForDraw(dec_->GetDecVtxFmt());
if (useElements) {
render_->DrawIndexed(inputLayout,
@@ -408,7 +410,7 @@ void DrawEngineGLES::Flush() {
ApplyDrawState(prim);
ApplyDrawStateLate(result.setStencil, result.stencilValue);
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_);
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, false);
if (!linked) {
// Not much we can do here. Let's skip drawing.
goto bail;
+4 -2
View File
@@ -808,7 +808,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess
return vs;
}
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState) {
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHwTransform) {
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty) {
if (lastShader_)
@@ -820,7 +820,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
FShaderID FSID;
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs());
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHwTransform);
} else {
FSID = lastFSID_;
}
@@ -862,6 +862,8 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
if (ls == nullptr) {
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
if (vs == nullptr || fs == nullptr) {
// Can't draw. This shouldn't really happen (but can happen if fragment shader generation fails)
+1 -1
View File
@@ -171,7 +171,7 @@ public:
// This is the old ApplyShader split into two parts, because of annoying information dependencies.
// If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards.
Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, VShaderID *VSID);
LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState);
LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHWTransform);
void DeviceLost() override;
void DeviceRestore(Draw::DrawContext *draw) override;
+4 -4
View File
@@ -200,11 +200,11 @@ const CommonCommandTableEntry commonCommandTable[] = {
{ GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
// Z range.
{ GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET},
{ GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET},
// Z range and clipping
{ GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE },
{ GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
{ GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
// Region
{ GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE },
+3 -3
View File
@@ -241,13 +241,12 @@ void DrawEngineVulkan::Flush() {
provokingVertexOk = true;
}
bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk;
if (useHWTransform && boundingDepths_.valid) {
if (boundingDepths_.hitClipSpaceZW) {
// Revert to software transform so we can clip more accurately.
useHWTransform = false;
}
if ((boundingDepths_.minProjZ < 0.0 || boundingDepths_.maxProjZ > 65535.0) && !gstate_c.Use(GPU_USE_DEPTH_CLAMP)) {
if ((boundingDepths_.minProjZ < gstate.getDepthRangeMin() || boundingDepths_.maxProjZ > gstate.getDepthRangeMax()) && !gstate_c.Use(GPU_USE_DEPTH_CLAMP)) {
// Revert to software transform so we can clamp more accurately.
useHWTransform = false;
}
@@ -255,7 +254,8 @@ void DrawEngineVulkan::Flush() {
}
if (useHWTransform != lastUseHwTransform_) {
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_RASTER_STATE);
// Need to re-evaluate software transform fallbacks.
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
lastUseHwTransform_ = useHWTransform;
}
+9 -1
View File
@@ -249,8 +249,11 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
VShaderID VSID;
VulkanVertexShader *vs = nullptr;
bool recomputedVS = false;
bool recomputedFS = false;
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
recomputedVS = true;
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode);
if (VSID == lastVSID_) {
_dbg_assert_(lastVShader_ != nullptr);
@@ -281,7 +284,8 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
VulkanFragmentShader *fs = nullptr;
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs());
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform);
recomputedFS = true;
if (FSID == lastFSID_) {
_dbg_assert_(lastFShader_ != nullptr);
fs = lastFShader_;
@@ -305,8 +309,12 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
}
*fshader = fs;
// If you hit these, look at recomputedVS and recomputedFS to determine if it's a dirty-flag problem
// or an ID generation problem (if any of them are false, it's a dirty-flag problem).
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
_dbg_assert_msg_((*vshader)->UseHWTransform() == useHWTransform, "Bad vshader was computed");
}