diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index 4b4578d4..8717bd93 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -47,7 +47,7 @@ jobs: rustup default ${{ matrix.rustup-toolchain }} - name: Build Lambda & other default workspace members. - run: cargo build + run: cargo test --all - uses: actions/setup-ruby@v1 - name: Send Webhook Notification for build status. diff --git a/Cargo.lock b/Cargo.lock index 3041c7eb..965f4d54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,6 +670,12 @@ dependencies = [ "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "dispatch" version = "0.2.0" @@ -685,6 +691,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "downcast-rs" version = "1.2.0" @@ -700,6 +712,12 @@ dependencies = [ "serde", ] +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "external-memory" version = "0.0.1" @@ -740,6 +758,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -771,6 +798,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" + [[package]] name = "fs-err" version = "2.6.0" @@ -1113,6 +1146,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.1" @@ -1160,6 +1202,7 @@ dependencies = [ "cargo-audit", "cargo-tarpaulin", "lambda-platform", + "mockall", ] [[package]] @@ -1173,6 +1216,7 @@ dependencies = [ "gfx-backend-metal", "gfx-backend-vulkan", "gfx-hal", + "mockall", "shaderc", "winit", ] @@ -1376,6 +1420,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mockall" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2be9a9090bc1cac2930688fa9478092a64c6a92ddc6ae0692d46b37d9cab709" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d702a0530a0141cf4ed147cf5ec7be6f2c187d4e37fcbefc39cf34116bfe8f" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "naga" version = "0.5.0" @@ -1495,6 +1566,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-integer" version = "0.1.44" @@ -1690,6 +1767,36 @@ dependencies = [ "serde", ] +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-crate" version = "1.1.0" @@ -1702,11 +1809,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.34" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2133,13 +2240,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2163,6 +2270,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "textwrap" version = "0.11.0" @@ -2326,6 +2439,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + [[package]] name = "unicode-normalization" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index 68f8db45..508bc344 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,6 @@ members = [ default-members = [ "lambda", + "crates/lambda-platform", "tools/lambda_rs_demo" ] diff --git a/crates/lambda-platform/Cargo.toml b/crates/lambda-platform/Cargo.toml index d45a1c5a..4b7e6187 100644 --- a/crates/lambda-platform/Cargo.toml +++ b/crates/lambda-platform/Cargo.toml @@ -20,6 +20,9 @@ gfx-backend-vulkan = { version="=0.9.0", optional = true } gfx-backend-dx11 = { version="=0.9.0", optional = true } gfx-backend-dx12 = { version="=0.9.0", optional = true } +[dev-dependencies] +mockall = "=0.11.2" + [features] default=["shaderc/build-from-source"] detect-platform=[] diff --git a/crates/lambda-platform/src/gfx/api.rs b/crates/lambda-platform/src/gfx/api.rs index c03d368b..207dc7ed 100644 --- a/crates/lambda-platform/src/gfx/api.rs +++ b/crates/lambda-platform/src/gfx/api.rs @@ -1,3 +1,6 @@ +//! GPU API exports to set the platforms primary rendering API for rendering +//! implementations to use. + cfg_if::cfg_if! { if #[cfg(feature = "with-gl")] { pub use gfx_backend_gl as RenderingAPI; diff --git a/crates/lambda-platform/src/gfx/assembler.rs b/crates/lambda-platform/src/gfx/assembler.rs index 325c8b58..f2488dd1 100644 --- a/crates/lambda-platform/src/gfx/assembler.rs +++ b/crates/lambda-platform/src/gfx/assembler.rs @@ -1,21 +1,6 @@ -pub mod internal { - pub use gfx_hal::{ - pso::{ - EntryPoint, - InputAssemblerDesc, - Primitive, - PrimitiveAssemblerDesc, - }, - Backend, - }; +//! Primitive assembly for the graphics pipeline. - #[inline] - pub fn into_primitive_assembler<'shader, RenderBackend: Backend>( - primitive_assembler: super::PrimitiveAssembler<'shader, RenderBackend>, - ) -> PrimitiveAssemblerDesc<'shader, RenderBackend> { - return primitive_assembler.primitive_assembler; - } -} +use gfx_hal::pso; /// PrimitiveAssemblerBuilder for preparing PrimitiveAssemblers to use in the /// lambda-platform Rendering pipeline. @@ -32,14 +17,13 @@ impl PrimitiveAssemblerBuilder { self, vertex_shader: &'shader super::shader::ShaderModule, ) -> PrimitiveAssembler<'shader, RenderBackend> { - // TODO(vmarcella): The builder should expose more fields for the - let primitive_assembler = internal::PrimitiveAssemblerDesc::Vertex { + let primitive_assembler = pso::PrimitiveAssemblerDesc::Vertex { buffers: &[], attributes: &[], - input_assembler: internal::InputAssemblerDesc::new( - internal::Primitive::TriangleList, + input_assembler: pso::InputAssemblerDesc::new( + pso::Primitive::TriangleList, ), - vertex: internal::EntryPoint { + vertex: pso::EntryPoint { entry: vertex_shader.entry(), module: super::internal::module_for(vertex_shader), specialization: vertex_shader.specializations().clone(), @@ -57,11 +41,22 @@ impl PrimitiveAssemblerBuilder { /// PrimitiveAssembler for used for describing how Vertex Shaders should /// construct primitives. Each constructed Primitive Assembler should be alive /// for as long as the shader module that created it is. -pub struct PrimitiveAssembler<'shader, RenderBackend: internal::Backend> { - primitive_assembler: internal::PrimitiveAssemblerDesc<'shader, RenderBackend>, +pub struct PrimitiveAssembler<'shader, RenderBackend: gfx_hal::Backend> { + primitive_assembler: pso::PrimitiveAssemblerDesc<'shader, RenderBackend>, } -impl<'shader, RenderBackend: internal::Backend> +impl<'shader, RenderBackend: gfx_hal::Backend> PrimitiveAssembler<'shader, RenderBackend> { } + +/// Internal functions for the primitive assembler. User applications most +/// likely should not use these functions directly nor should they need to. +pub mod internal { + #[inline] + pub fn into_primitive_assembler<'shader, RenderBackend: gfx_hal::Backend>( + primitive_assembler: super::PrimitiveAssembler<'shader, RenderBackend>, + ) -> gfx_hal::pso::PrimitiveAssemblerDesc<'shader, RenderBackend> { + return primitive_assembler.primitive_assembler; + } +} diff --git a/crates/lambda-platform/src/gfx/fence.rs b/crates/lambda-platform/src/gfx/fence.rs index 2cbb3f6e..9b235443 100644 --- a/crates/lambda-platform/src/gfx/fence.rs +++ b/crates/lambda-platform/src/gfx/fence.rs @@ -1,3 +1,7 @@ +//! GPU fence & semaphore implementations for rendering synchronizations. These +//! implementations built on top of gfx-hal and are used by the lambda-platform +//! rendering implementations to synchronize GPU operations. + use gfx_hal::device::Device; pub struct RenderSemaphoreBuilder {} @@ -7,6 +11,8 @@ impl RenderSemaphoreBuilder { return Self {}; } + /// Builds a new render semaphore using the provided GPU. This semaphore can + /// only be used with the GPU that it was created with. pub fn build( self, gpu: &mut super::gpu::Gpu, @@ -18,6 +24,7 @@ impl RenderSemaphoreBuilder { return RenderSemaphore { semaphore }; } } + pub struct RenderSemaphore { semaphore: RenderBackend::Semaphore, } @@ -36,6 +43,8 @@ pub struct RenderSubmissionFenceBuilder { } impl RenderSubmissionFenceBuilder { + /// Creates a new Render Submission Fence Builder that defaults to a 1 second + /// timeout for waiting on the fence. pub fn new() -> Self { return Self { default_render_timeout: 1_000_000_000, @@ -49,6 +58,8 @@ impl RenderSubmissionFenceBuilder { return self; } + /// Builds a new submission fence using the provided GPU. This fence can only + /// be used to block operation on the GPU that created it. pub fn build( self, gpu: &mut super::gpu::Gpu, diff --git a/crates/lambda-platform/src/gfx/framebuffer.rs b/crates/lambda-platform/src/gfx/framebuffer.rs index 11489f85..621497f1 100644 --- a/crates/lambda-platform/src/gfx/framebuffer.rs +++ b/crates/lambda-platform/src/gfx/framebuffer.rs @@ -1,11 +1,6 @@ -use std::borrow::Borrow; - use gfx_hal::{ device::Device, - image::{ - Extent, - FramebufferAttachment, - }, + image::Extent, }; use super::{ @@ -14,14 +9,7 @@ use super::{ surface::Surface, }; -pub mod internal { - pub fn frame_buffer_for( - frame_buffer: &super::Framebuffer, - ) -> &RenderBackend::Framebuffer { - return &frame_buffer.frame_buffer; - } -} - +/// Framebuffer for the given render backend. pub struct Framebuffer { frame_buffer: RenderBackend::Framebuffer, } @@ -35,7 +23,6 @@ impl Framebuffer { } } } - pub struct FramebufferBuilder {} impl FramebufferBuilder { @@ -43,6 +30,7 @@ impl FramebufferBuilder { return Self {}; } + /// Build a frame buffer on a given GPU for the given surface. pub fn build( self, gpu: &mut Gpu, @@ -51,7 +39,7 @@ impl FramebufferBuilder { ) -> Framebuffer { use super::surface::internal::frame_buffer_attachment_from; - let [width, height] = surface.size().expect("A surface without a swapchain cannot be used in a framebeen configured with a swapchain"); + let (width, height) = surface.size().expect("A surface without a swapchain cannot be used in a framebeen configured with a swapchain"); let image = frame_buffer_attachment_from(surface).unwrap(); let frame_buffer = unsafe { @@ -70,3 +58,13 @@ impl FramebufferBuilder { return Framebuffer { frame_buffer }; } } + +/// Internal functions to work with gfx-hal framebuffers directly. Applications +/// should not need to use these functions directly. +pub mod internal { + pub fn frame_buffer_for( + frame_buffer: &super::Framebuffer, + ) -> &RenderBackend::Framebuffer { + return &frame_buffer.frame_buffer; + } +} diff --git a/crates/lambda-platform/src/gfx/gpu.rs b/crates/lambda-platform/src/gfx/gpu.rs index c95431cf..f23e3c73 100644 --- a/crates/lambda-platform/src/gfx/gpu.rs +++ b/crates/lambda-platform/src/gfx/gpu.rs @@ -1,5 +1,3 @@ -use std::mem::size_of; - use gfx_hal::{ adapter::Adapter, device::Device, @@ -7,18 +5,14 @@ use gfx_hal::{ PhysicalDevice, QueueFamily, }, - pso::ShaderStageFlags, queue::{ Queue, QueueGroup, }, - window::{ - Extent2D, - PresentError, - PresentationSurface, - Suboptimal, - }, + window::Extent2D, }; +#[cfg(test)] +use mockall::automock; use super::{ command::CommandBuffer, @@ -35,14 +29,14 @@ pub struct GpuBuilder { } impl GpuBuilder { - #[inline] + /// Create a new GpuBuilder to configure and build a GPU to use for rendering. pub fn new() -> Self { return Self { render_queue_type: RenderQueueType::Graphical, }; } - #[inline] + /// Set the type of queue to use for rendering. The GPU defaults to graphical. pub fn with_render_queue_type(mut self, queue_type: RenderQueueType) -> Self { self.render_queue_type = queue_type; return self; @@ -85,16 +79,15 @@ impl GpuBuilder { } } -/// /// Commands oriented around creating resources on & for the GPU. -/// pub struct Gpu { adapter: gfx_hal::adapter::Adapter, gpu: gfx_hal::adapter::Gpu, queue_group: QueueGroup, } -#[derive(Clone, Copy)] +/// The render queue types that the GPU can use for +#[derive(Clone, Copy, Debug, PartialEq)] pub enum RenderQueueType { Compute, Graphical, @@ -104,7 +97,8 @@ pub enum RenderQueueType { impl Gpu { /// Instantiates a new GPU given an adapter that is implemented by the GPUs - /// current rendering backend B. A new GPU does not come with a command pool unless specified. + /// current rendering backend B. A new GPU does not come with a command pool + /// unless specified. pub fn new( adapter: Adapter, queue_family: gfx_hal::queue::QueueFamilyId, @@ -177,31 +171,37 @@ impl Gpu { return Ok(()); } +} - /// Create a frame buffer on the GPU. - pub fn create_frame_buffer( - &mut self, - render_pass: &RenderBackend::RenderPass, - image: gfx_hal::image::FramebufferAttachment, - dimensions: &Extent2D, - ) -> RenderBackend::Framebuffer { - unsafe { - use gfx_hal::image::Extent; - return self - .gpu - .device - .create_framebuffer( - &render_pass, - vec![image].into_iter(), - Extent { - width: dimensions.width, - height: dimensions.height, - depth: 1, - }, - ) - .unwrap(); - } +#[cfg(test)] +mod tests { + #[test] + fn test_gpu_builder_default_state() { + use super::{ + GpuBuilder, + RenderQueueType, + }; + + let builder = GpuBuilder::new(); + + assert_eq!(builder.render_queue_type, RenderQueueType::Graphical); + } + + #[test] + fn test_gpu_builder_with_render_queue_type() { + use super::{ + GpuBuilder, + RenderQueueType, + }; + + let builder = + GpuBuilder::new().with_render_queue_type(RenderQueueType::Compute); + + assert_eq!(builder.render_queue_type, RenderQueueType::Compute); } + + #[test] + fn test_gpu_builder_build() {} } // --------------------------------- GPU INTERNALS ----------------------------- @@ -217,6 +217,7 @@ pub mod internal { return &gpu.gpu.device; } + /// Retrieves the gfx_hal physical device for a given GPU. #[inline] pub fn physical_device_for( gpu: &Gpu, @@ -224,6 +225,7 @@ pub mod internal { return &gpu.adapter.physical_device; } + /// Retrieves the gfx_hal queue group for a given GPU. #[inline] pub fn queue_family_for( gpu: &Gpu, diff --git a/crates/lambda-platform/src/gfx/mod.rs b/crates/lambda-platform/src/gfx/mod.rs index 914d9dd7..067e8e06 100644 --- a/crates/lambda-platform/src/gfx/mod.rs +++ b/crates/lambda-platform/src/gfx/mod.rs @@ -15,6 +15,44 @@ pub mod viewport; use gfx_hal::Instance as _; +// ----------------------- INSTANCE BUILDER AND INSTANCE ------------------------------- + +pub struct InstanceBuilder {} + +#[cfg(test)] +use mockall::automock; + +#[cfg_attr(test, automock)] +impl InstanceBuilder { + pub fn new() -> Self { + return Self {}; + } + + /// Builds a graphical instance for the current platform. + pub fn build( + self, + name: &str, + ) -> Instance { + return Instance::new(name); + } +} + +pub struct Instance { + gfx_hal_instance: RenderBackend::Instance, +} + +impl Instance { + /// Create a new GfxInstance connected to the current platforms primary backend. + fn new(name: &str) -> Self { + let instance = RenderBackend::Instance::create(name, 1) + .expect("gfx backend not supported by the current platform"); + + return Self { + gfx_hal_instance: instance, + }; + } +} + // ----------------------- INTERNAL INSTANCE OPERATIONS ------------------------ pub mod internal { @@ -43,7 +81,7 @@ pub mod internal { let surface = instance .gfx_hal_instance .create_surface(&window_handle.window_handle) - .unwrap(); + .expect("Failed to create a surface using the current instance and window handle."); return surface; }; @@ -70,34 +108,3 @@ pub mod internal { .remove(adapter_num); } } - -pub struct InstanceBuilder {} - -impl InstanceBuilder { - pub fn new() -> Self { - return Self {}; - } - - pub fn build( - self, - name: &str, - ) -> Instance { - return Instance::new(name); - } -} - -pub struct Instance { - gfx_hal_instance: RenderBackend::Instance, -} - -impl Instance { - /// Create a new GfxInstance connected to the platforms primary backend. - fn new(name: &str) -> Self { - let instance = RenderBackend::Instance::create(name, 1) - .expect("gfx backend not supported by the current platform"); - - return Self { - gfx_hal_instance: instance, - }; - } -} diff --git a/crates/lambda-platform/src/gfx/shader.rs b/crates/lambda-platform/src/gfx/shader.rs index 4bfcbc86..3fb881b6 100644 --- a/crates/lambda-platform/src/gfx/shader.rs +++ b/crates/lambda-platform/src/gfx/shader.rs @@ -1,36 +1,30 @@ +//! Low level shader implementations used by the lambda-platform crate to load +//! RISC-V compiled shaders into the GPU. + use gfx_hal::{ device::Device, pso::Specialization as ShaderSpecializations, }; +#[cfg(test)] +use mockall::automock; use super::gpu; -pub mod internal { - use super::ShaderModule; - - /// Retrieve the underlying gfx-hal shader module given the lambda-platform - /// implemented shader module. Useful for creating gfx-hal entry points and - /// attaching the shader to rendering pipelines. - #[inline] - pub fn module_for( - shader_module: &ShaderModule, - ) -> &RenderBackend::ShaderModule { - return &shader_module.shader_module; - } -} - +/// The type of shader that a shader module represents. Different shader types +/// are used for different operations in the rendering pipeline. pub enum ShaderModuleType { Vertex, Fragment, Compute, } -/// Builder class for +/// Builder class for creating a shader module. pub struct ShaderModuleBuilder { entry_name: String, specializations: ShaderSpecializations<'static>, } +#[cfg_attr(test, automock)] impl ShaderModuleBuilder { pub fn new() -> Self { return Self { @@ -86,10 +80,10 @@ pub struct ShaderModule { shader_type: ShaderModuleType, } +#[cfg_attr(test, automock)] impl ShaderModule { + /// Destroy the shader module and free the memory on the GPU. pub fn destroy(self, gpu: &mut gpu::Gpu) { - // TODO(vmarcella): Add documentation for the shader module. - println!("Destroying shader module."); unsafe { gpu::internal::logical_device_for(gpu) .destroy_shader_module(self.shader_module) @@ -102,7 +96,57 @@ impl ShaderModule { } /// Get the specializations being applied to the current shader module. - pub fn specializations(&self) -> &ShaderSpecializations { + pub fn specializations(&self) -> &ShaderSpecializations<'static> { return &self.specializations; } } + +#[cfg(test)] +mod tests { + + /// Test that we can create a shader module builder and it has the correct + /// defaults. + #[test] + fn shader_builder_initial_state() { + let shader_builder = super::ShaderModuleBuilder::new(); + assert_eq!(shader_builder.entry_name, "main"); + assert_eq!(shader_builder.specializations.data.len(), 0); + } + + /// Test that we can create a shader module builder with a custom entry point + /// & default specializations. + #[test] + fn shader_builder_with_properties() { + let shader_builder = super::ShaderModuleBuilder::new() + .with_entry_name("test") + .with_specializations(super::ShaderSpecializations::default()); + assert_eq!(shader_builder.entry_name, "test"); + assert_eq!( + shader_builder.specializations.data, + super::ShaderSpecializations::default().data + ); + } + + #[test] + fn shader_builder_builds_correctly() { + let shader_builder = super::ShaderModuleBuilder::new() + .with_entry_name("test") + .with_specializations(super::ShaderSpecializations::default()); + } +} + +/// Internal functions for the shader module. User applications most likely +/// should not use these functions directly nor should they need to. +pub mod internal { + use super::ShaderModule; + + /// Retrieve the underlying gfx-hal shader module given the lambda-platform + /// implemented shader module. Useful for creating gfx-hal entry points and + /// attaching the shader to rendering pipelines. + #[inline] + pub fn module_for( + shader_module: &ShaderModule, + ) -> &RenderBackend::ShaderModule { + return &shader_module.shader_module; + } +} diff --git a/crates/lambda-platform/src/gfx/surface.rs b/crates/lambda-platform/src/gfx/surface.rs index 9b75e940..8bd37170 100644 --- a/crates/lambda-platform/src/gfx/surface.rs +++ b/crates/lambda-platform/src/gfx/surface.rs @@ -1,124 +1,30 @@ -use std::borrow::Borrow; - /// ColorFormat for the surface. pub use gfx_hal::format::Format as ColorFormat; -use gfx_hal::{ - queue::Queue, - window::{ - PresentationSurface, - Surface as _, - }, +use gfx_hal::window::{ + PresentationSurface, + Surface as _, }; +#[cfg(test)] +use mockall::automock; use super::{ - gpu::{ - internal::primary_queue_for, - Gpu, - }, + gpu::Gpu, Instance, }; -/// Internal Surface functions. -pub mod internal { - use std::{ - borrow::Borrow, - fmt::Debug, - }; - - use gfx_hal::window::{ - PresentationSurface, - Surface as _, - }; - - /// Checks the queue family if the current Surface can support the GPU. - pub fn can_support_queue_family( - surface: &super::Surface, - queue_family: &RenderBackend::QueueFamily, - ) -> bool { - return surface.gfx_hal_surface.supports_queue_family(queue_family); - } - - /// Get the supported gfx_hal color formats for a given format. - pub fn get_supported_formats( - surface: &super::Surface, - physical_device: &RenderBackend::PhysicalDevice, - ) -> Vec { - return surface - .gfx_hal_surface - .supported_formats(physical_device) - .unwrap_or(vec![]); - } - - /// Helper function to retrieve the first supported format given a physical - /// GPU device. - pub fn get_first_supported_format( - surface: &super::Surface, - physical_device: &RenderBackend::PhysicalDevice, - ) -> gfx_hal::format::Format { - let supported_formats = get_supported_formats(&surface, physical_device); - - let default_format = *supported_formats - .get(0) - .unwrap_or(&gfx_hal::format::Format::Rgba8Srgb); - - return supported_formats - .into_iter() - .find(|format| -> bool { - format.base_format().1 == gfx_hal::format::ChannelType::Srgb - }) - .unwrap_or(default_format); - } - - /// Acquires a surface image for attaching to a framebuffer. - pub fn take_surface_image_for( - surface: &mut super::Surface, - ) -> Option<>::SwapchainImage>{ - return surface.image.take(); - } - - /// Acquires a surface image for attaching to a framebuffer. - pub fn borrow_surface_image_for( - surface: &super::Surface, - ) -> Option<&>::SwapchainImage>{ - return surface.image.as_ref(); - } - - /// FrameBuffer Attachment - pub fn frame_buffer_attachment_from( - surface: &super::Surface, - ) -> Option { - return surface.frame_buffer_attachment.clone(); - } - - pub fn surface_for( - surface: &mut super::Surface, - ) -> &mut RenderBackend::Surface { - return &mut surface.gfx_hal_surface; - } - - /// Borrow the surface and take the image. This internal function is used for - /// rendering and composes surface_for + take image. - pub fn borrow_surface_and_take_image( - surface: &mut super::Surface, - ) -> (&mut RenderBackend::Surface, >::SwapchainImage){ - return ( - &mut surface.gfx_hal_surface, - surface.image.take().expect(""), - ); - } -} - -#[derive(Debug, Clone)] /// The API to use for building surfaces from a graphical instance. +#[derive(Debug, Clone)] pub struct SurfaceBuilder { name: Option, } +#[cfg_attr(test, automock)] impl SurfaceBuilder { pub fn new() -> Self { return Self { name: None }; } + /// Set the name of the surface. pub fn with_name(mut self, name: &str) -> Self { self.name = Some(name.to_string()); return self; @@ -169,15 +75,16 @@ pub struct Swapchain { format: gfx_hal::format::Format, } +#[cfg_attr(test, automock)] impl Surface { /// Apply a swapchain to the current surface. This is required whenever a /// swapchain has been invalidated (I.E. by window resizing) - pub fn apply_swapchain( + pub fn apply_swapchain<'surface>( &mut self, gpu: &Gpu, swapchain: Swapchain, timeout_in_nanoseconds: u64, - ) -> Result<(), &str> { + ) -> Result<(), &'surface str> { let device = super::gpu::internal::logical_device_for(gpu); self.extent = Some(swapchain.config.extent); @@ -223,13 +130,11 @@ impl Surface { } /// private function to invalidate the surface swapchain. - #[inline] fn invalidate_swapchain(&mut self) { self.swapchain_is_valid = false; } /// Destroy the current surface and it's underlying resources. - #[inline] pub fn destroy(self, instance: &Instance) { println!("Destroying the surface: {}", self.name); @@ -238,9 +143,9 @@ impl Surface { /// Get the size of the surface's extent. Will only return a size if a /// swapchain has been applied to the surface to render with. - pub fn size(&self) -> Option<[u32; 2]> { + pub fn size(&self) -> Option<(u32, u32)> { return match self.extent { - Some(extent) => Some([extent.width, extent.height]), + Some(extent) => Some((extent.width, extent.height)), None => None, }; } @@ -249,16 +154,17 @@ impl Surface { // ------------------------------ SWAPCHAIN BUILDER ---------------------------- pub struct SwapchainBuilder { - size: [u32; 2], + size: (u32, u32), } impl SwapchainBuilder { pub fn new() -> Self { - return Self { size: [480, 360] }; + return Self { size: (480, 360) }; } + /// Set the size of the swapchain for the surface image. pub fn with_size(mut self, width: u32, height: u32) -> Self { - self.size = [width, height]; + self.size = (width, height); return self; } @@ -270,14 +176,12 @@ impl SwapchainBuilder { let physical_device = super::gpu::internal::physical_device_for(gpu); let caps = surface.gfx_hal_surface.capabilities(physical_device); let format = internal::get_first_supported_format(surface, physical_device); + let (width, height) = self.size; let mut swapchain_config = gfx_hal::window::SwapchainConfig::from_caps( &caps, format, - gfx_hal::window::Extent2D { - width: self.size[0], - height: self.size[1], - }, + gfx_hal::window::Extent2D { width, height }, ); // TODO(vmarcella) Profile the performance on MacOS to see if this slows @@ -292,3 +196,118 @@ impl SwapchainBuilder { }; } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::gfx::MockInstanceBuilder; + + #[test] + fn test_surface_builder() { + let surface_builder = SurfaceBuilder::new(); + assert_eq!(surface_builder.name, None); + + let surface_builder = SurfaceBuilder::new().with_name("TestSurface"); + assert_eq!(surface_builder.name, Some("TestSurface".to_string())); + } + + #[test] + fn test_swapchain_builder() { + let swapchain_builder = SwapchainBuilder::new(); + assert_eq!(swapchain_builder.size, (480, 360)); + + let swapchain_builder = SwapchainBuilder::new().with_size(1920, 1080); + assert_eq!(swapchain_builder.size, (1920, 1080)); + } + + #[test] + fn test_surface_builder_e2e() { + //let instance = MockInstanceBuilder::new().build("TestInstance"); + let surface_builder = SurfaceBuilder::new().with_name("TestSurface"); + //let surface = surface_builder.build(&instance); + + //assert_eq!(surface.name, "TestSurface".to_string()); + //assert_eq!(surface.swapchain_is_valid, false); + //assert_eq!(surface.image, None); + //assert_eq!(surface.frame_buffer_attachment, None); + } +} + +/// Internal functions to work with the gfx-hal surface components +pub mod internal { + use gfx_hal::window::{ + PresentationSurface, + Surface as _, + }; + + /// Checks the queue family if the current Surface can support the GPU. + pub fn can_support_queue_family( + surface: &super::Surface, + queue_family: &RenderBackend::QueueFamily, + ) -> bool { + return surface.gfx_hal_surface.supports_queue_family(queue_family); + } + + /// Get the supported gfx_hal color formats for a given format. + pub fn get_supported_formats( + surface: &super::Surface, + physical_device: &RenderBackend::PhysicalDevice, + ) -> Vec { + return surface + .gfx_hal_surface + .supported_formats(physical_device) + .unwrap_or(vec![]); + } + + /// Helper function to retrieve the first supported format given a physical + /// GPU device. + pub fn get_first_supported_format( + surface: &super::Surface, + physical_device: &RenderBackend::PhysicalDevice, + ) -> gfx_hal::format::Format { + let supported_formats = get_supported_formats(&surface, physical_device); + + let default_format = *supported_formats + .get(0) + .unwrap_or(&gfx_hal::format::Format::Rgba8Srgb); + + return supported_formats + .into_iter() + .find(|format| -> bool { + format.base_format().1 == gfx_hal::format::ChannelType::Srgb + }) + .unwrap_or(default_format); + } + + /// Acquires a surface image for attaching to a framebuffer. + pub fn take_surface_image_for( + surface: &mut super::Surface, + ) -> Option<>::SwapchainImage>{ + return surface.image.take(); + } + + /// Acquires a surface image for attaching to a framebuffer. + pub fn borrow_surface_image_for( + surface: &super::Surface, + ) -> Option<&>::SwapchainImage>{ + return surface.image.as_ref(); + } + + /// FrameBuffer Attachment + pub fn frame_buffer_attachment_from( + surface: &super::Surface, + ) -> Option { + return surface.frame_buffer_attachment.clone(); + } + + /// Borrow the surface and take the image. This internal function is used for + /// rendering and composes surface_for + take image. + pub fn borrow_surface_and_take_image( + surface: &mut super::Surface, + ) -> (&mut RenderBackend::Surface, >::SwapchainImage){ + return ( + &mut surface.gfx_hal_surface, + surface.image.take().expect("Surface image is not present"), + ); + } +} diff --git a/crates/lambda-platform/src/gfx/viewport.rs b/crates/lambda-platform/src/gfx/viewport.rs index 4ecae8c7..ab8e2ed6 100644 --- a/crates/lambda-platform/src/gfx/viewport.rs +++ b/crates/lambda-platform/src/gfx/viewport.rs @@ -1,16 +1,22 @@ +//! viewport.rs - Low level viewport management for the render context. + +/// Viewport is a rectangle that defines the area of the screen that will be +/// rendered to. For instance, if the viewport is set to x=0, y=0, width=100, +/// height=100, then only the top left 100x100 pixels of the screen will be +/// rendered to. #[derive(Debug, Clone, PartialEq)] pub struct ViewPort { viewport: gfx_hal::pso::Viewport, } -impl ViewPort {} - +/// A builder for `Viewport`. pub struct ViewPortBuilder { x: i16, y: i16, } impl ViewPortBuilder { + /// Create a new `ViewportBuilder`. pub fn new() -> Self { return ViewPortBuilder { x: 0, y: 0 }; } @@ -46,3 +52,35 @@ pub mod internal { return viewport.viewport.clone(); } } + +#[cfg(test)] +pub mod tests { + /// Test the viewport builder in it's default state. + #[test] + fn viewport_builder_default_initial_state() { + let viewport_builder = super::ViewPortBuilder::new(); + assert_eq!(viewport_builder.x, 0); + assert_eq!(viewport_builder.y, 0); + } + + /// Test that the viewport builder can be configured with specific + /// coordinates. + #[test] + fn viewport_builder_with_coordinates() { + let viewport_builder = + super::ViewPortBuilder::new().with_coordinates(10, 10); + assert_eq!(viewport_builder.x, 10); + assert_eq!(viewport_builder.y, 10); + } + + #[test] + fn viewport_builder_builds_successfully() { + let viewport_builder = + super::ViewPortBuilder::new().with_coordinates(10, 10); + let viewport = viewport_builder.build(100, 100); + assert_eq!(viewport.viewport.rect.x, 10); + assert_eq!(viewport.viewport.rect.y, 10); + assert_eq!(viewport.viewport.rect.w, 100); + assert_eq!(viewport.viewport.rect.h, 100); + } +} diff --git a/crates/lambda-platform/src/winit/mod.rs b/crates/lambda-platform/src/winit/mod.rs index 77cd2464..1ea9ef74 100644 --- a/crates/lambda-platform/src/winit/mod.rs +++ b/crates/lambda-platform/src/winit/mod.rs @@ -19,7 +19,6 @@ use winit::{ /// Embedded module for exporting data/types from winit as minimally/controlled /// as possible. The exports from this module are not guaranteed to be stable. -// TODO(ahlawat) = Remove all these except WindowEvent since we're abstracting them already? Double check pub mod winit_exports { pub use winit::{ event::{ @@ -164,25 +163,6 @@ impl WindowHandleBuilder { } } -/// Construct WindowSize metdata from the window dimensions and scale factor of -/// the monitor being rendered to. -#[inline] -fn construct_window_size( - window_size: (u32, u32), - scale_factor: f64, -) -> WindowSize { - let logical: LogicalSize = window_size.into(); - let physical: PhysicalSize = logical.to_physical(scale_factor); - - let (width, height) = window_size; - return WindowSize { - width, - height, - logical, - physical, - }; -} - /// Event loop publisher wrapper for pushing events into a winit event loop. pub struct LoopPublisher { winit_proxy: EventLoopProxy, diff --git a/lambda/Cargo.toml b/lambda/Cargo.toml index f92e6e12..cc2517dd 100644 --- a/lambda/Cargo.toml +++ b/lambda/Cargo.toml @@ -12,6 +12,7 @@ lambda-platform = { path = "../crates/lambda-platform", version = "0.1.0" } [dev-dependencies] cargo-audit = "0.16.0" +mockall = "0.11.2" [features] default = ["lambda-platform/detect-platform"] diff --git a/lambda/src/core/events.rs b/lambda/src/core/events.rs index badd073d..ca9571d4 100644 --- a/lambda/src/core/events.rs +++ b/lambda/src/core/events.rs @@ -29,11 +29,11 @@ pub use lambda_platform::winit::winit_exports::VirtualKeyCode as VirtualKey; pub enum KeyEvent { KeyPressed { scan_code: u32, - virtual_key: VirtualKey, + virtual_key: Option, }, KeyReleased { scan_code: u32, - virtual_key: VirtualKey, + virtual_key: Option, }, ModifierPressed { modifier: u32, diff --git a/lambda/src/core/render/mod.rs b/lambda/src/core/render/mod.rs index aca48b70..982adeea 100644 --- a/lambda/src/core/render/mod.rs +++ b/lambda/src/core/render/mod.rs @@ -1,3 +1,6 @@ +//! High level Rendering API designed for cross platform rendering and +//! windowing. + pub mod command; pub mod pipeline; pub mod render_pass; @@ -5,69 +8,6 @@ pub mod shader; pub mod viewport; pub mod window; -pub mod internal { - use std::rc::Rc; - - use lambda_platform::gfx::api::RenderingAPI as RenderContext; - pub type RenderBackend = RenderContext::Backend; - - pub use lambda_platform::{ - gfx::{ - command::{ - CommandBuffer, - CommandBufferBuilder, - CommandPool, - CommandPoolBuilder, - }, - fence::{ - RenderSemaphore, - RenderSemaphoreBuilder, - RenderSubmissionFence, - RenderSubmissionFenceBuilder, - }, - framebuffer::Framebuffer, - gpu::{ - Gpu, - GpuBuilder, - RenderQueueType, - }, - pipeline::RenderPipelineBuilder, - render_pass::{ - RenderPass, - RenderPassBuilder, - }, - surface::{ - Surface, - SurfaceBuilder, - }, - Instance, - InstanceBuilder, - }, - shaderc::ShaderKind, - }; - - /// Returns the GPU instance for the given render context. - pub fn gpu_from_context( - context: &super::RenderContext, - ) -> &Gpu { - return &context.gpu; - } - - /// Returns a mutable GPU instance for the given render context. - pub fn mut_gpu_from_context( - context: &mut super::RenderContext, - ) -> &mut Gpu { - return &mut context.gpu; - } - - /// Gets the surface for the given render context. - pub fn surface_from_context( - context: &super::RenderContext, - ) -> Rc> { - return context.surface.clone(); - } -} - use std::{ mem::swap, rc::Rc, @@ -261,13 +201,13 @@ impl RenderContext { /// Allocates a command buffer and records commands to the GPU. pub fn render(&mut self, commands: Vec) { - let dimensions = self + let (width, height) = self .surface .size() .expect("Surface has no size configured."); let swapchain = SwapchainBuilder::new() - .with_size(dimensions[0], dimensions[1]) + .with_size(width, height) .build(&self.gpu, &self.surface); Rc::get_mut(&mut self.surface) @@ -320,6 +260,79 @@ impl RenderContext { None => {} } } + + pub fn resize(&mut self, width: u32, height: u32) { + let swapchain = SwapchainBuilder::new() + .with_size(width, height) + .build(&self.gpu, &self.surface); + Rc::get_mut(&mut self.surface) + .expect("Failed to acquire the surface while attempting to resize.") + .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) + .expect("Failed to apply the swapchain to the surface while attempting to resize."); + } } type PlatformRenderCommand = Command; + +pub mod internal { + use std::rc::Rc; + + use lambda_platform::gfx::api::RenderingAPI as RenderContext; + pub type RenderBackend = RenderContext::Backend; + + pub use lambda_platform::{ + gfx::{ + command::{ + CommandBuffer, + CommandBufferBuilder, + CommandPool, + CommandPoolBuilder, + }, + fence::{ + RenderSemaphore, + RenderSemaphoreBuilder, + RenderSubmissionFence, + RenderSubmissionFenceBuilder, + }, + framebuffer::Framebuffer, + gpu::{ + Gpu, + GpuBuilder, + RenderQueueType, + }, + pipeline::RenderPipelineBuilder, + render_pass::{ + RenderPass, + RenderPassBuilder, + }, + surface::{ + Surface, + SurfaceBuilder, + }, + Instance, + InstanceBuilder, + }, + shaderc::ShaderKind, + }; + + /// Returns the GPU instance for the given render context. + pub fn gpu_from_context( + context: &super::RenderContext, + ) -> &Gpu { + return &context.gpu; + } + + /// Returns a mutable GPU instance for the given render context. + pub fn mut_gpu_from_context( + context: &mut super::RenderContext, + ) -> &mut Gpu { + return &mut context.gpu; + } + + /// Gets the surface for the given render context. + pub fn surface_from_context( + context: &super::RenderContext, + ) -> Rc> { + return context.surface.clone(); + } +} diff --git a/lambda/src/runtimes/mod.rs b/lambda/src/runtimes/mod.rs index 8a6ba046..05d8b3e9 100644 --- a/lambda/src/runtimes/mod.rs +++ b/lambda/src/runtimes/mod.rs @@ -33,8 +33,8 @@ use crate::core::{ pub struct GenericRuntimeBuilder { app_name: String, - render_api: RenderContextBuilder, - window_size: (u32, u32), + render_context_builder: RenderContextBuilder, + window_builder: WindowBuilder, components: Vec>>, } @@ -42,8 +42,8 @@ impl GenericRuntimeBuilder { pub fn new(app_name: &str) -> Self { return Self { app_name: app_name.to_string(), - render_api: RenderContextBuilder::new(app_name), - window_size: (800, 600), + render_context_builder: RenderContextBuilder::new(app_name), + window_builder: WindowBuilder::new(), components: Vec::new(), }; } @@ -54,20 +54,29 @@ impl GenericRuntimeBuilder { return self; } - pub fn with_window_size(mut self, width: u32, height: u32) -> Self { - self.window_size = (width, height); + /// Configures the `RenderAPIBuilder` before the `RenderContext` is built + /// using a callback provided by the user. The renderer in it's default + /// state will be good enough for most applications, but if you need to + /// customize the renderer you can do so here. + pub fn with_renderer_configured_as( + mut self, + configuration: impl FnOnce(RenderContextBuilder) -> RenderContextBuilder, + ) -> Self { + self.render_context_builder = configuration(self.render_context_builder); return self; } - /// Configures the RenderAPIBuilder before the RenderingAPI is built using a - /// callback provided by the user. - pub fn with_renderer( + /// Configures the WindowBuilder before the Window is built using a callback + /// provided by the user. If you need to customize the window you can do so + /// here. + pub fn with_window_configured_as( mut self, - configure: impl FnOnce(RenderContextBuilder) -> RenderContextBuilder, + configuration: impl FnOnce(WindowBuilder) -> WindowBuilder, ) -> Self { - self.render_api = configure(self.render_api); + self.window_builder = configuration(self.window_builder); return self; } + /// Attach a component to the current runnable. pub fn with_component + 'static>( self, @@ -85,14 +94,10 @@ impl GenericRuntimeBuilder { pub fn build(self) -> GenericRuntime { let name = self.app_name; let mut event_loop = LoopBuilder::new().build(); - let (width, height) = self.window_size; + let window = self.window_builder.build(&mut event_loop); - let window = WindowBuilder::new() - .with_name(name.as_str()) - .with_dimensions(width, height) - .build(&mut event_loop); let component_stack = self.components; - let render_api = self.render_api.build(&window); + let render_api = self.render_context_builder.build(&window); return GenericRuntime { name, @@ -118,14 +123,14 @@ pub struct GenericRuntime { impl GenericRuntime {} impl Runtime for GenericRuntime { - /// Initiates an event loop that captures the context of the LambdaKernel - /// and generates events from the windows event loop until the end of the event loops - /// lifetime (Whether that be initiated intentionally or via error). + /// Runs the event loop for the GenericRuntime which takes ownership of all + /// components, the windowing the render context, and anything else relevant + /// to the runtime. fn run(self) { - // Decompose Kernel components for transferring ownership to the - // closure. + // Decompose Runtime components to transfer ownership from the runtime to + // the event loop closure which will run until the app is closed. let GenericRuntime { - mut window, + window, mut event_loop, mut component_stack, name, @@ -153,6 +158,11 @@ impl Runtime for GenericRuntime { }); } WinitWindowEvent::Resized(dims) => { + active_render_api + .as_mut() + .unwrap() + .resize(dims.width, dims.height); + publisher.publish_event(Events::Window { event: WindowEvent::Resize { width: dims.width, @@ -162,6 +172,11 @@ impl Runtime for GenericRuntime { }) } WinitWindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + active_render_api + .as_mut() + .unwrap() + .resize(new_inner_size.width, new_inner_size.height); + publisher.publish_event(Events::Window { event: WindowEvent::Resize { width: new_inner_size.width, @@ -186,7 +201,7 @@ impl Runtime for GenericRuntime { publisher.publish_event(Events::Keyboard { event: KeyEvent::KeyPressed { scan_code: input.scancode, - virtual_key: input.virtual_keycode.unwrap(), + virtual_key: input.virtual_keycode, }, issued_at: Instant::now(), }) @@ -195,13 +210,16 @@ impl Runtime for GenericRuntime { publisher.publish_event(Events::Keyboard { event: KeyEvent::KeyReleased { scan_code: input.scancode, - virtual_key: input.virtual_keycode.unwrap(), + virtual_key: input.virtual_keycode, }, issued_at: Instant::now(), }) } _ => { - println!("Unhandled synthetic keyboard event: {:?}", input); + println!( + "[WARN] Unhandled synthetic keyboard event: {:?}", + input + ); } }, WinitWindowEvent::ModifiersChanged(_) => {} @@ -242,12 +260,12 @@ impl Runtime for GenericRuntime { current_frame = Instant::now(); let duration = ¤t_frame.duration_since(last_frame); + let render_api = active_render_api.as_mut().unwrap(); // Update and render commands. for component in &mut component_stack { component.on_update(duration); - let commands = component - .on_render(active_render_api.as_mut().unwrap(), duration); - active_render_api.as_mut().unwrap().render(commands); + let commands = component.on_render(render_api, duration); + render_api.render(commands); } window.redraw(); @@ -258,7 +276,7 @@ impl Runtime for GenericRuntime { WinitEvent::UserEvent(lambda_event) => match lambda_event { Events::Runtime { event, issued_at } => match event { RuntimeEvent::Initialized => { - println!("Starting the kernel {}", name); + println!("[INFO] Initializing all of the components for the runtime: {}", name); for component in &mut component_stack { component.on_attach(); component @@ -284,9 +302,12 @@ impl Runtime for GenericRuntime { WinitEvent::Resumed => {} WinitEvent::RedrawEventsCleared => {} WinitEvent::LoopDestroyed => { - active_render_api.take().unwrap().destroy(); + active_render_api + .take() + .expect("[ERROR] The render API has been already taken.") + .destroy(); - println!("All resources were successfully deleted."); + println!("[INFO] All resources were successfully deleted."); } } }); @@ -295,10 +316,10 @@ impl Runtime for GenericRuntime { /// When the generic runtime starts, it will attach all of the components that /// have been added during the construction phase in the users code. fn on_start(&mut self) { - println!("Starting the runtime {}", self.name); + println!("[INFO] Starting the runtime {}", self.name); } fn on_stop(&mut self) { - println!("Stopping {}", self.name) + println!("[INFO] Stopping {}", self.name) } } diff --git a/tools/lambda_rs_demo/src/main.rs b/tools/lambda_rs_demo/src/main.rs index a106a628..15bc226e 100644 --- a/tools/lambda_rs_demo/src/main.rs +++ b/tools/lambda_rs_demo/src/main.rs @@ -6,7 +6,13 @@ use lambda::{ Component, RenderableComponent, }, - events::Events, + events::{ + ComponentEvent, + Events, + KeyEvent, + RuntimeEvent, + WindowEvent, + }, render::{ command::RenderCommand, pipeline::{ @@ -35,11 +41,13 @@ pub struct DemoComponent { vertex_shader: Shader, render_pass: Option>, render_pipeline: Option>, + width: u32, + height: u32, } impl Component for DemoComponent { fn on_attach(&mut self) { - println!("Attached the first layer to lambda"); + println!("Attached the DemoComponent."); } fn on_detach(self: &mut DemoComponent) {} @@ -53,27 +61,29 @@ impl Component for DemoComponent { _ => {} }, Events::Window { event, issued_at } => match event { - lambda::core::events::WindowEvent::Resize { width, height } => { + WindowEvent::Resize { width, height } => { println!("Window resized to {}x{}", width, height); + self.width = *width; + self.height = *height; } - lambda::core::events::WindowEvent::Close => { + WindowEvent::Close => { println!("Window closed"); } }, Events::Keyboard { event, issued_at } => match event { - lambda::core::events::KeyEvent::KeyPressed { + KeyEvent::KeyPressed { scan_code, virtual_key, } => { println!("Key pressed: {:?}", virtual_key); } - lambda::core::events::KeyEvent::KeyReleased { + KeyEvent::KeyReleased { scan_code, virtual_key, } => { println!("Key released: {:?}", virtual_key); } - lambda::core::events::KeyEvent::ModifierPressed { + KeyEvent::ModifierPressed { modifier, virtual_key, } => { @@ -81,10 +91,10 @@ impl Component for DemoComponent { } }, Events::Component { event, issued_at } => match event { - lambda::core::events::ComponentEvent::Attached { name } => { + ComponentEvent::Attached { name } => { println!("Component attached: {:?}", name); } - lambda::core::events::ComponentEvent::Detached { name } => { + ComponentEvent::Detached { name } => { println!("Component detached: {:?}", name); } }, @@ -128,7 +138,8 @@ impl RenderableComponent for DemoComponent { _render_context: &mut lambda::core::render::RenderContext, _last_render: &std::time::Duration, ) -> Vec { - let viewport = viewport::ViewportBuilder::new().build(800, 600); + let viewport = + viewport::ViewportBuilder::new().build(self.width, self.height); // This array of commands will be executed in linear order return vec![ @@ -200,17 +211,24 @@ impl Default for DemoComponent { triangle_vertex: fs, render_pass: None, render_pipeline: None, + width: 800, + height: 600, }; } } fn main() { let runtime = GenericRuntimeBuilder::new("2D Triangle Demo") - .with_renderer(move |render_context_builder| { - return render_context_builder; + .with_renderer_configured_as(move |render_context_builder| { + return render_context_builder.with_render_timeout(1_000_000_000); + }) + .with_window_configured_as(move |window_builder| { + return window_builder + .with_dimensions(1200, 600) + .with_name("2D Triangle Window"); }) - .with_component(move |kernel, demo: DemoComponent| { - return (kernel, demo); + .with_component(move |runtime, demo: DemoComponent| { + return (runtime, demo); }) .build();