Skip to content

perf: replace pow(x, 5.0) with multiplies in Schlick Fresnel#8774

Merged
willeastcott merged 6 commits into
mainfrom
perf/schlick-fresnel-no-pow
May 26, 2026
Merged

perf: replace pow(x, 5.0) with multiplies in Schlick Fresnel#8774
willeastcott merged 6 commits into
mainfrom
perf/schlick-fresnel-no-pow

Conversation

@willeastcott
Copy link
Copy Markdown
Contributor

@willeastcott willeastcott commented May 24, 2026

Summary

  • Rewrites pow(1.0 - saturate(cosTheta), 5.0) as a manual chain of multiplies (x2 = x*x; fresnel = x2*x2*x) in both getFresnel and getFresnelCC.
  • Extracted as a local pow5() helper in fresnelSchlickPS (GLSL + WGSL).

pow(x, 5.0) compiles to exp2(5.0 * log2(x)) on most hardware — two transcendental ops. The four multiplies that compute x^5 directly are bit-exact and consistently cheaper, especially on mobile GPUs where transcendentals run at a lower throughput. With clearcoat enabled both Fresnel functions are invoked per light per fragment, so the saving compounds in lit scenes.

This is a standard industry optimization — Unreal, Unity, and Filament all use the same form for Schlick.

SPIR-V verification

Compiled both shaders with glslangValidator -V -Os -H (same opt tier drivers apply):

OLD (pow(x, 5.0)) NEW (pow5(x))
ExtInst Pow 1 0
OpFMul in fresnel block 2 5

The Pow ExtInst survives -Os in the OLD shader, meaning the driver would otherwise lower it to a log2 + mul + exp2 sequence. The NEW shader emits three OpFMuls in its place, with pow5 inlined by the optimizer. So the source-level rewrite is doing real work, not duplicating something the compiler already does.

Test plan

  • No visual change in lit examples (Fresnel output is mathematically identical to the prior code).
  • Spot-check graphics_clustered-lighting and a clearcoat material — pixel diff should be zero modulo last-bit FP noise.
  • npm test and npm run lint pass (only pre-existing unrelated lint error in utils/esbuild-build-target.mjs).

🤖 Generated with Claude Code

pow(x, 5.0) compiles to log2 + mul + exp2 on most hardware (two
transcendentals); the four multiplies that compute x^5 directly are
bit-exact and consistently cheaper, especially on mobile GPUs where
transcendentals are slower. Applied to both the main getFresnel and
clearcoat getFresnelCC, in both GLSL and WGSL chunks.

Output is mathematically identical to the prior implementation; no
visual change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an inline comment at each call site so a future reader does not
"clean up" the manual multiply chain back into pow(x, 5.0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the pre-existing square() helper. Collapses the four duplicated
x*x*x*x*x expansions in fresnelSchlick into single pow5() calls and
keeps the rationale comment in one place. Same idea as Filament's
common_math.glsl pow5 helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the Schlick Fresnel term in the lit fragment shader chunks by replacing pow(x, 5) with an x^5 multiply chain, reducing reliance on transcendental ops in both GLSL and WGSL shader paths.

Changes:

  • Added a pow5 helper to the lit fragment base chunk (GLSL + WGSL).
  • Updated getFresnel and getFresnelCC in fresnelSchlick chunks to use pow5(...) (GLSL + WGSL).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/scene/shader-lib/wgsl/chunks/lit/frag/fresnelSchlick.js Switch Fresnel exponent from pow(..., 5) to pow5(...).
src/scene/shader-lib/wgsl/chunks/lit/frag/base.js Introduce pow5 helper in WGSL base chunk.
src/scene/shader-lib/glsl/chunks/lit/frag/fresnelSchlick.js Switch Fresnel exponent from pow(..., 5) to pow5(...).
src/scene/shader-lib/glsl/chunks/lit/frag/base.js Introduce pow5 helper in GLSL base chunk.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scene/shader-lib/glsl/chunks/lit/frag/base.js Outdated
Comment thread src/scene/shader-lib/wgsl/chunks/lit/frag/base.js Outdated
Per review feedback: basePS is user-overridable, so adding a required
symbol to it would silently break any project that overrides basePS
when fresnelSchlickPS calls into it. pow5 has only two callers and they
are both in fresnelSchlickPS, so it belongs there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 4 changed files in this pull request and generated no new comments.

pow5 lives in fresnelSchlickPS now, so base.js no longer needs to be
touched. Reverting the operator-spacing tidy keeps this PR scoped to
the Fresnel perf change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willeastcott willeastcott merged commit f224069 into main May 26, 2026
8 checks passed
@willeastcott willeastcott deleted the perf/schlick-fresnel-no-pow branch May 26, 2026 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: graphics Graphics related issue performance Relating to load times or frame rate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants