mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-11 01:34:17 +02:00
1861 lines
58 KiB
Metal
1861 lines
58 KiB
Metal
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "GSMTLShaderCommon.h"
|
|
|
|
constant uint FMT_32 = 0;
|
|
constant uint FMT_24 = 1;
|
|
constant uint FMT_16 = 2;
|
|
|
|
constant uint SHUFFLE_READ = 1;
|
|
[[maybe_unused]] constant uint SHUFFLE_WRITE = 2;
|
|
constant uint SHUFFLE_READWRITE = 3;
|
|
|
|
constant bool HAS_FBFETCH [[function_constant(GSMTLConstantIndex_FRAMEBUFFER_FETCH)]];
|
|
constant bool DEPTH_FEEDBACK [[function_constant(GSMTLConstantIndex_DEPTH_FEEDBACK)]];
|
|
constant bool ROV_NEEDS_R32 [[function_constant(GSMTLConstantIndex_ROV_NEEDS_R32)]];
|
|
constant bool FST [[function_constant(GSMTLConstantIndex_FST)]];
|
|
constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]];
|
|
constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]];
|
|
constant uint VS_EXPAND_TYPE_RAW [[function_constant(GSMTLConstantIndex_VS_EXPAND_TYPE)]];
|
|
constant uint PS_AEM_FMT [[function_constant(GSMTLConstantIndex_PS_AEM_FMT)]];
|
|
constant uint PS_PAL_FMT [[function_constant(GSMTLConstantIndex_PS_PAL_FMT)]];
|
|
constant uint PS_DST_FMT [[function_constant(GSMTLConstantIndex_PS_DST_FMT)]];
|
|
constant uint PS_DEPTH_FMT [[function_constant(GSMTLConstantIndex_PS_DEPTH_FMT)]];
|
|
constant bool PS_AEM [[function_constant(GSMTLConstantIndex_PS_AEM)]];
|
|
constant bool PS_FBA [[function_constant(GSMTLConstantIndex_PS_FBA)]];
|
|
constant bool PS_FOG [[function_constant(GSMTLConstantIndex_PS_FOG)]];
|
|
constant uint PS_DATE [[function_constant(GSMTLConstantIndex_PS_DATE)]];
|
|
constant uint PS_ATST_RAW [[function_constant(GSMTLConstantIndex_PS_ATST)]];
|
|
constant uint PS_AFAIL_RAW [[function_constant(GSMTLConstantIndex_PS_AFAIL)]];
|
|
constant uint PS_ZTST_RAW [[function_constant(GSMTLConstantIndex_PS_ZTST)]];
|
|
constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TFX)]];
|
|
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
|
|
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
|
|
constant uint PS_WMT [[function_constant(GSMTLConstantIndex_PS_WMT)]];
|
|
constant bool PS_ADJS [[function_constant(GSMTLConstantIndex_PS_ADJS)]];
|
|
constant bool PS_ADJT [[function_constant(GSMTLConstantIndex_PS_ADJT)]];
|
|
constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]];
|
|
constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]];
|
|
constant bool PS_SHUFFLE_SAME [[function_constant(GSMTLConstantIndex_PS_SHUFFLE_SAME)]];
|
|
constant uint PS_PROCESS_BA [[function_constant(GSMTLConstantIndex_PS_PROCESS_BA)]];
|
|
constant uint PS_PROCESS_RG [[function_constant(GSMTLConstantIndex_PS_PROCESS_RG)]];
|
|
constant bool PS_SHUFFLE_ACROSS [[function_constant(GSMTLConstantIndex_PS_SHUFFLE_ACROSS)]];
|
|
constant bool PS_READ16_SRC [[function_constant(GSMTLConstantIndex_PS_READ16_SRC)]];
|
|
constant bool PS_WRITE_RG [[function_constant(GSMTLConstantIndex_PS_WRITE_RG)]];
|
|
constant bool PS_FBMASK [[function_constant(GSMTLConstantIndex_PS_FBMASK)]];
|
|
constant uint PS_BLEND_A [[function_constant(GSMTLConstantIndex_PS_BLEND_A)]];
|
|
constant uint PS_BLEND_B [[function_constant(GSMTLConstantIndex_PS_BLEND_B)]];
|
|
constant uint PS_BLEND_C [[function_constant(GSMTLConstantIndex_PS_BLEND_C)]];
|
|
constant uint PS_BLEND_D [[function_constant(GSMTLConstantIndex_PS_BLEND_D)]];
|
|
constant uint PS_BLEND_HW [[function_constant(GSMTLConstantIndex_PS_BLEND_HW)]];
|
|
constant bool PS_A_MASKED [[function_constant(GSMTLConstantIndex_PS_A_MASKED)]];
|
|
constant bool PS_COLCLIP_HW [[function_constant(GSMTLConstantIndex_PS_COLCLIP_HW)]];
|
|
constant bool PS_RTA_CORRECTION [[function_constant(GSMTLConstantIndex_PS_RTA_CORRECTION)]];
|
|
constant bool PS_RTA_SRC_CORRECTION [[function_constant(GSMTLConstantIndex_PS_RTA_SRC_CORRECTION)]];
|
|
constant bool PS_COLCLIP [[function_constant(GSMTLConstantIndex_PS_COLCLIP)]];
|
|
constant uint PS_BLEND_MIX [[function_constant(GSMTLConstantIndex_PS_BLEND_MIX)]];
|
|
constant bool PS_ROUND_INV [[function_constant(GSMTLConstantIndex_PS_ROUND_INV)]];
|
|
constant bool PS_FIXED_ONE_A [[function_constant(GSMTLConstantIndex_PS_FIXED_ONE_A)]];
|
|
constant bool PS_PABE [[function_constant(GSMTLConstantIndex_PS_PABE)]];
|
|
constant bool PS_NO_COLOR [[function_constant(GSMTLConstantIndex_PS_NO_COLOR)]];
|
|
constant bool PS_NO_COLOR1 [[function_constant(GSMTLConstantIndex_PS_NO_COLOR1)]];
|
|
constant uint PS_CHANNEL [[function_constant(GSMTLConstantIndex_PS_CHANNEL)]];
|
|
constant uint PS_DITHER [[function_constant(GSMTLConstantIndex_PS_DITHER)]];
|
|
constant uint PS_DITHER_ADJUST [[function_constant(GSMTLConstantIndex_PS_DITHER_ADJUST)]];
|
|
constant bool PS_ZCLAMP [[function_constant(GSMTLConstantIndex_PS_ZCLAMP)]];
|
|
constant bool PS_ZFLOOR [[function_constant(GSMTLConstantIndex_PS_ZFLOOR)]];
|
|
constant bool PS_TCOFFSETHACK [[function_constant(GSMTLConstantIndex_PS_TCOFFSETHACK)]];
|
|
constant bool PS_URBAN_CHAOS_HLE [[function_constant(GSMTLConstantIndex_PS_URBAN_CHAOS_HLE)]];
|
|
constant bool PS_TALES_OF_ABYSS_HLE [[function_constant(GSMTLConstantIndex_PS_TALES_OF_ABYSS_HLE)]];
|
|
constant bool PS_TEX_IS_FB [[function_constant(GSMTLConstantIndex_PS_TEX_IS_FB)]];
|
|
constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AUTOMATIC_LOD)]];
|
|
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
|
|
constant bool PS_REGION_RECT [[function_constant(GSMTLConstantIndex_PS_REGION_RECT)]];
|
|
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
|
|
constant uint PS_AA1_RAW [[function_constant(GSMTLConstantIndex_PS_AA1)]];
|
|
constant bool PS_ABE [[function_constant(GSMTLConstantIndex_PS_ABE)]];
|
|
constant uint PS_SW_ANISO [[function_constant(GSMTLConstantIndex_PS_SW_ANISO)]];
|
|
constant bool PS_ROV_COLOR [[function_constant(GSMTLConstantIndex_PS_ROV_COLOR)]];
|
|
constant uint PS_ROV_DEPTH_RAW [[function_constant(GSMTLConstantIndex_PS_ROV_DEPTH)]];
|
|
|
|
using GSShader::VSExpand;
|
|
using AFAIL = GSShader::PS_AFAIL;
|
|
using ATST = GSShader::PS_ATST;
|
|
using GSShader::ZTST;
|
|
using AA1 = GSShader::PS_AA1;
|
|
using ROV_DEPTH = GSShader::PS_ROV_DEPTH;
|
|
constant VSExpand VS_EXPAND_TYPE = static_cast<VSExpand>(VS_EXPAND_TYPE_RAW);
|
|
constant AFAIL PS_AFAIL = static_cast<AFAIL>(PS_AFAIL_RAW);
|
|
constant ATST PS_ATST = static_cast<ATST>(PS_ATST_RAW);
|
|
constant ZTST PS_ZTST = static_cast<ZTST>(PS_ZTST_RAW);
|
|
constant AA1 PS_AA1 = static_cast<AA1>(PS_AA1_RAW);
|
|
constant ROV_DEPTH PS_ROV_DEPTH = static_cast<ROV_DEPTH>(PS_ROV_DEPTH_RAW);
|
|
|
|
#if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220
|
|
#define PRIMID_SUPPORT 1
|
|
#else
|
|
#define PRIMID_SUPPORT 0
|
|
#endif
|
|
|
|
#if defined(__METAL_IOS__) || __METAL_VERSION__ >= 230
|
|
#define FBFETCH_SUPPORT 1
|
|
#else
|
|
#define FBFETCH_SUPPORT 0
|
|
#endif
|
|
|
|
constant bool PS_PRIM_CHECKING_INIT = PS_DATE == 1 || PS_DATE == 2;
|
|
constant bool PS_PRIM_CHECKING_READ = PS_DATE == 3;
|
|
#if PRIMID_SUPPORT
|
|
constant bool NEEDS_PRIMID = PS_PRIM_CHECKING_INIT || PS_PRIM_CHECKING_READ;
|
|
#endif
|
|
constant bool PS_TEX_IS_DEPTH = PS_URBAN_CHAOS_HLE || PS_TALES_OF_ABYSS_HLE || PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2;
|
|
constant bool PS_TEX_IS_COLOR = !PS_TEX_IS_DEPTH;
|
|
constant bool PS_HAS_PALETTE = PS_PAL_FMT != 0 || (PS_CHANNEL >= 1 && PS_CHANNEL <= 5);
|
|
constant bool NOT_IIP = !IIP;
|
|
constant bool SW_BLEND = (PS_BLEND_A != PS_BLEND_B) || PS_BLEND_D;
|
|
constant bool SW_AD_TO_HW = (PS_BLEND_C == 1 && PS_A_MASKED);
|
|
constant bool NEEDS_RT_FOR_BLEND = (((PS_BLEND_A != PS_BLEND_B) && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1)) || PS_BLEND_D == 1 || SW_AD_TO_HW);
|
|
constant bool NEEDS_RT_EARLY = PS_TEX_IS_FB || PS_DATE >= 5;
|
|
constant bool NEEDS_RT_FOR_AFAIL = PS_AFAIL == AFAIL::ZB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY_SW_Z;
|
|
constant bool NEEDS_RT = NEEDS_RT_FOR_AFAIL || NEEDS_RT_EARLY || (!PS_PRIM_CHECKING_INIT && (PS_FBMASK || NEEDS_RT_FOR_BLEND));
|
|
constant bool NEEDS_DEPTH_FOR_AFAIL = PS_AFAIL == AFAIL::FB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY_SW_Z;
|
|
constant bool NEEDS_DEPTH_FOR_ZTST = PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER;
|
|
constant bool NEEDS_DEPTH_FOR_AA1 = PS_AA1 == AA1::TRIANGLE_SW_Z;
|
|
constant bool SW_DEPTH = NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1;
|
|
|
|
constant bool PS_OUTPUT_COLOR0 = !PS_NO_COLOR && !PS_ROV_COLOR;
|
|
constant bool PS_OUTPUT_COLOR1 = !PS_NO_COLOR1 && !PS_ROV_COLOR;
|
|
constant bool PS_ZOUTPUT = (PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
|
constant bool PS_ZOUTPUT_LESS = PS_ZOUTPUT && !SW_DEPTH;
|
|
constant bool PS_ZOUTPUT_ANY = PS_ZOUTPUT && SW_DEPTH;
|
|
constant bool PS_ZOUTPUT_COLOR = PS_ZOUTPUT_ANY && !DEPTH_FEEDBACK;
|
|
constant bool VS_NEEDS_INDEX_BUFFER = VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
|
constant bool VS_COVERAGE = VS_EXPAND_TYPE == VSExpand::LineAA1 || VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
|
constant bool VS_INTERIOR = VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
|
constant bool PS_COVERAGE = PS_AA1 != AA1::NONE;
|
|
constant bool PS_INTERIOR = PS_AA1 == AA1::TRIANGLE_SW_Z;
|
|
|
|
struct MainVSIn
|
|
{
|
|
float2 st [[attribute(GSMTLAttributeIndexST)]];
|
|
float4 c [[attribute(GSMTLAttributeIndexC)]];
|
|
float q [[attribute(GSMTLAttributeIndexQ)]];
|
|
uint2 p [[attribute(GSMTLAttributeIndexXY)]];
|
|
uint z [[attribute(GSMTLAttributeIndexZ)]];
|
|
uint2 uv [[attribute(GSMTLAttributeIndexUV)]];
|
|
float4 f [[attribute(GSMTLAttributeIndexF)]];
|
|
};
|
|
|
|
struct MainVSOut
|
|
{
|
|
float4 p [[position]];
|
|
float4 t;
|
|
float4 ti;
|
|
float4 c [[function_constant(IIP)]];
|
|
float4 fc [[flat, function_constant(NOT_IIP)]];
|
|
float inv_cov [[function_constant(VS_COVERAGE)]];
|
|
uint interior [[function_constant(VS_INTERIOR)]];
|
|
float point_size [[point_size, function_constant(VS_POINT_SIZE)]];
|
|
};
|
|
|
|
struct MainPSIn
|
|
{
|
|
float4 p [[position]];
|
|
float4 t;
|
|
float4 ti;
|
|
float4 c [[function_constant(IIP)]];
|
|
float4 fc [[flat, function_constant(NOT_IIP)]];
|
|
float inv_cov [[function_constant(PS_COVERAGE)]];
|
|
uint interior [[function_constant(PS_INTERIOR)]];
|
|
};
|
|
|
|
struct MainResult
|
|
{
|
|
float4 c0;
|
|
float4 c1;
|
|
float depth;
|
|
};
|
|
|
|
struct MainPSOut
|
|
{
|
|
float4 c0 [[color(0), index(0), function_constant(PS_OUTPUT_COLOR0)]];
|
|
float4 c1 [[color(0), index(1), function_constant(PS_OUTPUT_COLOR1)]];
|
|
float depthColor [[color(1), function_constant(PS_ZOUTPUT_COLOR)]];
|
|
float depthLess [[depth(less), function_constant(PS_ZOUTPUT_LESS)]];
|
|
float depthAny [[depth(any), function_constant(PS_ZOUTPUT_ANY)]];
|
|
MainPSOut(MainResult res)
|
|
{
|
|
if (PS_OUTPUT_COLOR0)
|
|
c0 = res.c0;
|
|
if (PS_OUTPUT_COLOR1)
|
|
c1 = res.c1;
|
|
if (PS_ZOUTPUT_LESS)
|
|
depthLess = res.depth;
|
|
if (PS_ZOUTPUT_ANY)
|
|
depthAny = res.depth;
|
|
if (PS_ZOUTPUT_COLOR)
|
|
depthColor = res.depth;
|
|
}
|
|
};
|
|
|
|
// MARK: - Vertex functions
|
|
|
|
static void texture_coord(thread const MainVSIn& v, thread MainVSOut& out, constant GSMTLMainVSUniform& cb)
|
|
{
|
|
float2 uv = float2(v.uv) - cb.texture_offset;
|
|
float2 st = v.st - cb.texture_offset;
|
|
|
|
// Float coordinate
|
|
out.t.xy = st;
|
|
out.t.w = v.q;
|
|
|
|
// Integer coordinate => normalized
|
|
out.ti.xy = uv * cb.texture_scale;
|
|
|
|
if (FST)
|
|
{
|
|
// Integer coordinate => integral
|
|
out.ti.zw = uv;
|
|
}
|
|
else
|
|
{
|
|
// Some games uses float coordinate for post-processing effects
|
|
out.ti.zw = st / cb.texture_scale;
|
|
}
|
|
}
|
|
|
|
static MainVSOut vs_main_run(thread const MainVSIn& v, constant GSMTLMainVSUniform& cb)
|
|
{
|
|
constexpr float exp_min32 = 0x1p-32;
|
|
MainVSOut out;
|
|
// Clamp to max depth, gs doesn't wrap
|
|
uint z = min(v.z, cb.max_depth);
|
|
out.p.xy = float2(v.p) - float2(0.05, 0.05);
|
|
out.p.xy = out.p.xy * float2(cb.vertex_scale.x, -cb.vertex_scale.y) - float2(cb.vertex_offset.x, -cb.vertex_offset.y);
|
|
out.p.w = 1;
|
|
out.p.z = float(z) * exp_min32;
|
|
|
|
texture_coord(v, out, cb);
|
|
|
|
if (IIP)
|
|
out.c = v.c;
|
|
else
|
|
out.fc = v.c;
|
|
|
|
out.t.z = v.f.x; // pack fog with texture
|
|
|
|
if (VS_POINT_SIZE)
|
|
out.point_size = cb.point_size.x;
|
|
|
|
return out;
|
|
}
|
|
|
|
vertex MainVSOut vs_main(MainVSIn v [[stage_in]], constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
|
{
|
|
return vs_main_run(v, cb);
|
|
}
|
|
|
|
static MainVSIn load_vertex(GSMTLMainVertex base)
|
|
{
|
|
MainVSIn out;
|
|
out.st = base.st;
|
|
out.c = float4(base.rgba);
|
|
out.q = base.q;
|
|
out.p = uint2(base.xy);
|
|
out.z = base.z;
|
|
out.uv = uint2(base.uv);
|
|
out.f = float4(static_cast<float>(base.fog) / 255.f);
|
|
return out;
|
|
}
|
|
|
|
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
|
|
static float2 get_xy_unscaled(float2 xy, constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
|
{
|
|
return round(xy / cb.vertex_scale) / 16.0f;
|
|
}
|
|
|
|
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
|
|
static float2x2 get_xy_deltas_unscaled(thread const MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
|
|
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
|
{
|
|
float2 xy0 = get_xy_unscaled(v0.p.xy, cb);
|
|
float2 xy1 = get_xy_unscaled(v1.p.xy, cb);
|
|
float2 xy2 = get_xy_unscaled(v2.p.xy, cb);
|
|
return float2x2(xy1 - xy0, xy2 - xy0);
|
|
}
|
|
|
|
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
|
|
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
|
|
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
|
|
// expand on on the side towards the outside of the triangle.
|
|
float2 get_aa1_triangle_expand_dir(thread const MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
|
|
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
|
{
|
|
float2x2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2, cb);
|
|
float2 line_delta = xy_deltas[0];
|
|
float2 line_opposite = xy_deltas[1];
|
|
|
|
float2 line_normal = float2(line_delta.y, -line_delta.x);
|
|
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 1.0f) : float2(1.0f, 0.0f);
|
|
|
|
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
|
|
{
|
|
// Expand direction point towards the interior so flip it.
|
|
line_expand = -line_expand;
|
|
}
|
|
|
|
return line_expand;
|
|
}
|
|
|
|
float2x2 get_inverse(const thread float2x2& mat, float det)
|
|
{
|
|
return float2x2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1 / det);
|
|
}
|
|
|
|
// Extrapolate triangle attributes from the first vertex along the given direction.
|
|
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
|
|
void extrapolate_aa1_triangle_edge(thread MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
|
|
thread const float2x2& dp_mat, float2 dp, constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
|
{
|
|
// Get texture deltas
|
|
float2x2 dt;
|
|
if (FST)
|
|
{
|
|
dt = float2x2(v1.ti.zw - v0.ti.zw, v2.ti.zw - v0.ti.zw);
|
|
}
|
|
else
|
|
{
|
|
dt = float2x2(v1.t.xy - v0.t.xy, v2.t.xy - v0.t.xy);
|
|
}
|
|
|
|
// Get color delta if interpolating
|
|
float2x4 dc;
|
|
if (IIP)
|
|
{
|
|
dc = float2x4(v1.c - v0.c, v2.c - v0.c);
|
|
}
|
|
|
|
float2 dz = float2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
|
|
|
|
float2 df = float2(v1.t.z - v0.t.z, v2.t.z - v0.t.z); // Fog deltas
|
|
|
|
float2 dq = float2(v1.t.w - v0.t.w, v2.t.w - v0.t.w); // Q deltas
|
|
|
|
// To prevent unstable extrapolation, do not extrapolate if the
|
|
// minimum perpendicular length of the triangle is < 2 pixels.
|
|
float dp_det = determinant(dp_mat); // Twice signed triangle area.
|
|
float len0 = length(dp_mat[0]);
|
|
float len1 = length(dp_mat[1]);
|
|
float len2 = length(dp_mat[1] - dp_mat[0]);
|
|
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
|
|
|
|
// Get the position -> barycentric weight matrix
|
|
float2x2 inv_dp_mat = get_inverse(dp_mat, dp_det);
|
|
|
|
float2 weights = min_perp_length < 2 ? 0 : inv_dp_mat * dp;
|
|
|
|
v0.p.xy += dp * cb.point_size; // Extrapolate position
|
|
|
|
// Extrapolate texture coords
|
|
if (FST)
|
|
{
|
|
v0.ti.zw += dt * weights;
|
|
v0.ti.xy = v0.ti.zw * cb.texture_scale;
|
|
}
|
|
else
|
|
{
|
|
v0.t.xy += dt * weights;
|
|
v0.ti.zw = v0.t.xy / cb.texture_scale;
|
|
v0.t.w += dot(dq, weights);
|
|
}
|
|
|
|
// Extrapolate and clamp color
|
|
if (IIP)
|
|
{
|
|
v0.c += dc * weights;
|
|
v0.c = clamp(v0.c, 0, 255);
|
|
}
|
|
|
|
v0.p.z += dot(dz, weights); // Extrapolate depth
|
|
|
|
v0.t.z += dot(df, weights); // Extrapolate fog
|
|
}
|
|
|
|
vertex MainVSOut vs_main_expand(
|
|
uint vid [[vertex_id]],
|
|
device const GSMTLMainVertex* vertices [[buffer(GSMTLBufferIndexHWVertices)]],
|
|
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
|
|
device const ushort* indices [[buffer(GSMTLBufferIndexHWIndices), function_constant(VS_NEEDS_INDEX_BUFFER)]])
|
|
{
|
|
switch (VS_EXPAND_TYPE)
|
|
{
|
|
case VSExpand::None:
|
|
return vs_main_run(load_vertex(vertices[vid]), cb);
|
|
case VSExpand::Point:
|
|
{
|
|
MainVSOut point = vs_main_run(load_vertex(vertices[vid >> 2]), cb);
|
|
if (vid & 1)
|
|
point.p.x += cb.point_size.x;
|
|
if (vid & 2)
|
|
point.p.y += cb.point_size.y;
|
|
return point;
|
|
}
|
|
case VSExpand::Line:
|
|
case VSExpand::LineAA1:
|
|
{
|
|
uint vid_base = vid >> 2;
|
|
bool is_bottom = vid & 2;
|
|
bool is_right = vid & 1;
|
|
uint vid_other = is_bottom ? vid_base - 1 : vid_base + 1;
|
|
MainVSOut point = vs_main_run(load_vertex(vertices[vid_base]), cb);
|
|
MainVSOut other = vs_main_run(load_vertex(vertices[vid_other]), cb);
|
|
|
|
// Use bottom minus top for delta regardless of which vertex we are expanding.
|
|
float2 line_delta = is_bottom ? point.p.xy - other.p.xy : other.p.xy - point.p.xy;
|
|
float2 line_vector = normalize(line_delta / cb.vertex_scale);
|
|
float2 line_expand = float2(line_vector.y, -line_vector.x);
|
|
|
|
if (VS_EXPAND_TYPE == VSExpand::LineAA1)
|
|
line_expand *= 2.f * cb.line_aa1_width;
|
|
|
|
float2 line_width = (line_expand * cb.point_size) / 2;
|
|
float2 offset = is_right ? line_width : -line_width;
|
|
point.p.xy += offset;
|
|
|
|
if (VS_EXPAND_TYPE == VSExpand::LineAA1)
|
|
point.inv_cov = is_right ? 1.f : -1.f;
|
|
|
|
// Lines will be run as (0 1 2) (1 2 3)
|
|
// This means that both triangles will have a point based off the top line point as their first point
|
|
// So we don't have to do anything for !IIP
|
|
|
|
return point;
|
|
}
|
|
case VSExpand::Sprite:
|
|
{
|
|
uint vid_base = vid >> 1;
|
|
bool is_bottom = vid & 2;
|
|
bool is_right = vid & 1;
|
|
// Sprite points are always in pairs
|
|
uint vid_lt = vid_base & ~1;
|
|
uint vid_rb = vid_base | 1;
|
|
|
|
MainVSOut lt = vs_main_run(load_vertex(vertices[vid_lt]), cb);
|
|
MainVSOut rb = vs_main_run(load_vertex(vertices[vid_rb]), cb);
|
|
MainVSOut out = rb;
|
|
|
|
if (!is_right)
|
|
{
|
|
out.p.x = lt.p.x;
|
|
out.t.x = lt.t.x;
|
|
out.ti.xz = lt.ti.xz;
|
|
}
|
|
|
|
if (!is_bottom)
|
|
{
|
|
out.p.y = lt.p.y;
|
|
out.t.y = lt.t.y;
|
|
out.ti.yw = lt.ti.yw;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
case VSExpand::TriangleAA1:
|
|
{
|
|
// Triangles with AA1 are expanded as follows:
|
|
// - Vertices 0-2: Interior of triangle (1 triangle).
|
|
// - Vertices 3-8: First edge expanded (2 triangles).
|
|
// - Vertices 9-14: Second edge expanded (2 triangles).
|
|
// - Vertices 15-20: Third edge expanded (2 triangles).
|
|
// - Vertices 21-26: First corner cap (2 triangles).
|
|
// - Vertices 27-32: Second corner cap (2 triangles).
|
|
// - Vertices 33-38: Third corner cap (2 triangles).
|
|
|
|
uint prim_id = vid / 39;
|
|
uint prim_offset = vid - 39 * prim_id; // range: 0-38
|
|
bool interior = prim_offset < 3;
|
|
bool edge = 3 <= prim_offset && prim_offset < 21;
|
|
|
|
MainVSOut out;
|
|
if (interior)
|
|
{
|
|
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + prim_offset]]), cb);
|
|
out.inv_cov = 0.f;
|
|
out.interior = 1;
|
|
}
|
|
else if (edge)
|
|
{
|
|
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
|
|
uint prim_offset_edges = prim_offset - 3; // range: 0-17
|
|
uint i0 = prim_offset_edges / 6;
|
|
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
|
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
|
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
|
|
|
|
// Note: order of top/bottom, inside/outside is arbitrary,
|
|
// as long as it assembles into two triangles forming a quad.
|
|
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
|
|
bool is_outside = edge_offset & 1;
|
|
|
|
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_bottom ? i1 : i0)]]), cb);
|
|
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_bottom ? i0 : i1)]]), cb);
|
|
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i2]]), cb);
|
|
|
|
float2x2 pos_deltas = get_xy_deltas_unscaled(out, other, opposite, cb);
|
|
|
|
float2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(out, other, opposite, cb) : 0;
|
|
|
|
// Do actual extrapolation, or no-op if expand_dir == 0.
|
|
extrapolate_aa1_triangle_edge(out, other, opposite, pos_deltas, expand_dir, cb);
|
|
|
|
out.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
|
|
|
|
out.interior = 0;
|
|
}
|
|
else // Corner cap
|
|
{
|
|
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
|
|
uint prim_offset_cap = prim_offset - 21; // range: 0-8
|
|
uint i0 = prim_offset_cap / 6;
|
|
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
|
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
|
uint cap_offset = prim_offset_cap - 6 * i0; // range: 0-5
|
|
|
|
bool is_near_corner = cap_offset == 0 || cap_offset == 3;
|
|
bool is_far_corner = cap_offset == 2 || cap_offset == 5;
|
|
bool is_first_tri = cap_offset < 3;
|
|
|
|
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i0]]), cb);
|
|
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_first_tri ? i1 : i2)]]), cb);
|
|
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_first_tri ? i2 : i1)]]), cb);
|
|
|
|
float2x2 pos_deltas = get_xy_deltas_unscaled(out, other, opposite, cb);
|
|
|
|
// Get the edge expansion directions of both incident edges.
|
|
float2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(out, other, opposite, cb);
|
|
float2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(out, opposite, other, cb);
|
|
|
|
// Check if the corner is already filled by the expanded edges.
|
|
// This happens if the expand directions are the same.
|
|
// If so we output a degenerate triangle at this corner.
|
|
bool corner_filled = all(edge_expand_dir_0 == edge_expand_dir_1);
|
|
|
|
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
|
|
float2 far_corner_dir = corner_filled ? 0 : -normalize((pos_deltas[0] + pos_deltas[1]) / 2);
|
|
|
|
// Determine the expand direction.
|
|
float2 expand_dir = is_near_corner ? 0 : // No extrapolation
|
|
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
|
|
edge_expand_dir_0; // Standard AA1 edge expansion
|
|
|
|
// Do the actual extrapolation (no-op if expand_dir == 0).
|
|
extrapolate_aa1_triangle_edge(out, other, opposite, pos_deltas, expand_dir, cb);
|
|
|
|
out.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
|
|
|
|
out.interior = 0;
|
|
|
|
if (NOT_IIP)
|
|
{
|
|
// Get the provoking vertex color (first vertex in Metal)
|
|
out.fc = i0 == 0 ? out.fc : (i1 == 0 ? other.fc : opposite.fc);
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Fragment functions
|
|
|
|
struct PSMain
|
|
{
|
|
texture2d<float> tex;
|
|
depth2d<float> tex_depth;
|
|
texture2d<float> palette;
|
|
texture2d<float> prim_id_tex;
|
|
sampler tex_sampler;
|
|
float4 current_color;
|
|
float current_depth;
|
|
uint prim_id;
|
|
bool color_discarded = false;
|
|
bool depth_discarded = false;
|
|
const thread MainPSIn& in;
|
|
constant GSMTLMainPSUniform& cb;
|
|
|
|
PSMain(const thread MainPSIn& in, constant GSMTLMainPSUniform& cb): in(in), cb(cb) {}
|
|
|
|
void discard()
|
|
{
|
|
if (PS_ROV_COLOR || PS_ROV_DEPTH != ROV_DEPTH::NONE)
|
|
color_discarded = depth_discarded = true;
|
|
else
|
|
discard_fragment();
|
|
}
|
|
|
|
void discard_color(thread float4& output)
|
|
{
|
|
if (PS_ROV_COLOR)
|
|
color_discarded = true;
|
|
else
|
|
output = current_color;
|
|
}
|
|
|
|
void discard_depth(thread float& output)
|
|
{
|
|
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE)
|
|
depth_discarded = true;
|
|
else
|
|
output = current_depth;
|
|
}
|
|
|
|
template <typename... Args>
|
|
float4 sample_tex(Args... args)
|
|
{
|
|
if (PS_TEX_IS_DEPTH)
|
|
return float4(tex_depth.sample(args...));
|
|
else
|
|
return tex.sample(args...);
|
|
}
|
|
|
|
float4 read_tex(uint2 pos, uint lod = 0)
|
|
{
|
|
if (PS_TEX_IS_DEPTH)
|
|
return float4(tex_depth.read(pos, lod));
|
|
else
|
|
return tex.read(pos, lod);
|
|
}
|
|
|
|
uint2 get_tex_dims()
|
|
{
|
|
if (PS_TEX_IS_DEPTH)
|
|
return uint2(tex_depth.get_width(), tex_depth.get_height());
|
|
else
|
|
return uint2(tex.get_width(), tex.get_height());
|
|
}
|
|
|
|
float manual_lod(float uv_w)
|
|
{
|
|
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
|
float K = cb.lod_params.x;
|
|
float L = cb.lod_params.y;
|
|
float bias = cb.lod_params.z;
|
|
float max_lod = cb.lod_params.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;
|
|
}
|
|
|
|
float4 sample_c_af(float2 uv, float uv_w)
|
|
{
|
|
// HW sampler will reject bad UVs, match that here.
|
|
uv = any(isnan(uv) | isinf(uv)) ? float2(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/
|
|
float2 sz = float2(get_tex_dims());
|
|
float2 dX = dfdx(uv) * sz;
|
|
float2 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) | isinf(dY) | isnan(dX) | 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 * (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 = rsqrt(denom_plus);
|
|
float inv_sqrt_denom_minus = rsqrt(denom_minus);
|
|
|
|
float2 new_dX = float2(
|
|
sqrtA * inv_sqrt_denom_plus,
|
|
sqrtB * inv_sqrt_denom_plus * signB
|
|
);
|
|
|
|
float2 new_dY = float2(
|
|
sqrtB * inv_sqrt_denom_minus * -signB,
|
|
sqrtA * inv_sqrt_denom_minus
|
|
);
|
|
|
|
d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | 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;
|
|
float2 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 = float2(0.0f, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
float2 aniso_line_dir = is_major_x ? dX : dY;
|
|
|
|
aniso_ratio = min(length_major / length_minor, float(PS_SW_ANISO));
|
|
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);
|
|
}
|
|
|
|
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0.0f;
|
|
|
|
float4 colour;
|
|
if (aniso_ratio == 1.0f)
|
|
{
|
|
colour = sample_tex(tex_sampler, uv, level(lod));
|
|
}
|
|
else
|
|
{
|
|
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
float2 segment = (2.0f * aniso_line) / aniso_ratio;
|
|
for (int i = 0; i < aniso_ratio; i++)
|
|
{
|
|
float2 d = -aniso_line + (0.5f + i) * segment;
|
|
float2 uv_sample = uv + d;
|
|
float4 sample_colour = sample_tex(tex_sampler, uv_sample, level(lod));
|
|
num += sample_colour;
|
|
}
|
|
|
|
colour = num / aniso_ratio;
|
|
}
|
|
return colour;
|
|
}
|
|
|
|
float4 sample_c(float2 uv)
|
|
{
|
|
if (PS_TEX_IS_FB)
|
|
return current_color;
|
|
if (PS_REGION_RECT)
|
|
return read_tex(uint2(uv));
|
|
|
|
if (!PS_ADJS && !PS_ADJT)
|
|
{
|
|
uv *= cb.st_scale;
|
|
}
|
|
else
|
|
{
|
|
if (PS_ADJS)
|
|
uv.x = (uv.x - cb.st_range.x) * cb.st_range.z;
|
|
else
|
|
uv.x = uv.x * cb.st_scale.x;
|
|
if (PS_ADJT)
|
|
uv.y = (uv.y - cb.st_range.y) * cb.st_range.w;
|
|
else
|
|
uv.y = uv.y * cb.st_scale.y;
|
|
}
|
|
|
|
if (PS_SW_ANISO > 1)
|
|
return sample_c_af(uv, in.t.w);
|
|
else if (PS_AUTOMATIC_LOD)
|
|
return sample_tex(tex_sampler, uv);
|
|
else if (PS_MANUAL_LOD)
|
|
return sample_tex(tex_sampler, uv, level(manual_lod(in.t.w)));
|
|
else
|
|
return sample_tex(tex_sampler, uv, level(0));
|
|
}
|
|
|
|
float4 sample_p(uint idx)
|
|
{
|
|
return palette.read(uint2(idx, 0));
|
|
}
|
|
|
|
float4 sample_p_norm(float u)
|
|
{
|
|
return sample_p(uint(u * 255.5f));
|
|
}
|
|
|
|
float4 clamp_wrap_uv(float4 uv)
|
|
{
|
|
float4 tex_size = cb.wh.xyxy;
|
|
|
|
if (PS_WMS == PS_WMT)
|
|
{
|
|
if (PS_REGION_RECT && PS_WMS == 0)
|
|
{
|
|
uv = fract(uv);
|
|
}
|
|
else if (PS_REGION_RECT && PS_WMS == 1)
|
|
{
|
|
uv = saturate(uv);
|
|
}
|
|
else if (PS_WMS == 2)
|
|
{
|
|
uv = clamp(uv, cb.uv_min_max.xyxy, cb.uv_min_max.zwzw);
|
|
}
|
|
else if (PS_WMS == 3)
|
|
{
|
|
// wrap negative uv coords to avoid an off by one error that shifted
|
|
// textures. Fixes Xenosaga's hair issue.
|
|
if (!FST)
|
|
uv = fract(uv);
|
|
|
|
uv = float4((ushort4(uv * tex_size) & ushort4(cb.uv_msk_fix.xyxy)) | ushort4(cb.uv_msk_fix.zwzw)) / tex_size;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PS_REGION_RECT && PS_WMS == 0)
|
|
{
|
|
uv.xz = fract(uv.xz);
|
|
}
|
|
else if (PS_REGION_RECT && PS_WMS == 1)
|
|
{
|
|
uv.xz = saturate(uv.xz);
|
|
}
|
|
else if (PS_WMS == 2)
|
|
{
|
|
uv.xz = clamp(uv.xz, cb.uv_min_max.xx, cb.uv_min_max.zz);
|
|
}
|
|
else if (PS_WMS == 3)
|
|
{
|
|
if (!FST)
|
|
uv.xz = fract(uv.xz);
|
|
|
|
uv.xz = float2((ushort2(uv.xz * tex_size.xx) & ushort2(cb.uv_msk_fix.xx)) | ushort2(cb.uv_msk_fix.zz)) / tex_size.xx;
|
|
}
|
|
|
|
if (PS_REGION_RECT && PS_WMT == 0)
|
|
{
|
|
uv.yw = fract(uv.yw);
|
|
}
|
|
else if (PS_REGION_RECT && PS_WMT == 1)
|
|
{
|
|
uv.yw = saturate(uv.yw);
|
|
}
|
|
else if (PS_WMT == 2)
|
|
{
|
|
uv.yw = clamp(uv.yw, cb.uv_min_max.yy, cb.uv_min_max.ww);
|
|
}
|
|
else if (PS_WMT == 3)
|
|
{
|
|
if (!FST)
|
|
uv.yw = fract(uv.yw);
|
|
|
|
uv.yw = float2((ushort2(uv.yw * tex_size.yy) & ushort2(cb.uv_msk_fix.yy)) | ushort2(cb.uv_msk_fix.ww)) / tex_size.yy;
|
|
}
|
|
}
|
|
|
|
if (PS_REGION_RECT)
|
|
{
|
|
// Normalized -> Integer Coordinates.
|
|
uv = clamp(uv * cb.wh.zwzw + cb.st_range.xyxy, cb.st_range.xyxy, cb.st_range.zwzw);
|
|
}
|
|
|
|
return uv;
|
|
}
|
|
|
|
float4x4 sample_4c(float4 uv)
|
|
{
|
|
return {
|
|
sample_c(uv.xy),
|
|
sample_c(uv.zy),
|
|
sample_c(uv.xw),
|
|
sample_c(uv.zw),
|
|
};
|
|
}
|
|
|
|
uint4 sample_4_index(float4 uv)
|
|
{
|
|
float4 c;
|
|
|
|
// Either GS will send a texture that contains a single 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;
|
|
|
|
uint4 i;
|
|
|
|
if (PS_RTA_SRC_CORRECTION)
|
|
{
|
|
i = uint4(round(c * 128.25f)); // Denormalize value
|
|
}
|
|
else
|
|
{
|
|
i = uint4(c * 255.5f); // Denormalize value
|
|
}
|
|
|
|
if (PS_PAL_FMT == 1)
|
|
return i & 0xF;
|
|
if (PS_PAL_FMT == 2)
|
|
return i >> 4;
|
|
|
|
return i;
|
|
}
|
|
|
|
float4x4 sample_4p(uint4 u)
|
|
{
|
|
return {
|
|
sample_p(u.x),
|
|
sample_p(u.y),
|
|
sample_p(u.z),
|
|
sample_p(u.w),
|
|
};
|
|
}
|
|
|
|
uint fetch_raw_depth()
|
|
{
|
|
return tex_depth.read(ushort2(in.p.xy + cb.channel_shuffle_offset)) * 0x1p32f;
|
|
}
|
|
|
|
float4 fetch_raw_color()
|
|
{
|
|
if (PS_TEX_IS_FB)
|
|
return current_color;
|
|
else
|
|
return tex.read(ushort2(in.p.xy + cb.channel_shuffle_offset));
|
|
}
|
|
|
|
float4 fetch_c(ushort2 uv)
|
|
{
|
|
if (PS_TEX_IS_FB)
|
|
return current_color;
|
|
else if (PS_TEX_IS_DEPTH)
|
|
return tex_depth.read(uv);
|
|
else
|
|
return tex.read(uv);
|
|
}
|
|
|
|
// MARK: Depth sampling
|
|
|
|
ushort2 clamp_wrap_uv_depth(ushort2 uv)
|
|
{
|
|
ushort2 uv_out = uv;
|
|
// Keep the full precision
|
|
// It allow to multiply the ScalingFactor before the 1/16 coeff
|
|
ushort4 mask = ushort4(cb.uv_msk_fix) << 4;
|
|
|
|
if (PS_WMS == PS_WMT)
|
|
{
|
|
if (PS_WMS == 2)
|
|
uv_out = clamp(uv, mask.xy, mask.zw);
|
|
else if (PS_WMS == 3)
|
|
uv_out = (uv & mask.xy) | mask.zw;
|
|
}
|
|
else
|
|
{
|
|
if (PS_WMS == 2)
|
|
uv_out.x = clamp(uv.x, mask.x, mask.z);
|
|
else if (PS_WMS == 3)
|
|
uv_out.x = (uv.x & mask.x) | mask.z;
|
|
|
|
if (PS_WMT == 2)
|
|
uv_out.y = clamp(uv.y, mask.y, mask.w);
|
|
else if (PS_WMT == 3)
|
|
uv_out.y = (uv.y & mask.y) | mask.w;
|
|
}
|
|
|
|
return uv_out;
|
|
}
|
|
|
|
float4 sample_depth(float2 st)
|
|
{
|
|
float2 uv_f = float2(clamp_wrap_uv_depth(ushort2(st))) * float2(cb.scale_factor.x);
|
|
|
|
if (PS_REGION_RECT)
|
|
uv_f = clamp(uv_f + cb.st_range.xy, cb.st_range.xy, cb.st_range.zw);
|
|
|
|
ushort2 uv = ushort2(uv_f);
|
|
float4 t = float4(0);
|
|
|
|
if (PS_TALES_OF_ABYSS_HLE)
|
|
{
|
|
// Warning: UV can't be used in channel effect
|
|
ushort depth = fetch_raw_depth();
|
|
// Convert msb based on the palette
|
|
t = palette.read(ushort2((depth >> 8) & 0xFF, 0)) * 255.f;
|
|
}
|
|
else if (PS_URBAN_CHAOS_HLE)
|
|
{
|
|
// 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
|
|
ushort depth = fetch_raw_depth();
|
|
|
|
// Convert lsb based on the palette
|
|
t = palette.read(ushort2(depth & 0xFF, 0)) * 255.f;
|
|
|
|
// Msb is easier
|
|
float green = float((depth >> 8) & 0xFF) * 36.f;
|
|
green = min(green, 255.0f);
|
|
|
|
t.g += green;
|
|
}
|
|
else if (PS_DEPTH_FMT == 1)
|
|
{
|
|
t = convert_depth32_rgba8(fetch_c(uv).r);
|
|
}
|
|
else if (PS_DEPTH_FMT == 2)
|
|
{
|
|
t = convert_depth16_rgba8(fetch_c(uv).r);
|
|
}
|
|
else if (PS_DEPTH_FMT == 3)
|
|
{
|
|
t = fetch_c(uv) * 255.f;
|
|
}
|
|
|
|
// macOS 10.15 ICE's on bool3(t.rgb), so use != 0 instead
|
|
if (PS_AEM_FMT == FMT_24)
|
|
t.a = (!PS_AEM || any(t.rgb != 0)) ? 255.f * cb.ta.x : 0.f;
|
|
else if (PS_AEM_FMT == FMT_16)
|
|
t.a = t.a >= 128.f ? 255.f * cb.ta.y : (!PS_AEM || any(t.rgb != 0)) ? 255.f * cb.ta.x : 0.f;
|
|
else if (PS_PAL_FMT != 0 && !PS_TALES_OF_ABYSS_HLE && !PS_URBAN_CHAOS_HLE)
|
|
t = trunc(sample_4p(uint4(t.aaaa))[0] * 255.0f + 0.05f);
|
|
|
|
return t;
|
|
}
|
|
|
|
// MARK: Fetch a Single Channel
|
|
|
|
float4 fetch_red()
|
|
{
|
|
float rt = PS_TEX_IS_DEPTH ? float(fetch_raw_depth() & 0xFF) / 255.f : fetch_raw_color().r;
|
|
return sample_p_norm(rt) * 255.f;
|
|
}
|
|
|
|
float4 fetch_green()
|
|
{
|
|
float rt = PS_TEX_IS_DEPTH ? float((fetch_raw_depth() >> 8) & 0xFF) / 255.f : fetch_raw_color().g;
|
|
return sample_p_norm(rt) * 255.f;
|
|
}
|
|
|
|
float4 fetch_blue()
|
|
{
|
|
float rt = PS_TEX_IS_DEPTH ? float((fetch_raw_depth() >> 16) & 0xFF) / 255.f : fetch_raw_color().b;
|
|
return sample_p_norm(rt) * 255.f;
|
|
}
|
|
|
|
float4 fetch_alpha()
|
|
{
|
|
return sample_p_norm(fetch_raw_color().a) * 255.f;
|
|
}
|
|
|
|
float4 fetch_rgb()
|
|
{
|
|
float4 rt = fetch_raw_color();
|
|
return float4(sample_p_norm(rt.r).r, sample_p_norm(rt.g).g, sample_p_norm(rt.b).b, 1) * 255.f;
|
|
}
|
|
|
|
float4 fetch_gXbY()
|
|
{
|
|
if (PS_TEX_IS_DEPTH)
|
|
{
|
|
uint depth = fetch_raw_depth();
|
|
uint bg = (depth >> (8 + cb.channel_shuffle.green_shift)) & 0xFF;
|
|
return float4(bg);
|
|
}
|
|
else
|
|
{
|
|
uint4 rt = uint4(fetch_raw_color() * 255.5f);
|
|
uint green = (rt.g >> cb.channel_shuffle.green_shift) & cb.channel_shuffle.green_mask;
|
|
uint blue = (rt.b >> cb.channel_shuffle.blue_shift) & cb.channel_shuffle.blue_mask;
|
|
return float4(green | blue);
|
|
}
|
|
}
|
|
|
|
float4 sample_color(float2 st)
|
|
{
|
|
if (PS_TCOFFSETHACK)
|
|
st += cb.tc_offset;
|
|
|
|
float4 t;
|
|
float4x4 c;
|
|
float2 dd;
|
|
|
|
if (!PS_LTF && PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && !PS_REGION_RECT && PS_WMS < 2 && PS_WMT < 2)
|
|
{
|
|
c[0] = sample_c(st);
|
|
}
|
|
else
|
|
{
|
|
float4 uv;
|
|
if (PS_LTF)
|
|
{
|
|
uv = st.xyxy + cb.half_texel;
|
|
dd = fract(uv.xy * cb.wh.zw);
|
|
if (!FST)
|
|
{
|
|
// Background in Shin Megami Tensei Lucifers
|
|
// I suspect that uv isn't a standard number, so fract is outside of the [0;1] range
|
|
dd = saturate(dd);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
// macOS 10.15 ICE's on bool3(c[i].rgb), so use != 0 instead
|
|
if (PS_AEM_FMT == FMT_24)
|
|
c[i].a = !PS_AEM || any(c[i].rgb != 0) ? cb.ta.x : 0.f;
|
|
else if (PS_AEM_FMT == FMT_16)
|
|
c[i].a = c[i].a >= 0.5 ? cb.ta.y : !PS_AEM || any((int3(c[i].rgb * 255.0f) & 0xF8) != 0) ? cb.ta.x : 0.f;
|
|
}
|
|
|
|
if (PS_LTF)
|
|
t = mix(mix(c[0], c[1], dd.x), mix(c[2], c[3], dd.x), dd.y);
|
|
else
|
|
t = c[0];
|
|
|
|
if (PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_RTA_SRC_CORRECTION)
|
|
t.a = t.a * (128.5f / 255.0f);
|
|
|
|
// 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.f + 0.05f);
|
|
}
|
|
|
|
float4 tfx(float4 T, float4 C)
|
|
{
|
|
float4 C_out;
|
|
float4 FxT = trunc((C * T) / 128.f);
|
|
if (PS_TFX == 0)
|
|
C_out = FxT;
|
|
else if (PS_TFX == 1)
|
|
C_out = T;
|
|
else if (PS_TFX == 2)
|
|
C_out = float4(FxT.rgb, T.a) + C.a;
|
|
else if (PS_TFX == 3)
|
|
C_out = float4(FxT.rgb + C.a, T.a);
|
|
else
|
|
C_out = C;
|
|
|
|
if (!PS_TCC)
|
|
C_out.a = C.a;
|
|
|
|
// Clamp only when it is useful
|
|
if (PS_TFX == 0 || PS_TFX == 2 || PS_TFX == 3)
|
|
C_out = min(C_out, 255.f);
|
|
|
|
return C_out;
|
|
}
|
|
|
|
bool atst(float4 C)
|
|
{
|
|
float a = C.a;
|
|
switch (PS_ATST)
|
|
{
|
|
case ATST::NONE:
|
|
break; // Nothing to do
|
|
case ATST::LEQUAL:
|
|
if (a > cb.aref)
|
|
return false;
|
|
break;
|
|
case ATST::GEQUAL:
|
|
if (a < cb.aref)
|
|
return false;
|
|
break;
|
|
case ATST::EQUAL:
|
|
if (abs(a - cb.aref) > 0.5f)
|
|
return false;
|
|
break;
|
|
case ATST::NOTEQUAL:
|
|
if (abs(a - cb.aref) < 0.5f)
|
|
return false;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void fog(thread float4& C, float f)
|
|
{
|
|
if (PS_FOG)
|
|
C.rgb = trunc(mix(cb.fog_color, C.rgb, (f * 255.0f) / 256.0f));
|
|
}
|
|
|
|
float4 ps_color()
|
|
{
|
|
float2 st, st_int;
|
|
if (!FST)
|
|
{
|
|
st = in.t.xy / in.t.w;
|
|
st_int = in.ti.zw / in.t.w;
|
|
}
|
|
else
|
|
{
|
|
// Note: xy are normalized coordinates
|
|
st = in.ti.xy;
|
|
st_int = in.ti.zw;
|
|
}
|
|
|
|
float4 T;
|
|
if (PS_CHANNEL == 1)
|
|
T = fetch_red();
|
|
else if (PS_CHANNEL == 2)
|
|
T = fetch_green();
|
|
else if (PS_CHANNEL == 3)
|
|
T = fetch_blue();
|
|
else if (PS_CHANNEL == 4)
|
|
T = fetch_alpha();
|
|
else if (PS_CHANNEL == 5)
|
|
T = fetch_rgb();
|
|
else if (PS_CHANNEL == 6)
|
|
T = fetch_gXbY();
|
|
else if (PS_DEPTH_FMT != 0)
|
|
T = sample_depth(st_int);
|
|
else
|
|
T = sample_color(st);
|
|
|
|
if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE))
|
|
{
|
|
uint4 denorm_c_before = uint4(T);
|
|
if (PS_PROCESS_BA & SHUFFLE_READ)
|
|
{
|
|
T.r = float((denorm_c_before.b << 3) & 0xF8);
|
|
T.g = float(((denorm_c_before.b >> 2) & 0x38) | ((denorm_c_before.a << 6) & 0xC0));
|
|
T.b = float((denorm_c_before.a << 1) & 0xF8);
|
|
T.a = float(denorm_c_before.a & 0x80);
|
|
}
|
|
else
|
|
{
|
|
T.r = float((denorm_c_before.r << 3) & 0xF8);
|
|
T.g = float(((denorm_c_before.r >> 2) & 0x38) | ((denorm_c_before.g << 6) & 0xC0));
|
|
T.b = float((denorm_c_before.g << 1) & 0xF8);
|
|
T.a = float(denorm_c_before.g & 0x80);
|
|
}
|
|
|
|
T.a = (T.a >= 127.5 ? cb.ta.y : !PS_AEM || any((int3(T.rgb) & 0xF8) != 0) ? cb.ta.x : 0.f) * 255.f;
|
|
}
|
|
|
|
float4 C = tfx(T, IIP ? in.c : in.fc);
|
|
|
|
fog(C, in.t.z);
|
|
|
|
return C;
|
|
}
|
|
|
|
void ps_fbmask(thread float4& C)
|
|
{
|
|
if (PS_FBMASK)
|
|
{
|
|
float multi = PS_COLCLIP_HW ? 65535.0 : 255.5;
|
|
C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(current_color * float4(multi, multi, multi, 255)) & cb.fbmask));
|
|
}
|
|
}
|
|
|
|
void ps_dither(thread float4& C, float As)
|
|
{
|
|
if (PS_DITHER == 0 || PS_DITHER == 3)
|
|
return;
|
|
ushort2 fpos;
|
|
if (PS_DITHER == 2)
|
|
fpos = ushort2(in.p.xy);
|
|
else
|
|
fpos = ushort2(in.p.xy * float2(cb.scale_factor.y));
|
|
float value = cb.dither_matrix[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)
|
|
{
|
|
float Alpha = PS_BLEND_C == 2 ? cb.alpha_fix : As;
|
|
value *= Alpha > 0.f ? min(1.f / Alpha, 1.f) : 1.f;
|
|
}
|
|
|
|
if (PS_ROUND_INV)
|
|
C.rgb -= value;
|
|
else
|
|
C.rgb += value;
|
|
}
|
|
|
|
void ps_color_clamp_wrap(thread float4& 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.rgb += 7.f; // Need to round up, not down since the shader will invert
|
|
|
|
// Correct the Color value based on the output format
|
|
if (PS_COLCLIP == 0 && PS_COLCLIP_HW == 0)
|
|
C.rgb = clamp(C.rgb, 0.f, 255.f); // Standard Clamp
|
|
|
|
// 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
|
|
// In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania
|
|
if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER))
|
|
C.rgb = float3(short3(C.rgb) & 0xF8);
|
|
else if (PS_COLCLIP == 1 || PS_COLCLIP_HW == 1)
|
|
C.rgb = float3(short3(C.rgb) & 0xFF);
|
|
}
|
|
else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0)
|
|
C.rgb = float3(short3(C.rgb) & 0xF8);
|
|
}
|
|
|
|
template <typename T>
|
|
static T pick(uint selector, T zero, T one, T two)
|
|
{
|
|
return selector == 0 ? zero : selector == 1 ? one : two;
|
|
}
|
|
|
|
void ps_blend(thread float4& Color, thread float4& 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.f)
|
|
{
|
|
As_rgba.rgb = float3(0.f);
|
|
return;
|
|
}
|
|
|
|
As_rgba.rgb = float3(1.f);
|
|
}
|
|
|
|
float Ad = PS_RTA_CORRECTION ? trunc(current_color.a * 128.1f) / 128.f : trunc(current_color.a * 255.1f) / 128.f;
|
|
|
|
if (PS_SHUFFLE && NEEDS_RT)
|
|
{
|
|
uint4 denorm_rt = uint4(current_color);
|
|
if (PS_PROCESS_BA & SHUFFLE_WRITE)
|
|
{
|
|
current_color.r = float((denorm_rt.b << 3) & 0xF8);
|
|
current_color.g = float(((denorm_rt.b >> 2) & 0x38) | ((denorm_rt.a << 6) & 0xC0));
|
|
current_color.b = float((denorm_rt.a << 1) & 0xF8);
|
|
current_color.a = float(denorm_rt.a & 0x80);
|
|
}
|
|
else
|
|
{
|
|
current_color.r = float((denorm_rt.r << 3) & 0xF8);
|
|
current_color.g = float(((denorm_rt.r >> 2) & 0x38) | ((denorm_rt.g << 6) & 0xC0));
|
|
current_color.b = float((denorm_rt.g << 1) & 0xF8);
|
|
current_color.a = float(denorm_rt.g & 0x80);
|
|
}
|
|
}
|
|
float multi = PS_COLCLIP_HW ? 65535.0 : 255.5;
|
|
float3 Cd = trunc(current_color.rgb * multi);
|
|
float3 Cs = Color.rgb;
|
|
|
|
float3 A = pick(PS_BLEND_A, Cs, Cd, float3(0.f));
|
|
float3 B = pick(PS_BLEND_B, Cs, Cd, float3(0.f));
|
|
float C = pick(PS_BLEND_C, As, Ad, cb.alpha_fix);
|
|
float3 D = pick(PS_BLEND_D, Cs, Cd, float3(0.f));
|
|
|
|
// 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 = saturate(C_clamped);
|
|
|
|
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).
|
|
else if (PS_BLEND_MIX == 2)
|
|
Color.rgb = ((A - B) * C_clamped + D) + (124.f/256.f);
|
|
else if (PS_BLEND_MIX == 1)
|
|
Color.rgb = ((A - B) * C_clamped + D) - (124.f/256.f);
|
|
else
|
|
Color.rgb = trunc((A - B) * C + D);
|
|
|
|
if (PS_BLEND_HW == 1)
|
|
{
|
|
// As or Af
|
|
As_rgba.rgb = float3(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.
|
|
float3 alpha_compensate = max(float3(1.f), Color.rgb / float3(255.f));
|
|
As_rgba.rgb -= alpha_compensate;
|
|
}
|
|
else if (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.f + C;
|
|
Color.rgb /= float3(division_alpha);
|
|
}
|
|
else if (PS_BLEND_HW == 3)
|
|
{
|
|
// As, Ad or Af clamped.
|
|
As_rgba.rgb = float3(C_clamped);
|
|
// Cs*(Alpha + 1) might overflow, if it does then adjust alpha value
|
|
// that is sent on second output to compensate.
|
|
float3 overflow_check = (Color.rgb - float3(255.f)) / 255.f;
|
|
float3 alpha_compensate = max(float3(0.f), overflow_check);
|
|
As_rgba.rgb -= alpha_compensate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PS_BLEND_HW == 1)
|
|
{
|
|
// Needed for Cd * (As/Ad/F + 1) blending modes
|
|
Color.rgb = 255.f;
|
|
}
|
|
else if (PS_BLEND_HW == 2)
|
|
{
|
|
// Cd*As,Cd*Ad or Cd*F
|
|
float Alpha = PS_BLEND_C == 2 ? cb.alpha_fix : As;
|
|
Color.rgb = saturate(Alpha - 1.f) * 255.f;
|
|
}
|
|
else if (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.f / max(128.f, max_color);
|
|
Color.rgb *= float3(color_compensate);
|
|
}
|
|
}
|
|
}
|
|
|
|
MainResult ps_main()
|
|
{
|
|
MainResult out = {};
|
|
float input_z = in.p.z;
|
|
if (PS_ZFLOOR)
|
|
input_z = floor(input_z * 0x1p32) * 0x1p-32;
|
|
|
|
if (PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER)
|
|
{
|
|
if (PS_ZTST == ZTST::GEQUAL && input_z < current_depth)
|
|
discard();
|
|
if (PS_ZTST == ZTST::GREATER && input_z <= current_depth)
|
|
discard();
|
|
}
|
|
|
|
if (PS_SCANMSK & 2)
|
|
{
|
|
if ((uint(in.p.y) & 1) == (PS_SCANMSK & 1))
|
|
discard();
|
|
}
|
|
|
|
if (PS_DATE >= 5)
|
|
{
|
|
// 1 => DATM == 0, 2 => DATM == 1
|
|
float rt_a = PS_WRITE_RG ? current_color.g : current_color.a;
|
|
bool bad = PS_RTA_CORRECTION ? ((PS_DATE & 3) == 1 ? (rt_a > (254.5f / 255.f)) : (rt_a < (254.5f / 255.f))) : ((PS_DATE & 3) == 1 ? (rt_a > 0.5) : (rt_a < 0.5));
|
|
|
|
if (bad)
|
|
discard();
|
|
}
|
|
|
|
if (PS_DATE == 3)
|
|
{
|
|
float stencil_ceil = prim_id_tex.read(uint2(in.p.xy)).r;
|
|
// Note prim_id == stencil_ceil will be the primitive that will update
|
|
// the bad alpha value so we must keep it.
|
|
if (float(prim_id) > stencil_ceil)
|
|
discard();
|
|
}
|
|
|
|
float4 C = ps_color();
|
|
|
|
// Must be done before alpha correction
|
|
|
|
if (PS_AA1 != AA1::NONE)
|
|
{
|
|
float cov = PS_AA1 == AA1::LINE
|
|
? saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) // Blur only outer part of the line by scaling coverage.
|
|
: saturate(1.f - abs(in.inv_cov));
|
|
if (!PS_ABE || floor(C.a) == 128.f) // The coverage is only used if the fragment alpha is 128.
|
|
C.a = 128.f * cov;
|
|
}
|
|
else if (PS_FIXED_ONE_A)
|
|
{
|
|
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
|
C.a = 128.0f;
|
|
}
|
|
|
|
bool atst_pass = atst(C);
|
|
if (PS_AFAIL == AFAIL::KEEP && !atst_pass)
|
|
discard();
|
|
|
|
float4 alpha_blend = float4(0.f);
|
|
if (SW_AD_TO_HW)
|
|
{
|
|
alpha_blend = PS_RTA_CORRECTION ? float4(trunc(current_color.a * 128.f) / 128.f) : float4(trunc(current_color.a * 255.5f) / 128.f);
|
|
}
|
|
else
|
|
{
|
|
alpha_blend = float4(C.a / 128.f);
|
|
}
|
|
|
|
if (PS_DST_FMT == FMT_16)
|
|
{
|
|
float A_one = 128.f;
|
|
C.a = (PS_FBA) ? A_one : step(128.f, C.a) * A_one;
|
|
}
|
|
else if (PS_DST_FMT == FMT_32 && PS_FBA)
|
|
{
|
|
if (C.a < 128.f)
|
|
C.a += 128.f;
|
|
}
|
|
|
|
// Get first primitive that will write a failing alpha value
|
|
if (PS_DATE == 1)
|
|
{
|
|
// DATM == 0, Pixel with alpha equal to 1 will failed (128-255)
|
|
out.c0 = C.a > 127.5f ? float(prim_id) : FLT_MAX;
|
|
return out;
|
|
}
|
|
else if (PS_DATE == 2)
|
|
{
|
|
// DATM == 1, Pixel with alpha equal to 0 will failed (0-127)
|
|
out.c0 = C.a < 127.5f ? float(prim_id) : FLT_MAX;
|
|
return out;
|
|
}
|
|
|
|
ps_blend(C, alpha_blend);
|
|
|
|
if (PS_SHUFFLE)
|
|
{
|
|
if (!PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE))
|
|
{
|
|
uint4 denorm_c_after = uint4(C);
|
|
if (PS_PROCESS_BA & SHUFFLE_READ)
|
|
{
|
|
C.b = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
|
|
C.a = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
|
|
}
|
|
else
|
|
{
|
|
C.r = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
|
|
C.g = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
|
|
}
|
|
}
|
|
|
|
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
|
|
if (PS_SHUFFLE_SAME)
|
|
{
|
|
uint4 denorm_c = uint4(C);
|
|
|
|
if (PS_PROCESS_BA & SHUFFLE_READ)
|
|
C = (denorm_c.b & 0x7F) | (denorm_c.a & 0x80);
|
|
else
|
|
C.ga = C.rg;
|
|
}
|
|
// Copy of a 16bit source in to this target
|
|
else if (PS_READ16_SRC)
|
|
{
|
|
uint4 denorm_c = uint4(C);
|
|
uint2 denorm_TA = uint2(cb.ta * 255.5f);
|
|
|
|
C.rb = (denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7) << 5);
|
|
C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80);
|
|
}
|
|
else if (PS_SHUFFLE_ACROSS)
|
|
{
|
|
if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)
|
|
{
|
|
C.br = C.rb;
|
|
C.ag = C.ga;
|
|
}
|
|
else if(PS_PROCESS_BA & SHUFFLE_READ)
|
|
{
|
|
C.rb = C.bb;
|
|
C.ga = C.aa;
|
|
}
|
|
else
|
|
{
|
|
C.rb = C.rr;
|
|
C.ga = C.gg;
|
|
}
|
|
}
|
|
}
|
|
|
|
ps_dither(C, alpha_blend.a);
|
|
|
|
// Color clamp/wrap needs to be done after sw blending and dithering
|
|
ps_color_clamp_wrap(C);
|
|
|
|
ps_fbmask(C);
|
|
|
|
// Use alpha blend factor to determine whether to update A.
|
|
if (PS_AFAIL == AFAIL::RGB_ONLY_DSB)
|
|
alpha_blend.a = float(atst_pass);
|
|
|
|
if (!PS_NO_COLOR)
|
|
{
|
|
out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f;
|
|
out.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.f) : C.rgb / 255.f;
|
|
}
|
|
if (!PS_NO_COLOR1)
|
|
out.c1 = alpha_blend;
|
|
|
|
if (PS_ZCLAMP)
|
|
input_z = min(input_z, cb.max_depth);
|
|
|
|
if (PS_AA1 == AA1::TRIANGLE_SW_Z && !in.interior)
|
|
discard_depth(input_z); // No depth update for triangle edges.
|
|
|
|
if (!atst_pass)
|
|
{
|
|
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::RGB_ONLY)
|
|
out.c0.a = current_color.a; // discard alpha
|
|
else if (PS_AFAIL == AFAIL::ZB_ONLY)
|
|
discard_color(out.c0);
|
|
|
|
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::FB_ONLY)
|
|
discard_depth(input_z);
|
|
}
|
|
|
|
out.depth = input_z;
|
|
|
|
return out;
|
|
}
|
|
};
|
|
|
|
#if FBFETCH_SUPPORT
|
|
fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]])
|
|
{
|
|
return in * 2;
|
|
}
|
|
|
|
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH && !PS_ROV_COLOR;
|
|
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH && !PS_ROV_COLOR;
|
|
constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
|
#else
|
|
constant bool NEEDS_RT_TEX = NEEDS_RT && !PS_ROV_COLOR;
|
|
constant bool NEEDS_DS_FBF = false;
|
|
constant float ds_fbf = 0;
|
|
#endif
|
|
constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
|
constant bool NEEDS_DS_DEPTH = (SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
|
constant bool NEEDS_RT_ROV = PS_ROV_COLOR && !ROV_NEEDS_R32;
|
|
constant bool NEEDS_RT_U32 = PS_ROV_COLOR && ROV_NEEDS_R32;
|
|
constant bool NEEDS_DS_ROV = PS_ROV_DEPTH != ROV_DEPTH::NONE;
|
|
|
|
fragment MainPSOut ps_main(
|
|
MainPSIn in [[stage_in]],
|
|
constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
|
|
sampler s [[sampler(0)]],
|
|
#if PRIMID_SUPPORT
|
|
uint primid [[primitive_id, function_constant(NEEDS_PRIMID)]],
|
|
#endif
|
|
#if FBFETCH_SUPPORT
|
|
float4 rt_fbf [[color(0), raster_order_group(0), function_constant(NEEDS_RT_FBF)]],
|
|
float ds_fbf [[color(1), raster_order_group(1), function_constant(NEEDS_DS_FBF)]],
|
|
#endif
|
|
texture2d<float> tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]],
|
|
depth2d<float> depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]],
|
|
texture2d<float> palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]],
|
|
texture2d<float> rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]],
|
|
texture2d<float> primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]],
|
|
texture2d<float> ds_tex [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_TEX)]],
|
|
depth2d<float> ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]],
|
|
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
|
|
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]],
|
|
texture2d<float, access::read_write> ds_rov [[texture(GSMTLTextureIndexDepthTarget), raster_order_group(1), function_constant(NEEDS_DS_ROV)]])
|
|
{
|
|
PSMain main(in, cb);
|
|
main.tex_sampler = s;
|
|
if (PS_TEX_IS_COLOR)
|
|
main.tex = tex;
|
|
else
|
|
main.tex_depth = depth;
|
|
if (PS_HAS_PALETTE)
|
|
main.palette = palette;
|
|
if (PS_PRIM_CHECKING_READ)
|
|
main.prim_id_tex = primidtex;
|
|
#if PRIMID_SUPPORT
|
|
if (NEEDS_PRIMID)
|
|
main.prim_id = primid;
|
|
#endif
|
|
|
|
uint2 coord = uint2(in.p.xy);
|
|
|
|
if (SW_DEPTH)
|
|
{
|
|
if (PS_ROV_DEPTH != ROV_DEPTH::NONE)
|
|
main.current_depth = ds_rov.read(coord).x;
|
|
else if (DEPTH_FEEDBACK)
|
|
main.current_depth = ds_depth.read(coord);
|
|
else if (NEEDS_DS_FBF)
|
|
main.current_depth = ds_fbf < 0 ? ds_depth.read(coord) : ds_fbf;
|
|
else
|
|
main.current_depth = ds_tex.read(coord).x;
|
|
}
|
|
|
|
if (NEEDS_RT || (PS_ROV_COLOR && any(cb.fbmask == 0xff)))
|
|
{
|
|
if (PS_ROV_COLOR)
|
|
{
|
|
if (ROV_NEEDS_R32)
|
|
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
|
|
else
|
|
main.current_color = rt_rov.read(coord);
|
|
}
|
|
else
|
|
{
|
|
#if FBFETCH_SUPPORT
|
|
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
|
|
#else
|
|
main.current_color = rt.read(coord);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
main.current_color = 0;
|
|
}
|
|
|
|
MainResult out = main.ps_main();
|
|
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE && !main.depth_discarded)
|
|
ds_rov.write(out.depth, coord);
|
|
if (PS_ROV_COLOR && !main.color_discarded)
|
|
{
|
|
if (!PS_FBMASK)
|
|
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
|
|
if (ROV_NEEDS_R32)
|
|
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
|
|
else
|
|
rt_rov.write(out.c0, coord);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Metal doesn't let you toggle eft with function constants so we need a separate function for it
|
|
[[early_fragment_tests]]
|
|
fragment void ps_main_rov_eft(
|
|
MainPSIn in [[stage_in]],
|
|
constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
|
|
sampler s [[sampler(0)]],
|
|
texture2d<float> tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]],
|
|
depth2d<float> depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]],
|
|
texture2d<float> palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]],
|
|
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
|
|
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]])
|
|
{
|
|
PSMain main(in, cb);
|
|
main.tex_sampler = s;
|
|
if (PS_TEX_IS_COLOR)
|
|
main.tex = tex;
|
|
else
|
|
main.tex_depth = depth;
|
|
if (PS_HAS_PALETTE)
|
|
main.palette = palette;
|
|
|
|
uint2 coord = uint2(in.p.xy);
|
|
if (ROV_NEEDS_R32)
|
|
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
|
|
else
|
|
main.current_color = rt_rov.read(coord);
|
|
MainPSOut out = main.ps_main();
|
|
if (!main.color_discarded)
|
|
{
|
|
if (!PS_FBMASK)
|
|
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
|
|
if (ROV_NEEDS_R32)
|
|
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
|
|
else
|
|
rt_rov.write(out.c0, coord);
|
|
}
|
|
}
|
|
|
|
#if PRIMID_SUPPORT
|
|
fragment uint primid_test(uint id [[primitive_id]])
|
|
{
|
|
return id;
|
|
}
|
|
#endif
|
|
|
|
// MARK: Markers for detecting the Metal version a metallib was compiled against
|
|
|
|
#if __METAL_VERSION__ >= 210
|
|
kernel void metal_version_21() {}
|
|
#endif
|
|
#if __METAL_VERSION__ >= 220
|
|
kernel void metal_version_22() {}
|
|
#endif
|
|
#if __METAL_VERSION__ >= 230
|
|
kernel void metal_version_23() {}
|
|
#endif
|