Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/RUNTIME.md
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,7 @@ The path-tracing kernel is one WESL module: `pathtrace.wgsl`, then `trace_units.
translation (`@if(flag)` on a declaration or a statement, `@if(flag) { .. } @else { .. }` on a block of
declarations). wesl-rs (WESL's compiler in Rust, natively and in a browser alike) makes a kernel's WGSL from a
variant (gpu.rs `KernelVariant`, `kernel_wgsl`): the traversal (`hwrt`), a device's switches (`strict`,
`cutout_lite`, `cutout_lite2`, `last_direct`, `light_pick`), software traversal's units (`units`), the binding layout (`merged`, the browser's; `uniform_tables`, Android's) and the loop guard (`guard`).
`cutout_lite`, `cutout_lite2`, `last_direct`, `last_sun`, `light_pick`), software traversal's units (`units`), the binding layout (`merged`, the browser's; `uniform_tables`, Android's) and the loop guard (`guard`).
pathtrace.wgsl's head names them; a flag the source names that a kernel is not given is an error. A scene's other
features are bits of `FEAT`, a pipeline-overridable constant: one kernel source for all of them (`OBJECT_STRIDE`
too is an override, from F_TINT).
Expand Down
41 changes: 39 additions & 2 deletions renderer/shaders/pathtrace.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// compiled for a device and a scene by WESL's conditional translation (`@if`, gpu.rs `kernel_wgsl`): a declaration
// or a statement under `@if(x)` is there only where x holds. The flags:
// hwrt hardware traversal (ray queries) in the software BVH walk's place, and the translucency pass
// strict, cutout_lite, cutout_lite2, last_direct, light_pick
// strict, cutout_lite, cutout_lite2, last_direct, last_sun, light_pick
// a device's switches (gpu.rs `KernelSwitches`: what a GPU the kernel as written is wrong or
// too slow on needs)
// units software traversal's second level (gpu.rs `F_UNITS`); a scene's other features are bits
Expand Down Expand Up @@ -1919,6 +1919,33 @@ fn hg_phase(g: f32, c: f32) -> f32 {
return (1.0 - g * g) / (4.0 * PI * d * sqrt(d));
}

// gpu.rs `KernelSwitches::last_sun`: the directional lights alone, as `direct_light` takes them (one shadow ray a
// light), for the path's last vertex.
@if(last_sun && !last_direct) fn sun_light(s: Surface, lb: Lobes, v: vec3<f32>) -> vec3<f32> {
var sum = vec3<f32>(0.0);
let o = offset_origin(s.p, s.ng);
for (var i = 0u; i < P.counts2.x; i++) {
let li = light_at(i);
if (all(li.color.rgb <= vec3<f32>(0.0))) { continue; }
let cos_max = li.dir.w;
let to_light = -li.dir.xyz;
let u = rng2(rng_sub(rng_dim(D_SUN), i));
let cos_t = 1.0 - u.x * (1.0 - cos_max);
let sin_t = sqrt(max(0.0, 1.0 - cos_t * cos_t));
let phi = 2.0 * PI * u.y;
let l = basis(to_light) * vec3<f32>(sin_t * cos(phi), sin_t * sin(phi), cos_t);
let f = eval_bsdf_k(s, lb, v, l, li.x.y);
if (any(f > vec3<f32>(0.0))) {
let vis = light_vis(li.ext.w >= 0.5, o, l, INF, rng_sub(rng_dim(D_SUN_VIS), i));
if (any(vis > vec3<f32>(0.0))) {
let pdf_l = 1.0 / (2.0 * PI * (1.0 - cos_max));
sum += li.color.rgb * f * power_heuristic(pdf_l, pdf_bsdf(s, lb, v, l)) * gtint(vis);
}
}
}
return sum;
}

fn direct_light(s: Surface, lb: Lobes, v: vec3<f32>) -> vec3<f32> {
var sum = vec3<f32>(0.0);
dl_spec = vec3<f32>(0.0);
Expand Down Expand Up @@ -3300,7 +3327,17 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
radiance += c;
}
if (s.card) { break; }
@if(!last_direct) if (depth == P.counts.z) { break; }
@if(!last_direct && !last_sun) if (depth == P.counts.z) { break; }
@if(last_sun && !last_direct) if (depth == P.counts.z) {
// gpu.rs `KernelSwitches::last_sun`: the last vertex's light from the directional lights alone.
if (depth > 0u) {
var dz = thr * sun_light(s, lobes(s, max(dot(s.n, -d), 1e-4)), -d);
let lum = luminance(dz);
if (lum > clamp_v && !straight) { dz *= clamp_v / lum; }
radiance += dz;
}
break;
}
@if(last_direct) if (depth == P.counts.z) {
// gpu.rs `KernelSwitches::last_direct`: the last vertex's direct light.
if (depth > 0u) {
Expand Down
30 changes: 23 additions & 7 deletions renderer/src/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -735,14 +735,15 @@ impl KernelVariant {
}

/// The flags of the kernel's conditional translation (pathtrace.wgsl's head).
pub fn flags(&self) -> [(&'static str, bool); 10] {
pub fn flags(&self) -> [(&'static str, bool); 11] {
let (k, f) = (&self.switches, |bit: u32| self.features & bit != 0);
[
("hwrt", self.hwrt),
("strict", k.strict),
("cutout_lite", k.cutout_lite >= 1),
("cutout_lite2", k.cutout_lite >= 2),
("last_direct", k.last_direct),
("last_sun", k.last_sun),
("light_pick", k.light_pick),
("units", f(F_UNITS)),
("merged", self.merged.is_some()),
Expand Down Expand Up @@ -1030,6 +1031,12 @@ pub struct KernelSwitches {
/// takes its direct light. A device that affords one bounce and no radiance cache otherwise lights that bounce
/// by sky and emitters alone: sunlit ground lit nothing, interiors were black.
pub last_direct: bool,
/// THREERT_LAST_DIRECT=sun (off everywhere unless set; the ThreeRT clients set it for a budget of one bounce on
/// Linux). The path's last vertex takes the directional lights' direct light alone (one shadow ray a light): what
/// one bounce indoors lacks most (sunlit floor lighting the ceiling: Daylight Home's ceiling was black on a Steam
/// Deck), at a fraction of `last_direct`'s cost where a scene has many local lights (OpenStrikeRT's Foundry on the
/// Deck: +1 ms a frame, `last_direct` +8). `last_direct` includes it.
pub last_sun: bool,
/// THREERT_LIGHT_PICK=1|0 (off everywhere unless set). Past the camera hit, `direct_light` takes the directional
/// lights and one of the other kinds a scene has (local lights, emitters, the sky), weighted by their number,
/// instead of all of them: two shadow rays a vertex instead of four. Unbiased; the indirect light is noisier.
Expand All @@ -1044,7 +1051,8 @@ impl KernelSwitches {
fn from_vars(var: &dyn Fn(&str) -> Option<String>, android: bool) -> KernelSwitches {
let num = |name: &str, default: u32| var(name).and_then(|v| v.parse::<u32>().ok()).unwrap_or(default);
let on = android as u32;
KernelSwitches { strict: num("THREERT_KERNEL_STRICT", on) != 0, cutout_lite: num("THREERT_CUTOUT_LITE", on), last_direct: num("THREERT_LAST_DIRECT", on) != 0, light_pick: num("THREERT_LIGHT_PICK", 0) != 0 }
let sun = var("THREERT_LAST_DIRECT").is_some_and(|v| v.trim() == "sun");
KernelSwitches { strict: num("THREERT_KERNEL_STRICT", on) != 0, cutout_lite: num("THREERT_CUTOUT_LITE", on), last_direct: !sun && num("THREERT_LAST_DIRECT", on) != 0, last_sun: sun, light_pick: num("THREERT_LIGHT_PICK", 0) != 0 }
}
}

Expand Down Expand Up @@ -3388,13 +3396,14 @@ mod tests {
let sha = |s: &str| Sha256::digest(s.as_bytes()).iter().map(|b| format!("{b:02x}")).collect::<String>();
assert_eq!(crate::live::live_source(), include_str!("../shaders/live.wgsl"));
let template = kernel_template();
let all = KernelSwitches { strict: true, cutout_lite: 2, last_direct: true, light_pick: true };
let all = KernelSwitches { strict: true, cutout_lite: 2, last_direct: true, last_sun: false, light_pick: true };
let switches = [
KernelSwitches::default(),
KernelSwitches { strict: true, ..Default::default() },
KernelSwitches { cutout_lite: 1, ..Default::default() },
KernelSwitches { cutout_lite: 2, ..Default::default() },
KernelSwitches { last_direct: true, ..Default::default() },
KernelSwitches { last_sun: true, ..Default::default() },
KernelSwitches { light_pick: true, ..Default::default() },
all,
];
Expand Down Expand Up @@ -3465,21 +3474,24 @@ mod tests {
let vars = |set: &'static [(&'static str, &'static str)]| move |name: &str| set.iter().find(|(n, _)| *n == name).map(|(_, v)| v.to_string());
let desktop = GpuOptions::from_vars(&vars(&[]), false);
assert_eq!(desktop, GpuOptions::default());
assert_eq!(desktop.kernel, KernelSwitches { strict: false, cutout_lite: 0, last_direct: false, light_pick: false });
assert_eq!(desktop.kernel, KernelSwitches { strict: false, cutout_lite: 0, last_direct: false, last_sun: false, light_pick: false });
assert!(!desktop.uniform_tables_on(false, false));
for hwrt in [false, true] {
assert!(KernelVariant::new(hwrt, desktop.kernel, 0).flags().iter().all(|&(name, on)| on == (name == "hwrt" && hwrt)));
}
let android = GpuOptions::from_vars(&vars(&[]), true);
assert_eq!(android.kernel, KernelSwitches { strict: true, cutout_lite: 1, last_direct: true, light_pick: false });
assert_eq!(android.kernel, KernelSwitches { strict: true, cutout_lite: 1, last_direct: true, last_sun: false, light_pick: false });
// `sun`: the directional lights alone at the last vertex, in place of all of them, on either platform.
let sun = GpuOptions::from_vars(&vars(&[("THREERT_LAST_DIRECT", "sun")]), true);
assert_eq!((sun.kernel.last_direct, sun.kernel.last_sun), (false, true));
assert_eq!((android.uniform_tables, android.pipeline_cache.clone()), (None, None));
assert!(android.uniform_tables_on(false, true) && !android.uniform_tables_on(true, true));
// Set: what is asked, on either platform.
let off = GpuOptions::from_vars(&vars(&[("THREERT_UNIFORM_TABLES", "0"), ("THREERT_KERNEL_STRICT", "0"), ("THREERT_CUTOUT_LITE", "0"), ("THREERT_LAST_DIRECT", "0")]), true);
assert_eq!((off.kernel, off.uniform_tables_on(false, true)), (KernelSwitches::default(), false));
let on = GpuOptions::from_vars(&vars(&[("THREERT_UNIFORM_TABLES", "1"), ("THREERT_CUTOUT_LITE", "2"), ("THREERT_LIGHT_PICK", "1"), ("THREERT_PIPELINE_CACHE", "/data/cache/pipelines")]), false);
assert!(on.uniform_tables_on(false, false) && !on.uniform_tables_on(true, false));
assert_eq!(on.kernel, KernelSwitches { strict: false, cutout_lite: 2, last_direct: false, light_pick: true });
assert_eq!(on.kernel, KernelSwitches { strict: false, cutout_lite: 2, last_direct: false, last_sun: false, light_pick: true });
assert_eq!(on.pipeline_cache.as_deref(), Some(std::path::Path::new("/data/cache/pipelines")));
assert_eq!(GpuOptions::from_vars(&vars(&[("THREERT_PIPELINE_CACHE", "")]), true).pipeline_cache, None);
// (And the environment map's light sampling, which is the scene's preparation's: lib.rs `env_nee_on`.)
Expand Down Expand Up @@ -3512,8 +3524,12 @@ mod tests {
assert_eq!(strict.matches("ray_bad(o, d, tmax").count(), queries, "every ray query is guarded");
assert_eq!(strict.matches("fn ray_bad(").count(), hwrt as usize);
assert!(!strict.contains("env_cdf[y - 1u]") && !strict.contains("env_cdf[base + x - 1u]") && plain.contains("env_cdf[y - 1u]"));
let lit = v(hwrt, KernelSwitches { strict: true, cutout_lite: 2, last_direct: true, light_pick: true });
let lit = v(hwrt, KernelSwitches { strict: true, cutout_lite: 2, last_direct: true, last_sun: false, light_pick: true });
assert_eq!(lit.matches("radiance += dz;").count(), 1);
assert!(!lit.contains("fn sun_light("), "last_direct takes the directional lights already");
let sun = v(hwrt, KernelSwitches { last_sun: true, ..Default::default() });
assert_eq!((sun.matches("radiance += dz;").count(), sun.matches("fn sun_light(").count(), sun.matches("sun_light(s, lobes(").count()), (1, 1, 1));
assert!(!plain.contains("sun_light"));
assert_eq!(lit.matches("env_on = take_env && has_env;").count(), 1);
assert_eq!(lit.contains("let steps = 4u;"), hwrt);
assert_eq!(v(hwrt, KernelSwitches { cutout_lite: 2, ..Default::default() }) == plain, !hwrt);
Expand Down