use super::RenderHandle;
use crate::{
GeometryBuffer, GeometryPixel, RenderConfig, RenderWorker, TileSizesRef,
VoxelSize,
config::{Tile, VoxelRenderConfig},
};
use fidget_core::{
eval::Function,
shape::{Shape, ShapeBulkEval, ShapeTracingEval, ShapeVars},
types::{Grad, Interval},
};
use nalgebra::{Point3, Vector2, Vector3};
struct Scratch {
x: Vec<f32>,
y: Vec<f32>,
z: Vec<f32>,
xg: Vec<Grad>,
yg: Vec<Grad>,
zg: Vec<Grad>,
columns: Vec<usize>,
}
impl Scratch {
fn new(tile_size: usize) -> Self {
let size2 = tile_size.pow(2);
let size3 = tile_size.pow(3);
Self {
x: vec![0.0; size3],
y: vec![0.0; size3],
z: vec![0.0; size3],
xg: vec![Grad::from(0.0); size2],
yg: vec![Grad::from(0.0); size2],
zg: vec![Grad::from(0.0); size2],
columns: vec![0; size2],
}
}
}
struct Worker<'a, F: Function> {
tile_sizes: TileSizesRef<'a>,
image_size: VoxelSize,
scratch: Scratch,
eval_float_slice: ShapeBulkEval<F::FloatSliceEval>,
eval_grad_slice: ShapeBulkEval<F::GradSliceEval>,
eval_interval: ShapeTracingEval<F::IntervalEval>,
tape_storage: Vec<F::TapeStorage>,
shape_storage: Vec<F::Storage>,
workspace: F::Workspace,
out: GeometryBuffer,
}
impl<'a, F: Function, T> RenderWorker<'a, F, T> for Worker<'a, F> {
type Config = VoxelRenderConfig<'a>;
type Output = GeometryBuffer;
fn new(cfg: &'a Self::Config) -> Self {
let tile_sizes = cfg.tile_sizes();
let buf_size = tile_sizes.last();
let scratch = Scratch::new(buf_size);
Worker {
scratch,
out: Default::default(),
tile_sizes,
image_size: cfg.image_size,
eval_float_slice: Default::default(),
eval_interval: Default::default(),
eval_grad_slice: Default::default(),
tape_storage: vec![],
shape_storage: vec![],
workspace: Default::default(),
}
}
fn render_tile(
&mut self,
shape: &mut RenderHandle<F, T>,
vars: &ShapeVars<f32>,
tile: super::config::Tile<2>,
) -> Self::Output {
let root_tile_size = self.tile_sizes[0];
self.out = GeometryBuffer::new(VoxelSize::from(root_tile_size as u32));
for k in (0..self.image_size[2].div_ceil(root_tile_size as u32)).rev() {
let tile = Tile::new(Point3::new(
tile.corner.x,
tile.corner.y,
k as usize * root_tile_size,
));
if !self.render_tile_recurse(shape, vars, 0, tile) {
break;
}
}
std::mem::take(&mut self.out)
}
}
impl<F: Function> Worker<'_, F> {
pub(crate) fn tile_row_offset(&self, tile: Tile<3>, row: usize) -> usize {
self.tile_sizes.pixel_offset(tile.add(Vector2::new(0, row)))
}
fn render_tile_recurse<T>(
&mut self,
shape: &mut RenderHandle<F, T>,
vars: &ShapeVars<f32>,
depth: usize,
tile: Tile<3>,
) -> bool {
let tile_size = self.tile_sizes[depth];
let fill_z = (tile.corner[2] + tile_size + 1) as f32;
if (0..tile_size).all(|y| {
let i = self.tile_row_offset(tile, y);
(0..tile_size).all(|x| self.out[i + x].depth >= fill_z)
}) {
return false;
}
let base = Point3::from(tile.corner).cast::<f32>();
let x = Interval::new(base.x, base.x + tile_size as f32);
let y = Interval::new(base.y, base.y + tile_size as f32);
let z = Interval::new(base.z, base.z + tile_size as f32);
let (i, trace) = self
.eval_interval
.eval_v(shape.i_tape(&mut self.tape_storage), x, y, z, vars)
.unwrap();
if i.upper() < 0.0 {
for y in 0..tile_size {
let i = self.tile_row_offset(tile, y);
for x in 0..tile_size {
self.out[i + x].depth = self.out[i + x].depth.max(fill_z);
}
}
return false; } else if i.lower() > 0.0 {
return true; }
let sub_tape = if let Some(trace) = trace.as_ref() {
shape.simplify(
trace,
&mut self.workspace,
&mut self.shape_storage,
&mut self.tape_storage,
)
} else {
shape
};
if let Some(next_tile_size) = self.tile_sizes.get(depth + 1) {
let n = tile_size / next_tile_size;
for j in 0..n {
for i in 0..n {
for k in (0..n).rev() {
self.render_tile_recurse(
sub_tape,
vars,
depth + 1,
Tile::new(
tile.corner
+ Vector3::new(i, j, k) * next_tile_size,
),
);
}
}
}
} else {
self.render_tile_pixels(sub_tape, vars, tile_size, tile);
};
true }
fn render_tile_pixels<T>(
&mut self,
shape: &mut RenderHandle<F, T>,
vars: &ShapeVars<f32>,
tile_size: usize,
tile: Tile<3>,
) {
let mut index = 0;
assert!(self.scratch.x.len() >= tile_size.pow(3));
assert!(self.scratch.y.len() >= tile_size.pow(3));
assert!(self.scratch.z.len() >= tile_size.pow(3));
self.scratch.columns.clear();
for xy in 0..tile_size.pow(2) {
let i = xy % tile_size;
let j = xy / tile_size;
let o = self.tile_sizes.pixel_offset(tile.add(Vector2::new(i, j)));
let zmax = (tile.corner[2] + tile_size) as f32;
if self.out[o].depth >= zmax {
continue;
}
for k in (0..tile_size).rev() {
unsafe {
*self.scratch.x.get_unchecked_mut(index) =
(tile.corner[0] + i) as f32;
*self.scratch.y.get_unchecked_mut(index) =
(tile.corner[1] + j) as f32;
*self.scratch.z.get_unchecked_mut(index) =
(tile.corner[2] + k) as f32;
}
index += 1;
}
self.scratch.columns.push(xy);
}
let size = index;
assert!(size > 0);
let out = self
.eval_float_slice
.eval_v(
shape.f_tape(&mut self.tape_storage),
&self.scratch.x[..index],
&self.scratch.y[..index],
&self.scratch.z[..index],
vars,
)
.unwrap();
let mut grad = 0;
let mut depth = out.chunks(tile_size);
for col in 0..self.scratch.columns.len() {
let depth = depth.next().unwrap();
let k = match depth.iter().enumerate().find(|(_, d)| **d < 0.0) {
Some((i, _)) => i,
None => continue,
};
let xy = self.scratch.columns[col];
let i = xy % tile_size;
let j = xy / tile_size;
let k = tile_size - 1 - k;
let o = self.tile_sizes.pixel_offset(tile.add(Vector2::new(i, j)));
let z = (tile.corner[2] + k + 1) as f32;
assert!(self.out[o].depth < z);
self.out[o].depth = z;
self.scratch.xg[grad] =
Grad::new((tile.corner[0] + i) as f32, 1.0, 0.0, 0.0);
self.scratch.yg[grad] =
Grad::new((tile.corner[1] + j) as f32, 0.0, 1.0, 0.0);
self.scratch.zg[grad] =
Grad::new((tile.corner[2] + k) as f32, 0.0, 0.0, 1.0);
self.scratch.columns[grad] = o;
grad += 1;
}
if grad > 0 {
let out = self
.eval_grad_slice
.eval_v(
shape.g_tape(&mut self.tape_storage),
&self.scratch.xg[..grad],
&self.scratch.yg[..grad],
&self.scratch.zg[..grad],
vars,
)
.unwrap();
for (index, o) in self.scratch.columns[0..grad].iter().enumerate() {
let g = out[index];
self.out[*o].normal = [g.dx, g.dy, g.dz];
}
}
}
}
pub fn render<F: Function>(
shape: Shape<F>,
vars: &ShapeVars<f32>,
config: &VoxelRenderConfig,
) -> Option<GeometryBuffer> {
let shape = shape.with_transform(config.mat());
let tiles = super::render_tiles::<F, Worker<F>, _>(shape, vars, config)?;
let tile_sizes = config.tile_sizes();
let width = config.image_size.width() as usize;
let height = config.image_size.height() as usize;
let mut image = GeometryBuffer::new(config.image_size);
for (tile, out) in tiles {
let mut index = 0;
for j in 0..tile_sizes[0] {
let y = j + tile.corner.y;
for i in 0..tile_sizes[0] {
let x = i + tile.corner.x;
if x < width && y < height {
let o = y * width + x;
if out[index].depth >= image[o].depth {
let d = (config.image_size.depth() - 1) as f32;
if out[index].depth >= d {
image[o] = GeometryPixel {
depth: d + 1.0,
normal: [0.0, 0.0, 1.0],
};
} else {
image[o] = out[index];
}
}
}
index += 1;
}
}
}
Some(image)
}
#[cfg(test)]
mod test {
use super::*;
use fidget_core::{Context, render::VoxelSize, vm::VmShape};
#[test]
fn test_tile_queues() {
let mut ctx = Context::new();
let x = ctx.x();
let shape = VmShape::new(&ctx, x).unwrap();
let cfg = VoxelRenderConfig {
image_size: VoxelSize::from(128), ..Default::default()
};
let image = cfg.run(shape).unwrap();
assert_eq!(image.len(), 128 * 128);
}
#[test]
fn cancel_render() {
let mut ctx = Context::new();
let x = ctx.x();
let shape = VmShape::new(&ctx, x).unwrap();
let cfg = VoxelRenderConfig {
image_size: VoxelSize::new(64, 64, 64),
..Default::default()
};
let cancel = cfg.cancel.clone();
cancel.cancel();
let out = cfg.run::<_>(shape);
assert!(out.is_none());
}
}