Skip to content

Commit 4bad0c4

Browse files
committed
Fix path-tracing history resets
1 parent 06eb43d commit 4bad0c4

6 files changed

Lines changed: 90 additions & 22 deletions

File tree

docs/temporal-history.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,23 @@ enable epoch:
9999

100100
Telemetry exposes `temporal_history.exposure_valid` and `exposure_index`.
101101

102+
## Path-tracing implementation
103+
104+
PT history validity remains the path tracer's own sample count, which the
105+
kernel already receives as `size.w`:
106+
107+
- every off/progressive/realtime mode transition resets the sample count,
108+
ping-pong index, deterministic sequence, and ownership;
109+
- a zero sample count suppresses reprojection and now also suppresses
110+
disocclusion neighbor seeding, so retained buffer bytes cannot resurrect
111+
history after a toggle, camera cut, seed change, or diagnostic reset;
112+
- buffers are retained when compatible and recreated only when trace-grid size
113+
changes, preserving the established steady-state memory and pass cost.
114+
115+
Telemetry exposes `temporal_history.pt_samples` and `pt_index`.
116+
102117
## Remaining #135 work
103118

104-
The next slices should give PT and any future temporal effect the same explicit
105-
lifetime rules, then add camera-cut/FOV-change resets, per-pixel rejection
106-
diagnostics, and the sequence-based motion corpus.
119+
The next slice should add the common camera-cut/FOV-change reset API, then
120+
continue with per-pixel rejection diagnostics and the sequence-based motion
121+
corpus.

native/shared/src/renderer/pt_pass.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ impl Renderer {
1313
let mode = mode.min(2);
1414
if self.pt_mode != mode {
1515
self.pt_mode = mode;
16+
self.reset_path_tracing_history(0);
1617
self.ssr_history_idx = 0;
1718
self.ssr_history_valid = false;
1819
self.probe_history_idx = 0;

native/shared/src/renderer/quality_capture.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,10 @@ impl Renderer {
550550
});
551551
out.push_str(",\"exposure_index\":");
552552
out.push_str(&self.exposure_current_idx.to_string());
553+
out.push_str(",\"pt_samples\":");
554+
out.push_str(&self.pt_accum_count.to_string());
555+
out.push_str(",\"pt_index\":");
556+
out.push_str(&self.pt_accum_idx.to_string());
553557
out.push('}');
554558
out.push_str(",\"transparent_gi\":{");
555559
out.push_str("\"enabled\":");

native/shared/src/renderer/shaders/pt.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,26 +1508,31 @@ fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
15081508
var seed_m2 = 0.0;
15091509
var seed_n = 0.0;
15101510
var seed_w = 0.0;
1511-
// 7x7: under TRANSLATION a whole COLUMN of texels streams in per
1512-
// frame (rotation only trickles a few px), so a 5x5 often found
1513-
// nothing but fellow newborns and the band stayed raw.
1514-
for (var by = -3; by <= 3; by = by + 1) {
1515-
for (var bx = -3; bx <= 3; bx = bx + 1) {
1516-
if (bx == 0 && by == 0) { continue; }
1517-
let q = vec2<i32>(i32(gid.x) + bx, i32(gid.y) + by);
1518-
if (q.x < 0 || q.y < 0 || q.x >= i32(u.size.x) || q.y >= i32(u.size.y)) {
1519-
continue;
1511+
// A global reset leaves old bytes allocated, but size.w = 0
1512+
// makes them invalid. Do not let neighbour seeding resurrect
1513+
// those moments after a mode toggle, camera cut, or seed change.
1514+
if (u.size.w > 0u) {
1515+
// 7x7: under TRANSLATION a whole COLUMN of texels streams in
1516+
// per frame (rotation only trickles a few px), so a 5x5 often
1517+
// found nothing but fellow newborns and the band stayed raw.
1518+
for (var by = -3; by <= 3; by = by + 1) {
1519+
for (var bx = -3; bx <= 3; bx = bx + 1) {
1520+
if (bx == 0 && by == 0) { continue; }
1521+
let q = vec2<i32>(i32(gid.x) + bx, i32(gid.y) + by);
1522+
if (q.x < 0 || q.y < 0 || q.x >= i32(u.size.x) || q.y >= i32(u.size.y)) {
1523+
continue;
1524+
}
1525+
let qidx = u32(q.y) * u.size.x + u32(q.x);
1526+
let m = moments[qidx];
1527+
if (m.w >= 0.9999999 || m.z < 4.0) { continue; }
1528+
if (abs(lin_depth(m.w) - zl_here) > btol) { continue; }
1529+
let wt = m.z;
1530+
seed_rgb += accum[qidx].rgb * wt;
1531+
seed_m1 += m.x * wt;
1532+
seed_m2 += m.y * wt;
1533+
seed_n += m.z * wt;
1534+
seed_w += wt;
15201535
}
1521-
let qidx = u32(q.y) * u.size.x + u32(q.x);
1522-
let m = moments[qidx];
1523-
if (m.w >= 0.9999999 || m.z < 4.0) { continue; }
1524-
if (abs(lin_depth(m.w) - zl_here) > btol) { continue; }
1525-
let wt = m.z;
1526-
seed_rgb += accum[qidx].rgb * wt;
1527-
seed_m1 += m.x * wt;
1528-
seed_m2 += m.y * wt;
1529-
seed_n += m.z * wt;
1530-
seed_w += wt;
15311536
}
15321537
}
15331538
if (seed_w > 0.0) {

native/shared/src/renderer/shaders/pt_tests.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,18 @@ fn base_transport_uses_bounded_reciprocal_layered_contract() {
5151
assert!(production.contains("return diffuse + spec;"));
5252
assert!(!production.contains("return alb * lit + spec;"));
5353
}
54+
55+
#[test]
56+
fn zero_sample_count_cannot_seed_from_retained_moments() {
57+
let production = pt_kernel_variant(false);
58+
let gate = production
59+
.find("if (u.size.w > 0u) {")
60+
.expect("neighbor seeding must have a global-history gate");
61+
let neighbor_read = production[gate..]
62+
.find("let m = moments[qidx];")
63+
.expect("gate must enclose the disocclusion neighbor read");
64+
let gate_end = production[gate..]
65+
.find("if (seed_w > 0.0) {")
66+
.expect("seed reduction follows the gated search");
67+
assert!(neighbor_read < gate_end);
68+
}

native/shared/tests/golden_render/temporal_history.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,31 @@ fn exposure_history_seeds_each_enable_epoch_without_advancing_while_off() {
264264
.quality_runtime_paths_json()
265265
.contains("\"exposure_valid\":true,\"exposure_index\":1"));
266266
}
267+
268+
#[test]
269+
fn path_tracing_mode_transitions_reset_incompatible_history() {
270+
let _rt_guard = lock_rt_goldens();
271+
let (mut eng, _) = match try_engine_rt() {
272+
Ok(Some(pair)) => pair,
273+
Ok(None) => {
274+
skip_rt_golden("pt_history_lifetime", "no-non-cpu-ray-query-adapter");
275+
return;
276+
}
277+
Err(err) => panic!("{err}"),
278+
};
279+
build_pt_scene(&mut eng);
280+
281+
eng.renderer.set_path_tracing(2);
282+
let _ = render(&mut eng, 1, draw_pt_static_frame);
283+
assert!(eng.renderer.path_tracing_sample_count() > 0);
284+
285+
eng.renderer.set_path_tracing(1);
286+
assert_eq!(eng.renderer.path_tracing_sample_count(), 0);
287+
assert!(eng
288+
.renderer
289+
.quality_runtime_paths_json()
290+
.contains("\"pt_samples\":0,\"pt_index\":0"));
291+
292+
eng.renderer.set_path_tracing(0);
293+
assert_eq!(eng.renderer.path_tracing_sample_count(), 0);
294+
}

0 commit comments

Comments
 (0)