Repository navigation
Conversation
… (PRD-493 phase 1) Sixteen same-size uncompressed layers bound one sampler each, which is what makes WebGPU's 16 sampled textures a stage the real cap on Android and iOS — the hosts with no KTX2 transcoder, where nothing stacks today. Each set now becomes one DataArrayTexture whose layers are filled by a renderer.copyTextureToTexture out of their own textures: the pixels never come back to the CPU, and the array is created with source.dataReady = false so three never uploads placeholder bytes over the copies. Mixed sizes still fall back, and TN_TERRAIN_SPLAT layers= samplers= stacked= reports what the material actually costs, one line per material. Also corrects the stale @constraint: the recipe is export_terrain_layers in export_world.py, and the cap is now a sampler count the marker names. capabilities.json and the generated reference carry both.
The material hard-coded metalness 0 / roughness 0.92 and gave every layer it, so a Megascans-grade layer's own occlusion, roughness and metalness never reached the screen. A layer with "orm": true in the table now reads <id>_orm.jpg as linear data (r occlusion, g roughness, b metalness) through the same stacked array and the same blend weights as its colour; a layer without one uses the roughness and metalness the table states. A layer that states neither now throws and names the recipe, rather than falling back to a number in the package — the constant is gone. The recipe writes _orm beside _diff and _nrm in both copies of export_world.py, and refuses a table whose ORM layer names no orm source.
… captures
Unit specs prove the mechanism in node; this example proves it on a real
WebGPU adapter. The package is sixteen layers with an albedo, a normal and an
ORM map each — 48 uncompressed JPEGs — written by the shipped
export_terrain_layers recipe from a table and map files a game would author,
so the thing under test is a DCC export, not a second format.
The run is green: TN_TERRAIN_SPLAT layers=16 samplers=4 stacked=3, 180 frames,
a non-blank changing viewport, and the scenario asserts those numbers through
material.userData rather than trusting a screenshot. The same package on
develop never reaches a frame:
Uncaptured WebGPU GPUValidationError: The number of samplers (34) in the
Fragment stage exceeds the maximum per-stage limit (16).
Captures for the judge, same package and same pose:
.afk/scratch/prd493-captures/phase-1-2/{before,after}.png (4 KB uniform vs a
textured 16-band surface). Not a Machinefall result, so AC-1 stays open.
The browser lane proves the stacking; the hosts that matter most are the ones with no KTX2 transcoder, and there nothing stacked before. This gives the example a native identity (threenative.config.ts, one description read by both the web dev server and `threenative build`) and the same assertions as a desktop scenario: sixteen layers, at most four samplers, three stacked sets. orbitSeconds is new because a native run that renders one frozen frame would otherwise satisfy every cost assertion; the camera pose has to advance. --target desktop on the built host: pass, layers=16 samplers=4 stacked=3, orbitSeconds 2.03 -> 5.03, diagnostics clean, 0 console errors. The web scenario with the same assertion is pass on the software-and-hardware WebGPU recipe; world-terrain-splat.spec.ts 11/11.
The hosts this PRD is for cannot see KTX2, so nothing stacked there, and nothing proved it: the browser lane cannot fail on a path the browser never takes. terrain-splat-array builds a sixteen-layer package with an albedo, a normal and an ORM map per layer — the shape Android and iOS ship — through loadTerrainSplat, and the scene throws unless the marker reads layers=16 samplers=4 stacked=3, so the row fails on a regression to one sampler per map rather than merely looking different. The 48 maps are DataTextures derived from a pure function of their cell and the mask grid gives each layer its own region, so both lanes derive the same frame from nothing but the scene file: the desktop host matches the browser reference at pixelMismatchRatio 0 and perceptualDeltaE 0 over 1280x720. The Android leg of the row is left open, not claimed: :app:verifyV8Dependency refuses to bundle without a V8 build receipt that only an hours-long source cross-compile writes, and the emulator was up and waiting when it stopped. Gates: parity --target web pass (reference captured); parity --target desktop pass, 0/0 metrics, 0 gpu validation errors; runtime-native conformance suite 76/76; pnpm typecheck and pnpm lint exit 0; pnpm check:docs exit 0.
Contributor
Author
4 of 10 tasks
Contributor
Author
|
Consolidated into #437 (single runbook PR). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Terrain layers past sixteen textures (PRD-493)
Why: a splat terrain's layers are one texture each until they stack. Compressed layers already
collapse into one array texture per set; uncompressed ones did not, so any game without a KTX2 cook —
and every Android and iOS build, which has no transcoder — bound one sampler per map and hit WebGPU's
16 sampled textures a stage. The
@constraintalso named a recipe that does not exist, and thematerial hard-coded
roughness: 0.92for every layer, so a Megascans-grade layer's own ORM neverreached the screen.
PRD:
docs/PRDs/open-world/PRD-493-terrain-layers-past-sixteen-textures.mdChecklist
copyTextureToTextureper layer (no CPU pixel read); mixed sizes still fall back.proof:
packages/core/__tests__/world-terrain-splat.spec.ts9/9 green;pnpm typecheckandpnpm lintexit 0.@constraintand recipe name corrected, manifest regenerated bypnpm build.proof:
capability-manifest.spec.ts+generate-capability-reference.spec.ts28/28 green.orm: truein the table drives roughness/AO/metalness from<id>_orm; a layer without one uses the table's numbers and the 0.92 look constant is gonefrom the package; the recipe writes
_ormbeside_diff/_nrmin both copies.proof:
world-terrain-splat.spec.ts11/11,export-world.spec.ts9/9 on Blender 5.2.0 LTS.terrain-splat-arraybuilds the 16-layer uncompressed package and throws unless the markerreads
layers=16 samplers=4 stacked=3.proof:
pnpm parity --target web --only-tests terrain-splat-arraycaptured the reference;--target desktoppass withpixelMismatchRatio: 0,perceptualDeltaE: 0, 0 GPU validationerrors; runtime-native conformance suite 76/76.
:app:verifyV8Dependencyneedsthird_party/v8-android/build-receipt.json, written only by aV8 source cross-compile for arm64-v8a + x86_64. The emulator was up (API 35, x86_64) and the
lane reached gradle, so the blocker is that dependency, not the device.
gpuMainp95.apps/clientis aprivate repo this branch cannot see, so neither was run; both are listed under
## Blocked onin the PRD rather than claimed here.
How the stacking works
A game passes
renderer: ctx.renderer; without it every layer keeps its own sampler andTN_TERRAIN_SPLAT layers= samplers= stacked=says what that costs.Proof on a GPU
examples/prd493-terrain-splatis sixteen layers × (albedo, normal, ORM) = 48 uncompressed JPEGs,written by the shipped
export_terrain_layersrecipe. On the web recipe and on the native desktophost it renders with
layers=16 samplers=4 stacked=3; the same package ondevelopnever reaches aframe:
Captures for a judge — before/after, same pose — are in
/home/joao/projects/threenative/threenative-engine/.afk/scratch/prd493-captures/:phase-1-2/(package on develop vs this branch) and
phase-3/(browser reference vs the desktop-native frame).Not claimed
The Android row, and Machinefall's own walk. Neither is ticked, and the PR stays a draft until AC-1
has a judge.