examples/relight_pixels.rs
Build-time source snapshot · Plain text
1//! Focused native GPU image checks. This is not a frame-rate benchmark.2use pajama_gaussian_lab::relight::{RelightFrame as Frame, RelightPass};3use std::{error::Error, fs};45fn main() -> Result<(), Box<dyn Error>> {6 pollster::block_on(run())7}89async fn run() -> Result<(), Box<dyn Error>> {10 let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();11 if cfg!(target_os = "windows") {12 descriptor.backends = wgpu::Backends::DX12;13 }14 let instance = wgpu::Instance::new(descriptor);15 let adapter = instance16 .request_adapter(&wgpu::RequestAdapterOptions {17 power_preference: wgpu::PowerPreference::HighPerformance,18 compatible_surface: None,19 force_fallback_adapter: false,20 apply_limit_buckets: false,21 })22 .await?;23 let info = adapter.get_info();24 let (device, queue) = adapter25 .request_device(&wgpu::DeviceDescriptor::default())26 .await?;27 let (width, height) = (256_u32, 192_u32);28 let extent = wgpu::Extent3d {29 width,30 height,31 depth_or_array_layers: 1,32 };33 let format = if std::env::args().any(|arg| arg == "--srgb") {34 wgpu::TextureFormat::Rgba8UnormSrgb35 } else {36 wgpu::TextureFormat::Rgba8Unorm37 };38 let texture = device.create_texture(&wgpu::TextureDescriptor {39 label: Some("relight-pixel-target"),40 size: extent,41 mip_level_count: 1,42 sample_count: 1,43 dimension: wgpu::TextureDimension::D2,44 format,45 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,46 view_formats: &[],47 });48 let target = texture.create_view(&Default::default());49 let stride = (width * 4).div_ceil(256) * 256;50 let readback = device.create_buffer(&wgpu::BufferDescriptor {51 label: Some("relight-readback"),52 size: u64::from(stride * height),53 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,54 mapped_at_creation: false,55 });56 let mut renderer = RelightPass::new(&device, format);57 let base = Frame::default();58 let cases = [59 ("combined", base),60 (61 "light-right",62 Frame {63 azimuth: 1.0,64 ..base65 },66 ),67 (68 "dark",69 Frame {70 intensity: 0.0,71 ..base72 },73 ),74 ("diffuse", Frame { mode: 1, ..base }),75 (76 "metal-diffuse",77 Frame {78 mode: 1,79 metallic: 1.0,80 ..base81 },82 ),83 ("specular", Frame { mode: 2, ..base }),84 (85 "rough-specular",86 Frame {87 mode: 2,88 roughness: 0.9,89 ..base90 },91 ),92 ("albedo", Frame { mode: 4, ..base }),93 (94 "albedo-dark",95 Frame {96 mode: 4,97 intensity: 0.0,98 azimuth: 2.0,99 ..base100 },101 ),102 ("deform", Frame { time: 0.25, ..base }),103 (104 "bad-normals",105 Frame {106 time: 0.25,107 wrong_normals: true,108 ..base109 },110 ),111 ("orbit", Frame { yaw: 0.8, ..base }),112 ("normals", Frame { mode: 3, ..base }),113 ];114 fs::create_dir_all("artifacts/relight")?;115 let mut images = std::collections::BTreeMap::new();116 let mut reports = Vec::new();117 for (name, frame) in cases {118 let mut encoder = device.create_command_encoder(&Default::default());119 renderer.render(120 &device,121 &queue,122 &mut encoder,123 &target,124 [width, height],125 frame,126 )?;127 encoder.copy_texture_to_buffer(128 wgpu::TexelCopyTextureInfo {129 texture: &texture,130 mip_level: 0,131 origin: wgpu::Origin3d::ZERO,132 aspect: wgpu::TextureAspect::All,133 },134 wgpu::TexelCopyBufferInfo {135 buffer: &readback,136 layout: wgpu::TexelCopyBufferLayout {137 offset: 0,138 bytes_per_row: Some(stride),139 rows_per_image: Some(height),140 },141 },142 extent,143 );144 queue.submit([encoder.finish()]);145 let (send, receive) = std::sync::mpsc::channel();146 readback147 .slice(..)148 .map_async(wgpu::MapMode::Read, move |result| {149 let _ = send.send(result);150 });151 device.poll(wgpu::PollType::wait_indefinitely())?;152 receive.recv()??;153 let mapped = readback.slice(..).get_mapped_range()?;154 let mut rgba = Vec::new();155 for row in mapped.chunks_exact(stride as usize) {156 rgba.extend_from_slice(&row[..(width * 4) as usize]);157 }158 drop(mapped);159 readback.unmap();160 let colored = rgba161 .chunks_exact(4)162 .filter(|p| p[..3].iter().any(|v| *v > 3))163 .count();164 let center = rgba[((height / 2 * width + width / 2) * 4) as usize..][..4].to_vec();165 let mut png = png::Encoder::new(166 fs::File::create(format!("artifacts/relight/{name}.png"))?,167 width,168 height,169 );170 png.set_color(png::ColorType::Rgba);171 png.set_depth(png::BitDepth::Eight);172 png.write_header()?.write_image_data(&rgba)?;173 reports174 .push(serde_json::json!({"name":name,"colored_pixels":colored,"center_rgba":center}));175 images.insert(name, rgba);176 }177 let changed = |a: &str, b: &str| {178 images[a]179 .iter()180 .zip(&images[b])181 .filter(|(a, b)| a.abs_diff(**b) > 2)182 .count()183 };184 assert_eq!(185 images["albedo"], images["albedo-dark"],186 "Albedo must not contain light"187 );188 assert_eq!(189 images["dark"], images["metal-diffuse"],190 "Metal has zero diffuse term"191 );192 for (a, b) in [193 ("combined", "light-right"),194 ("combined", "dark"),195 ("specular", "rough-specular"),196 ("deform", "bad-normals"),197 ("combined", "orbit"),198 ] {199 assert!(200 changed(a, b) > 500,201 "{a}/{b}: insufficient image difference"202 );203 }204 let center = ((height / 2 * width + width / 2) * 4) as usize;205 for (channel, expected) in [195_u8, 104, 60].into_iter().enumerate() {206 assert!(207 images["albedo"][center + channel].abs_diff(expected) <= 1,208 "Base color must be sRGB encoded exactly once"209 );210 }211 assert!(images["combined"][center] > 80, "Expected a lit surface");212 let report = serde_json::json!({"schema":"pajama.relight.pixel-check.v1","adapter":info.name,"backend":format!("{:?}",info.backend),"resolution":[width,height],"passed":true,"cases":reports,"claims":["light changes radiance","roughness changes highlight","albedo independent of light","metal diffuse zero","normal transform changes shading","camera changes pixels"],"note":"Native offscreen checks; not all-platform certification or paper reproduction."});213 fs::write(214 "artifacts/relight/report.json",215 serde_json::to_string_pretty(&report)?,216 )?;217 println!("{}", serde_json::to_string_pretty(&report)?);218 Ok(())219}220