rsx: Update programmable blending code to be in line with current emulator state

This commit is contained in:
kd-11
2026-09-07 21:09:01 +03:00
committed by kd-11
parent 065b490eb4
commit ab95783bc9
5 changed files with 130 additions and 79 deletions
@@ -1,5 +1,7 @@
R"(
#define _MRT_BUFFERS_COUNT 4 // TODO
#ifdef _32_BIT_OUTPUT
// Everything is fp32 on ouput channels
#define _mrt_color_t(expr) expr
@@ -15,16 +17,24 @@ R"(
// Default. Used when we're not utilizing native fp16
vec4 round_to_8bit(const in vec4 v4)
{
uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(v4), vec4(255.), vec4(0.5))), vec4(0.)));
const uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(v4), vec4(255.), vec4(0.5))), vec4(0.)));
return vec4(raw) / vec4(255.);
}
#ifndef _32_BIT_OUTPUT
f16vec4 round_to_8bit(const in f16vec4 v4)
{
uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(vec4(v4)), f16vec4(255.), f16vec4(0.5))), vec4(0.)));
const uvec4 raw = uvec4(max(floor(fma(_fx12_truncate(vec4(v4)), f16vec4(255.), f16vec4(0.5))), vec4(0.)));
return f16vec4(raw) / f16vec4(255.);
}
#endif
// Scalar variant of 8-bit rounding is only available for f32.
float round_to_8bit(const in float v)
{
const uint raw = uint(max(floor(fma(_fx12_truncate(v), 255.f, 0.5f)), 0.f));
return float(raw) / 255.f;
}
#endif
#ifdef _DISABLE_EARLY_DISCARD
@@ -35,6 +35,14 @@ R"(
#define BLEND_FUNC_ADD_SIGNED 0x0000F006
#define BLEND_FUNC_REVERSE_ADD_SIGNED 0x0000F007
#ifdef INT_FRAMEBUFFER_BIT
#define _blend_saturate _saturate
#define _blend_saturate_signed(v) clamp(v, -1.f, 1.f)
#else
#define _blend_saturate(v) v
#define _blend_saturate_signed(v) v
#endif
float get_blend_factor_a(const in uint op, const in vec4 src, const in vec4 dst)
{
switch (op)
@@ -51,9 +59,9 @@ float get_blend_factor_a(const in uint op, const in vec4 src, const in vec4 dst)
case BLEND_FACTOR_ONE_MINUS_DST_COLOR: return 1. - dst.a;
case BLEND_FACTOR_SRC_ALPHA_SATURATE: return 1;
case BLEND_FACTOR_CONSTANT_COLOR:
case BLEND_FACTOR_CONSTANT_ALPHA: return constants.a;
case BLEND_FACTOR_CONSTANT_ALPHA: return blend_constants.a;
case BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR:
case BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: return 1. - constants.a;
case BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: return 1. - blend_constants.a;
}
return 0.;
}
@@ -69,10 +77,10 @@ vec3 get_blend_factor_rgb(const in uint op, const in vec4 src, const in vec4 dst
case BLEND_FACTOR_ONE_MINUS_SRC_COLOR: return 1. - src.rgb;
case BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: return 1. - src.aaa;
case BLEND_FACTOR_DST_COLOR: return dst.rgb;
case BLEND_FACTOR_DST_ALPHA: return dst.a;
case BLEND_FACTOR_DST_ALPHA: return dst.aaa;
case BLEND_FACTOR_ONE_MINUS_DST_COLOR: return 1. - dst.rgb;
case BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return 1. - dst.a;
case BLEND_FACTOR_SRC_ALPHA_SATURATE: return src.rgb;
case BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return 1. - dst.aaa;
case BLEND_FACTOR_SRC_ALPHA_SATURATE: return vec3(min(src.a, 1. - dst.a));
case BLEND_FACTOR_CONSTANT_COLOR: return blend_constants.rgb;
case BLEND_FACTOR_CONSTANT_ALPHA: return blend_constants.aaa;
case BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: return 1. - blend_constants.rgb;
@@ -96,17 +104,17 @@ float apply_blend_func_a(const in vec4 src, const in vec4 dst)
switch (func)
{
case BLEND_FUNC_ADD: return _saturate(s) + d;
case BLEND_MIN: return min(_saturate(s), d);
case BLEND_MAX: return max(_saturate(s), d);
case BLEND_FUNC_SUBTRACT: return _saturate(s) - d;
case BLEND_FUNC_REVERSE_SUBTRACT: return d - _saturate(s);
case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - s;
case BLEND_FUNC_ADD_SIGNED: return s + d;
case BLEND_FUNC_REVERSE_ADD_SIGNED: return s + d;
case BLEND_FUNC_ADD: return _blend_saturate(s) + d;
case BLEND_MIN: return min(_blend_saturate(src.a), dst.a);
case BLEND_MAX: return max(_blend_saturate(src.a), dst.a);
case BLEND_FUNC_SUBTRACT: return _blend_saturate(s) - d;
case BLEND_FUNC_REVERSE_SUBTRACT: return d - _blend_saturate(s);
case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - _blend_saturate_signed(s);
case BLEND_FUNC_ADD_SIGNED: return _blend_saturate_signed(s) + d;
case BLEND_FUNC_REVERSE_ADD_SIGNED: return _blend_saturate(s) + _blend_saturate_signed(d);
}
return vec3(0.);
return 0.f;
}
vec3 apply_blend_func_rgb(const in vec4 src, const in vec4 dst)
@@ -124,14 +132,14 @@ vec3 apply_blend_func_rgb(const in vec4 src, const in vec4 dst)
switch (func)
{
case BLEND_FUNC_ADD: return _saturate(s) + d;
case BLEND_MIN: return min(_saturate(s), d);
case BLEND_MAX: return max(_saturate(s), d);
case BLEND_FUNC_SUBTRACT: return _saturate(s) - d;
case BLEND_FUNC_REVERSE_SUBTRACT: return d - _saturate(s);
case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - s;
case BLEND_FUNC_ADD_SIGNED: return s + d;
case BLEND_FUNC_REVERSE_ADD_SIGNED: return s + d;
case BLEND_FUNC_ADD: return _blend_saturate(s) + d;
case BLEND_MIN: return min(_blend_saturate(src.rgb), dst.rgb);
case BLEND_MAX: return max(_blend_saturate(src.rgb), dst.rgb);
case BLEND_FUNC_SUBTRACT: return _blend_saturate(s) - d;
case BLEND_FUNC_REVERSE_SUBTRACT: return d - _blend_saturate(s);
case BLEND_FUNC_REVERSE_SUBTRACT_SIGNED: return d - _blend_saturate_signed(s);
case BLEND_FUNC_ADD_SIGNED: return _blend_saturate_signed(s) + d;
case BLEND_FUNC_REVERSE_ADD_SIGNED: return _blend_saturate(s) + _blend_saturate_signed(d);
}
return vec3(0.);
@@ -145,8 +153,12 @@ vec4 do_blend(const in vec4 src, const in vec4 dst)
apply_blend_func_a(src, dst)
);
#ifdef _ENABLE_ROP_OUTPUT_ROUNDING
// Accurate int conversion with wrapping
return round_to_8bit(result);
#else
return result;
#endif
}
)"
@@ -7,25 +7,17 @@ R"(
}
#endif
#ifdef _ENABLE_FRAMEBUFFER_SRGB
col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb;
col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb;
col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb;
col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb;
#endif
#ifdef _ENABLE_ROP_OUTPUT_ROUNDING
col0 = round_to_8bit(col0);
col1 = round_to_8bit(col1);
col2 = round_to_8bit(col2);
col3 = round_to_8bit(col3);
#endif
// Post-output stages
// Alpha Testing
// Alpha Testing. This stage always runs even without a color output.
#ifdef _ENABLE_ALPHA_TEST
// TODO: Verify that alpha test actually runs on quantized output!
// Behavior was inferred from game observations but real tests will be needed here.
#if defined(_ENABLE_ROP_OUTPUT_ROUNDING)
#define _alpha_quantize(v) round_to_8bit(v)
#else
#define _alpha_quantize(v) (v)
#endif
const uint alpha_func = _get_bits(rop_control, ALPHA_TEST_FUNC_OFFSET, ALPHA_TEST_FUNC_LENGTH);
if (!comparison_passes(col0.a, alpha_ref, alpha_func))
if (!comparison_passes(_alpha_quantize(col0.a), alpha_ref, alpha_func))
{
discard;
}
@@ -38,6 +30,71 @@ R"(
}
#endif
// ================= Post-output color stages =================
#if _MRT_BUFFERS_COUNT >= 1
#ifdef _ENABLE_PROGRAMMABLE_BLENDING
// Stash the raw source data
vec4 blend_s0 = col0;
vec4 blend_s1 = col1;
vec4 blend_s2 = col2;
vec4 blend_s3 = col3;
#endif
#if defined(_ENABLE_ROP_OUTPUT_ROUNDING) && !defined(_ENABLE_PROGRAMMABLE_BLENDING)
col0 = round_to_8bit(col0);
col1 = round_to_8bit(col1);
col2 = round_to_8bit(col2);
col3 = round_to_8bit(col3);
#endif
// NOTE: Blending happens before output remapping as per hardware tests.
// Sources = raw shader output, Dest = Remapped output from previous draw
#ifdef _ENABLE_PROGRAMMABLE_BLENDING
#if _MRT_BUFFERS_COUNT >= 1
col0 = _mrt_color_t(do_blend(blend_s0, mrt_color[0]));
#endif
#if _MRT_BUFFERS_COUNT >= 2
col1 = _mrt_color_t(do_blend(blend_s1, mrt_color[1]));
#endif
#if _MRT_BUFFERS_COUNT >= 3
col2 = _mrt_color_t(do_blend(blend_s2, mrt_color[2]));
#endif
#if _MRT_BUFFERS_COUNT == 4
col3 = _mrt_color_t(do_blend(blend_s3, mrt_color[3]));
#endif
#endif
#ifdef _ENABLE_ROP_CHANNEL_REMAPPING
const uint ROP_remap = get_ROP_channel_remap();
col0 = _mrt_color_t(remap_ROP_output(col0, ROP_remap));
col1 = _mrt_color_t(remap_ROP_output(col1, ROP_remap));
col2 = _mrt_color_t(remap_ROP_output(col2, ROP_remap));
col3 = _mrt_color_t(remap_ROP_output(col3, ROP_remap));
#endif
// TODO: Check order on real hw. Does this happen before quantization or after?
#ifdef _ENABLE_FRAMEBUFFER_SRGB
col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb;
col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb;
col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb;
col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb;
#endif
// Commit COLOR aspect
ocol0 = col0;
ocol1 = col1;
ocol2 = col2;
ocol3 = col3;
#endif // _MRT_BUFFERS_COUNT >= 1
//// ====================== Depth Export ===========================
#ifdef _ENABLE_DEPTH_COMPARE
#ifdef _ENABLE_DEPTH_BUFFER_MULTISAMPLED
float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), gl_SampleID).r;
@@ -57,36 +114,4 @@ R"(
gl_FragDepth = srcDepth;
}
#endif
#ifdef _ENABLE_ROP_CHANNEL_REMAPPING
const uint ROP_remap = get_ROP_channel_remap();
col0 = _mrt_color_t(remap_ROP_output(col0, ROP_remap));
col1 = _mrt_color_t(remap_ROP_output(col1, ROP_remap));
col2 = _mrt_color_t(remap_ROP_output(col2, ROP_remap));
col3 = _mrt_color_t(remap_ROP_output(col3, ROP_remap));
#endif
#ifdef _ENABLE_PROGRAMMABLE_BLENDING
switch (framebufferCount)
{
case 4:
col3 = do_blend(col3, mrt_color[3]);
// Fallthrough
case 3:
col2 = do_blend(col2, mrt_color[2]);
// Fallthrough
case 2:
col1 = do_blend(col1, mrt_color[1]);
// Fallthrough
default:
col0 = do_blend(col0, mrt_color[0]);
break;
}
#endif
// Commit
ocol0 = col0;
ocol1 = col1;
ocol2 = col2;
ocol3 = col3;
)"
@@ -19,11 +19,12 @@ R"(
}
#endif
#ifdef _ENABLE_PROGRAMMABLE_BLENDING
vec4 mrt_color[4];
for (int n = 0; n < framebufferCount; ++n)
#if defined(_ENABLE_PROGRAMMABLE_BLENDING) && _MRT_BUFFERS_COUNT >= 1
// TODO: Multisampled output support.
vec4 mrt_color[_MRT_BUFFERS_COUNT];
for (int n = 0; n < _MRT_BUFFERS_COUNT; ++n)
{
mrt_color[n] = subPassLoad(mrtAttachments[n]);
mrt_color[n] = subpassLoad(mrtAttachments[n]);
}
#endif
)"
+5 -2
View File
@@ -450,10 +450,13 @@ namespace gcm
RSX_SHADER_CONTROL_USED_REGS_MASK = 0x0000000f,
RSX_SHADER_CONTROL_USED_TEMP_REGS_MASK = 0xff000000,
RSX_SHADER_CONTROL_USES_KIL = 0x00000080, // program uses KIL op
RSX_SHADER_CONTROL_UNKNOWN0 = 0x00000400, // seemingly always set
RSX_SHADER_CONTROL_USES_KIL = 0x00000080, // program uses KIL op
RSX_SHADER_CONTROL_UNKNOWN0 = 0x00000400, // seemingly always set
RSX_SHADER_CONTROL_UNKNOWN1 = 0x00008000, // seemingly set when srgb packer is used??
// Meta
RSX_SHADER_CONTROL_GCM_FLAGS_MASK = 0x000007ff, // Reserved range for GCM flags. We don't care about most GCM-specific flags.
// Custom
RSX_SHADER_CONTROL_FLAT_SHADING = 0x00000800, // Interpolate front/back colors using the provoking vertex
RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x00010000, // Rasterizing triangles and not lines or points