From 1f8248ba998835fccaedced7907ac2b0f2d835c0 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 23 Jul 2025 13:46:43 -0700 Subject: [PATCH] Metal: re-enable fastmath (#8911) --- .../backend/src/metal/MetalShaderCompiler.mm | 8 +----- shaders/src/common_math.glsl | 22 +++++++++------ shaders/src/surface_brdf.fs | 28 +++++++++++-------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index d4eba27252..d448bed910 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -113,13 +113,7 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept { case ShaderLanguage::MSL: { // By default, Metal uses the most recent language version. MTLCompileOptions* options = [MTLCompileOptions new]; - - // Disable Fast Math optimizations. - // This ensures that operations adhere to IEEE standards for floating-point - // arithmetic, which is crucial for half precision floats in scenarios where fast - // math optimizations lead to inaccuracies, such as in handling special values like - // NaN or Infinity. - options.fastMathEnabled = NO; + options.fastMathEnabled = YES; assert_invariant(source[source.size() - 1] == '\0'); // the shader string is null terminated and the length includes the null character diff --git a/shaders/src/common_math.glsl b/shaders/src/common_math.glsl index 74fd2a21cc..54264f283a 100644 --- a/shaders/src/common_math.glsl +++ b/shaders/src/common_math.glsl @@ -3,22 +3,26 @@ //------------------------------------------------------------------------------ /** @public-api */ -#define PI 3.14159265359 +#define PI 3.14159265359 /** @public-api */ -#define HALF_PI 1.570796327 +#define HALF_PI 1.570796327 -#define MEDIUMP_FLT_MAX 65504.0 -#define MEDIUMP_FLT_MIN 0.00006103515625 +#define MEDIUMP_FLT_MAX 65504.0 +#define MEDIUMP_FLT_MIN 0.00006103515625 #ifdef TARGET_MOBILE -#define FLT_EPS MEDIUMP_FLT_MIN -#define saturateMediump(x) min(x, MEDIUMP_FLT_MAX) +#define FLT_EPS MEDIUMP_FLT_MIN #else -#define FLT_EPS 1e-5 -#define saturateMediump(x) x +#define FLT_EPS 1e-5 #endif -#define saturate(x) clamp(x, 0.0, 1.0) +#define saturate(x) clamp(x, 0.0, 1.0) + +#ifdef TARGET_MOBILE +#define PREVENT_DIV0(n, d, magic) ((n) / max(d, magic)) +#else +#define PREVENT_DIV0(n, d, magic) ((n) / (d)) +#endif //------------------------------------------------------------------------------ // Scalar operations diff --git a/shaders/src/surface_brdf.fs b/shaders/src/surface_brdf.fs index 2f4d152255..0dad3b5e19 100644 --- a/shaders/src/surface_brdf.fs +++ b/shaders/src/surface_brdf.fs @@ -73,9 +73,9 @@ float D_GGX(float roughness, float NoH, const vec3 h) { #endif float a = NoH * roughness; - float k = roughness / (oneMinusNoHSquared + a * a); - float d = k * k * (1.0 / PI); - return saturateMediump(d); + float k = min(roughness / (oneMinusNoHSquared + a * a), 453.5); // 453.5 prevents fp16 overflow + float d = k * (k * (1.0 / PI)); + return d; } float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) { @@ -105,17 +105,18 @@ float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) { // TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); - float v = 0.5 / (lambdaV + lambdaL); + // 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow + float v = PREVENT_DIV0(0.5, lambdaV + lambdaL, 0.0000077); // a2=0 => v = 1 / 4*NoL*NoV => min=1/4, max=+inf // a2=1 => v = 1 / 2*(NoL+NoV) => min=1/4, max=+inf - // clamp to the maximum value representable in mediump - return saturateMediump(v); + return v; } float V_SmithGGXCorrelated_Fast(float roughness, float NoV, float NoL) { // Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces" - float v = 0.5 / mix(2.0 * NoL * NoV, NoL + NoV, roughness); - return saturateMediump(v); + // 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow + float v = PREVENT_DIV0(0.5, mix(2.0 * NoL * NoV, NoL + NoV, roughness), 0.0000077); + return v; } float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV, @@ -124,18 +125,21 @@ float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV, // TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV)); float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL)); - float v = 0.5 / (lambdaV + lambdaL); - return saturateMediump(v); + // 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow + float v = PREVENT_DIV0(0.5, lambdaV + lambdaL, 0.0000077); + return v; } float V_Kelemen(float LoH) { // Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling" - return saturateMediump(0.25 / (LoH * LoH)); + // 0.0000039 = nextafter(0.25 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow + return PREVENT_DIV0(0.25, LoH * LoH, 0.0000039); } float V_Neubelt(float NoV, float NoL) { // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" - return saturateMediump(1.0 / (4.0 * (NoL + NoV - NoL * NoV))); + // 0.00001532 = nextafter(1.0 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow + return PREVENT_DIV0(1.0, 4.0 * (NoL + NoV - NoL * NoV), 0.00001532); } vec3 F_Schlick(const vec3 f0, float f90, float VoH) {