1pub use crate::core::*;
20
21use thiserror::Error;
22#[derive(Error, Debug)]
26#[allow(missing_docs)]
27pub enum RendererError {
28 #[error("{0} buffer length must be {1}, actual length is {2}")]
29 InvalidBufferLength(String, usize, usize),
30 #[error("the material {0} is required by the geometry {1} but could not be found")]
31 MissingMaterial(String, String),
32 #[cfg(feature = "text")]
33 #[error("Failed to find font with index {0} in the given font collection")]
34 MissingFont(u32),
35}
36
37mod shader_ids;
38pub use shader_ids::*;
39
40mod viewer;
41pub use viewer::*;
42
43pub mod material;
44pub use material::*;
45
46pub mod effect;
47pub use effect::*;
48
49pub mod light;
50pub use light::*;
51
52pub mod geometry;
53pub use geometry::*;
54
55pub mod object;
56pub use object::*;
57
58pub mod control;
59pub use control::*;
60
61#[cfg(feature = "text")]
62mod text;
63#[cfg(feature = "text")]
64pub use text::*;
65
66macro_rules! impl_render_target_extensions_body {
67 () => {
68 pub fn render(
74 &self,
75 viewer: impl Viewer,
76 objects: impl IntoIterator<Item = impl Object>,
77 lights: &[&dyn Light],
78 ) -> &Self {
79 self.render_partially(self.scissor_box(), viewer, objects, lights)
80 }
81
82 pub fn render_partially(
88 &self,
89 scissor_box: ScissorBox,
90 viewer: impl Viewer,
91 objects: impl IntoIterator<Item = impl Object>,
92 lights: &[&dyn Light],
93 ) -> &Self {
94 let frustum = Frustum::new(viewer.projection() * viewer.view());
95 let (mut deferred_objects, mut forward_objects): (Vec<_>, Vec<_>) = objects
96 .into_iter()
97 .filter(|o| frustum.contains(o.aabb()))
98 .partition(|o| o.material_type() == MaterialType::Deferred);
99
100 if deferred_objects.len() > 0 {
102 let geometry_pass_camera = GeometryPassCamera(&viewer);
104 let viewport = geometry_pass_camera.viewport();
105 deferred_objects.sort_by(|a, b| cmp_render_order(&geometry_pass_camera, a, b));
106 let mut geometry_pass_texture = Texture2DArray::new_empty::<[u8; 4]>(
107 &self.context,
108 viewport.width,
109 viewport.height,
110 3,
111 Interpolation::Nearest,
112 Interpolation::Nearest,
113 None,
114 Wrapping::ClampToEdge,
115 Wrapping::ClampToEdge,
116 );
117 let mut geometry_pass_depth_texture = DepthTexture2D::new::<f32>(
118 &self.context,
119 viewport.width,
120 viewport.height,
121 Wrapping::ClampToEdge,
122 Wrapping::ClampToEdge,
123 );
124 let gbuffer_layers = [0, 1, 2];
125 RenderTarget::new(
126 geometry_pass_texture.as_color_target(&gbuffer_layers, None),
127 geometry_pass_depth_texture.as_depth_target(),
128 )
129 .clear(ClearState::default())
130 .write::<RendererError>(|| {
131 for object in deferred_objects {
132 object.render(&geometry_pass_camera, lights);
133 }
134 Ok(())
135 })
136 .unwrap();
137
138 self.apply_screen_effect_partially(
140 scissor_box,
141 &lighting_pass::LightingPassEffect {},
142 &viewer,
143 lights,
144 Some(ColorTexture::Array {
145 texture: &geometry_pass_texture,
146 layers: &gbuffer_layers,
147 }),
148 Some(DepthTexture::Single(&geometry_pass_depth_texture)),
149 );
150 }
151
152 forward_objects.sort_by(|a, b| cmp_render_order(&viewer, a, b));
154 self.write_partially::<RendererError>(scissor_box, || {
155 for object in forward_objects {
156 object.render(&viewer, lights);
157 }
158 Ok(())
159 })
160 .unwrap();
161 self
162 }
163
164 pub fn render_with_material(
169 &self,
170 material: &dyn Material,
171 viewer: impl Viewer,
172 geometries: impl IntoIterator<Item = impl Geometry>,
173 lights: &[&dyn Light],
174 ) -> &Self {
175 self.render_partially_with_material(
176 self.scissor_box(),
177 material,
178 viewer,
179 geometries,
180 lights,
181 )
182 }
183
184 pub fn render_partially_with_material(
189 &self,
190 scissor_box: ScissorBox,
191 material: &dyn Material,
192 viewer: impl Viewer,
193 geometries: impl IntoIterator<Item = impl Geometry>,
194 lights: &[&dyn Light],
195 ) -> &Self {
196 let frustum = Frustum::new(viewer.projection() * viewer.view());
197 self.write_partially::<RendererError>(scissor_box, || {
198 for geometry in geometries
199 .into_iter()
200 .filter(|o| frustum.contains(o.aabb()))
201 {
202 render_with_material(&self.context, &viewer, geometry, material, lights);
203 }
204 Ok(())
205 })
206 .unwrap();
207 self
208 }
209
210 pub fn render_with_effect(
215 &self,
216 effect: &dyn Effect,
217 viewer: impl Viewer,
218 geometries: impl IntoIterator<Item = impl Geometry>,
219 lights: &[&dyn Light],
220 color_texture: Option<ColorTexture>,
221 depth_texture: Option<DepthTexture>,
222 ) -> &Self {
223 self.render_partially_with_effect(
224 self.scissor_box(),
225 effect,
226 viewer,
227 geometries,
228 lights,
229 color_texture,
230 depth_texture,
231 )
232 }
233
234 pub fn render_partially_with_effect(
239 &self,
240 scissor_box: ScissorBox,
241 effect: &dyn Effect,
242 viewer: impl Viewer,
243 geometries: impl IntoIterator<Item = impl Geometry>,
244 lights: &[&dyn Light],
245 color_texture: Option<ColorTexture>,
246 depth_texture: Option<DepthTexture>,
247 ) -> &Self {
248 let frustum = Frustum::new(viewer.projection() * viewer.view());
249 self.write_partially::<RendererError>(scissor_box, || {
250 for geometry in geometries
251 .into_iter()
252 .filter(|o| frustum.contains(o.aabb()))
253 {
254 render_with_effect(
255 &self.context,
256 &viewer,
257 geometry,
258 effect,
259 lights,
260 color_texture,
261 depth_texture,
262 );
263 }
264 Ok(())
265 })
266 .unwrap();
267 self
268 }
269
270 pub fn apply_screen_material(
275 &self,
276 material: &dyn Material,
277 viewer: impl Viewer,
278 lights: &[&dyn Light],
279 ) -> &Self {
280 self.apply_screen_material_partially(self.scissor_box(), material, viewer, lights)
281 }
282
283 pub fn apply_screen_material_partially(
288 &self,
289 scissor_box: ScissorBox,
290 material: &dyn Material,
291 viewer: impl Viewer,
292 lights: &[&dyn Light],
293 ) -> &Self {
294 self.write_partially::<RendererError>(scissor_box, || {
295 apply_screen_material(&self.context, material, viewer, lights);
296 Ok(())
297 })
298 .unwrap();
299 self
300 }
301
302 pub fn apply_screen_effect(
307 &self,
308 effect: &dyn Effect,
309 viewer: impl Viewer,
310 lights: &[&dyn Light],
311 color_texture: Option<ColorTexture>,
312 depth_texture: Option<DepthTexture>,
313 ) -> &Self {
314 self.apply_screen_effect_partially(
315 self.scissor_box(),
316 effect,
317 viewer,
318 lights,
319 color_texture,
320 depth_texture,
321 )
322 }
323
324 pub fn apply_screen_effect_partially(
329 &self,
330 scissor_box: ScissorBox,
331 effect: &dyn Effect,
332 viewer: impl Viewer,
333 lights: &[&dyn Light],
334 color_texture: Option<ColorTexture>,
335 depth_texture: Option<DepthTexture>,
336 ) -> &Self {
337 self.write_partially::<RendererError>(scissor_box, || {
338 apply_screen_effect(
339 &self.context,
340 effect,
341 viewer,
342 lights,
343 color_texture,
344 depth_texture,
345 );
346 Ok(())
347 })
348 .unwrap();
349 self
350 }
351 };
352}
353
354macro_rules! impl_render_target_extensions {
355 ($name:ident < $a:ident : $ta:tt , $b:ident : $tb:tt >) => {
357 impl<$a: $ta, $b: $tb> $name<$a, $b> {
358 impl_render_target_extensions_body!();
359 }
360 };
361 ($name:ident < $a:ident : $ta:tt >) => {
363 impl<$a: $ta> $name<$a> {
364 impl_render_target_extensions_body!();
365 }
366 };
367 ($name:ident < $lt:lifetime >) => {
369 impl<$lt> $name<$lt> {
370 impl_render_target_extensions_body!();
371 }
372 };
373 ($name:ty) => {
375 impl $name {
376 impl_render_target_extensions_body!();
377 }
378 };
379}
380
381impl_render_target_extensions!(RenderTarget<'a>);
382impl_render_target_extensions!(ColorTarget<'a>);
383impl_render_target_extensions!(DepthTarget<'a>);
384impl_render_target_extensions!(
385 RenderTargetMultisample<C: TextureDataType, D: DepthTextureDataType>
386);
387impl_render_target_extensions!(ColorTargetMultisample<C: TextureDataType>);
388impl_render_target_extensions!(DepthTargetMultisample<D: DepthTextureDataType>);
389
390fn combine_ids(
394 geometry: GeometryId,
395 effect_material: EffectMaterialId,
396 lights: impl Iterator<Item = LightId>,
397) -> Vec<u8> {
398 let mut id = geometry.0.to_le_bytes().to_vec();
399 id.extend(effect_material.0.to_le_bytes());
400 id.extend(lights.map(|l| l.0));
401 return id;
402}
403
404pub fn render_with_material(
410 context: &Context,
411 viewer: impl Viewer,
412 geometry: impl Geometry,
413 material: impl Material,
414 lights: &[&dyn Light],
415) {
416 let id = combine_ids(geometry.id(), material.id(), lights.iter().map(|l| l.id()));
417
418 let mut programs = context.programs.write().unwrap();
419 let program = programs.entry(id).or_insert_with(|| {
420 match Program::from_source(
421 context,
422 &geometry.vertex_shader_source(),
423 &material.fragment_shader_source(lights),
424 ) {
425 Ok(program) => program,
426 Err(err) => panic!("{}", err.to_string()),
427 }
428 });
429 material.use_uniforms(program, &viewer, lights);
430 geometry.draw(&viewer, program, material.render_states());
431}
432
433pub fn render_with_effect(
439 context: &Context,
440 viewer: impl Viewer,
441 geometry: impl Geometry,
442 effect: impl Effect,
443 lights: &[&dyn Light],
444 color_texture: Option<ColorTexture>,
445 depth_texture: Option<DepthTexture>,
446) {
447 let id = combine_ids(
448 geometry.id(),
449 effect.id(color_texture, depth_texture),
450 lights.iter().map(|l| l.id()),
451 );
452
453 let mut programs = context.programs.write().unwrap();
454 let program = programs.entry(id).or_insert_with(|| {
455 match Program::from_source(
456 context,
457 &geometry.vertex_shader_source(),
458 &effect.fragment_shader_source(lights, color_texture, depth_texture),
459 ) {
460 Ok(program) => program,
461 Err(err) => panic!("{}", err.to_string()),
462 }
463 });
464 effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
465 geometry.draw(&viewer, program, effect.render_states());
466}
467
468pub fn apply_screen_material(
474 context: &Context,
475 material: impl Material,
476 viewer: impl Viewer,
477 lights: &[&dyn Light],
478) {
479 let id = combine_ids(
480 GeometryId::Screen,
481 material.id(),
482 lights.iter().map(|l| l.id()),
483 );
484
485 let mut programs = context.programs.write().unwrap();
486 let program = programs.entry(id).or_insert_with(|| {
487 match Program::from_source(
488 context,
489 full_screen_vertex_shader_source(),
490 &material.fragment_shader_source(lights),
491 ) {
492 Ok(program) => program,
493 Err(err) => panic!("{}", err.to_string()),
494 }
495 });
496 material.use_uniforms(program, &viewer, lights);
497 full_screen_draw(
498 context,
499 program,
500 material.render_states(),
501 viewer.viewport(),
502 );
503}
504
505pub fn apply_screen_effect(
511 context: &Context,
512 effect: impl Effect,
513 viewer: impl Viewer,
514 lights: &[&dyn Light],
515 color_texture: Option<ColorTexture>,
516 depth_texture: Option<DepthTexture>,
517) {
518 let id = combine_ids(
519 GeometryId::Screen,
520 effect.id(color_texture, depth_texture),
521 lights.iter().map(|l| l.id()),
522 );
523
524 let mut programs = context.programs.write().unwrap();
525 let program = programs.entry(id).or_insert_with(|| {
526 match Program::from_source(
527 context,
528 full_screen_vertex_shader_source(),
529 &effect.fragment_shader_source(lights, color_texture, depth_texture),
530 ) {
531 Ok(program) => program,
532 Err(err) => panic!("{}", err.to_string()),
533 }
534 });
535 effect.use_uniforms(program, &viewer, lights, color_texture, depth_texture);
536 full_screen_draw(context, program, effect.render_states(), viewer.viewport());
537}
538
539pub fn cmp_render_order(
545 viewer: impl Viewer,
546 obj0: impl Object,
547 obj1: impl Object,
548) -> std::cmp::Ordering {
549 if obj0.material_type() == MaterialType::Transparent
550 && obj1.material_type() != MaterialType::Transparent
551 {
552 std::cmp::Ordering::Greater
553 } else if obj0.material_type() != MaterialType::Transparent
554 && obj1.material_type() == MaterialType::Transparent
555 {
556 std::cmp::Ordering::Less
557 } else {
558 let distance_a = viewer.position().distance2(obj0.aabb().center());
559 let distance_b = viewer.position().distance2(obj1.aabb().center());
560 if distance_a.is_nan() || distance_b.is_nan() {
561 distance_a.is_nan().cmp(&distance_b.is_nan()) } else if obj0.material_type() == MaterialType::Transparent {
563 distance_b.partial_cmp(&distance_a).unwrap()
564 } else {
565 distance_a.partial_cmp(&distance_b).unwrap()
566 }
567 }
568}
569
570pub fn pick(
577 context: &Context,
578 camera: &Camera,
579 pixel: impl Into<PhysicalPoint> + Copy,
580 geometries: impl IntoIterator<Item = impl Geometry>,
581) -> Option<IntersectionResult> {
582 let pos = camera.position_at_pixel(pixel);
583 let dir = camera.view_direction_at_pixel(pixel);
584 ray_intersect(
585 context,
586 pos + dir * camera.z_near(),
587 dir,
588 camera.z_far() - camera.z_near(),
589 geometries,
590 )
591}
592
593#[derive(Debug, Clone, Copy)]
595pub struct IntersectionResult {
596 pub position: Vec3,
598 pub geometry_id: u32,
600 pub instance_id: u32,
603}
604
605pub fn ray_intersect(
610 context: &Context,
611 position: Vec3,
612 direction: Vec3,
613 max_depth: f32,
614 geometries: impl IntoIterator<Item = impl Geometry>,
615) -> Option<IntersectionResult> {
616 use crate::core::*;
617 let viewport = Viewport::new_at_origo(1, 1);
618 let up = if direction.dot(vec3(1.0, 0.0, 0.0)).abs() > 0.99 {
619 direction.cross(vec3(0.0, 1.0, 0.0))
620 } else {
621 direction.cross(vec3(1.0, 0.0, 0.0))
622 };
623 let camera = Camera::new_orthographic(
624 viewport,
625 position,
626 position + direction,
627 up,
628 0.01,
629 0.0,
630 max_depth,
631 );
632 let mut texture = Texture2D::new_empty::<[f32; 4]>(
633 context,
634 viewport.width,
635 viewport.height,
636 Interpolation::Nearest,
637 Interpolation::Nearest,
638 None,
639 Wrapping::ClampToEdge,
640 Wrapping::ClampToEdge,
641 );
642 let mut depth_texture = DepthTexture2D::new::<f32>(
643 context,
644 viewport.width,
645 viewport.height,
646 Wrapping::ClampToEdge,
647 Wrapping::ClampToEdge,
648 );
649 let mut material = IntersectionMaterial {
650 ..Default::default()
651 };
652 let result = RenderTarget::new(
653 texture.as_color_target(None),
654 depth_texture.as_depth_target(),
655 )
656 .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0))
657 .write::<RendererError>(|| {
658 for (id, geometry) in geometries.into_iter().enumerate() {
659 material.geometry_id = id as u32;
660 render_with_material(context, &camera, &geometry, &material, &[]);
661 }
662 Ok(())
663 })
664 .unwrap()
665 .read_color::<[f32; 4]>()[0];
666 let depth = result[0];
667 if depth < 1.0 {
668 Some(IntersectionResult {
669 position: position + direction * depth * max_depth,
670 geometry_id: result[1].to_bits(),
671 instance_id: result[2].to_bits(),
672 })
673 } else {
674 None
675 }
676}
677
678struct GeometryPassCamera<T>(T);
679
680impl<T: Viewer> Viewer for GeometryPassCamera<T> {
681 fn position(&self) -> Vec3 {
682 self.0.position()
683 }
684
685 fn view(&self) -> Mat4 {
686 self.0.view()
687 }
688
689 fn projection(&self) -> Mat4 {
690 self.0.projection()
691 }
692
693 fn viewport(&self) -> Viewport {
694 Viewport::new_at_origo(self.0.viewport().width, self.0.viewport().height)
695 }
696
697 fn z_near(&self) -> f32 {
698 self.0.z_near()
699 }
700
701 fn z_far(&self) -> f32 {
702 self.0.z_far()
703 }
704
705 fn color_mapping(&self) -> ColorMapping {
706 self.0.color_mapping()
707 }
708
709 fn tone_mapping(&self) -> ToneMapping {
710 self.0.tone_mapping()
711 }
712}