Skip to content

Unify shader sources: collapse TS duplication, embed C++ shader from a real file, add layout parity tests - #458

Merged
ford442 merged 2 commits into
mainfrom
claude/shader-unification
Jul 26, 2026
Merged

Unify shader sources: collapse TS duplication, embed C++ shader from a real file, add layout parity tests#458
ford442 merged 2 commits into
mainfrom
claude/shader-unification

Conversation

@ford442

@ford442 ford442 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to issue #434 (closed without actually fixing the drift). The drift turned out to be worse than the issue text suggested — real, verified duplication existed within the TS side alone, before even getting to C++:

  • src/webgpu/shaders/pbrBlocks.ts (675 lines) was marked @deprecated ... thin compatibility re-export but actually held its own independently-drifted copy of the block shader. It was dead at runtime, but tests/shader-optimizations.test.ts imported it directly, bypassing the real production path — so those regression tests were silently validating a shader that doesn't ship. Collapsed to an actual re-export; test repointed to the canonical block/blockShader.ts (every assertion verified to still hold).
  • That dead copy had diverged from production in real, non-cosmetic ways (different metal-mask sampling, different glass-transmission formula), and was the only place still calling applyMaterialParticleInteraction. Chasing that down surfaced a genuine, separate bug: viewMaterials.ts writes fUniforms.particleMaterialType every frame, but the shipping fragment shader never reads it — the gold/chrome/cyber tinted-particle-hit visual it was meant to drive is silently dead. Flagged with a KNOWN BUG comment, not fixed — changing shipped visual behavior needs a human to actually look at the result, not a plumbing refactor.
  • src/webgpu/shaders/_legacy/ (4 files, confirmed zero external imports) deleted.
  • A second, differently-drifted ParticleMaterialInteractionWGSL copy (dead, unused applyMaterialParticleInteraction WGSL) removed from src/webgpu/particleMaterialInteraction.ts; its CPU-side ParticleMaterialInteraction class (actually used by viewWebGPU.ts) is untouched.

With that cleaned up, "true single source" work:

  • TS: static (non-parametric) block-shader WGSL — vertex, fragment main(), PBR functions, the FragmentUniforms struct, particle-material-interaction — extracted into real .wgsl files under src/webgpu/shaders/wgsl/block/, loaded via Vite's ?raw (vite/client types already present, no new config). getSimpleTextureSamplingWGSL() stays TS-generated since it interpolates runtime config values into the shader text — genuinely not static.
  • C++: gpu_renderer.cpp's inline kBlockWgsl string moved to a real file, cpp/src/shaders/block/block.wgsl. New scripts/generate-cpp-shaders.mjs (pure Node, no emcc needed) embeds every cpp/src/shaders/**/*.wgsl into a gitignored, generated cpp/src/generated/shader_sources.h, wired into both scripts/build-cpp.mjs and cpp/CMakeLists.txt.
  • Important, stated explicitly in the docs: this does not claim the TS and C++ block shaders are the same program — they're intentionally different (C++'s 128-byte UniformData vs TS's 224-byte FragmentUniforms; no PBR/ghost/dissolve/audio-glow/particle-interaction on the C++ side). That's documented as intentional scope reduction in cpp/README.md already, and full parity is explicitly the issue's own "Phase B/C." What changed is that each shader now has exactly one real file as its source on both sides, so future parity work has real files to diff against instead of re-deriving WGSL from a JS/C++ string.
  • New tests/block-uniform-layout.test.ts: parses the WGSL struct's byte-offset comments and cross-checks them against BLOCK_FRAGMENT_UNIFORM_OFFSETS (fails on drift), and separately parses the C++ UniformData struct and the WGSL Uniforms struct to confirm matching byte sizes (128).
  • Extended tests/render-metrics.test.ts with a parity check between cpp/src/board_metrics.h's constants and renderMetrics.ts/renderConfig.ts — closes the "partially synced" gap called out in the issue.
  • cpp/README.md: new "Shader source of truth" section, "Adding a block-shader uniform" workflow for both backends, updated directory layout, roadmap items marked done.

Explicit non-goals (matching the issue's own framing): no attempt at full PBR parity in the C++ shader (that's the stated later phase); no WGSL validator (naga/tint) in CI — no clean, light npm-installable option found, and it was marked optional/lowest-priority in the issue.

Test plan

  • npm run typecheck — passes
  • npx eslint . — passes
  • npm test — 211/211 passing (202 existing + 9 new: uniform-layout parity, C++/TS render-metrics parity, build-cpp wiring checks)
  • npm run build — production bundle builds successfully with ?raw imports resolving
  • npm run build:all — still exits 0 without emcc present; confirms generate-cpp-shaders.mjs doesn't require the toolchain
  • node scripts/generate-cpp-shaders.mjs — runs standalone, produces cpp/src/generated/shader_sources.h with the shader text verbatim
  • Full npm run cpp:release compile with a real emsdk 5.0.7 install (needs CI or a machine with emsdk — not available in this sandbox)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsXNcchXYG3RQU5hpoT5Ar


Generated by Claude Code

claude added 2 commits July 26, 2026 12:26
… files

- src/webgpu/shaders/pbrBlocks.ts was marked @deprecated as a "thin
  compatibility re-export" but actually held its own independently-drifted
  675-line copy of the block shader, only exercised by
  tests/shader-optimizations.test.ts (which imported it directly, bypassing
  the real production path). Collapse it into an actual re-export of
  block/blockShader.ts, and repoint the test at the canonical shader (every
  assertion verified to still hold against it).
- Delete src/webgpu/shaders/_legacy/ (zero external imports, README already
  said unused).
- Delete the dead ParticleMaterialInteractionWGSL/applyMaterialParticleInteraction
  WGSL in src/webgpu/particleMaterialInteraction.ts — only pbrBlocks.ts/_legacy
  called it; the CPU-side ParticleMaterialInteraction class is untouched.
- Flag (don't fix) a real bug the drift uncovered: viewMaterials.ts writes
  fUniforms.particleMaterialType every frame but the shipping fragment shader
  never reads it, so the gold/chrome/cyber tinted-particle-hit visual it was
  meant to drive is dead. Left as a KNOWN BUG comment for a follow-up that can
  actually review the visual change.
- Extract the static (non-parametric) block shader WGSL bodies — vertex,
  fragment main, PBR functions, the FragmentUniforms struct, and the
  particle-material-interaction functions — into real .wgsl files under
  shaders/wgsl/block/, loaded via Vite's ?raw (vite/client types already in
  tsconfig.json). getSimpleTextureSamplingWGSL stays TS-generated since it
  interpolates runtime BlockTextureConfig values.

Verified: typecheck, lint, and all 202 tests pass; production build succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsXNcchXYG3RQU5hpoT5Ar
…t parity tests

- Move gpu_renderer.cpp's inline kBlockWgsl string into cpp/src/shaders/block/block.wgsl,
  a real file (matching the TS side's shaders/wgsl/block/*.wgsl from the previous commit).
- Add scripts/generate-cpp-shaders.mjs: pure Node, no emcc required, embeds every
  cpp/src/shaders/**/*.wgsl into gitignored cpp/src/generated/shader_sources.h. Wired
  into scripts/build-cpp.mjs's main() (runs even without emcc) and cpp/CMakeLists.txt
  (generate_cpp_shaders custom target).
- This does not make the TS and C++ block shaders the same program — they're
  intentionally different (C++'s 128-byte UniformData vs TS's 224-byte
  FragmentUniforms, no PBR/ghost/dissolve/particle-interaction on the C++ side).
  What changes is that each shader now has exactly one real file as its source,
  instead of a hand-copied string being the only copy.
- Add tests/block-uniform-layout.test.ts: parses the WGSL FragmentUniforms struct's
  byte-offset comments and cross-checks them against BLOCK_FRAGMENT_UNIFORM_OFFSETS
  (fails if they drift apart), and separately parses the C++ UniformData struct and
  the WGSL Uniforms struct to confirm they describe the same byte size (128).
- Extend tests/render-metrics.test.ts with a parity check between
  cpp/src/board_metrics.h's constexpr constants and renderMetrics.ts/renderConfig.ts.
- Document the new layout, the "how to add a uniform" workflow for both backends,
  and mark the relevant roadmap items done in cpp/README.md.

Verified: typecheck, lint, all 211 tests, npm run build and build:all (without emcc)
all pass; node scripts/generate-cpp-shaders.mjs runs standalone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsXNcchXYG3RQU5hpoT5Ar
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ford442, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb1c9956-90b7-451f-89f9-035ccef8ea8c

📥 Commits

Reviewing files that changed from the base of the PR and between afee31d and 93fcce6.

📒 Files selected for processing (31)
  • .gitignore
  • cpp/CMakeLists.txt
  • cpp/README.md
  • cpp/src/gpu_renderer.cpp
  • cpp/src/shaders/block/block.wgsl
  • eslint.config.js
  • scripts/build-cpp.mjs
  • scripts/generate-cpp-shaders.mjs
  • src/webgpu/particleMaterialInteraction.ts
  • src/webgpu/shaders/_legacy/README.md
  • src/webgpu/shaders/_legacy/blocks.ts
  • src/webgpu/shaders/_legacy/main.ts
  • src/webgpu/shaders/_legacy/premiumBlocks.ts
  • src/webgpu/shaders/_legacy/underwaterBlocks.ts
  • src/webgpu/shaders/block/fragmentMain.wgsl.ts
  • src/webgpu/shaders/block/pbrFunctions.wgsl.ts
  • src/webgpu/shaders/block/uniforms.ts
  • src/webgpu/shaders/block/vertex.wgsl.ts
  • src/webgpu/shaders/particleMaterialInteraction.ts
  • src/webgpu/shaders/pbrBlocks.ts
  • src/webgpu/shaders/wgsl/block/fragmentMain.wgsl
  • src/webgpu/shaders/wgsl/block/particleMaterialInteraction.wgsl
  • src/webgpu/shaders/wgsl/block/pbrFunctions.wgsl
  • src/webgpu/shaders/wgsl/block/uniforms.wgsl
  • src/webgpu/shaders/wgsl/block/vertex.wgsl
  • src/webgpu/viewMaterials.ts
  • tests/block-uniform-layout.test.ts
  • tests/build-cpp.test.ts
  • tests/render-metrics.test.ts
  • tests/shader-optimizations.test.ts
  • tsconfig.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/shader-unification

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ford442
ford442 merged commit 6c8ee77 into main Jul 26, 2026
5 checks passed
@ford442
ford442 deleted the claude/shader-unification branch July 26, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants