@@ -355,132 +355,148 @@ bool2 nan_or_inf(float2 xy)
355355float4 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