Files
ARMSX2/bin/resources/shaders/opengl/tfx_fs.glsl
T
jpolo1224 4d8701a49d GS: driver-bug database and per-driver shader workarounds
Ported from EmuCoreX with sashkinbro's approval. The GPU family alone was
never enough to decide behaviour: the same Mali part behaves differently
under ARM's proprietary driver than under Mesa PanVK, a lesson this tree
learned twice the expensive way - the r44p1 DEVICE_LOST fix had to be
gated on driverID rather than vendorID, and the 8 Elite push-descriptor
disable likewise. Driver identity, version and a bug set are now recorded
in a table, so the next device quirk is an entry rather than another
bespoke branch. Twenty-five rules, with match confidence so a vendor-wide
rule never overrides a driver-version-specific one.

The shaders gain gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not,
whose bodies the device emits: plain a & b normally, scalarized where the
database says the driver miscompiles vector bitwise ops. A device matching
no rule renders exactly as before.

opengl/tfx_fs.glsl already carried this fix as IAND3/UAND2/UAND4 macros
gated on GPU_PROFILE_MALI. Those are replaced by the shared helpers, but
the GL macro is deliberately (workaround || IsMaliGPUProfile()): Mali
reached through ANGLE or Panfrost resolves a non-ARM driver and matches no
rule, so a database-only gate would have silently removed a fix those
users have today. Widening only.

SHADER_CACHE_VERSION 108 -> 109. Every TFX and convert shader's source
text changed, so a blob cached from 108 no longer matches the source that
produced it; without the bump users would get stale binaries and garbage
rendering after updating.

RewriteConstantLoads is deliberately absent - the database records
BrokenConstantLoad with no workaround bits, so the macro would be
permanently zero and the shader code dead.

Shaders validated offline with glslc across 136 Vulkan and 296 GL/GLES
permutations, every macro on and off; the failure set is byte-identical to
the pre-change baseline.
2026-07-26 14:53:48 -04:00

1474 lines
38 KiB
GLSL

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
//#version 420 // Keep it for text editor detection
// When dual source blending is unavailable (GLES without GL_EXT_blend_func_extended),
// treat the second color output as disabled so no 'index' layout qualifiers are emitted.
#if defined(DISABLE_DUAL_SOURCE) && !PS_NO_COLOR1
#undef PS_NO_COLOR1
#define PS_NO_COLOR1 1
#endif
#define FMT_32 0
#define FMT_24 1
#define FMT_16 2
#define SHUFFLE_READ 1
#define SHUFFLE_WRITE 2
#define SHUFFLE_READWRITE 3
#ifndef ZTST_GEQUAL
#define ZTST_GEQUAL 2
#define ZTST_GREATER 3
#endif
#ifndef AFAIL_KEEP
#define AFAIL_KEEP 0
#define AFAIL_FB_ONLY 1
#define AFAIL_ZB_ONLY 2
#define AFAIL_RGB_ONLY 3
#define AFAIL_RGB_ONLY_DSB 4
#define AFAIL_RGB_ONLY_SW_Z 5
#endif
// Driver-compiler workarounds (gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not) are defined in
// the generated shader header, gated on the driver-bug database. They replace the old IAND3/UAND2/
// UAND4 macros, which fixed the same ARM Mali defect (a runtime vector & vector collapses to the
// constant operand, dropping the mask) but keyed it on the GPU vendor rather than the driver blob.
// On anything without a matching rule they compile down to the plain operator.
#ifndef PS_ATST_NONE
#define PS_ATST_NONE 0
#define PS_ATST_LEQUAL 1
#define PS_ATST_GEQUAL 2
#define PS_ATST_EQUAL 3
#define PS_ATST_NOTEQUAL 4
#endif
#ifndef PS_AA1_NONE
#define PS_AA1_NONE 0
#define PS_AA1_LINE 1
#define PS_AA1_TRIANGLE 2
#define PS_AA1_TRIANGLE_SW_Z 3
#endif
// TEX_COORD_DEBUG output the uv coordinate as color. It is useful
// to detect bad sampling due to upscaling
//#define TEX_COORD_DEBUG
// Just copy directly the texture coordinate
#ifdef TEX_COORD_DEBUG
#define PS_TFX 1
#define PS_TCC 1
#endif
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
#define SW_BLEND_NEEDS_RT (SW_BLEND && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1 || PS_BLEND_D == 1))
#define SW_AD_TO_HW (PS_BLEND_C == 1 && PS_A_MASKED)
#define PS_PRIMID_INIT (PS_DATE == 1 || PS_DATE == 2)
#define NEEDS_RT_EARLY (PS_TEX_IS_FB == 1 || PS_DATE >= 5)
#define NEEDS_RT_FOR_AFAIL (PS_AFAIL == AFAIL_ZB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
#define NEEDS_DEPTH_FOR_AFAIL (PS_AFAIL == AFAIL_FB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
#define NEEDS_DEPTH_FOR_ZTST (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER)
#define NEEDS_DEPTH_FOR_AA1 (PS_AA1 == PS_AA1_TRIANGLE_SW_Z)
#define NEEDS_RT (NEEDS_RT_EARLY || NEEDS_RT_FOR_AFAIL || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW)))
#define NEEDS_TEX (PS_TFX != 4)
#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1)
#define ZWRITE (SW_DEPTH || PS_ZCLAMP || PS_ZFLOOR)
layout(std140, binding = 0) uniform cb21
{
vec3 FogColor;
float AREF;
vec4 WH;
vec2 TA;
float MaxDepthPS;
float Af;
uvec4 FbMask;
vec4 HalfTexel;
vec4 MinMax;
vec4 LODParams;
vec4 STRange;
ivec4 ChannelShuffle;
vec2 ChannelShuffleOffset;
vec2 TC_OffsetHack;
vec2 STScale;
mat4 DitherMatrix;
float ScaledScaleFactor;
float RcpScaleFactor;
float _pad0_cb1;
float _pad1_cb1;
float LineCovScale;
float _pad2_cb1;
float _pad3_cb1;
float _pad4_cb1;
};
in SHADER
{
vec4 t_float;
vec4 t_int;
#if PS_IIP != 0
vec4 c;
#else
flat vec4 c;
#endif
float inv_cov; // We use the inverse to make it simpler to interpolate.
flat uint interior; // 1 for triangle interior; 0 for edge;
} PSin;
#define TARGET_0_QUALIFIER out
// Only enable framebuffer fetch when we actually need it.
#if HAS_FRAMEBUFFER_FETCH && NEEDS_RT
// We need to force the colour to be defined here, to read from it.
// Basically the only scenario where this'll happen is RGBA masked and DATE is active.
#undef PS_NO_COLOR
#define PS_NO_COLOR 0
// Mali devices use ARM_shader_framebuffer_fetch even when the EXT extension is
// also advertised — the EXT inout path is broken on every Mali driver tested
// and only `gl_LastFragColorARM` reads back the live tile pixel correctly.
// Selection is driven by GPU_PROFILE_MALI (emitted from the C++ side after
// the runtime profile is resolved).
#if GPU_PROFILE_MALI
#if HAS_ARM_SHADER_FRAMEBUFFER_FETCH
#define LAST_FRAG_COLOR gl_LastFragColorARM
#endif
#else
#if HAS_EXT_SHADER_FRAMEBUFFER_FETCH || HAS_EXT_SHADER_PIXEL_LOCAL_STORAGE
#undef TARGET_0_QUALIFIER
#define TARGET_0_QUALIFIER inout
#define LAST_FRAG_COLOR o_col0
#elif HAS_ARM_SHADER_FRAMEBUFFER_FETCH
#define LAST_FRAG_COLOR gl_LastFragColorARM
#endif
#endif
#endif
#if !PS_NO_COLOR && !PS_NO_COLOR1
// Same buffer but 2 colors for dual source blending
layout(location = 0, index = 0) TARGET_0_QUALIFIER vec4 o_col0;
layout(location = 0, index = 1) out vec4 o_col1;
#elif !PS_NO_COLOR
layout(location = 0) TARGET_0_QUALIFIER vec4 o_col0;
#endif
// Depth feedback mode 2 is for depth as color.
// Use FB fetch for the feedback if it's available.
#if SW_DEPTH && PS_NO_COLOR1 && (DEPTH_FEEDBACK_SUPPORT == 2)
#if HAS_FRAMEBUFFER_FETCH
layout(location = 1) inout float o_col1;
#else
layout(location = 1) out float o_col1;
#endif
#endif
#if NEEDS_TEX
layout(binding = 0) uniform sampler2D TextureSampler;
layout(binding = 1) uniform sampler2D PaletteSampler;
#endif
#if !HAS_FRAMEBUFFER_FETCH && NEEDS_RT
layout(binding = 2) uniform sampler2D RtSampler; // note 2 already use by the image below
#endif
#if PS_DATE == 3
layout(binding = 3) uniform sampler2D img_prim_min;
#endif
// Depth feedback mode 1 binds depth buffer directly as a texture.
// Depth feedback mode 2 (depth as color) can use FB fetch for the feedback,
// in which case we don't need to explicitly bind depth as a texture.
// HAS_ARM_DEPTH_FETCH reads prior depth from gl_LastFragDepthARM (coherent,
// tile-local) — no sampler needed, so skip the binding entirely.
#if (DEPTH_FEEDBACK_SUPPORT == 1 || (DEPTH_FEEDBACK_SUPPORT == 2 && !HAS_FRAMEBUFFER_FETCH)) && SW_DEPTH && !HAS_ARM_DEPTH_FETCH
layout(binding = 4) uniform sampler2D DepthSampler;
#endif
#if ZWRITE && PS_HAS_CONSERVATIVE_DEPTH && !SW_DEPTH
layout(depth_less) out float gl_FragDepth;
#endif
vec4 sample_from_rt()
{
#if !NEEDS_RT
return vec4(0.0);
#elif HAS_FRAMEBUFFER_FETCH
return LAST_FRAG_COLOR;
#else
return texelFetch(RtSampler, ivec2(gl_FragCoord.xy), 0);
#endif
}
float sample_from_depth()
{
#if !SW_DEPTH
return 0.0f;
#elif HAS_ARM_DEPTH_FETCH
// Coherent tile-local read of the current depth attachment — no sampler,
// no second fetch output, so it links on Adreno and doesn't read stale depth.
return gl_LastFragDepthARM;
#elif HAS_FRAMEBUFFER_FETCH && (DEPTH_FEEDBACK_SUPPORT == 2)
return o_col1;
#else
return texelFetch(DepthSampler, ivec2(gl_FragCoord.xy), 0).r;
#endif
}
#if NEEDS_TEX
#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1)
float manual_lod(float uv_w)
{
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
float K = LODParams.x;
float L = LODParams.y;
float bias = LODParams.z;
float max_lod = LODParams.w;
float gs_lod = K - log2(abs(uv_w)) * L;
// FIXME max useful ?
//return max(min(gs_lod, max_lod) - bias, 0.0f);
return min(gs_lod, max_lod) - bias;
}
#endif
#if PS_ANISOTROPIC_FILTERING > 1
vec4 sample_c_af(vec2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
vec2 sz = vec2(textureSize(TextureSampler, 0));
vec2 dX = dFdx(uv) * sz;
vec2 dY = dFdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
{
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2.0f * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = inversesqrt(denom_plus);
float inv_sqrt_denom_minus = inversesqrt(denom_minus);
vec2 new_dX = vec2(
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
vec2 new_dY = vec2(
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
vec2 aniso_line;
if (length_major <= 1.0f)
{
// A zero length_major would result in NaN Lod and break sampling.
// A small length_major would result in aniso_ratio getting clamped to 1.
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = vec2(0.0f, 0.0f);
}
else
{
vec2 aniso_line_dir = is_major_x ? dX : dY;
aniso_ratio = min(length_major / length_minor, float(PS_ANISOTROPIC_FILTERING));
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0.0f; // No Lod
#endif
vec4 colour;
if (aniso_ratio == 1.0f)
colour = textureLod(TextureSampler, uv, lod);
else
{
vec4 num = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec2 segment = (2.0f * aniso_line) / aniso_ratio;
for (float i = 0.0f; i < aniso_ratio; i += 1.0f)
{
vec2 d = -aniso_line + (0.5f + i) * segment;
vec2 uv_sample = uv + d;
vec4 sample_colour = textureLod(TextureSampler, uv_sample, lod);
num += sample_colour;
}
colour = num / aniso_ratio;
}
return colour;
}
#endif
vec4 sample_c(vec2 uv)
{
#if PS_TEX_IS_FB == 1
return sample_from_rt();
#elif PS_REGION_RECT
return texelFetch(TextureSampler, ivec2(uv), 0);
#else
#if !PS_ADJS && !PS_ADJT
uv *= STScale;
#else
#if PS_ADJS
uv.x = (uv.x - STRange.x) * STRange.z;
#else
uv.x = uv.x * STScale.x;
#endif
#if PS_ADJT
uv.y = (uv.y - STRange.y) * STRange.w;
#else
uv.y = uv.y * STScale.y;
#endif
#endif
#if PS_ANISOTROPIC_FILTERING > 1
return sample_c_af(uv, PSin.t_float.w);
#elif PS_AUTOMATIC_LOD == 1
return texture(TextureSampler, uv);
#elif PS_MANUAL_LOD == 1
return textureLod(TextureSampler, uv, manual_lod(PSin.t_float.w));
#else
return textureLod(TextureSampler, uv, 0.0f); // No lod
#endif
#endif
}
vec4 sample_p(uint idx)
{
return texelFetch(PaletteSampler, ivec2(int(idx), 0), 0);
}
vec4 sample_p_norm(float u)
{
return sample_p(uint(u * 255.5f));
}
vec4 clamp_wrap_uv(vec4 uv)
{
vec4 uv_out = uv;
vec4 tex_size = WH.xyxy;
#if PS_WMS == PS_WMT
#if PS_REGION_RECT == 1 && PS_WMS == 0
uv_out = fract(uv);
#elif PS_REGION_RECT == 1 && PS_WMS == 1
uv_out = clamp(uv, vec4(0.0f), vec4(1.0f));
#elif PS_WMS == 2
uv_out = clamp(uv, MinMax.xyxy, MinMax.zwzw);
#elif PS_WMS == 3
#if PS_FST == 0
// wrap negative uv coords to avoid an off by one error that shifted
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv_out = vec4(gpu_bitwise_and(uvec4(uv * tex_size), floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
#endif
#else // PS_WMS != PS_WMT
#if PS_REGION_RECT == 1 && PS_WMS == 0
uv.xz = fract(uv.xz);
#elif PS_REGION_RECT == 1 && PS_WMS == 1
uv.xz = clamp(uv.xz, vec2(0.0f), vec2(1.0f));
#elif PS_WMS == 2
uv_out.xz = clamp(uv.xz, MinMax.xx, MinMax.zz);
#elif PS_WMS == 3
#if PS_FST == 0
uv.xz = fract(uv.xz);
#endif
uv_out.xz = vec2(gpu_bitwise_and(uvec2(uv.xz * tex_size.xx), floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
#endif
#if PS_REGION_RECT == 1 && PS_WMT == 0
uv_out.yw = fract(uv.yw);
#elif PS_REGION_RECT == 1 && PS_WMT == 1
uv_out.yw = clamp(uv.yw, vec2(0.0f), vec2(1.0f));
#elif PS_WMT == 2
uv_out.yw = clamp(uv.yw, MinMax.yy, MinMax.ww);
#elif PS_WMT == 3
#if PS_FST == 0
uv.yw = fract(uv.yw);
#endif
uv_out.yw = vec2(gpu_bitwise_and(uvec2(uv.yw * tex_size.yy), floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
#endif
#endif
#if PS_REGION_RECT == 1
// Normalized -> Integer Coordinates.
uv_out = clamp(uv_out * WH.zwzw + STRange.xyxy, STRange.xyxy, STRange.zwzw);
#endif
return uv_out;
}
mat4 sample_4c(vec4 uv)
{
mat4 c;
// Note: texture gather can't be used because of special clamping/wrapping
// Also it doesn't support lod
c[0] = sample_c(uv.xy);
c[1] = sample_c(uv.zy);
c[2] = sample_c(uv.xw);
c[3] = sample_c(uv.zw);
return c;
}
uvec4 sample_4_index(vec4 uv)
{
vec4 c;
// Either GS will send a texture that contains a single channel
// in this case the red channel is remapped as alpha channel
//
// Or we have an old RT (ie RGBA8) that contains index (4/8) in the alpha channel
// Note: texture gather can't be used because of special clamping/wrapping
// Also it doesn't support lod
c.x = sample_c(uv.xy).a;
c.y = sample_c(uv.zy).a;
c.z = sample_c(uv.xw).a;
c.w = sample_c(uv.zw).a;
#if PS_RTA_SRC_CORRECTION
uvec4 i = uvec4(round(c * 128.25f)); // Denormalize value
#else
uvec4 i = uvec4(c * 255.5f); // Denormalize value
#endif
#if PS_PAL_FMT == 1
// 4HL
return i & 0xFu;
#elif PS_PAL_FMT == 2
// 4HH
return i >> 4u;
#else
// 8
return i;
#endif
}
mat4 sample_4p(uvec4 u)
{
mat4 c;
c[0] = sample_p(u.x);
c[1] = sample_p(u.y);
c[2] = sample_p(u.z);
c[3] = sample_p(u.w);
return c;
}
uint fetch_raw_depth()
{
#if HAS_CLIP_CONTROL
float multiplier = exp2(32.0f);
#else
float multiplier = exp2(24.0f);
#endif
#if PS_TEX_IS_FB == 1
return uint(sample_from_rt().r * multiplier);
#else
return uint(texelFetch(TextureSampler, ivec2(gl_FragCoord.xy + ChannelShuffleOffset), 0).r * multiplier);
#endif
}
vec4 fetch_raw_color()
{
#if PS_TEX_IS_FB == 1
return sample_from_rt();
#else
return texelFetch(TextureSampler, ivec2(gl_FragCoord.xy + ChannelShuffleOffset), 0);
#endif
}
vec4 fetch_c(ivec2 uv)
{
#if PS_TEX_IS_FB == 1
return sample_from_rt();
#else
return texelFetch(TextureSampler, ivec2(uv), 0);
#endif
}
//////////////////////////////////////////////////////////////////////
// Depth sampling
//////////////////////////////////////////////////////////////////////
ivec2 clamp_wrap_uv_depth(ivec2 uv)
{
ivec2 uv_out = uv;
// Keep the full precision
// It allow to multiply the ScalingFactor before the 1/16 coeff
ivec4 mask = floatBitsToInt(MinMax) << 4;
#if PS_WMS == PS_WMT
#if PS_WMS == 2
uv_out = clamp(uv, mask.xy, mask.zw);
#elif PS_WMS == 3
uv_out = (uv & mask.xy) | mask.zw;
#endif
#else // PS_WMS != PS_WMT
#if PS_WMS == 2
uv_out.x = clamp(uv.x, mask.x, mask.z);
#elif PS_WMS == 3
uv_out.x = (uv.x & mask.x) | mask.z;
#endif
#if PS_WMT == 2
uv_out.y = clamp(uv.y, mask.y, mask.w);
#elif PS_WMT == 3
uv_out.y = (uv.y & mask.y) | mask.w;
#endif
#endif
return uv_out;
}
vec4 sample_depth(vec2 st)
{
vec2 uv_f = vec2(clamp_wrap_uv_depth(ivec2(st))) * vec2(ScaledScaleFactor);
#if PS_REGION_RECT == 1
uv_f = clamp(uv_f + STRange.xy, STRange.xy, STRange.zw);
#endif
ivec2 uv = ivec2(uv_f);
vec4 t = vec4(0.0f);
#if PS_TALES_OF_ABYSS_HLE == 1
// Warning: UV can't be used in channel effect
uint depth = fetch_raw_depth();
// Convert msb based on the palette
t = texelFetch(PaletteSampler, ivec2((depth >> 8u) & 0xFFu, 0), 0) * 255.0f;
#elif PS_URBAN_CHAOS_HLE == 1
// Depth buffer is read as a RGB5A1 texture. The game try to extract the green channel.
// So it will do a first channel trick to extract lsb, value is right-shifted.
// Then a new channel trick to extract msb which will shifted to the left.
// OpenGL uses a FLOAT32 format for the depth so it requires a couple of conversion.
// To be faster both steps (msb&lsb) are done in a single pass.
// Warning: UV can't be used in channel effect
uint depth = fetch_raw_depth();
// Convert lsb based on the palette
t = texelFetch(PaletteSampler, ivec2((depth & 0xFFu), 0), 0) * 255.0f;
// Msb is easier
float green = float((depth >> 8u) & 0xFFu) * 36.0f;
green = min(green, 255.0f);
t.g += green;
#elif PS_DEPTH_FMT == 1
// Based on ps_convert_depth32_rgba8 of convert
// Convert a GL_FLOAT32 depth texture into a RGBA color texture
#if HAS_CLIP_CONTROL
uint d = uint(fetch_c(uv).r * exp2(32.0f));
#else
uint d = uint(fetch_c(uv).r * exp2(24.0f));
#endif
t = vec4(uvec4((d & 0xFFu), ((d >> 8) & 0xFFu), ((d >> 16) & 0xFFu), (d >> 24)));
#elif PS_DEPTH_FMT == 2
// Based on ps_convert_depth16_rgb5a1 of convert
// Convert a GL_FLOAT32 (only 16 lsb) depth into a RGB5A1 color texture
#if HAS_CLIP_CONTROL
uint d = uint(fetch_c(uv).r * exp2(32.0f));
#else
uint d = uint(fetch_c(uv).r * exp2(24.0f));
#endif
t = vec4(uvec4((d & 0x1Fu), ((d >> 5) & 0x1Fu), ((d >> 10) & 0x1Fu), (d >> 15) & 0x01u)) * vec4(8.0f, 8.0f, 8.0f, 128.0f);
#elif PS_DEPTH_FMT == 3
// Convert a RGBA/RGB5A1 color texture into a RGBA/RGB5A1 color texture
t = fetch_c(uv) * 255.0f;
#endif
// warning t ranges from 0 to 255
#if (PS_AEM_FMT == FMT_24)
t.a = ( (PS_AEM == 0) || any(bvec3(t.rgb)) ) ? 255.0f * TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
t.a = t.a >= 128.0f ? 255.0f * TA.y : ( (PS_AEM == 0) || any(bvec3(t.rgb)) ) ? 255.0f * TA.x : 0.0f;
#elif PS_PAL_FMT != 0 && !PS_TALES_OF_ABYSS_HLE && !PS_URBAN_CHAOS_HLE
t = trunc(sample_4p(uvec4(t.aaaa))[0] * 255.0f + 0.05f);
#endif
return t;
}
//////////////////////////////////////////////////////////////////////
// Fetch a Single Channel
//////////////////////////////////////////////////////////////////////
vec4 fetch_red()
{
#if PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2
uint depth = (fetch_raw_depth()) & 0xFFu;
vec4 rt = vec4(depth) / 255.0f;
#else
vec4 rt = fetch_raw_color();
#endif
return sample_p_norm(rt.r) * 255.0f;
}
vec4 fetch_green()
{
#if PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2
uint depth = (fetch_raw_depth() >> 8u) & 0xFFu;
vec4 rt = vec4(depth) / 255.0f;
#else
vec4 rt = fetch_raw_color();
#endif
return sample_p_norm(rt.g) * 255.0f;
}
vec4 fetch_blue()
{
#if PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2
uint depth = (fetch_raw_depth() >> 16u) & 0xFFu;
vec4 rt = vec4(depth) / 255.0f;
#else
vec4 rt = fetch_raw_color();
#endif
return sample_p_norm(rt.b) * 255.0f;
}
vec4 fetch_alpha()
{
vec4 rt = fetch_raw_color();
return sample_p_norm(rt.a) * 255.0f;
}
vec4 fetch_rgb()
{
vec4 rt = fetch_raw_color();
vec4 c = vec4(sample_p_norm(rt.r).r, sample_p_norm(rt.g).g, sample_p_norm(rt.b).b, 1.0f);
return c * 255.0f;
}
vec4 fetch_gXbY()
{
#if PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2
uint depth = fetch_raw_depth();
uint bg = (depth >> (8u + uint(ChannelShuffle.w))) & 0xFFu;
return vec4(bg);
#else
ivec4 rt = ivec4(fetch_raw_color() * 255.0f);
int green = (rt.g >> ChannelShuffle.w) & ChannelShuffle.z;
int blue = (rt.b << ChannelShuffle.y) & ChannelShuffle.x;
return vec4(green | blue);
#endif
}
//////////////////////////////////////////////////////////////////////
vec4 sample_color(vec2 st)
{
#if (PS_TCOFFSETHACK == 1)
st += TC_OffsetHack.xy;
#endif
vec4 t;
mat4 c;
vec2 dd;
// FIXME I'm not sure this condition is useful (I think code will be optimized)
#if (PS_LTF == 0 && PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_REGION_RECT == 0 && PS_WMS < 2 && PS_WMT < 2)
// No software LTF and pure 32 bits RGBA texure without special texture wrapping
c[0] = sample_c(st);
#ifdef TEX_COORD_DEBUG
c[0].rg = st.xy;
#endif
#else
vec4 uv;
if(PS_LTF != 0)
{
uv = st.xyxy + HalfTexel;
dd = fract(uv.xy * WH.zw);
#if (PS_FST == 0)
// Background in Shin Megami Tensei Lucifers
// I suspect that uv isn't a standard number, so fract is outside of the [0;1] range
// Note: it is free on GPU but let's do it only for float coordinate
dd = clamp(dd, vec2(0.0f), vec2(1.0f));
#endif
}
else
{
uv = st.xyxy;
}
uv = clamp_wrap_uv(uv);
#if PS_PAL_FMT != 0
c = sample_4p(sample_4_index(uv));
#else
c = sample_4c(uv);
#endif
#ifdef TEX_COORD_DEBUG
c[0].rg = uv.xy;
c[1].rg = uv.xy;
c[2].rg = uv.xy;
c[3].rg = uv.xy;
#endif
#endif
// PERF note: using dot product reduces by 1 the number of instruction
// but I'm not sure it is equivalent neither faster.
for (int i = 0; i < 4; i++)
{
//float sum = dot(c[i].rgb, vec3(1.0f));
#if (PS_AEM_FMT == FMT_24)
c[i].a = ( (PS_AEM == 0) || any(bvec3(c[i].rgb)) ) ? TA.x : 0.0f;
//c[i].a = ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || any(bvec3(gpu_bitwise_and(ivec3(c[i].rgb * 255.0f), ivec3(0xF8)))) ) ? TA.x : 0.0f;
//c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#endif
}
#if(PS_LTF != 0)
t = mix(mix(c[0], c[1], dd.x), mix(c[2], c[3], dd.x), dd.y);
#else
t = c[0];
#endif
#if PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_RTA_SRC_CORRECTION
t.a = t.a * (128.5f / 255.0f);
#endif
// The 0.05f helps to fix the overbloom of sotc
// I think the issue is related to the rounding of texture coodinate. The linear (from fixed unit)
// interpolation could be slightly below the correct one.
return trunc(t * 255.0f + 0.05f);
}
#endif // NEEDS_TEX
vec4 tfx(vec4 T, vec4 C)
{
vec4 C_out;
vec4 FxT = trunc((C * T) / 128.0f);
#if (PS_TFX == 0)
C_out = FxT;
#elif (PS_TFX == 1)
C_out = T;
#elif (PS_TFX == 2)
C_out.rgb = FxT.rgb + C.a;
C_out.a = T.a + C.a;
#elif (PS_TFX == 3)
C_out.rgb = FxT.rgb + C.a;
C_out.a = T.a;
#else
C_out = C;
#endif
#if (PS_TCC == 0)
C_out.a = C.a;
#endif
#if (PS_TFX == 0) || (PS_TFX == 2) || (PS_TFX == 3)
// Clamp only when it is useful
C_out = min(C_out, 255.0f);
#endif
return C_out;
}
bool atst(vec4 C)
{
float a = C.a;
#if PS_ATST == PS_ATST_LEQUAL
return (a <= AREF);
#elif PS_ATST == PS_ATST_GEQUAL
return (a >= AREF);
#elif PS_ATST == PS_ATST_EQUAL
return (abs(a - AREF) <= 0.5f);
#elif PS_ATST == PS_ATST_NOTEQUAL
return (abs(a - AREF) >= 0.5f);
#else
return true;
#endif
}
void fog(inout vec4 C, float f)
{
#if PS_FOG != 0
C.rgb = trunc(mix(FogColor, C.rgb, (f * 255.0f) / 256.0f));
#endif
}
vec4 ps_color()
{
//FIXME: maybe we can set gl_Position.w = q in VS
#if (PS_FST == 0)
vec2 st = PSin.t_float.xy / vec2(PSin.t_float.w);
vec2 st_int = PSin.t_int.zw / vec2(PSin.t_float.w);
#else
// Note xy are normalized coordinate
vec2 st = PSin.t_int.xy;
vec2 st_int = PSin.t_int.zw;
#endif
#if !NEEDS_TEX
vec4 T = vec4(0.0);
#elif PS_CHANNEL_FETCH == 1
vec4 T = fetch_red();
#elif PS_CHANNEL_FETCH == 2
vec4 T = fetch_green();
#elif PS_CHANNEL_FETCH == 3
vec4 T = fetch_blue();
#elif PS_CHANNEL_FETCH == 4
vec4 T = fetch_alpha();
#elif PS_CHANNEL_FETCH == 5
vec4 T = fetch_rgb();
#elif PS_CHANNEL_FETCH == 6
vec4 T = fetch_gXbY();
#elif PS_DEPTH_FMT > 0
// Integral coordinate
vec4 T = sample_depth(st_int);
#else
vec4 T = sample_color(st);
#endif
#if PS_SHUFFLE && !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)
uvec4 denorm_c_before = uvec4(T);
#if (PS_PROCESS_BA & SHUFFLE_READ)
T.r = float((denorm_c_before.b << 3) & 0xF8u);
T.g = float(((denorm_c_before.b >> 2) & 0x38u) | ((denorm_c_before.a << 6) & 0xC0u));
T.b = float((denorm_c_before.a << 1) & 0xF8u);
T.a = float(denorm_c_before.a & 0x80u);
#else
T.r = float((denorm_c_before.r << 3) & 0xF8u);
T.g = float(((denorm_c_before.r >> 2) & 0x38u) | ((denorm_c_before.g << 6) & 0xC0u));
T.b = float((denorm_c_before.g << 1) & 0xF8u);
T.a = float(denorm_c_before.g & 0x80u);
#endif
T.a = ((T.a >= 127.5f) ? TA.y : ((PS_AEM == 0 || any(bvec3(gpu_bitwise_and(ivec3(T.rgb), ivec3(0xF8))))) ? TA.x : 0.0f)) * 255.0f;
#endif
vec4 C = tfx(T, PSin.c);
fog(C, PSin.t_float.z);
return C;
}
void ps_fbmask(inout vec4 C)
{
// FIXME do I need special case for 16 bits
#if PS_FBMASK
#if PS_COLCLIP_HW == 1
vec4 RT = trunc(sample_from_rt() * 65535.0f);
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
C = vec4(gpu_bitwise_and(uvec4(C), gpu_bitwise_not(FbMask)) | gpu_bitwise_and(uvec4(RT), FbMask));
#endif
}
void ps_dither(inout vec3 C, float As)
{
#if PS_DITHER > 0 && PS_DITHER < 3
#if PS_DITHER == 2
ivec2 fpos = ivec2(gl_FragCoord.xy);
#else
ivec2 fpos = ivec2(gl_FragCoord.xy * RcpScaleFactor);
#endif
float value = DitherMatrix[fpos.y&3][fpos.x&3];
// The idea here is we add on the dither amount adjusted by the alpha before it goes to the hw blend
// so after the alpha blend the resulting value should be the same as (Cs - Cd) * As + Cd + Dither.
#if PS_DITHER_ADJUST
#if PS_BLEND_C == 2
float Alpha = Af;
#else
float Alpha = As;
#endif
value *= Alpha > 0.0f ? min(1.0f / Alpha, 1.0f) : 1.0f;
#endif
#if PS_ROUND_INV
C -= value;
#else
C += value;
#endif
#endif
}
void ps_color_clamp_wrap(inout vec3 C)
{
// When dithering the bottom 3 bits become meaningless and cause lines in the picture
// so we need to limit the color depth on dithered items
#if SW_BLEND || (PS_DITHER > 0 && PS_DITHER < 3) || PS_FBMASK
#if PS_DST_FMT == FMT_16 && PS_BLEND_MIX == 0 && PS_ROUND_INV
C += 7.0f; // Need to round up, not down since the shader will invert
#endif
// Correct the Color value based on the output format
#if PS_COLCLIP == 0 && PS_COLCLIP_HW == 0
// Standard Clamp
C = clamp(C, vec3(0.0f), vec3(255.0f));
#endif
// FIXME rouding of negative float?
// compiler uses trunc but it might need floor
// Warning: normally blending equation is mult(A, B) = A * B >> 7. GPU have the full accuracy
// GS: Color = 1, Alpha = 255 => output 1
// GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875
#if PS_DST_FMT == FMT_16 && PS_DITHER < 3 && (PS_BLEND_MIX == 0 || PS_DITHER)
// In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#elif PS_COLCLIP == 1 || PS_COLCLIP_HW == 1
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xFF)));
#endif
#elif PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#endif
}
void ps_blend(inout vec4 Color, inout vec4 As_rgba)
{
float As = As_rgba.a;
#if SW_BLEND
// PABE
#if PS_PABE
// As_rgba needed for accumulation blend to manipulate Cd.
// No blending so early exit
if (As < 1.0f)
{
As_rgba.rgb = vec3(0.0f);
return;
}
As_rgba.rgb = vec3(1.0f);
#endif
#if SW_BLEND_NEEDS_RT
vec4 RT = sample_from_rt();
#else
// Not used, but we define it to make the selection below simpler.
vec4 RT = vec4(0.0f);
#endif
#if PS_RTA_CORRECTION
float Ad = trunc(RT.a * 128.0f + 0.1f) / 128.0f;
#else
float Ad = trunc(RT.a * 255.0f + 0.1f) / 128.0f;
#endif
#if PS_SHUFFLE && SW_BLEND_NEEDS_RT
uvec4 denorm_rt = uvec4(RT);
#if (PS_PROCESS_BA & SHUFFLE_WRITE)
RT.r = float((denorm_rt.b << 3) & 0xF8u);
RT.g = float(((denorm_rt.b >> 2) & 0x38u) | ((denorm_rt.a << 6) & 0xC0u));
RT.b = float((denorm_rt.a << 1) & 0xF8u);
RT.a = float(denorm_rt.a & 0x80u);
#else
RT.r = float((denorm_rt.r << 3) & 0xF8u);
RT.g = float(((denorm_rt.r >> 2) & 0x38u) | ((denorm_rt.g << 6) & 0xC0u));
RT.b = float((denorm_rt.g << 1) & 0xF8u);
RT.a = float(denorm_rt.g & 0x80u);
#endif
#endif
// Let the compiler do its jobs !
#if PS_COLCLIP_HW == 1
vec3 Cd = trunc(RT.rgb * 65535.0f);
#else
vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f);
#endif
vec3 Cs = Color.rgb;
#if PS_BLEND_A == 0
vec3 A = Cs;
#elif PS_BLEND_A == 1
vec3 A = Cd;
#else
vec3 A = vec3(0.0f);
#endif
#if PS_BLEND_B == 0
vec3 B = Cs;
#elif PS_BLEND_B == 1
vec3 B = Cd;
#else
vec3 B = vec3(0.0f);
#endif
#if PS_BLEND_C == 0
float C = As;
#elif PS_BLEND_C == 1
float C = Ad;
#else
float C = Af;
#endif
#if PS_BLEND_D == 0
vec3 D = Cs;
#elif PS_BLEND_D == 1
vec3 D = Cd;
#else
vec3 D = vec3(0.0f);
#endif
// As/Af clamp alpha for Blend mix
// We shouldn't clamp blend mix with blend hw 1 as we want alpha higher
float C_clamped = C;
#if PS_BLEND_MIX > 0 && PS_BLEND_HW != 1 && PS_BLEND_HW != 2
C_clamped = min(C_clamped, 1.0f);
#endif
#if PS_BLEND_A == PS_BLEND_B
Color.rgb = D;
// In blend_mix, HW adds on some alpha factor * dst.
// Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation.
// Instead, apply an offset to convert HW's round to a floor.
// Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision.
// But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399
// Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause.
// 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256).
#elif PS_BLEND_MIX == 2
Color.rgb = ((A - B) * C_clamped + D) + (124.0f/256.0f);
#elif PS_BLEND_MIX == 1
Color.rgb = ((A - B) * C_clamped + D) - (124.0f/256.0f);
#else
Color.rgb = trunc((A - B) * C + D);
#endif
#if PS_BLEND_HW == 1
// As or Af
As_rgba.rgb = vec3(C);
// Subtract 1 for alpha to compensate for the changed equation,
// if c.rgb > 255.0f then we further need to adjust alpha accordingly,
// we pick the lowest overflow from all colors because it's the safest,
// we divide by 255 the color because we don't know Cd value,
// changed alpha should only be done for hw blend.
vec3 alpha_compensate = max(vec3(1.0f), Color.rgb / vec3(255.0f));
As_rgba.rgb -= alpha_compensate;
#elif PS_BLEND_HW == 2
// Since we can't do Cd*(Alpha + 1) - Cs*Alpha in hw blend
// what we can do is adjust the Cs value that will be
// subtracted, this way we can get a better result in hw blend.
// Result is still wrong but less wrong than before.
float division_alpha = 1.0f + C;
Color.rgb /= vec3(division_alpha);
#elif PS_BLEND_HW == 3
// As, Ad or Af clamped.
As_rgba.rgb = vec3(C_clamped);
// Cs*(Alpha + 1) might overflow, if it does then adjust alpha value
// that is sent on second output to compensate.
vec3 overflow_check = (Color.rgb - vec3(255.0f)) / 255.0f;
vec3 alpha_compensate = max(vec3(0.0f), overflow_check);
As_rgba.rgb -= alpha_compensate;
#endif
#else
#if PS_BLEND_C == 2
vec3 Alpha = vec3(Af);
#else
vec3 Alpha = vec3(As);
#endif
// Needed for Cd * (As/Ad/F + 1) blending modes
#if PS_BLEND_HW == 1
Color.rgb = vec3(255.0f);
#elif PS_BLEND_HW == 2
// Cd*As,Cd*Ad or Cd*F
Color.rgb = max(vec3(0.0f), (Alpha - vec3(1.0f)));
Color.rgb *= vec3(255.0f);
#elif PS_BLEND_HW == 3 && PS_RTA_CORRECTION == 0
// Needed for Cs*Ad, Cs*Ad + Cd, Cd - Cs*Ad
// Multiply Color.rgb by (255/128) to compensate for wrong Ad/255 value when rgb are below 128.
// When any color channel is higher than 128 then adjust the compensation automatically
// to give us more accurate colors, otherwise they will be wrong.
// The higher the value (>128) the lower the compensation will be.
float max_color = max(max(Color.r, Color.g), Color.b);
float color_compensate = 255.0f / max(128.0f, max_color);
Color.rgb *= vec3(color_compensate);
#elif PS_BLEND_HW == 4
// Needed for Cd * (1 - Ad) and Cd*(1 + Alpha).
As_rgba.rgb = Alpha * vec3(128.0f / 255.0f);
Color.rgb = vec3(127.5f);
#elif PS_BLEND_HW == 5
// Needed for Cs*Alpha + Cd*(1 - Alpha).
Alpha *= vec3(128.0f / 255.0f);
As_rgba.rgb = (Alpha - vec3(0.5f));
Color.rgb = (Color.rgb * Alpha);
#elif PS_BLEND_HW == 6
// Needed for Cd*Alpha + Cs*(1 - Alpha).
Alpha *= vec3(128.0f / 255.0f);
As_rgba.rgb = Alpha;
Color.rgb *= (Alpha - vec3(0.5f));
#endif
#endif
}
void ps_main()
{
float input_z = gl_FragCoord.z;
// Must floor before depth testing.
// Only valid with clip control (ZERO_TO_ONE depth range); on GLES without clip
// control gl_FragCoord.z is shifted/scaled and the floor gives wrong results.
#if PS_ZFLOOR && HAS_CLIP_CONTROL
input_z = floor(input_z * exp2(32.0f)) * exp2(-32.0f);
#endif
#if PS_ZTST == ZTST_GEQUAL
if (input_z < sample_from_depth())
discard;
#elif PS_ZTST == ZTST_GREATER
if (input_z <= sample_from_depth())
discard;
#endif
#if PS_SCANMSK & 2
// fail depth test on prohibited lines
if ((int(gl_FragCoord.y) & 1) == (PS_SCANMSK & 1))
discard;
#endif
#if PS_DATE >= 5
#if PS_WRITE_RG == 1
// Pseudo 16 bits access.
float rt_a = sample_from_rt().g;
#else
float rt_a = sample_from_rt().a;
#endif
#if (PS_DATE & 3) == 1
// DATM == 0: Pixel with alpha equal to 1 will failed
#if PS_RTA_CORRECTION
bool bad = (254.5f / 255.0f) < rt_a;
#else
bool bad = (127.5f / 255.0f) < rt_a;
#endif
#elif (PS_DATE & 3) == 2
// DATM == 1: Pixel with alpha equal to 0 will failed
#if PS_RTA_CORRECTION
bool bad = rt_a < (254.5f / 255.0f);
#else
bool bad = rt_a < (127.5f / 255.0f);
#endif
#endif
if (bad) {
discard;
}
#endif
#if PS_DATE == 3
int stencil_ceil = int(texelFetch(img_prim_min, ivec2(gl_FragCoord.xy), 0).r);
// Note gl_PrimitiveID == stencil_ceil will be the primitive that will update
// the bad alpha value so we must keep it.
if (gl_PrimitiveID > stencil_ceil) {
discard;
}
#endif
vec4 C = ps_color();
#if PS_AA1
#if PS_AA1 == PS_AA1_LINE
// Blur only outer part of the line by scaling coverage.
float cov = clamp(LineCovScale * (1.0f - abs(PSin.inv_cov)), 0.0f, 1.0f);
#else
float cov = clamp(1.0f - abs(PSin.inv_cov), 0.0f, 1.0f);
#endif
#if PS_ABE
if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128.
C.a = 128.0f * cov;
#else
C.a = 128.0f * cov;
#endif
#elif PS_FIXED_ONE_A
// AA (Fixed one) will output a coverage of 1.0 as alpha
C.a = 128.0f;
#endif
bool atst_pass = atst(C);
#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP
if (gpu_boolean_not(atst_pass))
discard;
#endif
#if SW_AD_TO_HW
#if PS_RTA_CORRECTION
vec4 RT = trunc(sample_from_rt() * 128.0f + 0.1f);
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
vec4 alpha_blend = vec4(RT.a / 128.0f);
#else
vec4 alpha_blend = vec4(C.a / 128.0f);
#endif
// Correct the ALPHA value based on the output format
#if (PS_DST_FMT == FMT_16)
float A_one = 128.0f; // alpha output will be 0x80
C.a = (PS_FBA != 0) ? A_one : step(128.0f, C.a) * A_one;
#elif (PS_DST_FMT == FMT_32) && (PS_FBA != 0)
if(C.a < 128.0f) C.a += 128.0f;
#endif
// Get first primitive that will write a failling alpha value
#if PS_DATE == 1
// DATM == 0
// Pixel with alpha equal to 1 will failed (128-255)
o_col0 = (C.a > 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#elif PS_DATE == 2
// DATM == 1
// Pixel with alpha equal to 0 will failed (0-127)
o_col0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#endif
ps_blend(C, alpha_blend);
#if PS_SHUFFLE
#if !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)
uvec4 denorm_c_after = uvec4(C);
#if (PS_PROCESS_BA & SHUFFLE_READ)
C.b = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u));
C.a = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u));
#else
C.r = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u));
C.g = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u));
#endif
#endif
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
#if PS_SHUFFLE_SAME
uvec4 denorm_c = uvec4(C);
#if (PS_PROCESS_BA & SHUFFLE_READ)
C = vec4(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u)));
#else
C.ga = C.rg;
#endif
// Copy of a 16bit source in to this target
#elif PS_READ16_SRC
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
#elif PS_SHUFFLE_ACROSS
#if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)
C.br = C.rb;
C.ag = C.ga;
#elif(PS_PROCESS_BA & SHUFFLE_READ)
C.rb = C.bb;
C.ga = C.aa;
#else
C.rb = C.rr;
C.ga = C.gg;
#endif // PS_PROCESS_BA
#endif // PS_SHUFFLE_ACROSS
#endif // PS_SHUFFLE
ps_dither(C.rgb, alpha_blend.a);
// Color clamp/wrap needs to be done after sw blending and dithering
ps_color_clamp_wrap(C.rgb);
ps_fbmask(C);
#if (PS_AFAIL == AFAIL_RGB_ONLY_DSB) && !PS_NO_COLOR1
// Use alpha blend factor to determine whether to update A.
alpha_blend.a = float(atst_pass);
#endif
#if !PS_NO_COLOR
#if PS_RTA_CORRECTION
C.a = C.a / 128.0f;
#else
C.a = C.a / 255.0f;
#endif
#if PS_COLCLIP_HW == 1
C.rgb = vec3(C.rgb / 65535.0f);
#else
C.rgb = C.rgb / 255.0f;
#endif
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (gpu_boolean_not(atst_pass))
input_z = sample_from_depth();
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (gpu_boolean_not(atst_pass))
C = sample_from_rt();
#elif (PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
if (gpu_boolean_not(atst_pass))
{
C.a = sample_from_rt().a;
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
input_z = sample_from_depth();
#endif
}
#endif
// Warning: do not write o_col0 until the end since the value might be needed for
// FB fetch in sample_from_rt().
o_col0 = C;
#if !PS_NO_COLOR1
o_col1 = alpha_blend;
#endif
#endif
#if PS_ZCLAMP
input_z = min(input_z, MaxDepthPS);
#endif
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
if (!bool(PSin.interior))
input_z = sample_from_depth(); // No depth update for triangle edges.
#endif
// Writing back depth
#if ZWRITE && (HAS_CLIP_CONTROL || PS_ZCLAMP || SW_DEPTH)
#if SW_DEPTH && PS_NO_COLOR1 && (DEPTH_FEEDBACK_SUPPORT == 2)
// Depth as color write. For depth as color feedback we write to both
// color copy and real depth to avoid having to copy back to real depth.
// Warning: do not write o_col1 until the end since the value might
// be needed for FB fetch in sample_from_depth().
o_col1 = input_z;
#endif
// Standard depth write.
gl_FragDepth = input_z;
#endif
}