Repository navigation
[Platform] Define renderer capability tiers and validate every fallback #138
Description
Parent: #126
Related existing platform issues: #18, #55, #74
Problem
Bloom targets Metal, DX12, Vulkan, WebGPU, Android, Apple mobile/spatial/watch platforms, but renderer feature behavior is not defined as one capability contract. Desktop defaults can exceed adapter limits (#55), some platforms contain stubs/no-ops, and CI does not execute every target. High-end work will worsen this unless fallbacks are explicit and testable.
Outcome
Data-driven renderer capability tiers selected from actual adapter features/limits, queryable by games, forceable in tests, and covered by a platform/fallback matrix.
Tier contract
Define names based on capabilities rather than marketing; an initial shape may be:
- Baseline: conventional material bindings, CPU draws, reduced MRT/post-FX, conventional shadows, software/screen-space GI fallback.
- Modern: storage-buffer scene data, indirect draws, texture-array/paged indirection, full temporal/post-FX path.
- High-end: descriptor-indexed resources, visibility shading, virtual geometry/shadows, ray-query enhancements.
The implementation may choose different names/count, but each tier must list required features and limits plus per-feature fallback. Never infer high-end solely because the machine is “desktop.”
Capability report
At initialization, produce a structured report containing:
- adapter identity, backend, driver where available;
- all requested vs available features and relevant limits;
- selected tier and reason;
- per-system path: geometry, materials, shadows, GI, reflections, AA/upscale, texture format, path tracing;
- disabled/limited features and their fallback;
- shader compiler state on Windows (DXC/FXC and ray-query consequences).
Expose this read-only report through a Perry-safe TypeScript API and concise startup log. A requested unsupported feature returns a result/error; it may not silently no-op.
Device creation
- Base required limits on
adapter.limits()and chosen tier requirements. - Request only features actually used by the selected tier.
- If preferred-tier device creation fails, retry a documented lower tier and record the cause.
- Add
BLOOM_FORCE_RENDER_TIER(or equivalent) for testing lower paths on capable hardware.
CI/qualification matrix
- Compile every supported target that can be compiled in hosted CI.
- Run headless/golden smoke at forced tiers on available desktop backends.
- Run web in a browser with a real canvas/WebGPU adapter, not only
cargo check. - Add scheduled/device-farm or documented release qualification for Android/iOS/tvOS/visionOS/watchOS where hosted runtime execution is unavailable.
- Treat deliberate platform stubs as an explicit capability response and document scope.
Acceptance criteria
- A single table in docs is generated or validated from the same tier definitions used by code.
- Forced lower tiers pass the relevant quality corpus on a high-end adapter.
- An adapter below a preferred limit starts on a lower tier instead of failing device creation.
- TypeScript can query every material rendering feature needed to choose content/settings without parsing logs.
- No public renderer setter silently succeeds when the active platform cannot implement it.
- Platform workflow covers macOS/Metal, Windows/DX12, Linux/Vulkan, web/WebGPU, and compile/release checks for declared mobile/Apple targets.
- Capability snapshots are uploaded with quality/performance results.
Likely files
- device creation under
native/*/src/lib.rs - new shared capability model under
native/shared/src/renderer/ src/core/public types/APItools/validate-ffi.js.github/workflows/test.yml, platform docs
Non-goals
- Identical images/performance on every tier.
- Requiring high-end features for engine startup.
- Keeping a platform target “supported” through silent no-op parity.
Dependencies
Should land early; render-foundation issues consume this contract.
Activity
proggeramlug commentedon Jul 28, 2026
Capability/fallback finding from the realtime PT qualification added at
1041a06:A supported ray-query adapter currently cannot run PT independently when SSGI is disabled. The frame graph schedules
accel_rebuild, card capture, SDF/card preparation, and related shared inputs only whenFRAME_FEATURE_SSGIis present.pt_active()and the PT graph node depend only on the PT mode and ray-query support, soset_path_tracing(2)plusset_ssgi_enabled(false)reports PT as supported/active but never prepares the TLAS/geometry inputs; accumulation remains at zero and the request effectively becomes a silent no-op.The new #135 diagnostics test deliberately retains the established SSGI infrastructure gate and documents the coupling; it does not broaden that capture-only patch into a scheduler rewrite.
This fits #138 acceptance directly. Recommended implementation scope:
SSGI || PT, with only the resources each consumer needs), rather than ownership by the SSGI feature bit.proggeramlug commentedon Jul 28, 2026
The PT-on/SSGI-off coupling reported above is fixed in draft PR #147 at
751be72.New ownership contract:
accel_rebuild+card_capture, then the established SDF/clipmap/WSRC/card-light chainaccel_rebuild+card_captureonlyPathTracingMode::Offin the frame-plan key, so they do not schedule ray-scene preparationRuntime qualification now reports
ray_scene_preparationasdisabled,ssgi,pt, orssgi+pt.Net-positive evidence:
sdf_bake,scene_sdf_clipmap,wsrc_bake,card_light) compared with the previous required workaround of leaving SSGI enabled.This resolves the concrete silent-no-op finding, while #138 remains open for the broader public capability/result API and forced-tier platform matrix.
proggeramlug commentedon Jul 28, 2026
Implemented and pushed the first capability-tier slice in PR #147 at
aa983c9.What landed:
baseline,modern, andhigh-endrenderer tiers selected only from grantedwgpu::Featuresandwgpu::Limits.docs/renderer-capability-tiers.mdis byte-for-byte validated against it.BLOOM_FORCE_RENDER_TIER=baseline|modern|high-end. A supported lower request constrains the active material tier, GPU-driven submission, hardware ray-query GI/reflections, and path tracing. Unsupported upward requests are rejected with a diagnostic.Regression evidence:
./scripts/ci-check.sh --quick: pass (303 shared unit tests, 44 golden/integration tests, 3 render-target tests, strict clippy, wasm check, FFI/contracts, quality governance).BLOOM_FORCE_RENDER_TIER=baseline ... --test golden_render: 44 passed, 0 failed, 2 pre-existing ignored hardware diagnostics.BLOOM_FORCE_RENDER_TIER=modern ... --test golden_render: 44 passed, 0 failed, 2 pre-existing ignored hardware diagnostics.git diff --check: pass.The first two acceptance boxes are now complete. Remaining #138 work is intentionally still open: tier-aware device request/retry with recorded downgrade causes (including #55), full Perry/TypeScript capability querying, unsupported-setter results, and the complete hosted/mobile qualification matrix. The device negotiation/retry slice is next.
proggeramlug commentedon Jul 28, 2026
Second #138 slice landed in PR #147 at
21f73d8: tier-aware native device negotiation and recorded fallback causes.Implemented:
wgpu::Limits::default()or the adapter's maximum values.adapter.device_negotiation.Regression/qualification evidence:
request_device. This found and fixed two initially under-specified requirements (64 KiB joint uniforms and the combined texture+sampler binding-array budget).max_color_attachments=5) deterministically requests 4, not 8.I am leaving the “adapter below preferred limit starts” acceptance box unchecked until the exact Asahi-class runtime (or equivalent lower-limit hardware runner) executes the renderer, rather than treating a pure plan simulation as runtime proof. Windows' direct window path and remaining mobile-specific device creation paths also still need migration/qualification.
proggeramlug commentedon Jul 28, 2026
Windows direct-window migration landed at
ddb3e6f.init_engine_for_hwndnow uses the same bounded, fallback-capable device planner as shared native and Linux startup.BLOOM_HW_GI,BLOOM_PT, or--pt(and can still be disabled byBLOOM_FORCE_SW_GI).90245812396passed, and Windows shared tests90245812375passed in run 30350261839.The remaining device-creation migration is now the platform-specific mobile/Apple profiles; those cannot blindly reuse the 5-bind-group/4-MRT desktop contract because Android/WebGPU intentionally compile folded/lean layouts.
proggeramlug commentedon Jul 28, 2026
Android/device-profile checkpoint:
4ab5419native-fullandfolded-mobileprofiles selected from the compiled shader layout, not platform marketing or adapter maxima.folded-mobile: 4 bind groups, 4 color attachments, 19 sampled textures/stage, 16 samplers/stage, 4 storage buffers/stage, and a 64 KiB uniform binding.fold_scene_inputs,lean_mrt, andmobile_lightsbehavior, optional BC/timestamps/Tier A/GPU-driven/ray-query policy, andBLOOM_FORCE_SW_GI; this changes device creation only, not render-pass selection or pixels.adapter.device_negotiationevidence used by desktop.aarch64-linux-androidwith NDK 30.Hosted checks for the pushed SHA are running. The below-preferred-limit acceptance item remains open until a real constrained adapter demonstrates the retry at runtime; plan/unit simulation alone is not being counted as closure evidence.
proggeramlug commentedon Jul 28, 2026
iOS checkpoint:
ed4757cnative-fullnegotiation profile.aarch64-apple-iosandaarch64-apple-ios-simcompile successfully with default features and--locked; FFI/schema/file-size contracts pass.No render passes, shaders, formats, surface configuration, or per-frame work changed.
proggeramlug commentedon Jul 28, 2026
Apple TV/spatial checkpoints:
8c17a76(tvOS),7944b5a(visionOS)native-fullnegotiation.BLOOM_FORCE_SW_GI, and optional rendering features.aarch64-apple-tvos,aarch64-apple-tvos-sim,aarch64-apple-visionos, andaarch64-apple-visionos-simchecks pass; device builds revalidated with--locked.Together with the prior macOS/Linux/Windows/Android/iOS work, every non-stub native platform startup path now delegates device creation to the same capability planner. Hosted checks are running on the pushed head.
proggeramlug commentedon Jul 28, 2026
Permanent mobile compile coverage:
491806ccross / target-checkCI lane with locked Cargo checks and deterministic Android NDK compiler selection.actionlint, CI command-inventory governance, FFI/schema parity, and file-size contracts pass.I will check the platform-workflow acceptance box only after the new hosted matrix completes successfully on GitHub’s Linux/macOS runner images.
proggeramlug commentedon Jul 28, 2026
Hosted PR qualification now covers the declared platform matrix at commit
491806c: macOS/Metal, Windows/DX12, Linux/Vulkan, the real-browser WebGPU known-frame smoke, plus successful compile checks for Android (aarch64/armv7/x86_64), iOS (device/arm64 simulator/x64 simulator), tvOS (device/simulator), visionOS (device/simulator), and watchOS (device/simulator). All target jobs passed in PR #147. This completes the platform-workflow acceptance item; runtime proof on constrained mobile adapters remains separate.proggeramlug commentedon Jul 28, 2026
Renderer capability/result API checkpoint is complete in draft PR #147 through
7b07a39.Public query contract:
getRendererCapabilities()now returns adapter/backend/driver identity, detected/selected/requested tier, granted features and limits, per-system material/geometry/shadow/GI/reflection/AA/texture/PT paths, the selected device request and fallback cause, live material binding capacities/residency, hardware ray-query and path-tracing availability, GPU-driven status, imported-refraction path, and supported transparency modes.availability: "unavailable"result rather than inventing desktop capabilities.Unsupported-setting contract:
boolean:trueonly when the active backend applied the request,falsefor unsupported/rejected paths.tools/validate-ffi.jsnow hard-gates the 79-function status contract across the manifest, shared native macros, web, watchOS, and every TypeScript declaration. Web parity is down to five documented hard gaps.Qualification on exact commit
7b07a39:(99, 177, 241).This completes the TypeScript material-capability query and no-silent-renderer-setter acceptance items. The constrained physical-adapter runtime item remains deliberately unchecked.
proggeramlug commentedon Jul 28, 2026
Capability snapshot propagation is complete in draft PR #147 at
cce6721.What changed:
capabilities.json, andresult.jsonreferences it asartifacts.capability_snapshot.if: always(), so the named snapshot is retained on both success and failure.bloom-render-perfnow uses the production bounded device-negotiation/fallback path rather than requesting the adapter's maximum limits, and embeds the complete validated snapshot underadapterin both timing and upload reports.Real Apple M1 Max / Metal smoke evidence:
availability=reported, selected tierhigh-end, selected requestbloom_device_preferred, bounded 5 bind groups / 4 MRT attachments,ray_query=falsecapability_snapshot_failuresvalidator with zero failurestiming_includes_trace_io=trueRegression evidence:
cargo fmt --manifest-path tools/render-perf/Cargo.toml -- --checkcargo check --manifest-path tools/render-perf/Cargo.toml./scripts/ci-check.sh --quick: pass (316 unit tests, negotiated-device production smoke, 49 golden/integration tests, render-target tests, strict clippy, wasm, FFI parity, quality governance/negative controls, and examples)Relevant code:
The only unchecked #138 criterion now is runtime proof on a genuinely constrained physical adapter; simulated plan tests are intentionally not being presented as hardware evidence.
proggeramlug commentedon Aug 9, 2026
Capability-tier checkpoint in 44d53ec: virtual visibility now has an explicit bounded submission backend for adapters that support primitive index + indirect first instance but lack MULTI_DRAW_INDIRECT_COUNT (verified on Apple M1 Max / Metal). Count-capable adapters retain the existing path. The fallback uses GPU binning plus 22 fixed indirect draws, with no CPU readback or per-cluster CPU loop; its extra buffers and three compute passes exist only on that capability branch. Full virtual-geometry filter passes 29/29.