1use super::RenderHandle;
3use crate::{
4 Image as GenericImage, RenderSize as _, RenderWorker, Tile, TileSizesRef,
5};
6use fidget_core::{
7 eval::Function,
8 render::{CancelToken, RenderHints, ThreadPool, TileSizes},
9 shape::{
10 BoundShape, ShapeBulkEval, ShapeBulkEvalError, ShapeTracingEval,
11 ShapeTracingEvalError, ShapeVars,
12 },
13 types::Interval,
14};
15use nalgebra::{Matrix3, Point2, Vector2};
16
17pub type Image = GenericImage<RawDistancePixel>;
21
22pub type RenderSize = fidget_core::render::ImageSize;
24
25#[derive(Copy, Clone, Debug)]
27pub struct RenderConfig {
28 pub image_size: RenderSize,
30
31 pub world_to_model: Matrix3<f32>,
33
34 pub pixel_perfect: bool,
36
37 pub z: f32,
39}
40
41pub struct EvalConfig<'a> {
43 pub tile_sizes: Option<TileSizes>,
48
49 pub threads: Option<&'a ThreadPool>,
54
55 pub cancel: CancelToken,
57}
58
59impl crate::EvalConfig for EvalConfig<'_> {
60 fn threads(&self) -> Option<&ThreadPool> {
61 self.threads
62 }
63 fn is_cancelled(&self) -> bool {
64 self.cancel.is_cancelled()
65 }
66}
67
68impl Default for EvalConfig<'_> {
69 fn default() -> Self {
70 Self {
71 tile_sizes: None,
72 threads: Some(&ThreadPool::Global),
73 cancel: CancelToken::new(),
74 }
75 }
76}
77
78impl crate::RenderSize for RenderConfig {
79 fn width(&self) -> u32 {
80 self.image_size.width()
81 }
82 fn height(&self) -> u32 {
83 self.image_size.height()
84 }
85}
86
87impl RenderConfig {
88 pub fn from_size(image_size: RenderSize) -> Self {
104 Self {
105 image_size,
106 world_to_model: Matrix3::identity(),
107 pixel_perfect: false,
108 z: 0.0,
109 }
110 }
111
112 pub fn run<F: Function + RenderHints>(
114 &self,
115 shape: BoundShape<F, f32>,
116 ) -> Image {
117 render(shape, self, &Default::default())
118 .expect("no cancellation is possible")
119 }
120
121 pub fn mat(&self) -> Matrix3<f32> {
123 self.world_to_model * self.image_size.screen_to_world()
124 }
125}
126
127struct Scratch {
130 x: Vec<f32>,
131 y: Vec<f32>,
132 z: Vec<f32>,
133}
134
135impl Scratch {
136 fn new(size: usize) -> Self {
137 Self {
138 x: vec![0.0; size],
139 y: vec![0.0; size],
140 z: vec![0.0; size],
141 }
142 }
143}
144
145#[derive(
160 Copy,
161 Clone,
162 Debug,
163 Default,
164 zerocopy::FromBytes,
165 zerocopy::Immutable,
166 zerocopy::IntoBytes,
167)]
168#[repr(C)]
169pub struct RawDistancePixel(f32);
170
171#[derive(Copy, Clone, Debug)]
173pub enum DistancePixel {
174 Value(f32),
176 Fill {
178 depth: u8,
180 inside: bool,
182 },
183}
184
185impl RawDistancePixel {
186 const KEY: u32 = 0b1111_0110 << 9;
187 const KEY_MASK: u32 = 0b1111_1111 << 9;
188
189 #[inline]
194 pub fn inside(self) -> bool {
195 match self.unpack() {
196 DistancePixel::Fill { inside, .. } => inside,
197 DistancePixel::Value(v) => v < 0.0,
198 }
199 }
200
201 #[inline]
203 pub fn is_distance(self) -> bool {
204 if !self.0.is_nan() {
205 return true;
206 }
207 let bits = self.0.to_bits();
208 (bits & Self::KEY_MASK) != Self::KEY
209 }
210
211 #[inline]
213 pub fn unpack(self) -> DistancePixel {
214 if self.is_distance() {
215 DistancePixel::Value(self.0)
216 } else {
217 let bits = self.0.to_bits();
218 let inside = (bits & 1) == 1;
219 let depth = (bits >> 1) as u8;
220 DistancePixel::Fill { inside, depth }
221 }
222 }
223
224 #[inline]
226 pub fn distance(self) -> Option<f32> {
227 if let DistancePixel::Value(v) = self.unpack() {
228 Some(v)
229 } else {
230 None
231 }
232 }
233}
234
235impl From<DistancePixel> for RawDistancePixel {
236 #[inline]
237 fn from(p: DistancePixel) -> Self {
238 match p {
239 DistancePixel::Value(f) => Self::from(f),
240 DistancePixel::Fill { depth, inside } => {
241 let bits = 0x7FC00000
242 | (u32::from(depth) << 1)
243 | u32::from(inside)
244 | Self::KEY;
245 Self(f32::from_bits(bits))
246 }
247 }
248 }
249}
250
251impl From<f32> for RawDistancePixel {
252 #[inline]
253 fn from(p: f32) -> Self {
254 Self(if p.is_nan() { f32::NAN } else { p })
256 }
257}
258
259struct Worker<'a, F: Function> {
263 tile_sizes: TileSizesRef<'a>,
264 vars: &'a ShapeVars<f32>,
265 z: f32,
266
267 pixel_perfect: bool,
268 scratch: Scratch,
269 transform: nalgebra::Matrix4<f32>,
270
271 eval_float_slice: ShapeBulkEval<F::FloatSliceEval>,
272 eval_interval: ShapeTracingEval<F::IntervalEval>,
273
274 tape_storage: Vec<F::TapeStorage>,
276
277 shape_storage: Vec<F::Storage>,
279
280 workspace: F::Workspace,
282
283 image: Image,
287}
288
289impl<'a, F: Function> RenderWorker<'a, F> for Worker<'a, F> {
290 type Config = RenderConfig;
291 type Output = Image;
292 fn new(
293 cfg: &'a Self::Config,
294 tile_sizes: TileSizesRef<'a>,
295 vars: &'a ShapeVars<f32>,
296 ) -> Self {
297 let transform = cfg.mat();
299 let transform = transform.insert_row(2, 0.0);
300 let mut transform = transform.insert_column(2, 0.0);
301 transform[(2, 2)] = 1.0; Worker::<F> {
304 tile_sizes,
305 transform,
306 vars,
307
308 scratch: Scratch::new(tile_sizes.last().pow(2)),
309 pixel_perfect: cfg.pixel_perfect,
310 z: cfg.z,
311 image: Default::default(),
312 eval_float_slice: Default::default(),
313 eval_interval: Default::default(),
314 tape_storage: vec![],
315 shape_storage: vec![],
316 workspace: Default::default(),
317 }
318 }
319
320 fn render_tile(
321 &mut self,
322 shape: &mut RenderHandle<F>,
323 tile: Tile<2>,
324 ) -> Self::Output {
325 self.image = Image::new((self.tile_sizes[0] as u32).into());
326 self.render_tile_recurse(shape, 0, tile);
327 std::mem::take(&mut self.image)
328 }
329}
330
331impl<F: Function> Worker<'_, F> {
332 fn render_tile_recurse(
333 &mut self,
334 shape: &mut RenderHandle<F>,
335 depth: usize,
336 tile: Tile<2>,
337 ) {
338 let tile_size = self.tile_sizes[depth];
339
340 let base = Point2::from(tile.corner).cast::<f32>();
342 let x = Interval::new(base.x, base.x + tile_size as f32);
343 let y = Interval::new(base.y, base.y + tile_size as f32);
344 let z = Interval::new(self.z, self.z);
345
346 let (i, simplify) =
349 match self.eval_interval.eval_with_transform_and_vars(
350 shape.i_tape(&mut self.tape_storage),
351 x,
352 y,
353 z,
354 &self.transform,
355 self.vars,
356 ) {
357 Ok(v) => v,
358 Err(ShapeTracingEvalError::MissingVar(..)) => unreachable!(),
359 };
360
361 if !self.pixel_perfect {
362 let pixel = if i.upper() < 0.0 {
363 Some(DistancePixel::Fill {
364 inside: true,
365 depth: depth as u8,
366 })
367 } else if i.lower() > 0.0 {
368 Some(DistancePixel::Fill {
369 inside: false,
370 depth: depth as u8,
371 })
372 } else {
373 None
374 };
375 if let Some(pixel) = pixel {
376 let fill = pixel.into();
377 for y in 0..tile_size {
378 let start = self
379 .tile_sizes
380 .pixel_offset(tile.add(Vector2::new(0, y)));
381 self.image[start..][..tile_size].fill(fill);
382 }
383 return;
384 }
385 }
386
387 let sub_tape = if let Some(trace) = simplify.as_ref() {
388 shape.simplify(
389 trace,
390 &mut self.workspace,
391 &mut self.shape_storage,
392 &mut self.tape_storage,
393 )
394 } else {
395 shape
396 };
397
398 if let Some(next_tile_size) = self.tile_sizes.get(depth + 1) {
399 let n = tile_size / next_tile_size;
400 for j in 0..n {
401 for i in 0..n {
402 self.render_tile_recurse(
403 sub_tape,
404 depth + 1,
405 Tile::new(
406 tile.corner + Vector2::new(i, j) * next_tile_size,
407 ),
408 );
409 }
410 }
411 } else {
412 self.render_tile_pixels(sub_tape, tile_size, tile);
413 }
414 }
415
416 fn render_tile_pixels(
417 &mut self,
418 shape: &mut RenderHandle<F>,
419 tile_size: usize,
420 tile: Tile<2>,
421 ) {
422 let mut index = 0;
423 for j in 0..tile_size {
424 for i in 0..tile_size {
425 self.scratch.x[index] = (tile.corner[0] + i) as f32;
426 self.scratch.y[index] = (tile.corner[1] + j) as f32;
427 self.scratch.z[index] = self.z;
428 index += 1;
429 }
430 }
431
432 let out = match self.eval_float_slice.eval_with_transform_and_vars(
433 shape.f_tape(&mut self.tape_storage),
434 &self.scratch.x,
435 &self.scratch.y,
436 &self.scratch.z,
437 &self.transform,
438 self.vars,
439 ) {
440 Ok(v) => v,
441 Err(ShapeBulkEvalError::MissingVar(..))
443 | Err(ShapeBulkEvalError::MismatchedVarSlices { .. })
445 => unreachable!(),
446 };
447
448 let mut index = 0;
449 for j in 0..tile_size {
450 let o = self.tile_sizes.pixel_offset(tile.add(Vector2::new(0, j)));
451 for i in 0..tile_size {
452 self.image[o + i] = out[index].into();
453 index += 1;
454 }
455 }
456 }
457}
458
459pub fn render<F: Function + RenderHints>(
469 b: BoundShape<F, f32>,
470 render_config: &RenderConfig,
471 eval_config: &EvalConfig,
472) -> Option<Image> {
473 let shape = b.shape();
474 let vars = b.vars();
475 let max_size = render_config.width().max(render_config.height()) as usize;
476 let default_tile_sizes;
477 let tile_sizes = if let Some(ts) = &eval_config.tile_sizes {
478 TileSizesRef::new(ts, max_size)
479 } else {
480 default_tile_sizes = F::tile_sizes_2d();
481 TileSizesRef::new(&default_tile_sizes, max_size)
482 };
483 let tiles = super::render_tiles::<F, Worker<F>, _>(
484 shape.clone(),
485 vars,
486 render_config,
487 eval_config,
488 tile_sizes,
489 )?;
490
491 let width = render_config.image_size.width() as usize;
492 let height = render_config.image_size.height() as usize;
493 let mut image = Image::new(render_config.image_size);
494 for (tile, data) in tiles.iter() {
495 let mut index = 0;
496 for j in 0..tile_sizes[0] {
497 let y = j + tile.corner.y;
498 for i in 0..tile_sizes[0] {
499 let x = i + tile.corner.x;
500 if y < height && x < width {
501 image[(y, x)] = data[index];
502 }
503 index += 1;
504 }
505 }
506 }
507 Some(image)
508}
509
510#[cfg(test)]
511mod test {
512 use super::*;
513 use fidget_core::{
514 Context,
515 shape::Shape,
516 var::Var,
517 vm::{VmFunction, VmShape},
518 };
519
520 const HI: &str =
521 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/hi.vm"));
522
523 #[test]
524 fn render2d_cancel() {
525 let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
526 let shape = Shape::<VmFunction>::new(&ctx, root)
527 .unwrap()
528 .try_into()
529 .expect("no vars");
530
531 let render_cfg = RenderConfig::from_size(128.into());
532 let eval_cfg = EvalConfig::default();
533 let cancel = eval_cfg.cancel.clone();
534 cancel.cancel();
535 assert!(render(shape, &render_cfg, &eval_cfg).is_none());
536 }
537
538 #[test]
539 fn shape_with_var() {
540 let mut ctx = Context::new();
541 let x = ctx.x();
542 let var = Var::new();
543 let v = ctx.var(var);
544 let s = ctx.sub(x, v).unwrap();
545 let shape = VmShape::new(&ctx, s).unwrap();
546
547 let cfg = RenderConfig::from_size(64.into());
548 let mut vars = ShapeVars::new();
549 let i = var.index().expect("expected Var::V");
550 vars.insert(i, 1.0);
551 cfg.run(shape.bind(&vars).expect("all vars present"));
552 }
553
554 #[test]
555 fn render_config_z() {
556 let mut ctx = Context::new();
557 let z = ctx.z();
558 let shape = VmShape::new(&ctx, z).unwrap();
559
560 let out = RenderConfig {
561 z: -1.0,
562 ..RenderConfig::from_size(64.into())
563 }
564 .run(shape.clone().try_into().expect("no vars"));
565 for p in out.iter() {
566 assert!(p.inside());
567 }
568
569 let out = RenderConfig {
570 z: 0.0,
571 ..RenderConfig::from_size(64.into())
572 }
573 .run(shape.clone().try_into().expect("no vars"));
574 for p in out.iter() {
575 assert!(!p.inside());
576 }
577
578 let out = RenderConfig {
579 z: 1.0,
580 ..RenderConfig::from_size(64.into())
581 }
582 .run(shape.try_into().expect("no vars"));
583 for p in out.iter() {
584 assert!(!p.inside());
585 }
586 }
587
588 #[test]
589 fn test_render_config_transforms() {
590 let config = RenderConfig::from_size(512.into());
591 let mat = config.mat();
592 assert_eq!(
593 mat.transform_point(&Point2::new(0.0, -1.0)),
594 Point2::new(-1.0, 1.0)
595 );
596 assert_eq!(
597 mat.transform_point(&Point2::new(512.0, -1.0)),
598 Point2::new(1.0, 1.0)
599 );
600 assert_eq!(
601 mat.transform_point(&Point2::new(512.0, 511.0)),
602 Point2::new(1.0, -1.0)
603 );
604
605 let config = RenderConfig::from_size(575.into());
606 let mat = config.mat();
607 assert_eq!(
608 mat.transform_point(&Point2::new(0.0, -1.0)),
609 Point2::new(-1.0, 1.0)
610 );
611 assert_eq!(
612 mat.transform_point(&Point2::new(
613 config.image_size.width() as f32,
614 -1.0
615 )),
616 Point2::new(1.0, 1.0)
617 );
618 assert_eq!(
619 mat.transform_point(&Point2::new(
620 config.image_size.width() as f32,
621 config.image_size.height() as f32 - 1.0,
622 )),
623 Point2::new(1.0, -1.0)
624 );
625 }
626}