use super::{WhittedGpuError, WhittedGpuRenderer};
use crate::{
geometry::tool::{
GPU_MAX_TOOL_PRIMITIVES, TOOL_RECORD_VECS, ToolPrimitive, pack_planes_and_tools,
},
optics::raytracer::FacetFinish,
renderer::{
buffers::{GpuCameraParams, whitted::GpuWhittedParams},
frame_scene::FrameScene,
gpu::compute,
whitted::{WhittedPrep, env::WhittedGpuEnv},
},
};
use wgpu::util::DeviceExt;
pub struct WhittedGpuScene {
pub(super) device_id: u64,
pub(super) camera: wgpu::Buffer,
pub(super) planes: wgpu::Buffer,
pub(super) environment: wgpu::Buffer,
pub(super) dimensions: wgpu::Buffer,
pub(super) frost: wgpu::Buffer,
pub(super) params: GpuWhittedParams,
pub pixels: u32,
pub taps: u32,
}
type EnvironmentUpload = (Vec<[f32; 4]>, [u32; 4]);
#[cfg_attr(
not(test),
expect(
clippy::match_wildcard_for_single_variants,
reason = "the only other variant, Constant, exists under cfg(test) alone"
)
)]
fn environment_upload(
env: &WhittedGpuEnv<'_>,
storage_limit: u64,
) -> Result<EnvironmentUpload, WhittedGpuError> {
Ok(match *env {
WhittedGpuEnv::Hdr(map) => {
let bytes = (map.width() as u64)
.saturating_mul(map.height() as u64)
.saturating_mul(16);
if bytes > storage_limit {
return Err(WhittedGpuError::Declined(
"HDR map exceeds Ray-Traced GPU storage limit".into(),
));
}
let data: Vec<[f32; 4]> = map
.pixels()
.iter()
.map(|p| [p[0], p[1], p[2], 0.0])
.collect();
let width = u32::try_from(map.width())
.expect("HDR width fits u32 (storage-limit checked above)");
let height = u32::try_from(map.height())
.expect("HDR height fits u32 (storage-limit checked above)");
(data, [width, height, 0, 0])
}
WhittedGpuEnv::Aset { table, .. } => (
table.iter().map(|v| [v.x, v.y, v.z, 0.0]).collect(),
[1024, 1, 0, 0],
),
_ => (vec![[0.0; 4]], [1, 1, 0, 0]),
})
}
fn frost_records(scene: &FrameScene<'_>, prep: &WhittedPrep<'_>, count: usize) -> Vec<[f32; 4]> {
(0..count)
.map(|index| {
let rgb = prep.frost.get(index).copied().unwrap_or(glam::Vec3::ZERO);
[
rgb.x,
rgb.y,
rgb.z,
if matches!(scene.facet_finishes.get(index), Some(FacetFinish::Frosted)) {
1.0
} else {
0.0
},
]
})
.collect()
}
impl WhittedGpuRenderer {
pub fn prepare(
&self,
scene: &FrameScene<'_>,
tools: &[ToolPrimitive],
prep: &WhittedPrep<'_>,
) -> Result<WhittedGpuScene, WhittedGpuError> {
self.healthy()?;
let pixels = u64::from(scene.width) * u64::from(scene.height);
if pixels == 0
|| pixels > u64::from(u32::MAX)
|| tools.len() > GPU_MAX_TOOL_PRIMITIVES
|| scene.planes.len() > u32::MAX as usize
{
return Err(WhittedGpuError::Declined(
"Ray-Traced GPU geometry or dimensions exceed supported limits".into(),
));
}
let storage_limit = self
.ctx
.device
.limits()
.max_storage_buffer_binding_size
.min(self.ctx.device.limits().max_buffer_size);
let packed_count = (scene.planes.len() as u64)
.saturating_add((tools.len() as u64).saturating_mul(TOOL_RECORD_VECS as u64))
.max(1);
let count = (scene.planes.len() + tools.len()).max(1);
if packed_count.saturating_mul(16) > storage_limit
|| (count as u64).saturating_mul(16) > storage_limit
{
return Err(WhittedGpuError::Declined(
"Ray-Traced geometry exceeds GPU storage budget".into(),
));
}
let (texels, mut dims) = environment_upload(&prep.env.gpu_data(), storage_limit)?;
let mut packed = pack_planes_and_tools(scene.planes, tools);
if packed.is_empty() {
packed
.to_mut()
.push(crate::geometry::GpuFacetPlane::new(glam::Vec3::ZERO, 0.0));
}
dims[2] = scene.width;
dims[3] = scene.height;
let frost = frost_records(scene, prep, count);
let camera = GpuCameraParams {
origin: scene.camera.origin.to_array(),
fov_tan: scene.camera.fov_tan,
forward: scene.camera.forward.to_array(),
width: scene.width as f32,
right: scene.camera.right.to_array(),
height: scene.height as f32,
up: scene.camera.up.to_array(),
num_samples: 1,
};
let upload = |label, bytes, usage| {
self.ctx
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(label),
contents: bytes,
usage,
})
};
let params = GpuWhittedParams::new(scene, tools.len(), prep);
Ok(WhittedGpuScene {
device_id: self.id,
camera: upload(
"Ray camera",
bytemuck::bytes_of(&camera),
wgpu::BufferUsages::UNIFORM,
),
planes: upload(
"Ray planes/tools",
bytemuck::cast_slice(&packed),
wgpu::BufferUsages::STORAGE,
),
environment: upload(
"Ray HDR/ASET",
bytemuck::cast_slice(&texels),
wgpu::BufferUsages::STORAGE,
),
dimensions: upload(
"Ray HDR dimensions",
bytemuck::bytes_of(&dims),
wgpu::BufferUsages::UNIFORM,
),
frost: compute::upload(
&self.ctx.device,
"Ray frost",
&frost,
wgpu::BufferUsages::STORAGE,
),
params,
pixels: u32::try_from(pixels).expect("pixel count checked against u32::MAX above"),
taps: params.counts[2],
})
}
}