Skip to content

Commit b60aa92

Browse files
committed
GS/HW: Tweak AF shader
1 parent 3df128d commit b60aa92

5 files changed

Lines changed: 438 additions & 378 deletions

File tree

bin/resources/shaders/dx11/tfx.fx

Lines changed: 112 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -355,132 +355,148 @@ bool2 nan_or_inf(float2 xy)
355355
float4 sample_c_af(float2 uv, float uv_w)
356356
{
357357
// HW sampler will reject bad UVs, match that here.
358-
uv = any(nan_or_inf(uv)) ? float2(0, 0) : uv;
358+
uv = any(nan_or_inf(uv)) ? float2(0.0f, 0.0f) : uv;
359359

360360
// Large floating point values risk NaN/Inf values.
361361
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
362362
uv = clamp(uv, -8388608.0f, 8388608.0f);
363363

364364
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
365+
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
365366
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
366367
float2 sz;
367368
Texture.GetDimensions(sz.x, sz.y);
368369
float2 dX = ddx(uv) * sz;
369370
float2 dY = ddy(uv) * sz;
370371

371-
// Calculate Ellipse Transform
372-
bool d_zero = length(dX) == 0 || length(dY) == 0;
373-
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
374-
bool d_per = dot(dX, dY) == 0;
375-
bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY));
372+
// Check if sample is magnification and if so, skip anisotropic calculations.
373+
float length_x = length(dX);
374+
float length_y = length(dY);
376375

377-
if (!(d_zero || d_par || d_per || d_inf_nan))
376+
[branch]
377+
if (max(length_x, length_y) < 0.75f)
378378
{
379-
float A = dX.y * dX.y + dY.y * dY.y;
380-
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
381-
float C = dX.x * dX.x + dY.x * dY.x;
382-
float f = (dX.x * dY.y - dY.x * dX.y);
383-
float F = f * f;
384-
385-
float p = A - C;
386-
float q = A + C;
387-
float t = sqrt(p * p + B * B);
388-
389-
float2 new_dX = float2(
390-
sqrt(F * (t + p) / (t * (q + t))),
391-
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
392-
);
393-
394-
float2 new_dY = float2(
395-
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
396-
sqrt(F * (t + p) / (t * (q - t)))
397-
);
398-
399-
d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY));
400-
if (!d_inf_nan)
401-
{
402-
dX = new_dX;
403-
dY = new_dY;
404-
}
405-
}
406-
407-
// Compute AF values
408-
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
409-
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
410-
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
411-
bool is_major_x = squared_length_x > squared_length_y;
412-
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
413-
float length_major = sqrt(squared_length_major);
414-
415-
float aniso_ratio;
416-
float length_lod;
417-
float2 aniso_line;
418-
if (length_major <= 1.0f)
419-
{
420-
// A zero length_major would result in NaN Lod and break sampling.
421-
// A small length_major would result in aniso_ratio getting clamped to 1.
422-
// Perform isotropic filtering instead.
423-
aniso_ratio = 1.0f;
424-
length_lod = length_major;
425-
aniso_line = float2(0, 0);
379+
#if PS_AUTOMATIC_LOD == 1
380+
return Texture.Sample(TextureSampler, uv);
381+
#else
382+
#if PS_MANUAL_LOD == 1
383+
float lod = manual_lod(uv_w);
384+
#else
385+
float lod = 0.0f; // No Lod
386+
#endif
387+
return Texture.SampleLevel(TextureSampler, uv, lod);
388+
#endif
426389
}
427390
else
428391
{
429-
float norm_major = 1.0f / length_major;
430-
431-
float2 aniso_line_dir = float2(
432-
(is_major_x ? dX.x : dY.x) * norm_major,
433-
(is_major_x ? dX.y : dY.y) * norm_major
434-
);
435-
436-
aniso_ratio = squared_length_major / determinant;
392+
// Calculate Ellipse Transform
393+
bool d_zero = length_x < 0.001f || length_y < 0.001f;
394+
float f = (dX.x * dY.y - dX.y * dY.x);
395+
bool d_par = f < 0.001f;
396+
bool d_per = dot(dX, dY) < 0.001f;
397+
bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY));
398+
399+
if (!(d_zero || d_par || d_per || d_inf_nan))
400+
{
401+
float A = dX.y * dX.y + dY.y * dY.y;
402+
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
403+
float C = dX.x * dX.x + dY.x * dY.x;
404+
float F = f * f;
405+
406+
float p = A - C;
407+
float q = A + C;
408+
float t = sqrt(p * p + B * B);
409+
410+
float sqrt_num_plus = sqrt(F * (t + p));
411+
float sqrt_num_minus = sqrt(F * (t - p));
412+
413+
float inv_sqrt_denom_plus = rsqrt(t * (q + t));
414+
float inv_sqrt_denom_minus = rsqrt(t * (q - t));
415+
416+
float signB = sign(B);
417+
418+
float2 new_dX = float2(
419+
sqrt_num_plus * inv_sqrt_denom_plus,
420+
sqrt_num_minus * inv_sqrt_denom_plus * signB
421+
);
422+
423+
float2 new_dY = float2(
424+
sqrt_num_minus * inv_sqrt_denom_minus * -signB,
425+
sqrt_num_plus * inv_sqrt_denom_minus
426+
);
427+
428+
d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY));
429+
if (!d_inf_nan)
430+
{
431+
dX = new_dX;
432+
dY = new_dY;
433+
length_x = length(dX);
434+
length_y = length(dY);
435+
}
436+
}
437437

438-
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
439-
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
438+
// Compute AF values
439+
bool is_major_x = length_x > length_y;
440+
float length_major = is_major_x ? length_x : length_y;
441+
float length_minor = is_major_x ? length_y : length_x;
442+
443+
float aniso_ratio;
444+
float length_lod;
445+
float2 aniso_line;
446+
if (length_major <= 1.0f)
440447
{
441-
// ratio is clamped - Lod is based on ratio (preserves area)
442-
aniso_ratio = PS_ANISOTROPIC_FILTERING;
443-
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
448+
// A zero length_major would result in NaN Lod and break sampling.
449+
// A small length_major would result in aniso_ratio getting clamped to 1.
450+
// Perform isotropic filtering instead.
451+
aniso_ratio = 1.0f;
452+
length_lod = length_major;
453+
aniso_line = float2(0.0f, 0.0f);
444454
}
445455
else
446456
{
447-
// ratio not clamped - Lod is based on area
448-
length_lod = determinant / length_major;
449-
}
457+
float2 aniso_line_dir = is_major_x ? dX : dY;
450458

451-
// clamp to top Lod
452-
if (length_lod < 1.0f)
453-
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
459+
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
460+
length_lod = length_major / aniso_ratio;
461+
462+
// clamp to top Lod
463+
if (length_lod < 1.0f)
464+
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
465+
466+
aniso_ratio = round(aniso_ratio);
467+
468+
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
469+
}
454470

455-
aniso_ratio = round(aniso_ratio);
456-
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
457-
}
458-
459471
#if PS_AUTOMATIC_LOD == 1
460-
float lod = log2(length_lod);
472+
float lod = log2(length_lod);
461473
#elif PS_MANUAL_LOD == 1
462-
float lod = manual_lod(uv_w);
474+
float lod = manual_lod(uv_w);
463475
#else
464-
float lod = 0; // No Lod
476+
float lod = 0.0f; // No Lod
465477
#endif
466-
467-
float4 colour;
468-
if (aniso_ratio == 1.0f)
469-
colour = Texture.SampleLevel(TextureSampler, uv, lod);
470-
else
471-
{
472-
float4 num = float4(0, 0, 0, 0);
473-
for (int i = 0; i < aniso_ratio; i++)
474-
{
475-
float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
476-
float2 uv_sample = uv + d;
477-
float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod);
478-
num += sample_colour;
479-
}
480478

481-
colour = num / aniso_ratio;
479+
float4 colour;
480+
if (aniso_ratio == 1.0f)
481+
colour = Texture.SampleLevel(TextureSampler, uv, lod);
482+
else
483+
{
484+
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
485+
float2 segment = (2.0f * aniso_line) / aniso_ratio;
486+
487+
int aniso_ratio_i = (int)aniso_ratio;
488+
for (int i = 0; i < aniso_ratio_i; i++)
489+
{
490+
float2 d = -aniso_line + (0.5f + i) * segment;
491+
float2 uv_sample = uv + d;
492+
float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod);
493+
num += sample_colour;
494+
}
495+
496+
colour = num / aniso_ratio;
497+
}
498+
return colour;
482499
}
483-
return colour;
484500
}
485501
#endif
486502

0 commit comments

Comments
 (0)