1#![cfg_attr(gpu, no_std)]
196#![deny(clippy::disallowed_methods)]
197
198#[cfg(doc)]
199use crate::{camera::Camera, geometry::*, primitive::Primitive, stage::Stage, transform::*};
200
201pub mod atlas;
202#[cfg(cpu)]
203pub(crate) mod bindgroup;
204pub mod bloom;
205pub mod bvol;
206pub mod camera;
207pub mod color;
208pub mod compositor;
209#[cfg(cpu)]
210pub mod context;
211pub mod convolution;
212pub mod cubemap;
213pub mod cull;
214pub mod debug;
215pub mod draw;
216pub mod geometry;
217#[cfg(all(cpu, gltf))]
218pub mod gltf;
219#[cfg(cpu)]
220pub mod internal;
221pub mod light;
222#[cfg(cpu)]
223pub mod linkage;
224pub mod material;
225pub mod math;
226pub mod pbr;
227pub mod primitive;
228pub mod sdf;
229pub mod skybox;
230pub mod stage;
231pub mod sync;
232#[cfg(cpu)]
233pub mod texture;
234pub mod tonemapping;
235pub mod transform;
236pub mod tutorial;
237#[cfg(cpu)]
238pub mod types;
239pub mod ui_slab;
240
241pub extern crate glam;
242
243#[macro_export]
247macro_rules! println {
249 ($($arg:tt)*) => {
250 #[cfg(cpu)]
251 {
252 std::println!($($arg)*);
253 }
254 }
255}
256
257#[cfg(all(cpu, any(test, feature = "test-utils")))]
258#[allow(unused, reason = "Used in debugging on macos")]
259pub fn capture_gpu_frame<T>(
260 ctx: &crate::context::Context,
261 path: impl AsRef<std::path::Path>,
262 f: impl FnOnce() -> T,
263) -> T {
264 let path = path.as_ref();
265 let parent = path.parent().unwrap();
266 std::fs::create_dir_all(parent).unwrap();
267
268 #[cfg(target_os = "macos")]
269 {
270 if path.exists() {
271 log::info!(
272 "deleting {} before writing gpu frame capture",
273 path.display()
274 );
275 std::fs::remove_dir_all(path).unwrap();
276 }
277
278 if std::env::var("METAL_CAPTURE_ENABLED").is_err() {
279 log::error!("Env var METAL_CAPTURE_ENABLED must be set");
280 panic!("missing METAL_CAPTURE_ENABLED=1");
281 }
282
283 let m = metal::CaptureManager::shared();
284 let desc = metal::CaptureDescriptor::new();
285
286 desc.set_destination(metal::MTLCaptureDestination::GpuTraceDocument);
287 desc.set_output_url(path);
288 let maybe_metal_device = unsafe { ctx.get_device().as_hal::<wgpu_core::api::Metal>() };
289 if let Some(metal_device) = maybe_metal_device {
290 desc.set_capture_device(metal_device.raw_device().try_lock().unwrap().as_ref());
291 } else {
292 panic!("not a capturable device")
293 }
294 m.start_capture(&desc).unwrap();
295 let t = f();
296 m.stop_capture();
297 t
298 }
299 #[cfg(not(target_os = "macos"))]
300 {
301 log::warn!("capturing a GPU frame is only supported on macos");
302 f()
303 }
304}
305
306#[cfg(all(cpu, any(test, feature = "test-utils")))]
307#[allow(unused, reason = "Used in sync tests in userland")]
308pub trait BlockOnFuture {
316 type Output;
317
318 fn block(self) -> Self::Output;
320}
321
322#[cfg(all(cpu, any(test, feature = "test-utils")))]
323impl<T: std::future::Future> BlockOnFuture for T {
324 type Output = <Self as std::future::Future>::Output;
325
326 fn block(self) -> Self::Output {
327 futures_lite::future::block_on(self)
328 }
329}
330
331#[cfg(test)]
332mod test {
333 use super::*;
334 use crate::{atlas::AtlasImage, context::Context, geometry::Vertex, light::Lux};
335
336 use glam::{Mat3, Mat4, Quat, UVec2, Vec2, Vec3, Vec4};
337 use img_diff::DiffCfg;
338 use light::AnalyticalLight;
339 use pretty_assertions::assert_eq;
340 use stage::Stage;
341
342 #[allow(unused_imports)]
343 pub use renderling_build::{test_output_dir, workspace_dir};
344
345 pub use super::BlockOnFuture;
346
347 #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)]
348 fn init_logging() {
349 let _ = env_logger::builder().is_test(true).try_init();
350 log::info!("logging is on");
351 }
352
353 #[allow(unused, reason = "Used in debugging on macos")]
354 pub fn capture_gpu_frame<T>(
355 ctx: &Context,
356 path: impl AsRef<std::path::Path>,
357 f: impl FnOnce() -> T,
358 ) -> T {
359 let path = workspace_dir().join("test_output").join(path);
360 super::capture_gpu_frame(ctx, path, f)
361 }
362
363 pub fn make_two_directional_light_setup(stage: &Stage) -> (AnalyticalLight, AnalyticalLight) {
364 let sunlight_a = stage
365 .new_directional_light()
366 .with_direction(Vec3::new(-0.8, -1.0, 0.5).normalize())
367 .with_color(Vec4::ONE)
368 .with_intensity(Lux::OUTDOOR_DIRECT_SUNLIGHT_HIGH);
369 let sunlight_b = stage
370 .new_directional_light()
371 .with_direction(Vec3::new(1.0, 1.0, -0.1).normalize())
372 .with_color(Vec4::ONE)
373 .with_intensity(Lux::OUTDOOR_FOXS_WEDDING);
374 (sunlight_a.into_generic(), sunlight_b.into_generic())
375 }
376
377 #[test]
378 fn sanity_transmute() {
379 let zerof32 = 0f32;
380 let zerof32asu32: u32 = zerof32.to_bits();
381 assert_eq!(0, zerof32asu32);
382
383 let foure_45 = 4e-45f32;
384 let in_u32: u32 = foure_45.to_bits();
385 assert_eq!(3, in_u32);
386
387 let u32max = u32::MAX;
388 let f32nan: f32 = f32::from_bits(u32max);
389 assert!(f32nan.is_nan());
390
391 let u32max: u32 = f32nan.to_bits();
392 assert_eq!(u32::MAX, u32max);
393 }
394
395 pub fn right_tri_vertices() -> Vec<Vertex> {
396 vec![
397 Vertex::default()
398 .with_position([0.0, 0.0, 0.0])
399 .with_color([0.0, 1.0, 1.0, 1.0]),
400 Vertex::default()
401 .with_position([0.0, 100.0, 0.0])
402 .with_color([1.0, 1.0, 0.0, 1.0]),
403 Vertex::default()
404 .with_position([100.0, 0.0, 0.0])
405 .with_color([1.0, 0.0, 1.0, 1.0]),
406 ]
407 }
408
409 #[test]
410 fn cmy_triangle_sanity() {
412 let ctx = Context::headless(100, 100).block();
413 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
414 let (p, v) = crate::camera::default_ortho2d(100.0, 100.0);
415 let _camera = stage.new_camera().with_projection_and_view(p, v);
416
417 let _prim = stage
418 .new_primitive()
419 .with_vertices(stage.new_vertices(right_tri_vertices()));
420
421 let frame = ctx.get_next_frame().unwrap();
422 stage.render(&frame.view());
423 frame.present();
424
425 let depth_texture = stage.get_depth_texture();
426 let depth_img = depth_texture.read_image().block().unwrap().unwrap();
427 img_diff::assert_img_eq("cmy_triangle/depth.png", depth_img);
428
429 let hdr_img = stage
430 .hdr_texture
431 .read()
432 .unwrap()
433 .read_hdr_image(&ctx)
434 .block()
435 .unwrap();
436 img_diff::assert_img_eq("cmy_triangle/hdr.png", hdr_img);
437
438 let bloom_mix = stage
439 .bloom
440 .get_mix_texture()
441 .read_hdr_image(&ctx)
442 .block()
443 .unwrap();
444 img_diff::assert_img_eq("cmy_triangle/bloom_mix.png", bloom_mix);
445 }
446
447 #[test]
448 fn cmy_triangle_backface() {
451 use img_diff::DiffCfg;
452
453 let ctx = Context::headless(100, 100).block();
454 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
455 let (p, v) = crate::camera::default_ortho2d(100.0, 100.0);
456 let _camera = stage.new_camera().with_projection_and_view(p, v);
457 let _rez = stage.new_primitive().with_vertices(stage.new_vertices({
458 let mut vs = right_tri_vertices();
459 vs.reverse();
460 vs
461 }));
462
463 let frame = ctx.get_next_frame().unwrap();
464 stage.render(&frame.view());
465 let img = frame.read_linear_image().block().unwrap();
466 img_diff::assert_img_eq_cfg(
467 "cmy_triangle/hdr.png",
468 img,
469 DiffCfg {
470 test_name: Some("cmy_triangle_backface.png"),
471 ..Default::default()
472 },
473 );
474 }
475
476 #[test]
477 fn cmy_triangle_update_transform() {
481 let ctx = Context::headless(100, 100).block();
482 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
483 let (p, v) = crate::camera::default_ortho2d(100.0, 100.0);
484 let _camera = stage.new_camera().with_projection_and_view(p, v);
485 let transform = stage.new_transform();
486 let _renderlet = stage
487 .new_primitive()
488 .with_vertices(stage.new_vertices(right_tri_vertices()))
489 .with_transform(&transform);
490
491 let frame = ctx.get_next_frame().unwrap();
492 stage.render(&frame.view());
493
494 transform
495 .set_translation(Vec3::new(100.0, 0.0, 0.0))
496 .set_rotation(Quat::from_axis_angle(Vec3::Z, std::f32::consts::FRAC_PI_2))
497 .set_scale(Vec3::new(0.5, 0.5, 1.0));
498
499 stage.render(&frame.view());
500 let img = frame.read_linear_image().block().unwrap();
501 img_diff::assert_img_eq("cmy_triangle/update_transform.png", img);
502 }
503
504 fn pyramid_points() -> [Vec3; 5] {
512 let tl = Vec3::new(-0.5, -0.5, -0.5);
513 let tr = Vec3::new(0.5, -0.5, -0.5);
514 let br = Vec3::new(0.5, -0.5, 0.5);
515 let bl = Vec3::new(-0.5, -0.5, 0.5);
516 let top = Vec3::new(0.0, 0.5, 0.0);
517 [tl, tr, br, bl, top]
518 }
519
520 fn pyramid_indices() -> [u16; 18] {
521 let (tl, tr, br, bl, top) = (0, 1, 2, 3, 4);
522 [
523 tl, br, bl, tl, tr, br, br, top, bl, bl, top, tl, tl, top, tr, tr, top, br,
524 ]
525 }
526
527 fn cmy_gpu_vertex(p: Vec3) -> Vertex {
528 let r: f32 = p.z + 0.5;
529 let g: f32 = p.x + 0.5;
530 let b: f32 = p.y + 0.5;
531 Vertex::default()
532 .with_position([p.x.min(1.0), p.y.min(1.0), p.z.min(1.0)])
533 .with_color([r, g, b, 1.0])
534 }
535
536 pub fn gpu_cube_vertices() -> Vec<Vertex> {
537 math::UNIT_INDICES
538 .iter()
539 .map(|i| cmy_gpu_vertex(math::UNIT_POINTS[*i]))
540 .collect()
541 }
542
543 #[test]
544 fn cmy_cube_sanity() {
546 let ctx = Context::headless(100, 100).block();
547 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
548 let camera_position = Vec3::new(0.0, 12.0, 20.0);
549 let _camera = stage.new_camera().with_projection_and_view(
550 Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 0.1, 100.0),
551 Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y),
552 );
553 let _rez = stage
554 .new_primitive()
555 .with_vertices(stage.new_vertices(gpu_cube_vertices()))
556 .with_transform(
557 stage
558 .new_transform()
559 .with_scale(Vec3::new(6.0, 6.0, 6.0))
560 .with_rotation(Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4)),
561 );
562
563 let frame = ctx.get_next_frame().unwrap();
564 stage.render(&frame.view());
565 let img = frame.read_image().block().unwrap();
566 img_diff::assert_img_eq("cmy_cube/sanity.png", img);
567 }
568
569 #[test]
570 fn cmy_cube_indices() {
572 let ctx = Context::headless(100, 100).block();
573 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
574 let camera_position = Vec3::new(0.0, 12.0, 20.0);
575 let _camera = stage.new_camera().with_projection_and_view(
576 Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 0.1, 100.0),
577 Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y),
578 );
579
580 let _rez = stage
581 .new_primitive()
582 .with_vertices(stage.new_vertices(math::UNIT_POINTS.map(cmy_gpu_vertex)))
583 .with_indices(stage.new_indices(math::UNIT_INDICES.map(|i| i as u32)))
584 .with_transform(
585 stage
586 .new_transform()
587 .with_scale(Vec3::new(6.0, 6.0, 6.0))
588 .with_rotation(Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4)),
589 );
590
591 let frame = ctx.get_next_frame().unwrap();
592 stage.render(&frame.view());
593 let img = frame.read_image().block().unwrap();
594 img_diff::assert_img_eq_cfg(
595 "cmy_cube/sanity.png",
596 img,
597 DiffCfg {
598 test_name: Some("cmy_cube/indices"),
599 ..Default::default()
600 },
601 );
602 }
603
604 #[test]
605 fn cmy_cube_visible() {
608 let ctx = Context::headless(100, 100).block();
609 let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
610 let (projection, view) = camera::default_perspective(100.0, 100.0);
611 let _camera = stage
612 .new_camera()
613 .with_projection_and_view(projection, view);
614 let geometry = stage.new_vertices(gpu_cube_vertices());
615 let _cube_one = stage
616 .new_primitive()
617 .with_vertices(&geometry)
618 .with_transform(
619 stage
620 .new_transform()
621 .with_translation(Vec3::new(-4.5, 0.0, 0.0))
622 .with_scale(Vec3::new(6.0, 6.0, 6.0))
623 .with_rotation(Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4)),
624 );
625
626 let cube_two = stage
627 .new_primitive()
628 .with_vertices(&geometry)
629 .with_transform(
630 stage
631 .new_transform()
632 .with_translation(Vec3::new(4.5, 0.0, 0.0))
633 .with_scale(Vec3::new(6.0, 6.0, 6.0))
634 .with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_4)),
635 );
636
637 let frame = ctx.get_next_frame().unwrap();
639 stage.render(&frame.view());
640 let img = frame.read_image().block().unwrap();
641 img_diff::assert_img_eq("cmy_cube/visible_before.png", img.clone());
642 let img_before = img;
643 frame.present();
644
645 cube_two.set_visible(false);
647
648 let frame = ctx.get_next_frame().unwrap();
650 stage.render(&frame.view());
651 let img = frame.read_image().block().unwrap();
652 img_diff::assert_img_eq("cmy_cube/visible_after.png", img);
653 frame.present();
654
655 cube_two.set_visible(true);
657
658 let frame = ctx.get_next_frame().unwrap();
660 stage.render(&frame.view());
661 let img = frame.read_image().block().unwrap();
662 img_diff::assert_eq("cmy_cube/visible_before_again.png", img_before, img);
663 }
664
665 #[test]
666 fn cmy_cube_remesh() {
669 let ctx = Context::headless(100, 100).block();
670 let stage = ctx
671 .new_stage()
672 .with_lighting(false)
673 .with_background_color(Vec4::splat(1.0));
674 let (projection, view) = camera::default_perspective(100.0, 100.0);
675 let _camera = stage
676 .new_camera()
677 .with_projection_and_view(projection, view);
678 let cube = stage
679 .new_primitive()
680 .with_vertices(
681 stage
682 .new_vertices(math::UNIT_INDICES.map(|i| cmy_gpu_vertex(math::UNIT_POINTS[i]))),
683 )
684 .with_transform(
685 stage
686 .new_transform()
687 .with_scale(Vec3::new(10.0, 10.0, 10.0)),
688 );
689
690 let frame = ctx.get_next_frame().unwrap();
692 stage.render(&frame.view());
693 let img = frame.read_image().block().unwrap();
694 img_diff::assert_img_eq("cmy_cube/remesh_before.png", img);
695 frame.present();
696
697 let pyramid_points = pyramid_points();
700 let pyramid_geometry = stage
701 .new_vertices(pyramid_indices().map(|i| cmy_gpu_vertex(pyramid_points[i as usize])));
702 cube.set_vertices(pyramid_geometry);
703
704 let frame = ctx.get_next_frame().unwrap();
706 stage.render(&frame.view());
707 let img = frame.read_image().block().unwrap();
708 img_diff::assert_img_eq("cmy_cube/remesh_after.png", img);
709 }
710
711 fn gpu_uv_unit_cube() -> Vec<Vertex> {
712 let p: [Vec3; 8] = math::UNIT_POINTS;
713 let tl = Vec2::new(0.0, 0.0);
714 let tr = Vec2::new(1.0, 0.0);
715 let bl = Vec2::new(0.0, 1.0);
716 let br = Vec2::new(1.0, 1.0);
717
718 vec![
719 Vertex::default().with_position(p[0]).with_uv0(bl),
721 Vertex::default().with_position(p[2]).with_uv0(tr),
722 Vertex::default().with_position(p[1]).with_uv0(tl),
723 Vertex::default().with_position(p[0]).with_uv0(bl),
724 Vertex::default().with_position(p[3]).with_uv0(br),
725 Vertex::default().with_position(p[2]).with_uv0(tr),
726 Vertex::default().with_position(p[4]).with_uv0(bl),
728 Vertex::default().with_position(p[6]).with_uv0(tr),
729 Vertex::default().with_position(p[5]).with_uv0(tl),
730 Vertex::default().with_position(p[4]).with_uv0(bl),
731 Vertex::default().with_position(p[7]).with_uv0(br),
732 Vertex::default().with_position(p[6]).with_uv0(tr),
733 Vertex::default().with_position(p[7]).with_uv0(bl),
735 Vertex::default().with_position(p[0]).with_uv0(tr),
736 Vertex::default().with_position(p[1]).with_uv0(tl),
737 Vertex::default().with_position(p[7]).with_uv0(bl),
738 Vertex::default().with_position(p[4]).with_uv0(br),
739 Vertex::default().with_position(p[0]).with_uv0(tr),
740 Vertex::default().with_position(p[5]).with_uv0(bl),
742 Vertex::default().with_position(p[2]).with_uv0(tr),
743 Vertex::default().with_position(p[3]).with_uv0(tl),
744 Vertex::default().with_position(p[5]).with_uv0(bl),
745 Vertex::default().with_position(p[6]).with_uv0(br),
746 Vertex::default().with_position(p[2]).with_uv0(tr),
747 Vertex::default().with_position(p[4]).with_uv0(bl),
749 Vertex::default().with_position(p[3]).with_uv0(tr),
750 Vertex::default().with_position(p[0]).with_uv0(tl),
751 Vertex::default().with_position(p[4]).with_uv0(bl),
752 Vertex::default().with_position(p[5]).with_uv0(br),
753 Vertex::default().with_position(p[3]).with_uv0(tr),
754 ]
755 }
756
757 #[test]
758 fn unlit_textured_cube_material() {
761 let ctx = Context::headless(100, 100).block();
762 let stage = ctx.new_stage().with_background_color(Vec4::splat(0.0));
763 let (projection, view) = camera::default_perspective(100.0, 100.0);
764 let _camera = stage
765 .new_camera()
766 .with_projection_and_view(projection, view);
767
768 let sandstone = AtlasImage::from(image::open("../../img/sandstone.png").unwrap());
769 let dirt = AtlasImage::from(image::open("../../img/dirt.jpg").unwrap());
770 let entries = stage.set_images([sandstone, dirt]).unwrap();
771
772 let material = stage
773 .new_material()
774 .with_albedo_texture(&entries[0])
775 .with_has_lighting(false);
776 let cube = stage
777 .new_primitive()
778 .with_vertices(stage.new_vertices(gpu_uv_unit_cube()))
779 .with_transform(
780 stage
781 .new_transform()
782 .with_scale(Vec3::new(10.0, 10.0, 10.0)),
783 )
784 .with_material(&material);
785 println!("cube: {:?}", cube.descriptor());
786
787 let frame = ctx.get_next_frame().unwrap();
789 stage.render(&frame.view());
790 let img = frame.read_image().block().unwrap();
791 img_diff::assert_img_eq("unlit_textured_cube_material_before.png", img);
792 frame.present();
793
794 material.set_albedo_texture(&entries[1]);
796
797 let frame = ctx.get_next_frame().unwrap();
799 stage.render(&frame.view());
800 let img = frame.read_image().block().unwrap();
801 img_diff::assert_img_eq("unlit_textured_cube_material_after.png", img);
802
803 }
812
813 #[test]
814 fn multi_node_scene() {
817 let ctx = Context::headless(100, 100).block();
818 let stage = ctx
819 .new_stage()
820 .with_background_color(Vec3::splat(0.0).extend(1.0));
821
822 let (projection, view) = camera::default_ortho2d(100.0, 100.0);
823 let _camera = stage
824 .new_camera()
825 .with_projection_and_view(projection, view);
826
827 let img = AtlasImage::from_path("../../img/cheetah.jpg").unwrap();
829 let entries = stage.set_images([img]).unwrap();
830
831 let geometry = stage.new_vertices([
832 Vertex {
833 position: Vec3::new(0.0, 0.0, 0.0),
834 color: Vec4::new(1.0, 1.0, 0.0, 1.0),
835 uv0: Vec2::new(0.0, 0.0),
836 uv1: Vec2::new(0.0, 0.0),
837 ..Default::default()
838 },
839 Vertex {
840 position: Vec3::new(100.0, 100.0, 0.0),
841 color: Vec4::new(0.0, 1.0, 1.0, 1.0),
842 uv0: Vec2::new(1.0, 1.0),
843 uv1: Vec2::new(1.0, 1.0),
844 ..Default::default()
845 },
846 Vertex {
847 position: Vec3::new(100.0, 0.0, 0.0),
848 color: Vec4::new(1.0, 0.0, 1.0, 1.0),
849 uv0: Vec2::new(1.0, 0.0),
850 uv1: Vec2::new(1.0, 0.0),
851 ..Default::default()
852 },
853 ]);
854 let _color_prim = stage.new_primitive().with_vertices(&geometry);
855
856 let material = stage
857 .new_material()
858 .with_albedo_texture(&entries[0])
859 .with_has_lighting(false);
860 let transform = stage
861 .new_transform()
862 .with_translation(Vec3::new(15.0, 35.0, 0.5))
863 .with_scale(Vec3::new(0.5, 0.5, 1.0));
864 let _rez = stage
865 .new_primitive()
866 .with_vertices(&geometry)
867 .with_material(material)
868 .with_transform(transform);
869
870 let frame = ctx.get_next_frame().unwrap();
871 stage.render(&frame.view());
872 let img = frame.read_image().block().unwrap();
873 img_diff::assert_img_eq("stage/shared_node_with_different_materials.png", img);
874 }
875
876 #[test]
877 fn scene_cube_directional() {
879 let ctx = Context::headless(100, 100).block();
880 let stage = ctx
881 .new_stage()
882 .with_bloom(false)
883 .with_background_color(Vec3::splat(0.0).extend(1.0));
884
885 let (projection, _) = camera::default_perspective(100.0, 100.0);
886 let view = Mat4::look_at_rh(Vec3::new(1.8, 1.8, 1.8), Vec3::ZERO, Vec3::Y);
887 let _camera = stage
888 .new_camera()
889 .with_projection_and_view(projection, view);
890
891 let red = Vec3::X.extend(1.0);
892 let green = Vec3::Y.extend(1.0);
893 let blue = Vec3::Z.extend(1.0);
894 let _dir_red = stage
895 .new_directional_light()
896 .with_direction(Vec3::NEG_Y)
897 .with_color(red)
898 .with_intensity(Lux::OUTDOOR_FULL_DAYLIGHT_LOW);
899 let _dir_green = stage
900 .new_directional_light()
901 .with_direction(Vec3::NEG_X)
902 .with_color(green)
903 .with_intensity(Lux::OUTDOOR_FULL_DAYLIGHT_LOW);
904 let _dir_blue = stage
905 .new_directional_light()
906 .with_direction(Vec3::NEG_Z)
907 .with_color(blue)
908 .with_intensity(Lux::OUTDOOR_FULL_DAYLIGHT_LOW);
909
910 let _rez = stage
911 .new_primitive()
912 .with_material(stage.default_material());
913
914 let frame = ctx.get_next_frame().unwrap();
915 stage.render(&frame.view());
916 println!(
917 "lighting_descriptor: {:#?}",
918 stage.lighting.lighting_descriptor.get()
919 );
920 let img = frame.read_image().block().unwrap();
921 let depth_texture = stage.get_depth_texture();
922 let depth_img = depth_texture.read_image().block().unwrap().unwrap();
923 img_diff::assert_img_eq("stage/cube_directional_depth.png", depth_img);
924 img_diff::assert_img_eq("stage/cube_directional.png", img);
925 }
926
927 #[test]
928 fn square_scale_norm_check() {
937 let quat = Quat::from_axis_angle(Vec3::Z, std::f32::consts::FRAC_PI_4);
938 let scale = Vec3::new(10.0, 20.0, 1.0);
939 let model_matrix = Mat4::from_translation(Vec3::new(10.0, 10.0, 20.0))
940 * Mat4::from_quat(quat)
941 * Mat4::from_scale(scale);
942 let normal_matrix = model_matrix.inverse().transpose();
943 let scale2 = scale * scale;
944
945 for i in 0..9 {
946 for j in 0..9 {
947 for k in 0..9 {
948 if i == 0 && j == 0 && k == 0 {
949 continue;
950 }
951 let norm = Vec3::new(i as f32, j as f32, k as f32).normalize();
952 let model = Mat3::from_mat4(model_matrix);
953 let norm_a = (Mat3::from_mat4(normal_matrix) * norm).normalize();
954 let norm_b = (model * (norm / scale2)).normalize();
955 assert!(
956 norm_a.abs_diff_eq(norm_b, f32::EPSILON),
957 "norm:{norm}, scale2:{scale2}"
958 );
959 }
960 }
961 }
962 }
963
964 #[test]
965 fn scene_parent_sanity() {
968 let ctx = Context::headless(100, 100).block();
969 let stage = ctx.new_stage().with_background_color(Vec4::splat(0.0));
970 let (projection, view) = camera::default_ortho2d(100.0, 100.0);
971 let _camera = stage
972 .new_camera()
973 .with_projection_and_view(projection, view);
974
975 let root_node = stage
976 .new_nested_transform()
977 .with_local_scale(Vec3::new(25.0, 25.0, 1.0));
978 println!("root_node: {:#?}", root_node.global_descriptor());
979
980 let offset = Vec3::new(1.0, 1.0, 0.0);
981
982 let cyan_node = stage.new_nested_transform().with_local_translation(offset);
983 println!("cyan_node: {:#?}", cyan_node.global_descriptor());
984
985 let yellow_node = stage.new_nested_transform().with_local_translation(offset);
986 println!("yellow_node: {:#?}", yellow_node.global_descriptor());
987
988 let red_node = stage.new_nested_transform().with_local_translation(offset);
989 println!("red_node: {:#?}", red_node.global_descriptor());
990
991 root_node.add_child(&cyan_node);
992 println!("cyan_node: {:#?}", cyan_node.global_descriptor());
993 cyan_node.add_child(&yellow_node);
994 println!("yellow_node: {:#?}", yellow_node.global_descriptor());
995 yellow_node.add_child(&red_node);
996 println!("red_node: {:#?}", red_node.global_descriptor());
997
998 let geometry = stage.new_vertices({
999 let size = 1.0;
1000 [
1001 Vertex::default().with_position([0.0, 0.0, 0.0]),
1002 Vertex::default().with_position([size, size, 0.0]),
1003 Vertex::default().with_position([size, 0.0, 0.0]),
1004 ]
1005 });
1006 let _cyan_primitive = stage
1007 .new_primitive()
1008 .with_vertices(&geometry)
1009 .with_material(
1010 stage
1011 .new_material()
1012 .with_albedo_factor(Vec4::new(0.0, 1.0, 1.0, 1.0))
1013 .with_has_lighting(false),
1014 )
1015 .with_transform(&cyan_node);
1016 let _yellow_primitive = stage
1017 .new_primitive()
1018 .with_vertices(&geometry)
1019 .with_material(
1020 stage
1021 .new_material()
1022 .with_albedo_factor(Vec4::new(1.0, 1.0, 0.0, 1.0))
1023 .with_has_lighting(false),
1024 )
1025 .with_transform(&yellow_node);
1026 let _red_primitive = stage
1027 .new_primitive()
1028 .with_vertices(&geometry)
1029 .with_material(
1030 stage
1031 .new_material()
1032 .with_albedo_factor(Vec4::new(1.0, 0.0, 0.0, 1.0))
1033 .with_has_lighting(false),
1034 )
1035 .with_transform(&red_node);
1036
1037 let frame = ctx.get_next_frame().unwrap();
1038 stage.render(&frame.view());
1039 let img = frame.read_image().block().unwrap();
1040 img_diff::assert_img_eq("scene_parent_sanity.png", img);
1041 }
1042
1043 #[test]
1044 fn camera_position_from_view_matrix() {
1047 let position = Vec3::new(1.0, 2.0, 12.0);
1048 let view = Mat4::look_at_rh(position, Vec3::new(1.0, 2.0, 0.0), Vec3::Y);
1049 let extracted_position = view.inverse().transform_point3(Vec3::ZERO);
1050 assert_eq!(position, extracted_position);
1051 }
1052
1053 #[test]
1054 fn can_resize_context_and_stage() {
1055 let size = UVec2::new(100, 100);
1056 let mut ctx = Context::headless(size.x, size.y).block();
1057 let stage = ctx.new_stage();
1058
1059 let camera_position = Vec3::new(0.0, 12.0, 20.0);
1061 let _camera = stage.new_camera().with_projection_and_view(
1062 Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 0.1, 100.0),
1063 Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y),
1064 );
1065 let _rez = stage
1066 .new_primitive()
1067 .with_vertices(stage.new_vertices(gpu_cube_vertices()))
1068 .with_transform(
1069 stage
1070 .new_transform()
1071 .with_scale(Vec3::new(6.0, 6.0, 6.0))
1072 .with_rotation(Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4)),
1073 );
1074
1075 let frame = ctx.get_next_frame().unwrap();
1076 stage.render(&frame.view());
1077 let img = frame.read_image().block().unwrap();
1078 assert_eq!(size, UVec2::new(img.width(), img.height()));
1079 img_diff::assert_img_eq("stage/resize_100.png", img);
1080 frame.present();
1081
1082 let new_size = UVec2::new(200, 200);
1083 ctx.set_size(new_size);
1084 stage.set_size(new_size);
1085
1086 let frame = ctx.get_next_frame().unwrap();
1087 stage.render(&frame.view());
1088 let img = frame.read_image().block().unwrap();
1089 assert_eq!(new_size, UVec2::new(img.width(), img.height()));
1090 img_diff::assert_img_eq("stage/resize_200.png", img);
1091 frame.present();
1092 }
1093
1094 #[test]
1095 fn can_direct_draw_cube() {
1096 let size = UVec2::new(100, 100);
1097 let ctx = Context::headless(size.x, size.y)
1098 .block()
1099 .with_use_direct_draw(true);
1100 let stage = ctx.new_stage();
1101
1102 let camera_position = Vec3::new(0.0, 12.0, 20.0);
1104 let _camera = stage.new_camera().with_projection_and_view(
1105 Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 0.1, 100.0),
1106 Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y),
1107 );
1108 let _rez = stage
1109 .new_primitive()
1110 .with_vertices(stage.new_vertices(gpu_cube_vertices()))
1111 .with_transform(
1112 stage
1113 .new_transform()
1114 .with_scale(Vec3::new(6.0, 6.0, 6.0))
1115 .with_rotation(Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4)),
1116 );
1117
1118 let frame = ctx.get_next_frame().unwrap();
1119 stage.render(&frame.view());
1120 let img = frame.read_image().block().unwrap();
1121 assert_eq!(size, UVec2::new(img.width(), img.height()));
1122 img_diff::assert_img_eq("stage/resize_100.png", img);
1123 frame.present();
1124 }
1125}