use crate::builtin::{AovKind, AovRenderer};
use crate::camera::Camera3d;
use crate::context::Context;
use crate::light::LightCollection;
use crate::scene::SceneNode3d;
use image::{ImageBuffer, Luma, Rgb};
use super::Window;
impl Window {
pub fn snap_depth_raw(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> Vec<f32> {
self.render_aov::<f32>(AovKind::Depth, scene, camera, 1)
}
pub fn snap_depth(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> ImageBuffer<Luma<u8>, Vec<u8>> {
let (w, h) = self.canvas.size();
let depth = self.snap_depth_raw(scene, camera);
Self::depth_to_luma8(&depth, w, h)
}
pub fn snap_normals(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
self.snap_normals_kind(AovKind::Normals, scene, camera)
}
pub fn snap_camera_normals(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
self.snap_normals_kind(AovKind::CameraNormals, scene, camera)
}
fn snap_normals_kind(
&mut self,
kind: AovKind,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
let (w, h) = self.canvas.size();
let data = self.render_aov::<f32>(kind, scene, camera, 4);
let mut img = ImageBuffer::new(w, h);
for (i, px) in img.pixels_mut().enumerate() {
let base = i * 4;
let to_u8 = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
*px = Rgb([
to_u8(data[base]),
to_u8(data[base + 1]),
to_u8(data[base + 2]),
]);
}
img
}
pub fn snap_segmentation(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> Vec<u32> {
self.render_aov::<u32>(AovKind::Segmentation, scene, camera, 1)
}
pub fn snap_segmentation_colored(
&mut self,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
let (w, h) = self.canvas.size();
let ids = self.snap_segmentation(scene, camera);
let mut img = ImageBuffer::new(w, h);
for (i, px) in img.pixels_mut().enumerate() {
*px = Rgb(colorize_id(ids[i]));
}
img
}
pub fn render_aov_3d(
&mut self,
kind: AovKind,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
depth_range: f32,
) {
let w = self.width().max(1);
let h = self.height().max(1);
let ctxt = Context::get();
camera.update(&self.canvas);
let mut lights = LightCollection::with_ambient(self.ambient_intensity);
scene.data_mut().prepare(0, camera, &mut lights, w, h);
let color = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("aov_color_texture"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: kind.format(),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let color_view = color.create_view(&wgpu::TextureViewDescriptor::default());
let depth = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("aov_depth_texture"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Context::depth_format(),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let depth_view = depth.create_view(&wgpu::TextureViewDescriptor::default());
if self.aov_renderer.is_none() {
self.aov_renderer = Some(AovRenderer::new());
}
let out_view = self.offscreen_output_view();
let out_format = self.canvas.surface_format();
let mut encoder = ctxt.create_command_encoder(Some("aov_encoder"));
let aov = self.aov_renderer.as_mut().unwrap();
aov.render(kind, scene, camera, &mut encoder, &color_view, &depth_view);
aov.visualize_into(
kind,
&mut encoder,
&color_view,
&out_view,
out_format,
depth_range,
);
ctxt.submit(std::iter::once(encoder.finish()));
let out_color = self
.offscreen_output_target
.as_ref()
.expect("offscreen output target was just created")
.color_texture()
.expect("offscreen render target is never the screen")
.clone();
self.canvas.copy_texture_to_readback(&out_color);
}
pub fn depth_to_luma8(
depth: &[f32],
width: u32,
height: u32,
) -> ImageBuffer<Luma<u8>, Vec<u8>> {
let mut min = f32::INFINITY;
let mut max = f32::NEG_INFINITY;
for &d in depth {
if d.is_finite() && d > 0.0 {
min = min.min(d);
max = max.max(d);
}
}
let range = max - min;
let mut img = ImageBuffer::new(width, height);
for (i, px) in img.pixels_mut().enumerate() {
let d = depth[i];
let v = if d.is_finite() && d > 0.0 && range > 0.0 {
let t = (d - min) / range;
((1.0 - t) * 255.0).round() as u8
} else if d.is_finite() && d > 0.0 {
255
} else {
0
};
*px = Luma([v]);
}
img
}
fn render_aov<T: bytemuck::Pod + Default>(
&mut self,
kind: AovKind,
scene: &mut SceneNode3d,
camera: &mut dyn Camera3d,
channels: usize,
) -> Vec<T> {
let w = self.width().max(1);
let h = self.height().max(1);
let ctxt = Context::get();
camera.update(&self.canvas);
let mut lights = LightCollection::with_ambient(self.ambient_intensity);
scene.data_mut().prepare(0, camera, &mut lights, w, h);
let color = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("aov_color_texture"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: kind.format(),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let color_view = color.create_view(&wgpu::TextureViewDescriptor::default());
let depth = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("aov_depth_texture"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Context::depth_format(),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let depth_view = depth.create_view(&wgpu::TextureViewDescriptor::default());
if self.aov_renderer.is_none() {
self.aov_renderer = Some(AovRenderer::new());
}
let mut encoder = ctxt.create_command_encoder(Some("aov_encoder"));
self.aov_renderer.as_mut().unwrap().render(
kind,
scene,
camera,
&mut encoder,
&color_view,
&depth_view,
);
ctxt.submit(std::iter::once(encoder.finish()));
read_texture::<T>(&color, w, h, channels)
}
}
fn read_texture<T: bytemuck::Pod + Default>(
texture: &wgpu::Texture,
width: u32,
height: u32,
channels: usize,
) -> Vec<T> {
let ctxt = Context::get();
let elem_size = std::mem::size_of::<T>();
let bytes_per_pixel = elem_size * channels;
let unpadded_bytes_per_row = width as usize * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
let buffer_size = padded_bytes_per_row * height as usize;
let staging = ctxt.create_buffer(&wgpu::BufferDescriptor {
label: Some("aov_staging_buffer"),
size: buffer_size as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder = ctxt.create_command_encoder(Some("aov_readback_encoder"));
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &staging,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row as u32),
rows_per_image: Some(height),
},
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
ctxt.submit(std::iter::once(encoder.finish()));
let slice = staging.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
let _ = ctxt.device.poll(wgpu::PollType::wait_indefinitely());
rx.recv().unwrap().unwrap();
let mapped = slice.get_mapped_range();
let mut data: Vec<T> = vec![T::default(); width as usize * height as usize * channels];
for row in 0..height as usize {
let src = row * padded_bytes_per_row;
let dst = row * width as usize * channels;
let row_bytes = &mapped[src..src + unpadded_bytes_per_row];
let row_elems: &[T] = bytemuck::cast_slice(row_bytes);
data[dst..dst + width as usize * channels].copy_from_slice(row_elems);
}
drop(mapped);
staging.unmap();
data
}
fn colorize_id(id: u32) -> [u8; 3] {
if id == 0 {
return [0, 0, 0];
}
let hue = (id as f32 * 0.618_034).fract();
hsv_to_rgb(hue, 0.65, 0.95)
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [u8; 3] {
let i = (h * 6.0).floor();
let f = h * 6.0 - i;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
let (r, g, b) = match (i as i32).rem_euclid(6) {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q),
};
[
(r * 255.0).round() as u8,
(g * 255.0).round() as u8,
(b * 255.0).round() as u8,
]
}