Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
efdb03d
WIP snapshot: rendering roadmap before PR integration
proggeramlug Jul 27, 2026
4dc3818
Merge remote-tracking branch 'origin/fix/audit-followups-ci-rt-safety…
proggeramlug Jul 27, 2026
c5b106a
Merge remote-tracking branch 'origin/feat/apple-gamepad-lifecycle-aud…
proggeramlug Jul 27, 2026
001be00
Merge remote-tracking branch 'origin/codex/linux-worker' into codex/p…
proggeramlug Jul 27, 2026
90d4923
Merge rendering WIP with PRs #124, #125, and #146
proggeramlug Jul 27, 2026
a650a80
Merge remote-tracking branch 'origin/main' into codex/pr-integration-…
proggeramlug Jul 27, 2026
570b811
Improve HDR specular prefilter stability
proggeramlug Jul 27, 2026
80a7e9f
Expose layered PBR material descriptor
proggeramlug Jul 27, 2026
337a9b2
Align path tracing with layered PBR base contract
proggeramlug Jul 27, 2026
37a596c
Keep path tracer shader under line limit
proggeramlug Jul 27, 2026
0e8ef78
Fix web build and integrated file-line gate
proggeramlug Jul 27, 2026
a550c0a
Add lazy layered path tracing sidecar
proggeramlug Jul 27, 2026
a035ba4
Add scalar clearcoat path transport
proggeramlug Jul 27, 2026
e39deee
Add scalar specular IOR path transport
proggeramlug Jul 27, 2026
832f20d
Add scalar sheen path transport
proggeramlug Jul 27, 2026
3e218cc
Add scalar anisotropic GGX path transport
proggeramlug Jul 27, 2026
45719bf
Add scalar iridescent path transport
proggeramlug Jul 27, 2026
4f6df63
Add textured specular path transport
proggeramlug Jul 27, 2026
008d598
Add UV1 specular path transport
proggeramlug Jul 27, 2026
34f4554
Add textured clearcoat path transport
proggeramlug Jul 27, 2026
5014499
Add textured sheen path transport
proggeramlug Jul 27, 2026
aedf05c
Add textured iridescence path transport
proggeramlug Jul 27, 2026
ef11c9e
Add textured anisotropy path transport
proggeramlug Jul 27, 2026
cf5254c
Fix SSR temporal history lifetime
proggeramlug Jul 27, 2026
2e67803
Fix SSGI temporal history lifetime
proggeramlug Jul 27, 2026
4be5888
Fix TAA temporal history ownership
proggeramlug Jul 27, 2026
06eb43d
Fix auto-exposure history lifetime
proggeramlug Jul 27, 2026
4bad0c4
Fix path-tracing history resets
proggeramlug Jul 27, 2026
51bf151
Add unified temporal camera-cut reset
proggeramlug Jul 27, 2026
08b5f41
Add capture-only temporal diagnostics
proggeramlug Jul 27, 2026
697bf68
Add temporal camera motion sequence gate
proggeramlug Jul 27, 2026
45e72f7
Expand temporal motion regression corpus
proggeramlug Jul 27, 2026
b065211
Fix cached model temporal motion
proggeramlug Jul 27, 2026
d3365df
Gate emissive temporal convergence
proggeramlug Jul 27, 2026
79ea278
Bound and diagnose SSR fireflies
proggeramlug Jul 27, 2026
b252637
Gate refractive temporal motion
proggeramlug Jul 27, 2026
810bc94
Add capture-only SSR temporal diagnostics
proggeramlug Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
self-hosted-runner:
labels:
- bloom-quality
- apple-m1-max
- nvidia-rtx4080

config-variables: null
185 changes: 185 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: Renderer quality

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "17 3 * * 1"
workflow_dispatch:
inputs:
suite:
description: Qualification suite
type: choice
options: [full, quick]
default: full
machine:
description: Hardware runner
type: choice
options: [all, apple-m1-max-metal, nvidia-rtx4080-vulkan]
default: all

concurrency:
group: renderer-quality-${{ github.ref }}
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always
MACOSX_DEPLOYMENT_TARGET: "13.0"

jobs:
contract:
name: Quality contract
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- uses: dtolnay/rust-toolchain@stable

- name: Python orchestration and governance tests
run: |
python3 -m py_compile \
tools/quality/run.py \
tools/quality/build_example.py \
tools/quality/prepare_bistro.py
python3 -m unittest tools/quality/test_run.py -v

- name: Visual metric and fault-engine tests
run: cargo test --release --manifest-path tools/bloom-diff/Cargo.toml

metal:
name: Full quality — Apple M1 Max / Metal
if: >-
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' &&
(inputs.machine == 'all' || inputs.machine == 'apple-m1-max-metal'))
runs-on: [self-hosted, bloom-quality, macos, apple-m1-max]
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: dtolnay/rust-toolchain@stable

- name: Runner preflight
run: |
python3 --version
cargo --version
command -v perry

- name: Validate manifest, assets, and approved baselines
run: python3 tools/quality/run.py check

- name: Prove seeded regressions are detected
run: >-
python3 tools/quality/run.py faults
--out tools/quality/out/ci-faults
--timeout 900

- name: Run selected high-end suite
if: always()
env:
REQUESTED_SUITE: ${{ inputs.suite }}
run: |
suite="${REQUESTED_SUITE:-full}"
python3 tools/quality/run.py run "$suite" \
--machine-class apple-m1-max-metal \
--out tools/quality/out/ci-metal \
--timeout 1800

- name: Hard-gate the constrained tier
if: always()
run: |
python3 tools/quality/run.py run full \
--case pbr-spheres-constrained \
--machine-class apple-m1-metal-constrained \
--out tools/quality/out/ci-metal-constrained \
--timeout 1800

- name: Publish compact job summary
if: always()
run: |
if [[ -f tools/quality/out/ci-metal/summary.md ]]; then
cat tools/quality/out/ci-metal/summary.md >> "$GITHUB_STEP_SUMMARY"
fi
if [[ -f tools/quality/out/ci-metal-constrained/summary.md ]]; then
cat tools/quality/out/ci-metal-constrained/summary.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload all success/failure evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: quality-apple-m1-max-metal-${{ github.run_attempt }}
path: |
tools/quality/out/ci-metal
tools/quality/out/ci-metal-constrained
tools/quality/out/ci-faults
if-no-files-found: warn
retention-days: 30

vulkan:
name: Full quality — RTX 4080 / Vulkan
if: >-
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' &&
(inputs.machine == 'all' || inputs.machine == 'nvidia-rtx4080-vulkan'))
runs-on: [self-hosted, bloom-quality, linux, nvidia-rtx4080]
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: dtolnay/rust-toolchain@stable

- name: Runner preflight
run: |
python3 --version
cargo --version
command -v perry
nvidia-smi

- name: Validate manifest, assets, and approved baselines
run: python3 tools/quality/run.py check

- name: Prove seeded regressions are detected
run: >-
python3 tools/quality/run.py faults
--out tools/quality/out/ci-faults
--timeout 900

- name: Run selected high-end suite
if: always()
env:
REQUESTED_SUITE: ${{ inputs.suite }}
run: |
suite="${REQUESTED_SUITE:-full}"
python3 tools/quality/run.py run "$suite" \
--machine-class nvidia-rtx4080-vulkan \
--out tools/quality/out/ci-vulkan \
--timeout 1800

- name: Publish compact job summary
if: always()
run: |
if [[ -f tools/quality/out/ci-vulkan/summary.md ]]; then
cat tools/quality/out/ci-vulkan/summary.md >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload all success/failure evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: quality-nvidia-rtx4080-vulkan-${{ github.run_attempt }}
path: |
tools/quality/out/ci-vulkan
tools/quality/out/ci-faults
if-no-files-found: warn
retention-days: 30
118 changes: 118 additions & 0 deletions docs/compiled-render-graph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Compiled render graph

Bloom's retained 3D renderer executes from an immutable, cached frame plan.
The implementation deliberately preserves the established serial command
order and rendered pixels while making resource lifetime and pass contracts
explicit.

## Runtime contract

`renderer/graph/model.rs` is the declaration API. Textures and buffers use
typed logical handles; each write produces a new version. Imported persistent
and external resources declare their initial/final usage and ownership.
Transient descriptors include extent policy, format, dimension, mip/sample
counts, allowed usage, load policy, and alias class.

`renderer/graph/compiler.rs`:

- validates producers, versions, usage permissions, typed handles, divergent
writers, and cycles before wgpu validation;
- derives deterministic resource and explicit-dependency edges;
- topologically sorts by declaration ID when multiple orders are valid;
- computes inclusive first/last-use positions and usage/queue transitions;
- assigns compatible, strictly non-overlapping transient lifetimes to
physical slots; and
- generates a stable plan ID from the complete execution contract.

The live topology is declared in `renderer/graph/frame_plan.rs`. Optional
SSAO, SSR, SSGI (including acceleration/card/SDF/radiance-cache preparation),
bloom, scene-snapshot, and capture work is selected before compilation by
`FramePlanKey`. Uniform-only changes do not affect the key.
`ExecutableGraph` binds only frame-local recording closures to cached pass
positions; it does not rebuild or schedule a declaration graph per frame.

Current backends execute the compiled order on one serial wgpu queue. Queue
capability and transitions are diagnostic metadata, not a second barrier or
multi-queue API.

## Allocation and resize

`TransientPool::prepare_compiled_plan` caches physical texture allocations by
plan ID and exact render/output extents. A stable plan and extent performs no
new physical allocation. An exact resize invalidates the allocation
generation. Persistent resources and temporal histories never enter the
alias set.

The current Ultra Sponza qualification topology has no eligible transient
texture: its post-FX targets are persistent imports with stable views or
temporal/history ownership. It therefore reports 0 unaliased bytes, 0
physical slots, and no meaningful percentage reduction.

The only currently materialized optional transients are the refractive
material's scene-color and scene-depth snapshots. Their lifetimes overlap in
the translucent pass and their exact descriptors differ (`Rgba16Float`
color versus `Depth32Float` depth), so conservative aliasing correctly keeps
two slots. At 1920×1080 they total 24,883,200 bytes and save 0%; at the
800×450 Sponza qualification extent they would total 4,320,000 bytes.
Converting persistent post-FX targets would require a broader view/bind-group
rebinding migration and was excluded from this no-regression change.

Synthetic allocator tests cover the positive case and reduce two compatible,
non-overlapping textures from two physical allocations to one (50%). They
also prove that overlapping or descriptor-incompatible resources never
alias.

## Capture and diagnostics

Set `BLOOM_GRAPH_DEBUG_MARKERS=1` while taking a GPU capture to bracket every
compiled pass with its stable graph name using wgpu debug groups. The opt-in
keeps ordinary release frames free of marker encoding overhead while making
passes visible in RenderDoc, Xcode GPU captures, and PIX-compatible backends.
Qualification readback is a keyed terminal `capture_readback` pass.
It copies the output plus these logical resource names:

- `hdr-scene`
- `scene-depth`
- `shadow-cascade-0`
- `shadow-cascade-1`
- `shadow-cascade-2`

The capture path resolves names explicitly and rejects unknown resources.
Normal frames do not include the capture pass or allocate staging buffers.

Set `BLOOM_GRAPH_DUMP_DIR` to write each distinct plan once:

```sh
BLOOM_GRAPH_DEBUG_MARKERS=1 \
BLOOM_GRAPH_DUMP_DIR=/tmp/bloom-graphs \
python3 tools/quality/run.py run quick --report-only
```

Each `bloom-frame-<plan-id>.json` contains pass accesses, dependencies,
resource lifetimes, physical slots, and transitions. The adjacent DOT file
is a compact dependency graph.

Native qualification telemetry exposes:

- `compile_count`, `cache_hit_count`, and `cached_plan_count`;
- the current `plan_id` and `pass_count`;
- aliasing state, planned/unaliased transient bytes, and physical slot count.

A 420-frame high-quality PBR run records one compile and 419 cache hits for
the stable normal-frame configuration. The post-measurement capture uses a
separate cached topology; telemetry is intentionally serialized before that
debug-only frame.

## Regression procedure

Run compiler and allocator tests first:

```sh
cd native/shared
cargo test --release renderer::graph
cargo test --release renderer::transient
```

Then run the report-only quality corpus and compare final plus intermediate
outputs to the pre-change evidence. A missing human-approved baseline remains
a qualification failure; `--report-only` does not convert it into a pass.
Loading
Loading