name: quality-knob-as-wgsl-specialization-constant description: When a RenderQuality knob changes infrequently (at config load, not per-frame) and needs to reach a WGSL shader, use a specialization constant (override) set at pipeline creation rather than a UBO field — avoids UBO plumbing and per-frame write_buffer source: auto-skill extracted_at: '2026-06-15T21:07:04.805Z'
Quality knob as WGSL specialization constant
When a RenderQuality knob changes at config-load time (not per-frame) and
the WGSL shader reads it at pipeline creation, use a WGSL specialization
constant (override) set via PipelineCompilationOptions::constants rather
than adding a UBO field.
Why: A UBO field requires: extending the UBO struct (Rust + WGSL), writing
the value per-frame in queue.write_buffer, and consuming a binding slot. A
specialization constant requires only: declaring override in WGSL, passing
the value in ShaderModuleDescriptor::compilation_options.constants at pipeline
creation. The value is baked into the compiled shader — it cannot change
without pipeline re-creation, but for quality knobs that change at config
reload (not per-frame), pipeline re-creation (ms) is cheaper than per-frame
UBO writes (µs × N_frames).
How to apply:
- Declare in WGSL (top of shader file):
override vsm_pcf_kernel: u32;
- Set at pipeline creation in Rust:
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("stratum_shading.wgsl"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/stratum_shading.wgsl").into()),
compilation_options: wgpu::PipelineCompilationOptions {
constants: &[("vsm_pcf_kernel".to_string(), quality.vsm_pcf_kernel as f64)],
zero_initialize_workgroup_memory: true,
},
});
- The shader body uses the constant directly (no UBO binding needed):
if vsm_pcf_kernel <= 3u {
vis = textureSampleCompare(...);
} else {
// Gather4-based larger kernel
}
When NOT to use: If the value changes every frame (e.g., camera position,
time, per-frame jitter), use a UBO. Pipeline re-creation every frame is too
expensive. If the value changes at higher frequency than config reload
(e.g., dynamic quality scaling responding to frame time), use a UBO with
queue.write_buffer.