@@ -219,6 +219,148 @@ cbuffer cb1
219219 float RcpScaleFactor;
220220};
221221
222+ #if (PS_AUTOMATIC_LOD != 1 ) && (PS_MANUAL_LOD == 1 )
223+ float manual_lod (float uv_w)
224+ {
225+ // FIXME add LOD: K - ( LOG2(Q) * (1 << L))
226+ float K = LODParams.x;
227+ float L = LODParams.y;
228+ float bias = LODParams.z;
229+ float max_lod = LODParams.w;
230+
231+ float gs_lod = K - log2 (abs (uv_w)) * L;
232+ // FIXME max useful ?
233+ //return max(min(gs_lod, max_lod) - bias, 0.0f);
234+ return min (gs_lod, max_lod) - bias;
235+ }
236+ #endif
237+
238+ #if PS_ANISOTROPIC_FILTERING > 1
239+ float4 sample_c_af (float2 uv, float uv_w)
240+ {
241+ // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
242+ // With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
243+ float2 sz;
244+ Texture.GetDimensions (sz.x, sz.y);
245+ float2 dX = ddx (uv) * sz;
246+ float2 dY = ddy (uv) * sz;
247+
248+ // Calculate Ellipse Transform
249+ bool d_zero = length (dX) == 0 || length (dY) == 0 ;
250+ bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0 ;
251+ bool d_per = dot (dX, dY) == 0 ;
252+ bool d_inf_nan = any (isinf (dX) | isinf (dY) | isnan (dX) | isnan (dY));
253+ // TODO: check if we might cause inf/nan
254+ if (!(d_zero || d_par || d_per || d_inf_nan))
255+ {
256+ float A = dX.y * dX.y + dY.y * dY.y;
257+ float B = -2 * (dX.x * dX.y + dY.x * dY.y);
258+ float C = dX.x * dX.x + dY.x * dY.x;
259+ float f = (dX.x * dY.y - dY.x * dX.y);
260+ float F = f * f;
261+
262+ float p = A - C;
263+ float q = A + C;
264+ float t = sqrt (p * p + B * B);
265+
266+ float2 new_dX = float2 (
267+ sqrt (F * (t + p) / (t * (q + t))),
268+ sqrt (F * (t - p) / (t * (q + t))) * sign (B)
269+ );
270+
271+ float2 new_dY = float2 (
272+ sqrt (F * (t - p) / (t * (q - t))) * -sign (B),
273+ sqrt (F * (t + p) / (t * (q - t)))
274+ );
275+
276+ d_inf_nan = any (isinf (new_dX) | isinf (new_dY) | isnan (new_dX) | isnan (new_dY));
277+ if (!d_inf_nan)
278+ {
279+ dX = new_dX;
280+ dY = new_dY;
281+ }
282+ }
283+
284+ // Compute AF values
285+ float squared_length_x = dX.x * dX.x + dX.y * dX.y;
286+ float squared_length_y = dY.x * dY.x + dY.y * dY.y;
287+ float determinant = abs (dX.x * dY.y - dX.y * dY.x);
288+ bool is_major_x = squared_length_x > squared_length_y;
289+ float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
290+ float length_major = sqrt (squared_length_major);
291+
292+ float aniso_ratio;
293+ float length_lod;
294+ float2 aniso_line;
295+ if (length_major <= 1.0f )
296+ {
297+ // A zero length_major would result in NaN Lod and break sampling.
298+ // A small length_major would result in aniso_ratio getting clamped to 1.
299+ // Perform isotropic filtering instead.
300+ aniso_ratio = 1.0f ;
301+ length_lod = length_major;
302+ aniso_line = float2 (0 , 0 );
303+ }
304+ else
305+ {
306+ float norm_major = 1.0f / length_major;
307+
308+ float2 aniso_line_dir = float2 (
309+ (is_major_x ? dX.x : dY.x) * norm_major,
310+ (is_major_x ? dX.y : dY.y) * norm_major
311+ );
312+
313+ aniso_ratio = squared_length_major / determinant;
314+
315+ // Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
316+ if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
317+ {
318+ // ratio is clamped - Lod is based on ratio (preserves area)
319+ aniso_ratio = PS_ANISOTROPIC_FILTERING;
320+ length_lod = length_major / PS_ANISOTROPIC_FILTERING;
321+ }
322+ else
323+ {
324+ // ratio not clamped - Lod is based on area
325+ length_lod = determinant / length_major;
326+ }
327+
328+ // clamp to top Lod
329+ if (length_lod < 1.0f )
330+ aniso_ratio = max (1.0f , aniso_ratio * length_lod);
331+
332+ aniso_ratio = round (aniso_ratio);
333+ aniso_line = aniso_line_dir * 0.5 * length_major * (1.0f / sz);
334+ }
335+
336+ #if PS_AUTOMATIC_LOD == 1
337+ float lod = log2 (length_lod);
338+ #elif PS_MANUAL_LOD == 1
339+ float lod = manual_lod (uv_w);
340+ #else
341+ float lod = 0 ; // No Lod
342+ #endif
343+
344+ float4 colour;
345+ if (aniso_ratio == 1.0f )
346+ colour = Texture.SampleLevel (TextureSampler, uv, lod);
347+ else
348+ {
349+ float4 num = float4 (0 , 0 , 0 , 0 );
350+ for (int i = 0 ; i < aniso_ratio; i++)
351+ {
352+ float2 d = -aniso_line + (0.5 + i) * (2.0 * aniso_line) / aniso_ratio;
353+ float2 uv_sample = uv + d;
354+ float4 sample_colour = Texture.SampleLevel (TextureSampler, uv_sample, lod);
355+ num += sample_colour;
356+ }
357+
358+ colour = num / aniso_ratio;
359+ }
360+ return colour;
361+ }
362+ #endif
363+
222364float4 sample_c (float2 uv, float uv_w, int2 xy)
223365{
224366#if PS_TEX_IS_FB == 1
@@ -251,21 +393,12 @@ float4 sample_c(float2 uv, float uv_w, int2 xy)
251393 #endif
252394#endif
253395
254- #if PS_AUTOMATIC_LOD == 1
396+ #if PS_ANISOTROPIC_FILTERING > 1
397+ return sample_c_af (uv, uv_w);
398+ #elif PS_AUTOMATIC_LOD == 1
255399 return Texture.Sample (TextureSampler, uv);
256400#elif PS_MANUAL_LOD == 1
257- // FIXME add LOD: K - ( LOG2(Q) * (1 << L))
258- float K = LODParams.x;
259- float L = LODParams.y;
260- float bias = LODParams.z;
261- float max_lod = LODParams.w;
262-
263- float gs_lod = K - log2 (abs (uv_w)) * L;
264- // FIXME max useful ?
265- //float lod = max(min(gs_lod, max_lod) - bias, 0.0f);
266- float lod = min (gs_lod, max_lod) - bias;
267-
268- return Texture.SampleLevel (TextureSampler, uv, lod);
401+ return Texture.SampleLevel (TextureSampler, uv, manual_lod (uv_w));
269402#else
270403 return Texture.SampleLevel (TextureSampler, uv, 0 ); // No lod
271404#endif
0 commit comments