Skip to content

refactor: gate MountainLit NormalMapMix behind _NORMALMAP - #9836

Draft
eordano wants to merge 1 commit into
devfrom
perf/mountainlit-normalmap-gate
Draft

refactor: gate MountainLit NormalMapMix behind _NORMALMAP#9836
eordano wants to merge 1 commit into
devfrom
perf/mountainlit-normalmap-gate

Conversation

@eordano

@eordano eordano commented Aug 22, 2026

Copy link
Copy Markdown
Member

What

Wraps the NormalMapMix call in MountainLitInput.hlsl in #ifdef _NORMALMAP (else normalTS = (0,0,1)).

Why it is provably a no-op today

  • normalTS is only consumed inside #ifdef _NORMALMAP (MountainLitForwardPass.hlsl InitializeInputData); the DepthNormals pass samples its own normal independently. The keyword is already declared (shader_feature_local), and the sole shipping material runs keyword-off.
  • Verified with a D3D11 static gate (editor ShaderData.CompileVariant, keyword-off ForwardLit): compiled DXBC is bit-identical before/after — the compiler already dead-code-eliminates the chain.

Why land it anyway

Source-level the keyword-off variant reads as paying 2 texture samples + unpack + normalize per terrain pixel for an unread value; the guard makes the deadness explicit and protects future variants (decal/debug paths that start reading surfaceData) from silently paying it for real. Zero behavior, property, keyword-set, or batching surface.

Found during a shader-optimization audit; landing separately from the measurable optimizations since it needs no measurement.

normalTS is only consumed under _NORMALMAP (InitializeInputData); in keyword-off variants the splat normal-map sampling chain feeds a value that is never read. D3D11 DXBC for the shipping keyword-off ForwardLit variant is bit-identical before/after - source hygiene plus insurance for future variants that start reading surfaceData.
@eordano
eordano requested review from a team as code owners August 22, 2026 14:20
@github-actions
github-actions Bot requested a review from anicalbano August 22, 2026 14:20
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 22, 2026
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Build skipped — no changes detected under Explorer/.

Lint

No C# files changed — lint ratchet skipped.

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25365 0 13
PlayMode ✅ Passed 243 0 37

@github-actions

Copy link
Copy Markdown
Contributor

Slack notification sent to #explorer-ext-contributions for external review.
To re-send, delete this comment and re-add the ext-contribution label.

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR Review: perf: gate MountainLit NormalMapMix behind _NORMALMAP

STEP 2 — Root-cause check: PASS

This PR is not fixing a bug — it's making a dead-code path explicit at the source level. The NormalMapMix call produces normalTS, which is only consumed inside #ifdef _NORMALMAP in InitializeInputData (MountainLitForwardPass.hlsl:72). The #else branch (MountainLitForwardPass.hlsl:74) uses input.normalWS directly and ignores normalTS entirely. The DepthNormals pass has its own local normalTS sampling at MountainLitDepthNormalsPass.hlsl:98. The change correctly gates computation to match its consumption guard — not a symptom fix.

STEP 3 — Design & integration: PASS

No new long-lived units, systems, or lifecycle management. This is a 7-line preprocessor guard in an HLSL shader file. The guard is placed at the call site in InitializeSimpleLitSurfaceData (line 254), which mirrors the consumption guard in InitializeInputData (MountainLitForwardPass.hlsl:70-74). This is the correct placement — gating at the call site rather than inside NormalMapMix itself keeps the function reusable for future passes that might need the normal under _NORMALMAP.

The _NORMALMAP keyword is declared as shader_feature_local in MountainLit.shader (ForwardLit and DepthNormals passes), and the sole shipping material runs keyword-off. No design concerns.

STEP 4 — Member audit: N/A

HLSL shader code — no C# public properties or accessors to audit.

STEP 5 — Line-level review

Code correctness:

  • half3(0.0h, 0.0h, 1.0h) is the correct tangent-space identity normal ("no perturbation").
  • ✅ The #ifdef _NORMALMAP guard correctly matches the consumption guard in InitializeInputData.
  • ✅ The comment is accurate, explains why the guard exists, and does not narrate caller behavior.
  • ✅ No resource leaks, no security issues, no performance regressions.
  • ✅ No issues found across any of the blocking-issue categories (1–11) or design smell categories.

Observation (not a finding): The NormalMapMix function body (lines ~189–199) still contains commented-out #if defined(_NORMALMAP) / #endif guards from a previous iteration. This is pre-existing dead code, not introduced by this PR. No action required here.

CI: ⚠️ Semantic title check failing

[P2] The semantic / title-matches-convention check is failing because perf is not in the repo's accepted type list:

Available types: break, chore, docs, feat, fix, refactor, revert, style, test, release

Suggested fix: rename the PR title to refactor: gate MountainLit NormalMapMix behind _NORMALMAP — the change restructures shader code to be explicit about preprocessor guards without changing behavior, which fits refactor semantics.

Security review

No security concerns. This is a pure HLSL preprocessor guard change — no network, authentication, user input, or data processing code is involved.


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Single HLSL shader file, 7-line preprocessor guard addition — no ECS, async, or C# changes.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by unknown (<@unknown>) via Slack

@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9836, run #32580290117

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times. The values are the median, and (min–max) is the lowest and highest of those runs — a wide range means the metric is noisy and small differences are not trustworthy.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a real difference: larger than both 3% and the run-to-run range.
  • ⚪ within noise — the difference is smaller than how much the build varies between its own runs, so it cannot be told apart from random variation. Treat it as no change.
  • Exceptions per run — the average number of exceptions in a run's log; more than the baseline is flagged 🔴 even when frame times look fine. The Exception breakdown under each table groups them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.

Intel Core i5

Metric Baseline Change Δ Result
Samples 2309 (×3) 2407 (×3)
CPU average 38.7 ms (35.5–38.8) 37.2 ms (37.1–38.1) -1.4 ms ⚪ within noise
CPU 1% worst 339.3 ms (269.4–350.7) 303.4 ms (291.4–309.5) -35.9 ms ⚪ within noise
CPU 0.1% worst 358.7 ms (353.5–360.8) 321.5 ms (311.7–323.9) -37.2 ms 🟢 10% faster
GPU average 7.8 ms (7.8–7.9) 8.4 ms (8.4–8.5) 0.6 ms 🔴 8% slower
GPU 1% worst 18.7 ms (18.6–19.0) 19.2 ms (18.6–20.2) 0.5 ms ⚪ within noise
GPU 0.1% worst 19.3 ms (19.1–19.6) 20.8 ms (20.8–21.4) 1.5 ms 🔴 8% slower
Exceptions per run 65 0 -65 🟢 fewer errors
Exception breakdown
Exception Baseline (3 runs) Change (3 runs)
[UI] DllNotFoundException 192 0
[ENGINE] ObjectDisposedException 3 0

Apple M1

Metric Baseline Change Δ Result
Samples 3981 (×3) 4043 (×3)
CPU average 22.5 ms (21.5–22.6) 22.2 ms (21.9–22.8) -0.3 ms ⚪ within noise
CPU 1% worst 208.5 ms (203.9–222.5) 232.0 ms (214.1–233.1) 23.6 ms 🔴 11% slower
CPU 0.1% worst 229.7 ms (226.0–229.9) 234.0 ms (231.6–235.3) 4.3 ms ⚪ within noise
GPU average 3.4 ms (2.3–9.1) 2.6 ms (2.1–10.0) -0.7 ms ⚪ within noise
GPU 1% worst 35.9 ms (34.0–36.4) 35.3 ms (34.2–35.9) -0.6 ms ⚪ within noise
GPU 0.1% worst 37.0 ms (35.4–38.4) 36.4 ms (36.3–37.9) -0.5 ms ⚪ within noise
Exceptions per run 0 0 0 ⚪ none new

@eordano
eordano marked this pull request as draft August 22, 2026 15:20
@eordano eordano changed the title perf: gate MountainLit NormalMapMix behind _NORMALMAP refactor: gate MountainLit NormalMapMix behind _NORMALMAP Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-contribution Identifies a contribution which was not initiated by a Unity Developer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants