use crate::{
geometry::GpuFacetPlane,
renderer::{
buffers::{
DispersionParams, GpuAbsorptionBand, GpuCameraParams, GpuGemMaterial, GpuHitRecord,
GpuRay, GpuTransportParams, GpuWavefrontParams, MAX_ABSORPTION_BANDS,
},
env_map_gpu::{GpuDistDims, GpuHdrEnvDims},
gpu::{compute, frame::GpuReduceParams},
},
};
const SHADER_SRC: &str = include_str!("../shaders/layout_echo.wgsl");
const PHASE1_SHADER_SRC: &str = include_str!("../shaders/phase1_layout_echo.wgsl");
const PHASE2_SHADER_SRC: &str = include_str!("../shaders/phase2_layout_echo.wgsl");
#[derive(Debug, Clone, Copy)]
pub struct ByteMismatch {
pub offset: usize,
pub expected: u8,
pub actual: u8,
}
#[derive(Debug, Clone)]
pub struct LayoutCheckResult {
pub input_bytes: Vec<u8>,
pub output_bytes: Vec<u8>,
pub mismatches: Vec<ByteMismatch>,
}
impl LayoutCheckResult {
#[must_use]
pub const fn passed(&self) -> bool {
self.mismatches.is_empty()
}
}
#[must_use]
pub fn sample_material() -> GpuGemMaterial {
let mut m = GpuGemMaterial::zeroed_material();
m.dispersion.model_type = crate::renderer::buffers::dispersion_model_type::SELLMEIER3;
m.dispersion.param_a = [1.431_349, 0.650_547, 5.341_402, 0.0];
m.dispersion.param_b = [0.00528, 0.01424, 325.015, 0.0];
m.dispersion.param_c = [11.0, 12.0, 13.0, 14.0];
m.dispersion.c_axis_and_birefringence = [0.1, 0.2, 0.3, -0.0081];
m.dispersion.is_anisotropic = 1;
m.dispersion.biaxial_delta_beta_alpha = 0.001_951;
m.dispersion.has_biaxial_delta = 1;
m.crystal_system = crate::renderer::buffers::crystal_system::TRIGONAL;
m.optical_character = crate::renderer::buffers::optical_character::UNIAXIAL_NEGATIVE;
m.is_pleochroic = 1;
m.o_ray_band_count = 2;
m.e_ray_band_count = 3;
for (i, band) in m.o_ray_bands.iter_mut().enumerate() {
*band = GpuAbsorptionBand {
center_nm: 400.0 + i as f32,
width_nm: 20.0 + i as f32,
peak: (i as f32).mul_add(0.1, 1.0),
shape: (i % 2) as u32,
};
}
for (i, band) in m.e_ray_bands.iter_mut().enumerate() {
*band = GpuAbsorptionBand {
center_nm: 500.0 + i as f32,
width_nm: 30.0 + i as f32,
peak: (i as f32).mul_add(0.1, 2.0),
shape: ((i + 1) % 2) as u32,
};
}
m.scattering_sigma_s = 0.734;
m.scattering_g = 0.417;
m.edge_rounding_radius = 0.0512;
m.has_beta_ray = 1;
m.beta_ray_band_count = 2;
for (i, band) in m.beta_ray_bands.iter_mut().enumerate() {
*band = GpuAbsorptionBand {
center_nm: 600.0 + i as f32,
width_nm: 40.0 + i as f32,
peak: (i as f32).mul_add(0.1, 3.0),
shape: (i % 2) as u32,
};
}
m.absorption_path_scale = 2.375;
m.has_extraordinary_dispersion = 1;
m.extraordinary_model_type = crate::renderer::buffers::dispersion_model_type::SELLMEIER3;
m.extraordinary_param_a = [0.288_518, 1.095_099, 1.156_625, 0.0];
m.extraordinary_param_b = [0.0, 0.010_210, 100.0, 0.0];
m
}
trait ZeroedMaterial {
fn zeroed_material() -> Self;
}
impl ZeroedMaterial for GpuGemMaterial {
fn zeroed_material() -> Self {
bytemuck::Zeroable::zeroed()
}
}
#[must_use]
pub fn run(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let material = sample_material();
let input_bytes = bytemuck::bytes_of(&material).to_vec();
let pipeline = compute::create_compute_pipeline(&ctx.device, "layout_echo", SHADER_SRC, "main");
let input_buf = compute::upload(
&ctx.device,
"layout_echo input",
std::slice::from_ref(&material),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let output_buf = compute::zeroed_buffer::<GpuGemMaterial>(
&ctx.device,
"layout_echo output",
1,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"layout_echo bind group",
&pipeline,
&[(0, &input_buf), (1, &output_buf)],
);
compute::dispatch_and_wait(&ctx.device, &ctx.queue, &pipeline, &bind_group, (1, 1, 1));
let output: Vec<GpuGemMaterial> = compute::readback(&ctx.device, &ctx.queue, &output_buf, 1);
let output_bytes = bytemuck::bytes_of(&output[0]).to_vec();
let mismatches = diff_bytes(&input_bytes, &output_bytes);
LayoutCheckResult {
input_bytes,
output_bytes,
mismatches,
}
}
fn diff_bytes(input_bytes: &[u8], output_bytes: &[u8]) -> Vec<ByteMismatch> {
input_bytes
.iter()
.zip(output_bytes.iter())
.enumerate()
.filter_map(|(offset, (&expected, &actual))| {
(expected != actual).then_some(ByteMismatch {
offset,
expected,
actual,
})
})
.take(32)
.collect()
}
fn run_echo<T: bytemuck::Pod + bytemuck::Zeroable>(
ctx: &crate::renderer::gpu::GpuContext,
entry_point: &str,
in_binding: u32,
out_binding: u32,
sample: T,
) -> LayoutCheckResult {
let input_bytes = bytemuck::bytes_of(&sample).to_vec();
let pipeline =
compute::create_compute_pipeline(&ctx.device, entry_point, PHASE1_SHADER_SRC, entry_point);
let input_buf = compute::upload(
&ctx.device,
"phase1 layout_echo input",
std::slice::from_ref(&sample),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let output_buf = compute::zeroed_buffer::<T>(
&ctx.device,
"phase1 layout_echo output",
1,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"phase1 layout_echo bind group",
&pipeline,
&[(in_binding, &input_buf), (out_binding, &output_buf)],
);
compute::dispatch_and_wait(&ctx.device, &ctx.queue, &pipeline, &bind_group, (1, 1, 1));
let output: Vec<T> = compute::readback(&ctx.device, &ctx.queue, &output_buf, 1);
let output_bytes = bytemuck::bytes_of(&output[0]).to_vec();
let mismatches = diff_bytes(&input_bytes, &output_bytes);
LayoutCheckResult {
input_bytes,
output_bytes,
mismatches,
}
}
#[must_use]
pub fn run_facet_plane(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuFacetPlane {
normal: [0.267_261, 0.534_522, 0.801_784],
d: -0.732_051,
};
run_echo(ctx, "echo_plane", 0, 1, sample)
}
#[must_use]
pub fn run_camera_params(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuCameraParams {
origin: [1.1, 2.2, 3.3],
fov_tan: 0.4663,
forward: [4.4, 5.5, 6.6],
width: 800.0,
right: [7.7, 8.8, 9.9],
height: 600.0,
up: [10.1, 11.2, 12.3],
num_samples: 64,
};
run_echo(ctx, "echo_camera", 2, 3, sample)
}
#[must_use]
pub fn run_ray(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuRay::new([0.1, 0.2, 0.3], [0.4, 0.5, 0.6]);
run_echo(ctx, "echo_ray", 4, 5, sample)
}
#[must_use]
pub fn run_hit_record(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuHitRecord::hit(12.75, 23, [0.301_511, 0.492_386, 0.816_497]);
run_echo(ctx, "echo_hit", 6, 7, sample)
}
#[must_use]
pub fn run_facet_finish(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample: Vec<u32> = vec![0, 1, 0, 1, 1, 0, 1, 0];
let input_bytes = bytemuck::cast_slice(&sample).to_vec();
let pipeline = compute::create_compute_pipeline(
&ctx.device,
"echo_facet_finishes",
PHASE1_SHADER_SRC,
"echo_facet_finishes",
);
let input_buf = compute::upload(
&ctx.device,
"facet finish layout_echo input",
&sample,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let output_buf = compute::zeroed_buffer::<u32>(
&ctx.device,
"facet finish layout_echo output",
sample.len(),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"facet finish layout_echo bind group",
&pipeline,
&[(8, &input_buf), (9, &output_buf)],
);
compute::dispatch_and_wait(&ctx.device, &ctx.queue, &pipeline, &bind_group, (1, 1, 1));
let output: Vec<u32> = compute::readback(&ctx.device, &ctx.queue, &output_buf, sample.len());
let output_bytes = bytemuck::cast_slice(&output).to_vec();
let mismatches = diff_bytes(&input_bytes, &output_bytes);
LayoutCheckResult {
input_bytes,
output_bytes,
mismatches,
}
}
#[must_use]
pub fn run_transport_params(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuTransportParams::new(
1234,
9,
4096,
1,
2.5,
6500.0,
1.6,
1.1,
0.4,
0.35,
[1.05, 1.0, 0.92],
)
.with_studio_use_d65(true)
.with_studio_model(crate::renderer::buffers::studio_model::LIGHT_TENT)
.with_backdrop(0.23);
let input_bytes = bytemuck::bytes_of(&sample).to_vec();
let pipeline = compute::create_compute_pipeline(
&ctx.device,
"echo_transport_params",
PHASE2_SHADER_SRC,
"echo_transport_params",
);
let input_buf = compute::upload(
&ctx.device,
"phase2 layout_echo input",
std::slice::from_ref(&sample),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let output_buf = compute::zeroed_buffer::<GpuTransportParams>(
&ctx.device,
"phase2 layout_echo output",
1,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"phase2 layout_echo bind group",
&pipeline,
&[(0, &input_buf), (1, &output_buf)],
);
compute::dispatch_and_wait(&ctx.device, &ctx.queue, &pipeline, &bind_group, (1, 1, 1));
let output: Vec<GpuTransportParams> =
compute::readback(&ctx.device, &ctx.queue, &output_buf, 1);
let output_bytes = bytemuck::bytes_of(&output[0]).to_vec();
let mismatches = diff_bytes(&input_bytes, &output_bytes);
LayoutCheckResult {
input_bytes,
output_bytes,
mismatches,
}
}
fn run_phase2_echo<T: bytemuck::Pod + bytemuck::Zeroable>(
ctx: &crate::renderer::gpu::GpuContext,
entry_point: &str,
in_binding: u32,
out_binding: u32,
sample: T,
) -> LayoutCheckResult {
let input_bytes = bytemuck::bytes_of(&sample).to_vec();
let pipeline =
compute::create_compute_pipeline(&ctx.device, entry_point, PHASE2_SHADER_SRC, entry_point);
let input_buf = compute::upload(
&ctx.device,
"phase2 layout_echo input",
std::slice::from_ref(&sample),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
);
let output_buf = compute::zeroed_buffer::<T>(
&ctx.device,
"phase2 layout_echo output",
1,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
);
let bind_group = compute::bind_buffers(
&ctx.device,
"phase2 layout_echo bind group",
&pipeline,
&[(in_binding, &input_buf), (out_binding, &output_buf)],
);
compute::dispatch_and_wait(&ctx.device, &ctx.queue, &pipeline, &bind_group, (1, 1, 1));
let output: Vec<T> = compute::readback(&ctx.device, &ctx.queue, &output_buf, 1);
let output_bytes = bytemuck::bytes_of(&output[0]).to_vec();
let mismatches = diff_bytes(&input_bytes, &output_bytes);
LayoutCheckResult {
input_bytes,
output_bytes,
mismatches,
}
}
#[must_use]
pub fn run_reduce_params(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuReduceParams {
num_pixels: 1234,
num_samples: 8,
_pad0: 111,
_pad1: 222,
};
run_phase2_echo(ctx, "echo_reduce_params", 2, 3, sample)
}
#[must_use]
pub fn run_wavefront_params(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuWavefrontParams {
chunk_rays: 4096,
active_count: 3072,
bounce: 5,
workgroup_count: 48,
};
run_phase2_echo(ctx, "echo_wavefront_params", 4, 5, sample)
}
#[must_use]
pub fn run_hdr_env_dims(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuHdrEnvDims {
width: 640,
height: 320,
_pad0: 111,
_pad1: 222,
};
run_phase2_echo(ctx, "echo_hdr_env_dims", 6, 7, sample)
}
#[must_use]
pub fn run_dist_dims(ctx: &crate::renderer::gpu::GpuContext) -> LayoutCheckResult {
let sample = GpuDistDims {
width: 128,
height: 64,
marginal_func_int: 3.5,
_pad0: 7.25,
};
run_phase2_echo(ctx, "echo_dist_dims", 8, 9, sample)
}
#[must_use]
pub const fn field_name_at_offset(offset: usize) -> &'static str {
match offset {
0..=15 => "dispersion.model_type (+ implicit pad)",
16..=31 => "dispersion.param_a",
32..=47 => "dispersion.param_b",
48..=63 => "dispersion.param_c",
64..=79 => "dispersion.c_axis_and_birefringence",
80..=83 => "dispersion.is_anisotropic",
84..=87 => "dispersion.biaxial_delta_beta_alpha",
88..=91 => "dispersion.has_biaxial_delta",
92..=95 => "dispersion._pad_tail",
96..=99 => "crystal_system",
100..=103 => "optical_character",
104..=107 => "is_pleochroic",
108..=111 => "o_ray_band_count",
112..=115 => "e_ray_band_count",
116..=243 => "o_ray_bands[..]",
244..=371 => "e_ray_bands[..]",
372..=375 => "scattering_sigma_s",
376..=379 => "scattering_g",
380..=383 => "edge_rounding_radius",
384..=387 => "has_beta_ray",
388..=391 => "beta_ray_band_count",
392..=519 => "beta_ray_bands[..]",
520..=523 => "absorption_path_scale",
524..=527 => "has_extraordinary_dispersion",
528..=531 => "extraordinary_model_type",
532..=543 => "_pad_before_extraordinary_params",
544..=559 => "extraordinary_param_a",
560..=575 => "extraordinary_param_b",
_ => "out of range (GpuGemMaterial is 576 bytes)",
}
}
const _: () = assert!(size_of::<DispersionParams>() == 96);
const _: () = assert!(MAX_ABSORPTION_BANDS == 8);