docs: Improve Clouds example performance - #2807
Conversation
|
pkg.pr.new packages benchmark commit |
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.67, 1.35, 2.88, 4.39, 5.34, 8.37, 16.18, 17.71]
line [0.71, 1.42, 2.81, 4.71, 5.50, 7.18, 16.57, 16.80]
line [0.70, 1.33, 2.72, 4.82, 5.33, 7.47, 16.10, 17.61]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.23, 0.37, 0.49, 0.59, 0.76, 0.80, 0.93, 1.09]
line [0.26, 0.40, 0.52, 0.61, 0.79, 0.79, 1.03, 1.11]
line [0.21, 0.33, 0.47, 0.54, 0.71, 0.79, 0.91, 1.02]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.68, 1.59, 2.69, 4.83, 8.54, 18.22, 39.66, 78.63]
line [0.66, 1.62, 2.91, 4.69, 8.51, 19.00, 39.27, 79.60]
line [0.60, 1.43, 3.21, 5.36, 8.74, 17.69, 37.14, 78.29]
|
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 0.88%) | ❔ Unknown |
|---|---|---|---|
| 0 | 298 | 26 | 0 |
import * as ... in PR vs import * as ... in target (did bundle size increase?):
| Test | tsdown |
|---|---|
| d_arrayOf.ts | 27.97 kB ( |
| d_builtin.ts | 30.43 kB ( |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 274.51 kB ( |
| tgpu_initFromDevice.ts | 273.97 kB ( |
| tgpu_resolve.ts | 174.80 kB ( |
| tgpu_resolveWithContext.ts | 174.74 kB ( |
| tgpu_bindGroupLayout.ts | 74.47 kB ( |
| tgpu_mutableAccessor.ts | 69.19 kB ( |
| tgpu_accessor.ts | 69.19 kB ( |
| tgpu_privateVar.ts | 67.88 kB ( |
| tgpu_workgroupVar.ts | 67.88 kB ( |
| tgpu_const.ts | 67.30 kB ( |
| tgpu_lazy.ts | 67.10 kB ( |
| tgpu_fragmentFn.ts | 39.65 kB ( |
| tgpu_fn.ts | 39.60 kB ( |
| tgpu_vertexFn.ts | 39.47 kB ( |
| tgpu_computeFn.ts | 39.17 kB ( |
| tgpu_vertexLayout.ts | 28.30 kB ( |
| tgpu_comptime.ts | 15.91 kB ( |
| tgpu_unroll.ts | 1.75 kB ( |
| tgpu_slot.ts | 1.70 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
There was a problem hiding this comment.
Pull request overview
This PR optimizes the TypeGPU docs “clouds” rendering example by shifting expensive per-pixel noise/shadow work into precomputation and by rendering clouds at a reduced resolution before compositing/upscaling to the presentation surface.
Changes:
- Adds a compute prepass to precompute a 3D density/shadow volume and switches raymarching to sample it.
- Splits rendering into two passes: half-resolution cloud render → fullscreen upscale/composite pass.
- Updates constants (noise/density sizes, extinction, render scale) and refreshes the example shader snapshot test expectations.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts | Updates snapshot expectations and call count for the new compute + 2-pass render setup. |
| apps/typegpu-docs/src/examples/rendering/clouds/utils.ts | Implements density precompute helpers, packing/unpacking, and updates raymarching to sample the precomputed volume. |
| apps/typegpu-docs/src/examples/rendering/clouds/types.ts | Adds new bind group layouts for density precompute and upscale/composite stages. |
| apps/typegpu-docs/src/examples/rendering/clouds/index.ts | Wires up compute prepass, half-res cloud target, upscale pipeline, resize handling, and updated render loop. |
| apps/typegpu-docs/src/examples/rendering/clouds/consts.ts | Introduces render scale/extinction constants and adjusts noise/density texture sizing constants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
ℹ️ No critical issues — minor cleanup suggestions inline.
Reviewed changes
Reviewed the Clouds example optimization that precomputes a 3D density/shadow texture, renders clouds at half resolution, and composites them back to the swap chain. All changed files, the updated test snapshot, and the generated WGSL were inspected; the example test passes and the shader output is valid.
- Precomputed density texture — a 256³
rgba8unorm3D texture is filled at startup via a guarded compute pipeline, packing cloud density and a precomputed shadow offset into twovec2fencodings. - Half-resolution rendering — clouds are rendered at 0.5× canvas resolution and upscaled/composited in a second pass with a 4-tap sample.
- Raymarch update — absorption is now computed as
1 - exp(-density * CLOUD_EXTINCTION * stepLength), and the olddraw(6)bug is fixed todraw(3)for the full-screen triangle. - Test snapshot — updated to reflect the compute pass plus the two render passes (
expectedCalls: 3).
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the incremental delta since the last Pullfrog review (041645b):
- Removed the now-unused
renderusage fromnoiseTexture(it is only uploaded and sampled, never rendered to). This addresses the prior nitpick about the stale'render'usage. - Removed the unused
paramsuniform binding fromprecomputeDensityLayout(and its bind group) — the compute shader never reads it. This addresses the prior nitpick about the dangling uniform. - Merged
maininto the branch, which regenerated the shader snapshot (seed→seed_1is a minifier/name-collision rename in generated WGSL — a test-only change).
All three prior Pullfrog review threads are resolved, and both substantive suggestions are correctly reflected in the current tree. The remaining // TODO: replace with >>> comment in packF32ToTwo8unorm is a pre-existing, already-resolved nitpick and not re-raised here.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (9233cd5), the "sun fix" commit a7b04b6:
- Sky shading fix — clamped the zenith tint to the below-horizon region (
SKY_ZENITH_TINT * max(-rayDir.y, 0) * 0.35), so the horizon gradient no longer darkens the sky above the horizon. - Upscale weighting — added a center tap at
UPSCALE_CENTER_WEIGHT(0.5) with the four corner taps reweighted toupscaleCornerWeight(0.125), preserving a total box-filter weight of 1.0. - Shadow consistency —
sampleDensitiesnow derivesshadowDensityfrom the same height-dependentcoverageascloudDensity, so shadow depth falls off with cloud altitude. - Renames —
precomputeNoise3d→noise3dandunpackF32ToTwo8unorm→unpackTwo8unormToF32for clarity (and to avoid the earlier minifier name collision). - Snapshot — regenerated to match the current WGSL;
clouds.test.tspasses.
The changes are confined to the example and its test; the impact scan found no external callers. No new issues.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The "zenith fix" commit changed the sky shading formula but did not regenerate the shader snapshot, so clouds.test.ts now fails.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (a7b04b6), the "zenith fix" commit fcb7d339:
- Sky shading refinement — replaced the single
max(-rayDir.y, 0) * 0.35clamp in the upscale fragment with separateup/downterms (up * 0.35 + down * 0.15), so the downward-looking hemisphere also receives a gentle tint instead of snapping to zero.
The change is confined to the example's upscale fragment shader. I ran the example test: it fails because the inline snapshot at clouds.test.ts:270 was not regenerated to match the new sky formula, and lint/format checks on the changed file pass.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The example test is failing — the shader snapshot in clouds.test.ts was not regenerated for the new sampleDensities vectorization, and it still carries the unaddressed zenith-fix sky formula.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (fcb7d33), the "more cleanup" commit 2a934986b:
- Renamed
cloudCompositeBindGroup→cloudUpscaleBindGroupfor consistency withupscalePipeline(no behavior change). - Vectorized
sampleDensitiestostd.saturate(fbmValues + coverage) - 0.5over the wholevec2f, which changes the generated WGSL shape.
Both changes are confined to the example. I ran clouds.test.ts: it fails because the inline snapshot was not regenerated. The generated code now emits let densities = (saturate((fbmValues + coverage)) - 0.5f); return vec2f(densities.x, densities.y); at the snapshot site, while the expected snapshot still holds the per-component cloudDensity/shadowDensity form (line 186). This is on top of the still-open zenith-fix mismatch at line 270. Regenerate the snapshot with pnpm vitest run --project=!browser apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts -u.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the incremental delta since the prior review (2a93498), the "test update" commit 7ed2440d:
- Regenerated the shader snapshot in
clouds.test.tsso it matches both the vectorizedsampleDensities(let densities = (saturate((fbmValues + coverage)) - 0.5f)) and the zenith-fixup/downsky formula. This resolves the previously-flagged stale snapshots.
I ran clouds.test.ts: it now passes. Both stale-snapshot threads are resolved and the previous blocking concern is cleared.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
* fix: Reject decorated element types in arrayOf * docs: Note that arrayOf rejects decorated elements other than @location * fix: Apply review suggestions --------- Co-authored-by: Michal <michal@Michals-MacBook-Pro.local>
There was a problem hiding this comment.
Important
The clouds.test.ts snapshot was only partially regenerated on this main merge, so the example test now fails.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (7ed2440d), the fifth main merge (7ba4d0f):
mainmerge — pulled in runtime shader minification (#2801), the new@typegpu/noisexoroshiro PRNG (#2830), and WGSLunroll/whitespace codegen changes that now emit brace-less iteration blocks with// ---separators.clouds.test.tsregeneration (partial) — the snapshot was updated for the PRNG change (seed_1/LCG →gpuSeed: vec2u+next()/u32To01F32), but theunrolloutput portions were left at the old braced form.
The example's own source files are byte-identical across the merge. Lint (oxlint) passes 0 warnings on the changed files.
I ran the example test — it fails with Snapshot ... mismatched because the committed snapshot still wraps the unroll output in { ... } blocks while the merged core now emits the brace-less form. Every other example test on main (liquid-glass, jump-flood, jelly-slider, etc.) was regenerated to the new brace-less format; the clouds snapshot was missed. Regenerate the snapshot, e.g. pnpm vitest run --project=!browser apps/typegpu-docs/tests/individual-example-tests/clouds.test.ts -u.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Co-authored-by: cieplypolar <103948576+cieplypolar@users.noreply.github.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (7ba4d0f), the cb315c62 "test update" commit:
- Regenerated the shader snapshot in
clouds.test.tsto the merged core's brace-lessunrollWGSL format (// ---separators infbmand the upscale fragment, plus theunroll/getRayDirectionfunction reorder). This is exactly the action requested by the prior blocking finding, soclouds.test.tspasses again against the merged core.
The example's own source files are unchanged in this delta. I ran clouds.test.ts — it now passes (the only stderr output is the known pre-existing >> WGSL deprecation warning tied to the >>> TODO at utils.ts:84, which has been tracked as a resolved nitpick and is not re-raised). No new issues.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
|
||
| export const precomputeDensity = (x: number, y: number, z: number) => { | ||
| 'use gpu'; | ||
| const uvw = (d.vec3f(x, y, z) + 0.5) / DENSITY_TEXTURE_SIZE; |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the incremental delta since the prior Pullfrog review (cb315c62), the single "review fixes" commit d871e8e2, which applies all the substantive human-review suggestions that landed earlier:
- Converted
getRayDirectionfrom atgpu.fnto a shell-less'use gpu'function with a single vectorized NDC expression — it is now emitted as a standalone WGSLfncopy in each pipeline that uses it. - Bound the params uniform directly (
params: paramsUniforminstead of.buffer) — both are valid bind values per the layout's uniform binding type. - Hoisted the
targetsentry above the shader entry-points in both pipelines. - Extracted
UPSCALE_CORNER_WEIGHT(0.125) intoconsts.ts, replacing the localupscaleCornerWeight. - Dropped the explicit
createView()on the cloud target —createCloudTargetnow returns the texture, used directly as both the sampled bind-group resource and the color attachment (the color-attachment type accepts a render-usage texture andunwrapAttachmentViewhandles it). - Replaced
quantized >> 8withquantized >>> 8and deleted the TODO comment —>>>ond.u32is now supported by the merged core (maps to WGSL>>for a u32 logical shift), which finally resolves the long-carried nitpick and silences the[deprecated] quantized >> 8stderr warning from earlier runs. - Vectorized
sampleDensitiestoreturn std.saturate(fbmValues + coverage) - 0.5;and regenerated the inline snapshot for the changed WGSL.
I re-ran clouds.test.ts — it passes with no deprecation warnings, and oxlint --max-warnings=0 is clean on the changed files. All prior Pullfrog threads remain resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Changes:
Frame time ("very high" preset, macbook m3 pro display resolution):
Side-by-side comparison (left is the updated version):
Screen.Recording.2026-08-07.at.20.27.09.mov