mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
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.
1474 lines
38 KiB
GLSL
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
|
|
}
|