use wgpu::BufferUsages;
use crate::{
geometry::GpuFacetPlane,
renderer::{
buffers::{GpuCameraParams, GpuGemMaterial, GpuTransportParams},
env_map_gpu::HdrEnvGpuData,
gpu::{GpuContext, compute},
},
};
use super::{
WAVEFRONT_SHADER_SRC,
scene_buffers::{FrameSceneBuffers, TransportOutputs, WavefrontRayBuffers},
};
pub struct TransportDispatchArgs<'a> {
pub(crate) ctx: &'a GpuContext,
pub(crate) pipeline: &'a wgpu::ComputePipeline,
pub(crate) camera_params: &'a GpuCameraParams,
pub(crate) params: &'a GpuTransportParams,
pub(crate) material: &'a GpuGemMaterial,
pub(crate) planes: &'a [GpuFacetPlane],
pub(crate) facet_finishes: &'a [u32],
pub(crate) outputs: &'a TransportOutputs,
pub(crate) hdr_env: &'a HdrEnvGpuData,
}
pub fn build_bind_group(args: &TransportDispatchArgs<'_>) -> wgpu::BindGroup {
let camera_buf = compute::upload(
&args.ctx.device,
"transport camera",
std::slice::from_ref(args.camera_params),
BufferUsages::UNIFORM,
);
let params_buf = compute::upload(
&args.ctx.device,
"transport params",
std::slice::from_ref(args.params),
BufferUsages::UNIFORM,
);
let material_buf = compute::upload(
&args.ctx.device,
"transport material",
std::slice::from_ref(args.material),
BufferUsages::STORAGE,
);
let planes_buf = compute::upload(
&args.ctx.device,
"transport planes",
args.planes,
BufferUsages::STORAGE,
);
let facet_finishes_buf = compute::upload(
&args.ctx.device,
"transport facet finishes",
args.facet_finishes,
BufferUsages::STORAGE,
);
compute::bind_buffers(
&args.ctx.device,
"transport bind group",
args.pipeline,
&[
(0, &camera_buf),
(1, ¶ms_buf),
(2, &material_buf),
(3, &planes_buf),
(4, args.outputs.xyz()),
(5, args.outputs.radiance()),
(6, args.outputs.lambdas()),
(7, args.outputs.path_pdf()),
(8, &facet_finishes_buf),
(9, args.outputs.compat()),
(10, &args.hdr_env.texels),
(11, &args.hdr_env.dims),
(12, &args.hdr_env.dist_func),
(13, &args.hdr_env.dist_cdf),
(14, &args.hdr_env.dist_dims),
],
)
}
pub fn build_chunk_bind_group(
ctx: &GpuContext,
pipeline: &wgpu::ComputePipeline,
frame_buffers: &FrameSceneBuffers,
params_buf: &wgpu::Buffer,
planes_len: usize,
outputs: &TransportOutputs,
) -> wgpu::BindGroup {
let planes_bytes = (planes_len * size_of::<GpuFacetPlane>()) as wgpu::BufferAddress;
let finishes_bytes = (planes_len * size_of::<u32>()) as wgpu::BufferAddress;
compute::bind_buffers_sized(
&ctx.device,
"transport bind group (pipelined production)",
pipeline,
&[
(0, &frame_buffers.camera, None),
(1, params_buf, None),
(2, &frame_buffers.material, None),
(3, &frame_buffers.planes, Some(planes_bytes)),
(4, outputs.xyz(), None),
(5, outputs.radiance(), None),
(6, outputs.lambdas(), None),
(7, outputs.path_pdf(), None),
(8, &frame_buffers.facet_finishes, Some(finishes_bytes)),
(9, outputs.compat(), None),
(10, &frame_buffers.hdr_env.texels, None),
(11, &frame_buffers.hdr_env.dims, None),
(12, &frame_buffers.hdr_env.dist_func, None),
(13, &frame_buffers.hdr_env.dist_cdf, None),
(14, &frame_buffers.hdr_env.dist_dims, None),
],
)
}
pub struct WavefrontPipelines {
pub(super) generate: wgpu::ComputePipeline,
pub(super) bounce: wgpu::ComputePipeline,
pub(super) compact_scan: wgpu::ComputePipeline,
pub(super) compact_scatter: wgpu::ComputePipeline,
pub(super) finalize_survivors: wgpu::ComputePipeline,
}
impl WavefrontPipelines {
pub(crate) fn new(device: &wgpu::Device) -> Self {
Self {
generate: compute::create_compute_pipeline(
device,
"wavefront_generate",
WAVEFRONT_SHADER_SRC,
"wavefront_generate",
),
bounce: compute::create_compute_pipeline(
device,
"wavefront_bounce",
WAVEFRONT_SHADER_SRC,
"wavefront_bounce",
),
compact_scan: compute::create_compute_pipeline(
device,
"wavefront_compact_scan",
WAVEFRONT_SHADER_SRC,
"wavefront_compact_scan",
),
compact_scatter: compute::create_compute_pipeline(
device,
"wavefront_compact_scatter",
WAVEFRONT_SHADER_SRC,
"wavefront_compact_scatter",
),
finalize_survivors: compute::create_compute_pipeline(
device,
"wavefront_finalize_survivors",
WAVEFRONT_SHADER_SRC,
"wavefront_finalize_survivors",
),
}
}
}
pub const WAVEFRONT_STOKES_BYTES_PER_TUPLE: usize = 8 * 16;
#[must_use]
pub fn wavefront_pixel_cap(max_binding_bytes: usize, spp: u32, byte_budget_pixels: usize) -> usize {
let tuple_cap = (max_binding_bytes / WAVEFRONT_STOKES_BYTES_PER_TUPLE).max(1);
let spp_usize = (spp as usize).max(1);
let pixel_cap = (tuple_cap / spp_usize).max(1);
byte_budget_pixels.min(pixel_cap)
}
pub fn exclusive_prefix_sum(counts: &[u32]) -> (Vec<u32>, u32) {
let mut offsets = Vec::with_capacity(counts.len());
let mut running = 0u32;
for &c in counts {
offsets.push(running);
running += c;
}
(offsets, running)
}
pub const fn wavefront_generate_bindings<'a>(
camera: &'a wgpu::Buffer,
params_buf: &'a wgpu::Buffer,
wf_params_buf: &'a wgpu::Buffer,
rays: &'a WavefrontRayBuffers,
) -> [(u32, &'a wgpu::Buffer); 19] {
[
(0, camera),
(1, params_buf),
(15, wf_params_buf),
(16, &rays.origin),
(17, &rays.dir),
(18, &rays.k),
(19, &rays.prev_plane_normal),
(20, &rays.flags),
(21, &rays.stokes),
(22, &rays.radiance),
(23, &rays.path_pdf),
(24, &rays.split_radiance),
(25, &rays.compat),
(26, &rays.pending_light_mis),
(27, &rays.lambdas),
(28, &rays.seed),
(29, &rays.active_in),
(34, &rays.pending_light_mis_dir),
(35, &rays.nee_xyz),
]
}
pub struct WavefrontRoundBindGroups {
pub(super) bounce: wgpu::BindGroup,
pub(super) compact_scan: wgpu::BindGroup,
pub(super) compact_scatter: wgpu::BindGroup,
pub(super) finalize: wgpu::BindGroup,
}
#[expect(
clippy::too_many_arguments,
reason = "one parameter per distinct buffer source this chunk's four bind groups draw \
from (device, pipelines, scene buffers, params, geometry length, transport \
outputs, wavefront params, ray state) -- bundling them into a context struct \
here would just re-introduce this exact parameter list one level removed, the \
same reasoning ChunkFrameState/TransportDispatchArgs/TurnSetup already \
document elsewhere in this module tree"
)]
pub fn build_wavefront_round_bind_groups(
device: &wgpu::Device,
pipelines: &WavefrontPipelines,
frame_buffers: &FrameSceneBuffers,
params_buf: &wgpu::Buffer,
planes_len: usize,
outputs: &TransportOutputs,
wf_params_buf: &wgpu::Buffer,
rays: &WavefrontRayBuffers,
) -> WavefrontRoundBindGroups {
WavefrontRoundBindGroups {
bounce: wavefront_bounce_bind_group(
device,
pipelines,
frame_buffers,
params_buf,
planes_len,
outputs,
wf_params_buf,
rays,
),
compact_scan: wavefront_compact_scan_bind_group(device, pipelines, wf_params_buf, rays),
compact_scatter: wavefront_compact_scatter_bind_group(
device,
pipelines,
wf_params_buf,
rays,
),
finalize: wavefront_finalize_bind_group(
device,
pipelines,
params_buf,
outputs,
wf_params_buf,
rays,
),
}
}
#[expect(
clippy::too_many_arguments,
reason = "see build_wavefront_round_bind_groups' own #[expect] just above"
)]
fn wavefront_bounce_bind_group(
device: &wgpu::Device,
pipelines: &WavefrontPipelines,
frame_buffers: &FrameSceneBuffers,
params_buf: &wgpu::Buffer,
planes_len: usize,
outputs: &TransportOutputs,
wf_params_buf: &wgpu::Buffer,
rays: &WavefrontRayBuffers,
) -> wgpu::BindGroup {
let planes_bytes = (planes_len * size_of::<GpuFacetPlane>()) as wgpu::BufferAddress;
let finishes_bytes = (planes_len * size_of::<u32>()) as wgpu::BufferAddress;
compute::bind_buffers_sized(
device,
"wavefront bounce bind group",
&pipelines.bounce,
&[
(0, &frame_buffers.camera, None),
(1, params_buf, None),
(2, &frame_buffers.material, None),
(3, &frame_buffers.planes, Some(planes_bytes)),
(4, outputs.xyz(), None),
(5, outputs.radiance(), None),
(6, outputs.lambdas(), None),
(7, outputs.path_pdf(), None),
(8, &frame_buffers.facet_finishes, Some(finishes_bytes)),
(9, outputs.compat(), None),
(10, &frame_buffers.hdr_env.texels, None),
(11, &frame_buffers.hdr_env.dims, None),
(12, &frame_buffers.hdr_env.dist_func, None),
(13, &frame_buffers.hdr_env.dist_cdf, None),
(14, &frame_buffers.hdr_env.dist_dims, None),
(15, wf_params_buf, None),
(16, &rays.origin, None),
(17, &rays.dir, None),
(18, &rays.k, None),
(19, &rays.prev_plane_normal, None),
(20, &rays.flags, None),
(21, &rays.stokes, None),
(22, &rays.radiance, None),
(23, &rays.path_pdf, None),
(24, &rays.split_radiance, None),
(25, &rays.compat, None),
(26, &rays.pending_light_mis, None),
(27, &rays.lambdas, None),
(28, &rays.seed, None),
(29, &rays.active_in, None),
(34, &rays.pending_light_mis_dir, None),
(35, &rays.nee_xyz, None),
],
)
}
fn wavefront_compact_scan_bind_group(
device: &wgpu::Device,
pipelines: &WavefrontPipelines,
wf_params_buf: &wgpu::Buffer,
rays: &WavefrontRayBuffers,
) -> wgpu::BindGroup {
compute::bind_buffers(
device,
"wavefront compact scan bind group",
&pipelines.compact_scan,
&[
(15, wf_params_buf),
(20, &rays.flags),
(29, &rays.active_in),
(31, &rays.local_offset),
(32, &rays.block_alive_count),
],
)
}
fn wavefront_compact_scatter_bind_group(
device: &wgpu::Device,
pipelines: &WavefrontPipelines,
wf_params_buf: &wgpu::Buffer,
rays: &WavefrontRayBuffers,
) -> wgpu::BindGroup {
compute::bind_buffers(
device,
"wavefront compact scatter bind group",
&pipelines.compact_scatter,
&[
(15, wf_params_buf),
(20, &rays.flags),
(29, &rays.active_in),
(30, &rays.active_out),
(31, &rays.local_offset),
(33, &rays.block_offset),
],
)
}
fn wavefront_finalize_bind_group(
device: &wgpu::Device,
pipelines: &WavefrontPipelines,
params_buf: &wgpu::Buffer,
outputs: &TransportOutputs,
wf_params_buf: &wgpu::Buffer,
rays: &WavefrontRayBuffers,
) -> wgpu::BindGroup {
compute::bind_buffers(
device,
"wavefront finalize survivors bind group",
&pipelines.finalize_survivors,
&[
(1, params_buf),
(4, outputs.xyz()),
(5, outputs.radiance()),
(6, outputs.lambdas()),
(7, outputs.path_pdf()),
(9, outputs.compat()),
(15, wf_params_buf),
(20, &rays.flags),
(22, &rays.radiance),
(23, &rays.path_pdf),
(24, &rays.split_radiance),
(25, &rays.compat),
(27, &rays.lambdas),
(29, &rays.active_in),
(35, &rays.nee_xyz),
],
)
}