diff --git a/Cargo.lock b/Cargo.lock index eb87ec44..4f2a2c42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,17 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "gfx-auxil" version = "0.10.0" @@ -1299,6 +1310,7 @@ dependencies = [ "gfx-backend-vulkan", "gfx-hal", "mockall", + "rand", "shaderc", "winit", ] @@ -1899,6 +1911,12 @@ dependencies = [ "miniz_oxide 0.5.4", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "predicates" version = "2.1.1" @@ -1987,6 +2005,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.2" diff --git a/crates/lambda-platform/Cargo.toml b/crates/lambda-platform/Cargo.toml index f98e4564..8f7d53bb 100644 --- a/crates/lambda-platform/Cargo.toml +++ b/crates/lambda-platform/Cargo.toml @@ -12,6 +12,7 @@ gfx-hal = "=0.9.0" winit = "=0.27.5" shaderc = "=0.7" cfg-if = "=1.0.0" +rand = "=0.8.5" # GFX-RS backends gfx-backend-gl = { version="=0.9.0", optional = true } @@ -33,6 +34,10 @@ gfx-with-metal=["dep:gfx-backend-metal"] gfx-with-dx11=["dep:gfx-backend-dx11"] gfx-with-dx12=["dep:gfx-backend-dx12"] +[profile.dev] +crate-type = ["cdylib", "rlib"] +incremental = true + [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.gfx-platform-backend] package = "gfx-backend-gl" version = "=0.9.0" diff --git a/crates/lambda-platform/src/gfx/api.rs b/crates/lambda-platform/src/gfx/api.rs index 8efabab0..3fe3fa65 100644 --- a/crates/lambda-platform/src/gfx/api.rs +++ b/crates/lambda-platform/src/gfx/api.rs @@ -14,6 +14,5 @@ cfg_if::cfg_if! { pub use gfx_backend_dx12 as RenderingAPI; } else if #[cfg(feature = "detect-platform")] { pub use gfx_platform_backend as RenderingAPI; - } else { - } + } else {} } diff --git a/crates/lambda-platform/src/gfx/assembler.rs b/crates/lambda-platform/src/gfx/assembler.rs index f2488dd1..a0a5fcf9 100644 --- a/crates/lambda-platform/src/gfx/assembler.rs +++ b/crates/lambda-platform/src/gfx/assembler.rs @@ -1,25 +1,81 @@ //! Primitive assembly for the graphics pipeline. -use gfx_hal::pso; +pub use gfx_hal::pso::Element as VertexElement; +use gfx_hal::pso::{ + self, + AttributeDesc, + VertexBufferDesc, +}; + +use super::{ + buffer::Buffer, + surface::ColorFormat, +}; + +/// Attributes for a vertex. +#[derive(Debug, Clone)] +pub struct VertexAttribute { + pub location: u32, + pub offset: u32, + pub element: VertexElement, +} /// PrimitiveAssemblerBuilder for preparing PrimitiveAssemblers to use in the /// lambda-platform Rendering pipeline. -pub struct PrimitiveAssemblerBuilder {} +pub struct PrimitiveAssemblerBuilder { + buffer_descriptions: Vec, + attribute_descriptions: Vec, +} impl PrimitiveAssemblerBuilder { pub fn new() -> Self { - return Self {}; + return Self { + buffer_descriptions: Vec::new(), + attribute_descriptions: Vec::new(), + }; } /// Build a primitive assembler given the lambda-platform vertex shader - /// module. + /// module. Buffers & attributes do not have to be tied to pub fn build<'shader, RenderBackend: gfx_hal::Backend>( - self, + &'shader mut self, vertex_shader: &'shader super::shader::ShaderModule, + buffers: Option<&Vec<&Buffer>>, + attributes: Option<&[VertexAttribute]>, ) -> PrimitiveAssembler<'shader, RenderBackend> { + let binding = self.buffer_descriptions.len() as u32; + + match (buffers, attributes) { + (Some(buffers), Some(attributes)) => { + println!( + "[DEBUG] Building primitive assembler with buffers and attributes" + ); + self.buffer_descriptions = buffers + .iter() + .map(|buffer| VertexBufferDesc { + binding, + stride: buffer.stride() as u32, + rate: pso::VertexInputRate::Vertex, + }) + .collect(); + + self.attribute_descriptions = attributes + .iter() + .map(|attribute| { + return AttributeDesc { + location: attribute.location, + binding, + element: attribute.element, + }; + }) + .collect(); + } + _ => {} + } + let primitive_assembler = pso::PrimitiveAssemblerDesc::Vertex { - buffers: &[], - attributes: &[], + buffers: self.buffer_descriptions.as_slice(), + attributes: self.attribute_descriptions.as_slice(), input_assembler: pso::InputAssemblerDesc::new( pso::Primitive::TriangleList, ), @@ -52,7 +108,7 @@ impl<'shader, RenderBackend: gfx_hal::Backend> /// Internal functions for the primitive assembler. User applications most /// likely should not use these functions directly nor should they need to. -pub mod internal { +pub(crate) mod internal { #[inline] pub fn into_primitive_assembler<'shader, RenderBackend: gfx_hal::Backend>( primitive_assembler: super::PrimitiveAssembler<'shader, RenderBackend>, diff --git a/crates/lambda-platform/src/gfx/buffer.rs b/crates/lambda-platform/src/gfx/buffer.rs new file mode 100644 index 00000000..c8a08246 --- /dev/null +++ b/crates/lambda-platform/src/gfx/buffer.rs @@ -0,0 +1,209 @@ +use gfx_hal::{ + memory::{ + Segment, + SparseFlags, + }, + prelude::Device, +}; + +use super::gpu::Gpu; + +// Reuse gfx-hal buffer usage & properties for now. +pub type Usage = gfx_hal::buffer::Usage; +pub type Properties = gfx_hal::memory::Properties; + +/// The type of buffers that can be allocated on the GPU. +#[derive(Debug, Clone, Copy)] +pub enum BufferType { + Vertex, + Index, + Uniform, + Storage, +} + +/// A buffer is a block of memory that can be used to store data that can be +/// accessed by the GPU. +#[derive(Debug, Clone, Copy)] +pub struct Buffer { + buffer: RenderBackend::Buffer, + memory: RenderBackend::Memory, + stride: usize, + buffer_type: BufferType, +} + +impl Buffer { + /// Destroy the buffer and all it's resources with the GPU that + /// created it. + pub fn destroy(self, gpu: &Gpu) { + unsafe { + gpu.internal_logical_device().free_memory(self.memory); + gpu.internal_logical_device().destroy_buffer(self.buffer); + } + } + + /// Size of the buffer in bytes. + pub fn stride(&self) -> usize { + return self.stride; + } +} + +impl Buffer { + /// Retrieve a reference to the internal buffer. + pub(super) fn internal_buffer(&self) -> &RenderBackend::Buffer { + return &self.buffer; + } +} + +pub struct BufferBuilder { + buffer_length: usize, + usage: Usage, + properties: Properties, + buffer_type: BufferType, +} + +impl BufferBuilder { + pub fn new() -> Self { + return Self { + buffer_length: 0, + usage: Usage::empty(), + properties: Properties::empty(), + buffer_type: BufferType::Vertex, + }; + } + + pub fn with_length(&mut self, length: usize) -> &mut Self { + self.buffer_length = length; + return self; + } + + pub fn with_usage(&mut self, usage: Usage) -> &mut Self { + self.usage = usage; + return self; + } + + pub fn with_properties(&mut self, properties: Properties) -> &mut Self { + self.properties = properties; + return self; + } + + pub fn with_buffer_type(&mut self, buffer_type: BufferType) -> &mut Self { + self.buffer_type = buffer_type; + return self; + } + + /// Builds & binds a buffer of memory to the GPU. If the buffer cannot be + /// bound to the GPU, the buffer memory is freed before the error is returned. + /// Data must represent the data that will be stored in the buffer, meaning + /// it must repr C and be the same size as the buffer length. + pub fn build( + &self, + gpu: &mut Gpu, + data: Vec, + ) -> Result, &'static str> { + use gfx_hal::{ + adapter::PhysicalDevice, + MemoryTypeId, + }; + let logical_device = gpu.internal_logical_device(); + let physical_device = super::internal::physical_device_for(gpu); + + // TODO(vmarcella): Add the ability for the user to specify the memory + // properties (I.E. SparseFlags::SPARSE_MEMORY). + println!("[DEBUG] Creating buffer of length: {}", self.buffer_length); + let buffer_result = unsafe { + logical_device.create_buffer( + self.buffer_length as u64, + self.usage, + SparseFlags::empty(), + ) + }; + + if buffer_result.is_err() { + return Err("Failed to create buffer for allocating memory."); + } + + let mut buffer = buffer_result.unwrap(); + + let requirements = + unsafe { logical_device.get_buffer_requirements(&buffer) }; + let memory_types = physical_device.memory_properties().memory_types; + + println!("[DEBUG] Buffer requirements: {:?}", requirements); + // Find a memory type that supports the requirements of the buffer. + let memory_type = memory_types + .iter() + .enumerate() + .find(|(id, memory_type)| { + let type_supported = requirements.type_mask & (1 << id) != 0; + type_supported && memory_type.properties.contains(self.properties) + }) + .map(|(id, _)| MemoryTypeId(id)) + .unwrap(); + + println!("Allocating memory for buffer."); + // Allocates the memory on the GPU for the buffer. + let buffer_memory_allocation = + unsafe { logical_device.allocate_memory(memory_type, requirements.size) }; + + if buffer_memory_allocation.is_err() { + return Err("Failed to allocate memory for buffer."); + } + + let mut buffer_memory = buffer_memory_allocation.unwrap(); + + // Bind the buffer to the GPU memory + let buffer_binding = unsafe { + logical_device.bind_buffer_memory(&buffer_memory, 0, &mut buffer) + }; + + // Destroy the buffer if we failed to bind it to memory. + if buffer_binding.is_err() { + unsafe { logical_device.destroy_buffer(buffer) }; + return Err("Failed to bind buffer memory."); + } + + // Get address of the buffer memory on the GPU so that we can write to it. + let get_mapping_to_memory = + unsafe { logical_device.map_memory(&mut buffer_memory, Segment::ALL) }; + + if get_mapping_to_memory.is_err() { + unsafe { logical_device.destroy_buffer(buffer) }; + return Err("Failed to map memory."); + } + let mapped_memory = get_mapping_to_memory.unwrap(); + + // Copy the data to the GPU memory. + unsafe { + std::ptr::copy_nonoverlapping( + data.as_ptr() as *const u8, + mapped_memory, + self.buffer_length, + ); + }; + + // Flush the data to ensure it is written to the GPU memory. + let memory_flush = unsafe { + logical_device + .flush_mapped_memory_ranges(std::iter::once(( + &buffer_memory, + Segment::ALL, + ))) + .map_err(|_| "Failed to flush memory.") + }; + + if memory_flush.is_err() { + unsafe { logical_device.destroy_buffer(buffer) }; + return Err("No memory available on the GPU."); + } + + // Unmap the memory now that it's no longer needed by the CPU. + unsafe { logical_device.unmap_memory(&mut buffer_memory) }; + + return Ok(Buffer { + buffer, + memory: buffer_memory, + stride: std::mem::size_of::(), + buffer_type: self.buffer_type, + }); + } +} diff --git a/crates/lambda-platform/src/gfx/command.rs b/crates/lambda-platform/src/gfx/command.rs index 8ffb9879..8ec6326a 100644 --- a/crates/lambda-platform/src/gfx/command.rs +++ b/crates/lambda-platform/src/gfx/command.rs @@ -96,6 +96,9 @@ pub enum Command { offset: u32, bytes: Vec, }, + BindVertexBuffer { + buffer: Rc>, + }, EndRecording, } @@ -184,7 +187,16 @@ impl<'command_pool, RenderBackend: gfx_hal::Backend> offset, bytes.as_slice(), ), - Command::Draw { vertices } => self.command_buffer.draw(vertices, 0..1), + Command::Draw { vertices } => { + self.command_buffer.draw(vertices.clone(), 0..1) + } + Command::BindVertexBuffer { buffer } => { + self.command_buffer.bind_vertex_buffers( + 0, + vec![(buffer.internal_buffer(), gfx_hal::buffer::SubRange::WHOLE)] + .into_iter(), + ) + } Command::EndRecording => self.command_buffer.finish(), } } @@ -384,7 +396,8 @@ impl CommandPool { pub fn destroy(mut self, gpu: &super::gpu::Gpu) { unsafe { self.command_pool.reset(true); - super::gpu::internal::logical_device_for(gpu) + gpu + .internal_logical_device() .destroy_command_pool(self.command_pool); } } diff --git a/crates/lambda-platform/src/gfx/fence.rs b/crates/lambda-platform/src/gfx/fence.rs index 29963cc8..3a3259a1 100644 --- a/crates/lambda-platform/src/gfx/fence.rs +++ b/crates/lambda-platform/src/gfx/fence.rs @@ -119,7 +119,7 @@ impl RenderSubmissionFence { } } -pub mod internal { +pub(crate) mod internal { /// Retrieve the underlying submission fence. pub fn mutable_fence_for( fence: &mut super::RenderSubmissionFence, diff --git a/crates/lambda-platform/src/gfx/framebuffer.rs b/crates/lambda-platform/src/gfx/framebuffer.rs index 329c290e..906270ca 100644 --- a/crates/lambda-platform/src/gfx/framebuffer.rs +++ b/crates/lambda-platform/src/gfx/framebuffer.rs @@ -62,7 +62,7 @@ impl FramebufferBuilder { /// Internal functions to work with gfx-hal framebuffers directly. Applications /// should not need to use these functions directly. -pub mod internal { +pub(crate) mod internal { pub fn frame_buffer_for( frame_buffer: &super::Framebuffer, ) -> &RenderBackend::Framebuffer { diff --git a/crates/lambda-platform/src/gfx/gpu.rs b/crates/lambda-platform/src/gfx/gpu.rs index 4519fc71..e97f9aaa 100644 --- a/crates/lambda-platform/src/gfx/gpu.rs +++ b/crates/lambda-platform/src/gfx/gpu.rs @@ -1,6 +1,5 @@ use gfx_hal::{ adapter::Adapter, - device::Device, prelude::{ PhysicalDevice, QueueFamily, @@ -9,7 +8,6 @@ use gfx_hal::{ Queue, QueueGroup, }, - window::Extent2D, }; #[cfg(test)] use mockall::automock; @@ -136,16 +134,21 @@ impl Gpu { vec![super::command::internal::command_buffer_for(command_buffer)] .into_iter(); unsafe { - self.queue_group.queues[0].submit( - commands, - vec![].into_iter(), - // TODO(vmarcella): This was needed to allow the push constants to - // properly render to the screen. Look into a better way to do this. - signal_semaphores.into_iter().map(|semaphore| { - return super::fence::internal::semaphore_for(semaphore); - }), - Some(super::fence::internal::mutable_fence_for(fence)), - ); + self + .queue_group + .queues + .first_mut() + .expect("Couldn't find the primary queue to submit commands to. ") + .submit( + commands, + vec![].into_iter(), + // TODO(vmarcella): This was needed to allow the push constants to + // properly render to the screen. Look into a better way to do this. + signal_semaphores.into_iter().map(|semaphore| { + return super::fence::internal::semaphore_for(semaphore); + }), + Some(super::fence::internal::mutable_fence_for(fence)), + ); } } @@ -167,6 +170,10 @@ impl Gpu { }; if result.is_err() { + println!( + "Failed to present to the surface: {:?}", + result.err().unwrap() + ); surface.remove_swapchain(self); return Err( "Rendering failed. Swapchain for the surface needs to be reconfigured.", @@ -175,6 +182,10 @@ impl Gpu { return Ok(()); } + + pub(super) fn internal_logical_device(&self) -> &RenderBackend::Device { + return &self.gpu.device; + } } #[cfg(test)] @@ -210,7 +221,7 @@ mod tests { // --------------------------------- GPU INTERNALS ----------------------------- -pub mod internal { +pub(crate) mod internal { use super::Gpu; /// Retrieves the gfx_hal logical device for a given GPU. diff --git a/crates/lambda-platform/src/gfx/mod.rs b/crates/lambda-platform/src/gfx/mod.rs index 067e8e06..d3edeb76 100644 --- a/crates/lambda-platform/src/gfx/mod.rs +++ b/crates/lambda-platform/src/gfx/mod.rs @@ -2,6 +2,7 @@ pub mod api; pub mod assembler; +pub mod buffer; pub mod command; pub mod fence; pub mod framebuffer; diff --git a/crates/lambda-platform/src/gfx/pipeline.rs b/crates/lambda-platform/src/gfx/pipeline.rs index 0aa608f5..f01cf815 100644 --- a/crates/lambda-platform/src/gfx/pipeline.rs +++ b/crates/lambda-platform/src/gfx/pipeline.rs @@ -36,6 +36,11 @@ use std::ops::Range; use gfx_hal::device::Device; use super::{ + assembler::{ + PrimitiveAssemblerBuilder, + VertexAttribute, + }, + buffer::Buffer, gpu::Gpu, shader::ShaderModule, }; @@ -44,6 +49,8 @@ use super::{ pub struct RenderPipelineBuilder { pipeline_layout: Option, push_constants: Vec, + buffers: Vec>, + attributes: Vec, } pub type PipelineStage = gfx_hal::pso::ShaderStageFlags; @@ -55,15 +62,27 @@ impl RenderPipelineBuilder { return Self { pipeline_layout: None, push_constants: Vec::new(), + buffers: Vec::new(), + attributes: Vec::new(), }; } + pub fn with_buffer( + &mut self, + buffer: Buffer, + attributes: Vec, + ) -> &mut Self { + self.buffers.push(buffer); + self.attributes.extend(attributes); + return self; + } + /// Adds a push constant to the render pipeline at the set PipelineStage(s) pub fn with_push_constant( - mut self, + &mut self, stage: PipelineStage, bytes: u32, - ) -> Self { + ) -> &mut Self { self.push_constants.push((stage, 0..bytes)); return self; } @@ -85,29 +104,36 @@ impl RenderPipelineBuilder { gpu: &Gpu, render_pass: &super::render_pass::RenderPass, vertex_shader: &ShaderModule, - fragment_shader: &ShaderModule, + fragment_shader: Option<&ShaderModule>, + buffers: &Vec<&Buffer>, + attributes: &[VertexAttribute], ) -> RenderPipeline { // TODO(vmarcella): The pipeline layout should be configurable through the // RenderPipelineBuilder. let push_constants = self.push_constants.into_iter(); let pipeline_layout = unsafe { - use internal::Device; - - super::internal::logical_device_for(gpu) + gpu + .internal_logical_device() .create_pipeline_layout(vec![].into_iter(), push_constants) .expect( "The GPU does not have enough memory to allocate a pipeline layout", ) }; + // TODO(vmarcella): The primitive assembler should be configurable through + // the RenderPipelineBuilder so that buffers & attributes can be bound. + let mut builder = PrimitiveAssemblerBuilder::new(); let primitive_assembler = - super::assembler::PrimitiveAssemblerBuilder::new().build(vertex_shader); - - let fragment_entry = internal::EntryPoint { - entry: fragment_shader.entry(), - module: super::internal::module_for(fragment_shader), - specialization: fragment_shader.specializations().clone(), + builder.build(vertex_shader, Some(buffers), Some(attributes)); + + let fragment_entry = match fragment_shader { + Some(shader) => Some(internal::EntryPoint:: { + entry: shader.entry(), + module: super::internal::module_for(shader), + specialization: shader.specializations().clone(), + }), + None => None, }; let mut pipeline_desc = internal::GraphicsPipelineDesc::new( @@ -116,7 +142,7 @@ impl RenderPipelineBuilder { cull_face: internal::Face::BACK, ..internal::Rasterizer::FILL }, - Some(fragment_entry), + fragment_entry, &pipeline_layout, internal::Subpass { index: 0, @@ -133,14 +159,20 @@ impl RenderPipelineBuilder { }); let pipeline = unsafe { - super::internal::logical_device_for(gpu) - .create_graphics_pipeline(&pipeline_desc, None) - .expect("Failed to create graphics pipeline") + let pipeline_build_result = gpu + .internal_logical_device() + .create_graphics_pipeline(&pipeline_desc, None); + + match pipeline_build_result { + Ok(pipeline) => pipeline, + Err(e) => panic!("Failed to create graphics pipeline: {:?}", e), + } }; return RenderPipeline { pipeline_layout, pipeline, + buffers: self.buffers, }; } } @@ -150,16 +182,24 @@ impl RenderPipelineBuilder { pub struct RenderPipeline { pipeline_layout: RenderBackend::PipelineLayout, pipeline: RenderBackend::GraphicsPipeline, + buffers: Vec>, } impl RenderPipeline { /// Destroys the pipeline layout and graphical pipeline pub fn destroy(self, gpu: &super::gpu::Gpu) { + println!("Destroying render pipeline"); unsafe { - super::gpu::internal::logical_device_for(gpu) + for buffer in self.buffers { + buffer.destroy(gpu); + } + + gpu + .internal_logical_device() .destroy_pipeline_layout(self.pipeline_layout); - super::gpu::internal::logical_device_for(gpu) + gpu + .internal_logical_device() .destroy_graphics_pipeline(self.pipeline); } } diff --git a/crates/lambda-platform/src/gfx/surface.rs b/crates/lambda-platform/src/gfx/surface.rs index 8bd37170..60cbae7c 100644 --- a/crates/lambda-platform/src/gfx/surface.rs +++ b/crates/lambda-platform/src/gfx/surface.rs @@ -39,7 +39,7 @@ impl SurfaceBuilder { let gfx_hal_surface = super::internal::create_surface(instance, window); let name = match self.name { Some(name) => name, - None => "LambdaSurface".to_string(), + None => "RenderSurface".to_string(), }; return Surface { @@ -69,7 +69,7 @@ pub struct Surface { frame_buffer_attachment: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Swapchain { config: gfx_hal::window::SwapchainConfig, format: gfx_hal::format::Format, @@ -85,7 +85,7 @@ impl Surface { swapchain: Swapchain, timeout_in_nanoseconds: u64, ) -> Result<(), &'surface str> { - let device = super::gpu::internal::logical_device_for(gpu); + let device = gpu.internal_logical_device(); self.extent = Some(swapchain.config.extent); unsafe { @@ -118,6 +118,10 @@ impl Surface { } } + pub fn needs_swapchain(&self) -> bool { + return self.swapchain_is_valid; + } + /// Remove the swapchain configuration that this surface used on this given /// GPU. pub fn remove_swapchain(&mut self, gpu: &Gpu) { @@ -129,11 +133,6 @@ impl Surface { } } - /// private function to invalidate the surface swapchain. - fn invalidate_swapchain(&mut self) { - self.swapchain_is_valid = false; - } - /// Destroy the current surface and it's underlying resources. pub fn destroy(self, instance: &Instance) { println!("Destroying the surface: {}", self.name); diff --git a/crates/lambda-platform/src/lib.rs b/crates/lambda-platform/src/lib.rs index c0836402..6166b9c4 100644 --- a/crates/lambda-platform/src/lib.rs +++ b/crates/lambda-platform/src/lib.rs @@ -1,3 +1,4 @@ pub mod gfx; +pub mod rand; pub mod shaderc; pub mod winit; diff --git a/crates/lambda-platform/src/rand/mod.rs b/crates/lambda-platform/src/rand/mod.rs new file mode 100644 index 00000000..b0a5a14e --- /dev/null +++ b/crates/lambda-platform/src/rand/mod.rs @@ -0,0 +1,27 @@ +use rand::{ + distributions::Uniform, + Rng, +}; + +/// Generate a random float within any given range. +#[inline(always)] +pub fn get_random_float_between(min: f32, max: f32) -> f32 { + let mut rng = rand::thread_rng(); + return rng.gen_range(min..max); +} + +/// Generate a vector of uniformally distributed random floats within any given +/// range. +pub fn get_uniformly_random_floats_between( + min: f32, + max: f32, + count: usize, +) -> Vec { + let distribution = rand::distributions::Uniform::new(min, max); + let mut rng = rand::thread_rng(); + let mut result = Vec::with_capacity(count); + for _ in 0..count { + result.push(rng.sample(distribution)); + } + return result; +} diff --git a/lambda/examples/push_constants.rs b/lambda/examples/push_constants.rs new file mode 100644 index 00000000..cbe83baf --- /dev/null +++ b/lambda/examples/push_constants.rs @@ -0,0 +1,355 @@ +use lambda::{ + core::{ + component::Component, + events::WindowEvent, + render::{ + buffer::BufferBuilder, + command::RenderCommand, + mesh::{ + Mesh, + MeshBuilder, + }, + pipeline::RenderPipelineBuilder, + render_pass::RenderPassBuilder, + shader::{ + Shader, + ShaderBuilder, + }, + vertex::{ + VertexAttribute, + VertexBuilder, + VertexElement, + }, + viewport, + ResourceId, + }, + runtime::start_runtime, + }, + math::{ + matrix, + matrix::Matrix, + vector::Vector, + }, + runtimes::GenericRuntimeBuilder, +}; +use lambda_platform::{ + gfx::{ + pipeline::PipelineStage, + surface::ColorFormat, + }, + shaderc::{ + ShaderKind, + VirtualShader, + }, +}; + +// ------------------------------ SHADER SOURCE -------------------------------- + +const VERTEX_SHADER_SOURCE: &str = r#" +#version 450 + +layout (location = 0) in vec3 vertex_position; +layout (location = 1) in vec3 vertex_normal; +layout (location = 2) in vec3 vertex_color; + +layout (location = 0) out vec3 frag_color; + +layout ( push_constant ) uniform PushConstant { + vec4 data; + mat4 render_matrix; +} push_constants; + +void main() { + gl_Position = push_constants.render_matrix * vec4(vertex_position, 1.0); + frag_color = vertex_color; +} + +"#; + +const FRAGMENT_SHADER_SOURCE: &str = r#" +#version 450 + +layout (location = 0) in vec3 frag_color; + +layout (location = 0) out vec4 fragment_color; + +void main() { + fragment_color = vec4(frag_color, 1.0); +} + +"#; + +// ------------------------------ PUSH CONSTANTS ------------------------------- + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct PushConstant { + data: [f32; 4], + render_matrix: [[f32; 4]; 4], +} + +pub fn push_constants_to_bytes(push_constants: &PushConstant) -> &[u32] { + let bytes = unsafe { + let size_in_bytes = std::mem::size_of::(); + let size_in_u32 = size_in_bytes / std::mem::size_of::(); + let ptr = push_constants as *const PushConstant as *const u32; + std::slice::from_raw_parts(ptr, size_in_u32) + }; + + return bytes; +} + +fn make_transform( + translate: [f32; 3], + angle: f32, + scale: f32, +) -> [[f32; 4]; 4] { + let c = angle.cos() * scale; + let s = angle.sin() * scale; + + let [x, y, z] = translate; + + return [ + [c, 0.0, s, 0.0], + [0.0, scale, 0.0, 0.0], + [-s, 0.0, c, 0.0], + [x, y, z, 1.0], + ]; +} + +// --------------------------------- COMPONENT --------------------------------- + +pub struct PushConstantsExample { + frame_number: u64, + shader: Shader, + fs: Shader, + mesh: Option, + render_pipeline: Option, + render_pass: Option, + last_frame: std::time::Duration, + width: u32, + height: u32, +} + +impl Component for PushConstantsExample { + fn on_attach( + &mut self, + render_context: &mut lambda::core::render::RenderContext, + ) { + let render_pass = RenderPassBuilder::new().build(render_context); + let push_constant_size = std::mem::size_of::() as u32; + + // Create triangle mesh. + let vertices = [ + VertexBuilder::new() + .with_position([1.0, 1.0, 0.0]) + .with_normal([0.0, 0.0, 0.0]) + .with_color([1.0, 0.0, 0.0]) + .build(), + VertexBuilder::new() + .with_position([-1.0, 1.0, 0.0]) + .with_normal([0.0, 0.0, 0.0]) + .with_color([0.0, 1.0, 0.0]) + .build(), + VertexBuilder::new() + .with_position([0.0, -1.0, 0.0]) + .with_normal([0.0, 0.0, 0.0]) + .with_color([0.0, 0.0, 1.0]) + .build(), + ]; + + let mut mesh_builder = MeshBuilder::new(); + vertices.iter().for_each(|vertex| { + mesh_builder.with_vertex(vertex.clone()); + }); + + let mesh = mesh_builder + .with_attributes(vec![ + VertexAttribute { + location: 0, + offset: 0, + element: VertexElement { + format: ColorFormat::Rgb32Sfloat, + offset: 0, + }, + }, + VertexAttribute { + location: 2, + offset: 0, + element: VertexElement { + format: ColorFormat::Rgb32Sfloat, + offset: 24, + }, + }, + ]) + .build(); + + println!("mesh: {:?}", mesh); + + let pipeline = RenderPipelineBuilder::new() + .with_push_constant(PipelineStage::VERTEX, push_constant_size) + .with_buffer( + BufferBuilder::build_from_mesh(&mesh, render_context) + .expect("Failed to create buffer"), + mesh.attributes().to_vec(), + ) + .build(render_context, &render_pass, &self.shader, Some(&self.fs)); + + self.render_pass = Some(render_context.attach_render_pass(render_pass)); + self.render_pipeline = Some(render_context.attach_pipeline(pipeline)); + self.mesh = Some(mesh); + } + + fn on_detach( + &mut self, + render_context: &mut lambda::core::render::RenderContext, + ) { + println!("Detaching component"); + } + + fn on_event(&mut self, event: lambda::core::events::Events) { + // Only handle resizes. + match event { + lambda::core::events::Events::Window { event, issued_at } => { + match event { + WindowEvent::Resize { width, height } => { + self.width = width; + self.height = height; + println!("Window resized to {}x{}", width, height); + } + _ => {} + } + } + _ => {} + } + } + + /// Update the frame number every frame. + fn on_update(&mut self, last_frame: &std::time::Duration) { + self.last_frame = *last_frame; + self.frame_number += 1; + } + + fn on_render( + &mut self, + render_context: &mut lambda::core::render::RenderContext, + ) -> Vec { + self.frame_number += 1; + let camera = [0.0, 0.0, -2.0]; + let view: [[f32; 4]; 4] = matrix::translation_matrix(camera); + + // Create a projection matrix. + let projection: [[f32; 4]; 4] = + matrix::perspective_matrix(0.25, (4 / 3) as f32, 0.1, 100.0); + + // Rotate model. + let model: [[f32; 4]; 4] = matrix::rotate_matrix( + matrix::identity_matrix(4, 4), + [0.0, 1.0, 0.0], + 0.001 * self.frame_number as f32, + ); + + // Create render matrix. + let mesh_matrix = projection.multiply(&view).multiply(&model); + let mesh_matrix = + make_transform([0.0, 0.0, 0.5], self.frame_number as f32 * 0.01, 0.5); + + // Create viewport. + let viewport = + viewport::ViewportBuilder::new().build(self.width, self.height); + + let render_pipeline = self + .render_pipeline + .expect("No render pipeline actively set for rendering."); + + return vec![ + RenderCommand::SetViewports { + start_at: 0, + viewports: vec![viewport.clone()], + }, + RenderCommand::SetScissors { + start_at: 0, + viewports: vec![viewport.clone()], + }, + RenderCommand::SetPipeline { + pipeline: render_pipeline.clone(), + }, + RenderCommand::BeginRenderPass { + render_pass: self + .render_pass + .expect("Cannot begin the render pass when it doesn't exist.") + .clone(), + viewport: viewport.clone(), + }, + RenderCommand::BindVertexBuffer { + pipeline: render_pipeline.clone(), + buffer: 0, + }, + RenderCommand::PushConstants { + pipeline: render_pipeline.clone(), + stage: PipelineStage::VERTEX, + offset: 0, + bytes: Vec::from(push_constants_to_bytes(&PushConstant { + data: [0.0, 0.0, 0.0, 0.0], + render_matrix: mesh_matrix, + })), + }, + RenderCommand::Draw { + vertices: 0..self.mesh.as_ref().unwrap().vertices().len() as u32, + }, + RenderCommand::EndRenderPass, + ]; + } +} + +impl Default for PushConstantsExample { + fn default() -> Self { + let triangle_in_3d = VirtualShader::Source { + source: VERTEX_SHADER_SOURCE.to_string(), + kind: ShaderKind::Vertex, + entry_point: "main".to_string(), + name: "push_constants".to_string(), + }; + + let triangle_fragment_shader = VirtualShader::Source { + source: FRAGMENT_SHADER_SOURCE.to_string(), + kind: ShaderKind::Fragment, + entry_point: "main".to_string(), + name: "push_constants".to_string(), + }; + + let mut builder = ShaderBuilder::new(); + let shader = builder.build(triangle_in_3d); + let fs = builder.build(triangle_fragment_shader); + + return Self { + frame_number: 0, + shader, + fs, + last_frame: std::time::Duration::from_secs(0), + mesh: None, + render_pipeline: None, + render_pass: None, + width: 800, + height: 600, + }; + } +} + +fn main() { + let runtime = GenericRuntimeBuilder::new("3D Push Constants Example") + .with_window_configured_as(move |window_builder| { + return window_builder + .with_dimensions(800, 600) + .with_name("3D Push Constants Example"); + }) + .with_renderer_configured_as(|renderer_builder| { + return renderer_builder.with_render_timeout(1_000_000_000); + }) + .with_component(move |runtime, triangles: PushConstantsExample| { + return (runtime, triangles); + }) + .build(); + + start_runtime(runtime); +} diff --git a/lambda/src/core/component.rs b/lambda/src/core/component.rs index b5cc1f3b..7e384a22 100644 --- a/lambda/src/core/component.rs +++ b/lambda/src/core/component.rs @@ -16,10 +16,10 @@ pub trait Component { fn on_detach(&mut self, render_context: &mut RenderContext); fn on_event(&mut self, event: Events); - /// When the application state should perform logic updates. + /// The update function is called every frame and is used to update + /// the state of the component. fn on_update(&mut self, last_frame: &Duration); - /// When the application state should perform rendering. fn on_render( &mut self, render_context: &mut RenderContext, diff --git a/lambda/src/core/events.rs b/lambda/src/core/events.rs index ca9571d4..38b15442 100644 --- a/lambda/src/core/events.rs +++ b/lambda/src/core/events.rs @@ -41,6 +41,14 @@ pub enum KeyEvent { }, } +#[derive(Debug, Clone)] +pub enum Mouse { + MouseMoved { x: f32, y: f32, dx: f32, dy: f32 }, + MouseWheelPressed { x: f32, y: f32, button: u32 }, + MousePressed { x: f32, y: f32, button: u32 }, + MouseReleased { x: f32, y: f32, button: u32 }, +} + /// Generic Event Enum which encapsulates all possible events that will be /// emitted by the LambdaKernel #[derive(Debug, Clone)] @@ -61,4 +69,8 @@ pub enum Events { event: KeyEvent, issued_at: Instant, }, + Mouse { + event: Mouse, + issued_at: Instant, + }, } diff --git a/lambda/src/core/render/buffer.rs b/lambda/src/core/render/buffer.rs new file mode 100644 index 00000000..b228df0c --- /dev/null +++ b/lambda/src/core/render/buffer.rs @@ -0,0 +1,151 @@ +mod internal { + // Placed these in an internal module to avoid a name collision with the + // high level Buffer & BufferBuilder types in the parent module. + pub use lambda_platform::gfx::buffer::{ + Buffer, + BufferBuilder, + }; +} + +use std::rc::Rc; + +// publicly use Properties and Usage from buffer.rs +pub use lambda_platform::gfx::buffer::{ + BufferType, + Properties, + Usage, +}; + +use super::{ + mesh::Mesh, + vertex::Vertex, + RenderContext, +}; + +#[derive(Debug)] +pub struct Buffer { + buffer: Rc>, + buffer_type: BufferType, +} + +/// Public interface for a buffer. +impl Buffer { + /// Destroy the buffer and all it's resources with the render context that + /// created it. + pub fn destroy(self, render_context: &RenderContext) { + Rc::try_unwrap(self.buffer) + .expect("Failed to get inside buffer") + .destroy(render_context.internal_gpu()); + } +} + +/// Internal interface for working with buffers. +impl Buffer { + /// Retrieve a reference to the internal buffer. + pub(super) fn internal_buffer_rc( + &self, + ) -> Rc> { + return self.buffer.clone(); + } + + pub(super) fn internal_buffer( + &self, + ) -> &internal::Buffer { + return &self.buffer; + } +} + +/// A buffer is a block of memory that can be used to store data that can be +/// accessed by the GPU. The buffer is created with a length, usage, and +/// properties that determine how the buffer can be used. +pub struct BufferBuilder { + buffer_builder: internal::BufferBuilder, + buffer_type: BufferType, +} + +impl BufferBuilder { + pub fn new() -> Self { + return Self { + buffer_builder: internal::BufferBuilder::new(), + buffer_type: BufferType::Vertex, + }; + } + + pub fn build_from_mesh( + mesh: &Mesh, + render_context: &mut RenderContext, + ) -> Result { + let mut buffer_builder = Self::new(); + + // Allocate a buffer with the size of the mesh's vertices. + let internal_buffer = buffer_builder + .buffer_builder + .with_length(mesh.vertices().len() * std::mem::size_of::()) + .with_usage(Usage::VERTEX) + .with_properties(Properties::CPU_VISIBLE) + .build( + render_context.internal_mutable_gpu(), + mesh.vertices().to_vec(), + ); + + match internal_buffer { + Ok(internal_buffer) => { + return Ok(Buffer { + buffer: Rc::new(internal_buffer), + buffer_type: BufferType::Vertex, + }); + } + Err(_) => { + return Err("Failed to create buffer from mesh."); + } + } + } + + /// Sets the length of the buffer (In bytes). + pub fn with_length(&mut self, size: usize) -> &mut Self { + self.buffer_builder.with_length(size); + return self; + } + + /// Sets the type of buffer to create. + pub fn with_buffer_type(&mut self, buffer_type: BufferType) -> &mut Self { + self.buffer_type = buffer_type; + self.buffer_builder.with_buffer_type(buffer_type); + return self; + } + + /// Sets the usage of the buffer. + pub fn with_usage(&mut self, usage: Usage) -> &mut Self { + self.buffer_builder.with_usage(usage); + return self; + } + + /// Sets the properties of the buffer. + pub fn with_properties(&mut self, properties: Properties) -> &mut Self { + self.buffer_builder.with_properties(properties); + return self; + } + + /// Build a buffer utilizing the current render context + pub fn build( + &self, + render_context: &mut RenderContext, + data: Vec, + ) -> Result { + let buffer_allocation = self + .buffer_builder + .build(render_context.internal_mutable_gpu(), data); + + match buffer_allocation { + Ok(buffer) => { + return Ok(Buffer { + buffer: Rc::new(buffer), + buffer_type: self.buffer_type, + }); + } + Err(error) => { + return Err(error); + } + } + } +} diff --git a/lambda/src/core/render/command.rs b/lambda/src/core/render/command.rs index cbc3391a..39ec3b7d 100644 --- a/lambda/src/core/render/command.rs +++ b/lambda/src/core/render/command.rs @@ -1,7 +1,4 @@ -use std::{ - ops::Range, - rc::Rc, -}; +use std::ops::Range; use lambda_platform::gfx::viewport::ViewPort as PlatformViewPort; @@ -12,6 +9,7 @@ use super::{ }; /// Commands that are used to render a frame within the RenderContext. +#[derive(Debug, Clone)] pub enum RenderCommand { /// sets the viewports for the render context. SetViewports { @@ -39,6 +37,10 @@ pub enum RenderCommand { offset: u32, bytes: Vec, }, + BindVertexBuffer { + pipeline: super::ResourceId, + buffer: u32, + }, /// Draws a graphical primitive. Draw { vertices: Range, @@ -49,7 +51,7 @@ impl RenderCommand { /// Converts the RenderCommand into a platform compatible render command. // TODO(vmarcella): implement this using Into pub fn into_platform_command( - self, + &self, render_context: &mut RenderContext, ) -> PlatformRenderCommand { return match self { @@ -57,20 +59,20 @@ impl RenderCommand { start_at, viewports, } => PlatformRenderCommand::SetViewports { - start_at, + start_at: *start_at, viewports: viewports .into_iter() - .map(|viewport| viewport.into_gfx_viewport()) + .map(|viewport| viewport.clone_gfx_viewport()) .collect::>(), }, RenderCommand::SetScissors { start_at, viewports, } => PlatformRenderCommand::SetScissors { - start_at, + start_at: *start_at, viewports: viewports .into_iter() - .map(|viewport| viewport.into_gfx_viewport()) + .map(|viewport| viewport.clone_gfx_viewport()) .collect::>(), }, RenderCommand::BeginRenderPass { @@ -80,18 +82,18 @@ impl RenderCommand { let surface = surface_from_context(render_context); let frame_buffer = render_context.allocate_and_get_frame_buffer( render_context - .get_render_pass(render_pass) + .get_render_pass(*render_pass) .into_gfx_render_pass() .as_ref(), ); PlatformRenderCommand::BeginRenderPass { render_pass: render_context - .get_render_pass(render_pass) + .get_render_pass(*render_pass) .into_gfx_render_pass(), surface: surface.clone(), frame_buffer: frame_buffer.clone(), - viewport: viewport.into_gfx_viewport(), + viewport: viewport.clone_gfx_viewport(), } } RenderCommand::EndRenderPass => PlatformRenderCommand::EndRenderPass, @@ -99,7 +101,7 @@ impl RenderCommand { PlatformRenderCommand::AttachGraphicsPipeline { pipeline: render_context .render_pipelines - .get(pipeline) + .get(*pipeline) .unwrap() .into_platform_render_pipeline(), } @@ -112,16 +114,28 @@ impl RenderCommand { } => PlatformRenderCommand::PushConstants { pipeline: render_context .render_pipelines - .get(pipeline) + .get(*pipeline) .unwrap() .into_platform_render_pipeline(), - stage, - offset, - bytes, + stage: *stage, + offset: *offset, + bytes: bytes.clone(), }, - RenderCommand::Draw { vertices } => { - PlatformRenderCommand::Draw { vertices } + RenderCommand::BindVertexBuffer { pipeline, buffer } => { + PlatformRenderCommand::BindVertexBuffer { + buffer: render_context + .render_pipelines + .get(*pipeline) + .unwrap() + .buffers() + .get(*buffer as usize) + .unwrap() + .internal_buffer_rc(), + } } + RenderCommand::Draw { vertices } => PlatformRenderCommand::Draw { + vertices: vertices.clone(), + }, }; } } diff --git a/lambda/src/core/render/mesh.rs b/lambda/src/core/render/mesh.rs new file mode 100644 index 00000000..d89f3cae --- /dev/null +++ b/lambda/src/core/render/mesh.rs @@ -0,0 +1,83 @@ +use super::{ + vertex::{ + Vertex, + VertexAttribute, + }, + RenderContext, +}; + +// ---------------------------------- Mesh ------------------------------------ + +/// Collection of vertices and indices that define a 3D object. +#[derive(Debug)] +pub struct Mesh { + vertices: Vec, + attributes: Vec, +} + +impl Mesh { + pub fn vertices(&self) -> &[Vertex] { + &self.vertices + } + + pub fn attributes(&self) -> &[VertexAttribute] { + &self.attributes + } +} + +// ------------------------------ MeshBuilder --------------------------------- + +/// Construction for a mesh. +#[derive(Clone, Debug)] +pub struct MeshBuilder { + capacity: usize, + vertices: Vec, + attributes: Vec, +} + +impl MeshBuilder { + pub fn new() -> Self { + return Self { + capacity: 0, + vertices: Vec::new(), + attributes: Vec::new(), + }; + } + + pub fn with_capacity(&mut self, size: usize) -> &mut Self { + self.capacity = size; + return self; + } + + pub fn with_vertex(&mut self, vertex: Vertex) -> &mut Self { + self.vertices.push(vertex); + return self; + } + + pub fn with_attributes( + &mut self, + attributes: Vec, + ) -> &mut Self { + self.attributes = attributes; + return self; + } + + /// Builds a mesh from the vertices and indices that have been added to the + /// builder and allocates the memory for the mesh on the GPU. + pub fn build(&self) -> Mesh { + return Mesh { + vertices: self.vertices.clone(), + attributes: self.attributes.clone(), + }; + } +} + +#[cfg(test)] +mod tests { + #[test] + fn mesh_building() { + let mut mesh = super::MeshBuilder::new(); + + assert_eq!(mesh.vertices.len(), 0); + } +} diff --git a/lambda/src/core/render/mod.rs b/lambda/src/core/render/mod.rs index cc5fe470..62711bb6 100644 --- a/lambda/src/core/render/mod.rs +++ b/lambda/src/core/render/mod.rs @@ -1,10 +1,14 @@ //! High level Rendering API designed for cross platform rendering and //! windowing. +// Module Exports +pub mod buffer; pub mod command; +pub mod mesh; pub mod pipeline; pub mod render_pass; pub mod shader; +pub mod vertex; pub mod viewport; pub mod window; @@ -85,14 +89,6 @@ impl RenderContextBuilder { // Create the image extent and initial frame buffer attachment description // for rendering. let (width, height) = window.dimensions(); - let swapchain = SwapchainBuilder::new() - .with_size(width, height) - .build(&gpu, &surface); - - Rc::get_mut(&mut surface) - .expect("Failed to get mutable reference to surface.") - .apply_swapchain(&gpu, swapchain, self.render_timeout) - .expect("Failed to apply the swapchain to the surface."); return RenderContext { name, @@ -216,7 +212,9 @@ impl RenderContext { return self.frame_buffer.as_ref().unwrap().clone(); } - /// Allocates a command buffer and records commands to the GPU. + /// Allocates a command buffer and records commands to the GPU. This is the + /// primary entry point for submitting commands to the GPU and where rendering + /// will occur. pub fn render(&mut self, commands: Vec) { let (width, height) = self .surface @@ -227,10 +225,18 @@ impl RenderContext { .with_size(width, height) .build(&self.gpu, &self.surface); - Rc::get_mut(&mut self.surface) - .expect("Failed to get mutable reference to surface.") - .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) - .expect("Failed to apply the swapchain to the surface."); + if self.surface.needs_swapchain() { + Rc::get_mut(&mut self.surface) + .expect("Failed to get mutable reference to surface.") + .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) + .expect("Failed to apply the swapchain to the surface."); + } + + self + .submission_fence + .as_mut() + .expect("Failed to get the submission fence.") + .block_until_ready(&mut self.gpu, None); let platform_command_list = commands .into_iter() @@ -240,7 +246,13 @@ impl RenderContext { let mut command_buffer = CommandBufferBuilder::new(CommandBufferLevel::Primary) .with_feature(CommandBufferFeatures::ResetEverySubmission) - .build(self.command_pool.as_mut().unwrap(), "primary"); + .build( + self + .command_pool + .as_mut() + .expect("No command pool to create a buffer from"), + "primary", + ); // Start recording commands, issue the high level render commands // that came from an application, and then submit the commands to the GPU @@ -252,10 +264,7 @@ impl RenderContext { self.gpu.submit_command_buffer( &mut command_buffer, vec![self.render_semaphore.as_ref().unwrap()], - self - .submission_fence - .as_mut() - .expect("Failed to get mutable reference to submission fence."), + self.submission_fence.as_mut().unwrap(), ); self @@ -267,6 +276,8 @@ impl RenderContext { ) .expect("Failed to render to the surface"); + // Destroys the frame buffer after the commands have been submitted and the + // frame buffer is no longer needed. match self.frame_buffer { Some(_) => { Rc::try_unwrap(self.frame_buffer.take().unwrap()) @@ -281,10 +292,13 @@ impl RenderContext { 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."); + + if self.surface.needs_swapchain() { + Rc::get_mut(&mut self.surface) + .expect("Failed to get mutable reference to surface.") + .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) + .expect("Failed to apply the swapchain to the surface."); + } } pub fn get_render_pass(&self, id: ResourceId) -> &RenderPass { @@ -296,9 +310,23 @@ impl RenderContext { } } +impl RenderContext { + /// Internal access to the RenderContext's GPU. + pub(super) fn internal_gpu(&self) -> &internal::Gpu { + return &self.gpu; + } + + /// Internal mutable access to the RenderContext's GPU. + pub(super) fn internal_mutable_gpu( + &mut self, + ) -> &mut internal::Gpu { + return &mut self.gpu; + } +} + type PlatformRenderCommand = Command; -pub mod internal { +pub(crate) mod internal { use std::rc::Rc; use lambda_platform::gfx::api::RenderingAPI as RenderContext; @@ -346,13 +374,6 @@ pub mod internal { 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, diff --git a/lambda/src/core/render/pipeline.rs b/lambda/src/core/render/pipeline.rs index f16507c3..b9955d12 100644 --- a/lambda/src/core/render/pipeline.rs +++ b/lambda/src/core/render/pipeline.rs @@ -1,17 +1,23 @@ -use std::rc::Rc; +use std::{ + borrow::Borrow, + ops::Deref, + rc::Rc, +}; -use lambda_platform::gfx::shader::{ - ShaderModuleBuilder, - ShaderModuleType, +use lambda_platform::gfx::{ + buffer::Buffer as InternalBuffer, + shader::{ + ShaderModuleBuilder, + ShaderModuleType, + }, }; use super::{ + buffer::Buffer, internal::{ gpu_from_context, - mut_gpu_from_context, RenderBackend, }, - render_pass::internal::platform_render_pass_from_render_pass, shader::Shader, RenderContext, }; @@ -23,6 +29,7 @@ pub struct RenderPipeline { super::internal::RenderBackend, >, >, + buffers: Vec>, } impl RenderPipeline { @@ -30,30 +37,60 @@ impl RenderPipeline { pub fn destroy(self, render_context: &RenderContext) { Rc::try_unwrap(self.pipeline) .expect("Failed to destroy render pipeline") - .destroy(gpu_from_context(render_context)); + .destroy(render_context.internal_gpu()); + + for buffer in self.buffers { + Rc::try_unwrap(buffer) + .expect("Failed to get high level buffer.") + .destroy(render_context); + } + } +} + +impl RenderPipeline { + pub(super) fn buffers(&self) -> &Vec> { + return &self.buffers; } - pub fn into_platform_render_pipeline( + pub(super) fn into_platform_render_pipeline( &self, ) -> Rc> { return self.pipeline.clone(); } } -pub use lambda_platform::gfx::pipeline::PipelineStage; use lambda_platform::gfx::pipeline::PushConstantUpload; +pub use lambda_platform::gfx::{ + assembler::VertexAttribute, + pipeline::PipelineStage, +}; pub struct RenderPipelineBuilder { push_constants: Vec, + buffers: Vec>, + attributes: Vec, } impl RenderPipelineBuilder { pub fn new() -> Self { return Self { push_constants: Vec::new(), + buffers: Vec::new(), + attributes: Vec::new(), }; } + /// Adds a buffer to the render pipeline. + pub fn with_buffer( + mut self, + buffer: Buffer, + attributes: Vec, + ) -> Self { + self.buffers.push(Rc::new(buffer)); + self.attributes.extend(attributes); + return self; + } + pub fn with_push_constant( mut self, stage: PipelineStage, @@ -69,35 +106,65 @@ impl RenderPipelineBuilder { render_context: &mut RenderContext, render_pass: &super::render_pass::RenderPass, vertex_shader: &Shader, - fragment_shader: &Shader, + fragment_shader: Option<&Shader>, ) -> RenderPipeline { + println!("[DEBUG] Building render pipeline..."); + + print!("[DEBUG] Building vertex shader... "); let vertex_shader_module = ShaderModuleBuilder::new().build( - mut_gpu_from_context(render_context), + render_context.internal_mutable_gpu(), &vertex_shader.as_binary(), ShaderModuleType::Vertex, ); - let fragment_shader_module = ShaderModuleBuilder::new().build( - mut_gpu_from_context(render_context), - &fragment_shader.as_binary(), - ShaderModuleType::Fragment, + println!( + " Done. (Vertex shader: {} bytes)", + vertex_shader.as_binary().len() + ); + + print!("[DEBUG] Building fragment shader... "); + let fragment_shader_module = match fragment_shader { + Some(shader) => Some(ShaderModuleBuilder::new().build( + render_context.internal_mutable_gpu(), + &shader.as_binary(), + ShaderModuleType::Fragment, + )), + None => None, + }; + + println!( + " Done. (Fragment shader: {} bytes)", + fragment_shader.map(|s| s.as_binary().len()).unwrap_or(0) ); - let render_pipeline = - lambda_platform::gfx::pipeline::RenderPipelineBuilder::new() - .with_push_constants(self.push_constants) - .build( - gpu_from_context(render_context), - &platform_render_pass_from_render_pass(render_pass), - &vertex_shader_module, - &fragment_shader_module, - ); + let builder = lambda_platform::gfx::pipeline::RenderPipelineBuilder::new(); + + let buffers = self.buffers; + let internal_buffers = buffers + .iter() + .map(|b| b.internal_buffer()) + .collect::>(); + + let render_pipeline = builder + .with_push_constants(self.push_constants.clone()) + .build( + gpu_from_context(render_context), + render_pass.internal_render_pass(), + &vertex_shader_module, + fragment_shader_module.as_ref(), + &internal_buffers, + self.attributes.as_slice(), + ); - vertex_shader_module.destroy(mut_gpu_from_context(render_context)); - fragment_shader_module.destroy(mut_gpu_from_context(render_context)); + // Clean up shader modules. + vertex_shader_module.destroy(render_context.internal_mutable_gpu()); + if let Some(fragment_shader_module) = fragment_shader_module { + fragment_shader_module.destroy(render_context.internal_mutable_gpu()); + } return RenderPipeline { pipeline: Rc::new(render_pipeline), + buffers, }; } } diff --git a/lambda/src/core/render/render_pass.rs b/lambda/src/core/render/render_pass.rs index 733da84d..2f7558a6 100644 --- a/lambda/src/core/render/render_pass.rs +++ b/lambda/src/core/render/render_pass.rs @@ -20,8 +20,15 @@ impl RenderPass { .destroy(gpu_from_context(render_context)); } + /// Retrieve a reference to the lower level render pass. + pub(super) fn internal_render_pass( + &self, + ) -> &Rc> { + return &self.render_pass; + } + /// Converts - pub fn into_gfx_render_pass( + pub(super) fn into_gfx_render_pass( &self, ) -> Rc> { return self.render_pass.clone(); @@ -45,14 +52,3 @@ impl RenderPassBuilder { }; } } - -pub mod internal { - use crate::core::render::internal::RenderBackend; - - /// Converts a render pass into a platform render pass. - pub fn platform_render_pass_from_render_pass( - render_pass: &super::RenderPass, - ) -> &lambda_platform::gfx::render_pass::RenderPass { - return &render_pass.render_pass; - } -} diff --git a/lambda/src/core/render/shader.rs b/lambda/src/core/render/shader.rs index c4eeaa13..52e733ea 100644 --- a/lambda/src/core/render/shader.rs +++ b/lambda/src/core/render/shader.rs @@ -6,7 +6,18 @@ pub use lambda_platform::shaderc::{ VirtualShader, }; -/// Reusable shader builder that utilizes a lower level platform +#[macro_export] +macro_rules! vertex_shader { + ($source:ident) => { + VirtualShader::Source { + source: String::from($stringify!($source)), + kind: ShaderKind::Vertex, + name: String::from("vertex-shader"), + entry_point: String::from("main"), + } + }; +} + pub struct ShaderBuilder { compiler: ShaderCompiler, } diff --git a/lambda/src/core/render/vertex.rs b/lambda/src/core/render/vertex.rs new file mode 100644 index 00000000..c3f6778d --- /dev/null +++ b/lambda/src/core/render/vertex.rs @@ -0,0 +1,77 @@ +pub use lambda_platform::gfx::assembler::{ + VertexAttribute, + VertexElement, +}; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct Vertex { + pub position: [f32; 3], + pub normal: [f32; 3], + pub color: [f32; 3], +} + +/// Construction for +#[derive(Clone, Copy, Debug)] +pub struct VertexBuilder { + pub position: [f32; 3], + pub normal: [f32; 3], + pub color: [f32; 3], +} + +impl VertexBuilder { + pub fn new() -> Self { + return Self { + position: [0.0, 0.0, 0.0], + normal: [0.0, 0.0, 0.0], + color: [0.0, 0.0, 0.0], + }; + } + + /// Set the position of the vertex. + pub fn with_position(&mut self, position: [f32; 3]) -> &mut Self { + self.position = position; + return self; + } + + /// Set the normal of the vertex. + pub fn with_normal(&mut self, normal: [f32; 3]) -> &mut Self { + self.normal = normal; + return self; + } + + pub fn with_color(&mut self, color: [f32; 3]) -> &mut Self { + self.color = color; + return self; + } + + pub fn build(&self) -> Vertex { + return Vertex { + position: self.position, + normal: self.normal, + color: self.color, + }; + } +} + +#[cfg(test)] +mod test { + #[test] + fn vertex_building() { + let mut vertex = super::VertexBuilder::new(); + + assert_eq!(vertex.position, [0.0, 0.0, 0.0]); + assert_eq!(vertex.normal, [0.0, 0.0, 0.0]); + assert_eq!(vertex.color, [0.0, 0.0, 0.0]); + + let vertex = vertex + .with_position([1.0, 2.0, 3.0]) + .with_normal([4.0, 5.0, 6.0]) + .with_color([7.0, 8.0, 9.0]) + .build(); + + assert_eq!(vertex.position, [1.0, 2.0, 3.0]); + assert_eq!(vertex.normal, [4.0, 5.0, 6.0]); + assert_eq!(vertex.color, [7.0, 8.0, 9.0]); + } +} diff --git a/lambda/src/core/render/viewport.rs b/lambda/src/core/render/viewport.rs index 053f4e44..22156937 100644 --- a/lambda/src/core/render/viewport.rs +++ b/lambda/src/core/render/viewport.rs @@ -8,8 +8,8 @@ pub struct Viewport { impl Viewport { /// Convert the viewport into a gfx platform viewport. // TODO(vmarcella): implement this using Into - pub fn into_gfx_viewport(self) -> gfx::viewport::ViewPort { - return self.viewport; + pub(crate) fn clone_gfx_viewport(&self) -> gfx::viewport::ViewPort { + return self.viewport.clone(); } } diff --git a/lambda/src/core/render/window.rs b/lambda/src/core/render/window.rs index c3f30b1d..ea590a15 100644 --- a/lambda/src/core/render/window.rs +++ b/lambda/src/core/render/window.rs @@ -10,6 +10,7 @@ use crate::core::events::Events; pub struct WindowBuilder { name: String, dimensions: (u32, u32), + vsync: bool, } impl WindowBuilder { @@ -21,6 +22,7 @@ impl WindowBuilder { return Self { name: String::from("Window"), dimensions: (480, 360), + vsync: false, }; } @@ -36,6 +38,10 @@ impl WindowBuilder { return self; } + pub fn with_vsync(mut self, vsync: bool) -> Self { + return self; + } + // TODO(vmarcella): Remove new call for window and construct the window directly. pub fn build(self, event_loop: &mut Loop) -> Window { return Window::new(self.name.as_str(), self.dimensions, event_loop); diff --git a/lambda/src/lib.rs b/lambda/src/lib.rs index 2a51855e..f622d8f1 100644 --- a/lambda/src/lib.rs +++ b/lambda/src/lib.rs @@ -1,3 +1,4 @@ pub mod components; pub mod core; +pub mod math; pub mod runtimes; diff --git a/lambda/src/math/matrix.rs b/lambda/src/math/matrix.rs new file mode 100644 index 00000000..417795ec --- /dev/null +++ b/lambda/src/math/matrix.rs @@ -0,0 +1,536 @@ +//! Matrix math types and functions. + +use lambda_platform::rand::get_uniformly_random_floats_between; + +use super::{ + turns_to_radians, + vector::Vector, +}; + +// -------------------------------- MATRIX ------------------------------------- + +pub trait Matrix { + fn add(&self, other: &Self) -> Self; + fn subtract(&self, other: &Self) -> Self; + fn multiply(&self, other: &Self) -> Self; + fn transpose(&self) -> Self; + fn inverse(&self) -> Self; + fn transform(&self, other: &V) -> V; + fn determinant(&self) -> f32; + fn size(&self) -> (usize, usize); + fn row(&self, row: usize) -> &V; + fn at(&self, row: usize, column: usize) -> V::Scalar; + fn update(&mut self, row: usize, column: usize, value: V::Scalar); +} + +// -------------------------------- FUNCTIONS ---------------------------------- + +/// Obtain the submatrix of the input matrix starting from the given row & +/// column. +pub fn submatrix, MatrixLike: Matrix>( + matrix: MatrixLike, + row: usize, + column: usize, +) -> Vec> { + let mut submatrix = Vec::new(); + let (rows, columns) = matrix.size(); + + for k in 0..rows { + if k != row { + let mut row = Vec::new(); + for l in 0..columns { + if l != column { + row.push(matrix.at(k, l)); + } + } + submatrix.push(row); + } + } + return submatrix; +} + +/// Creates a translation matrix with the given translation vector. The output vector +pub fn translation_matrix< + InputVector: Vector, + ResultingVector: Vector, + OutputMatrix: Matrix + Default, +>( + vector: InputVector, +) -> OutputMatrix { + let mut result = OutputMatrix::default(); + let (rows, columns) = result.size(); + assert_eq!( + rows - 1, + vector.size(), + "Vector must contain one less element than the vectors of the input matrix" + ); + + for i in 0..rows { + for j in 0..columns { + if i == j { + result.update(i, j, 1.0); + } else if j == columns - 1 { + result.update(i, j, vector.at(i)); + } else { + result.update(i, j, 0.0); + } + } + } + + return result; +} + +/// Rotates the input matrix by the given number of turns around the given axis. +/// The axis must be a unit vector and the turns must be in the range [0, 1). +/// The rotation is counter-clockwise when looking down the axis. +pub fn rotate_matrix< + InputVector: Vector, + ResultingVector: Vector, + OutputMatrix: Matrix + Default + Clone, +>( + matrix_to_rotate: OutputMatrix, + axis_to_rotate: InputVector, + angle_in_turns: f32, +) -> OutputMatrix { + let (rows, columns) = matrix_to_rotate.size(); + assert_eq!(rows, columns, "Matrix must be square"); + assert_eq!(rows, 4, "Matrix must be 4x4"); + assert_eq!( + axis_to_rotate.size(), + 3, + "Axis vector must have 3 elements (x, y, z)" + ); + + let angle_in_radians = turns_to_radians(angle_in_turns); + let cosine_of_angle = angle_in_radians.cos(); + let sin_of_angle = angle_in_radians.sin(); + + let t = 1.0 - cosine_of_angle; + let x = axis_to_rotate.at(0); + let y = axis_to_rotate.at(1); + let z = axis_to_rotate.at(2); + + let mut rotation_matrix = OutputMatrix::default(); + + let rotation = match (x as u8, y as u8, z as u8) { + (0, 0, 0) => { + // No rotation + return matrix_to_rotate; + } + (0, 0, 1) => { + // Rotate around z-axis + [ + [cosine_of_angle, sin_of_angle, 0.0, 0.0], + [-sin_of_angle, cosine_of_angle, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + } + (0, 1, 0) => { + // Rotate around y-axis + [ + [cosine_of_angle, 0.0, -sin_of_angle, 0.0], + [0.0, 1.0, 0.0, 0.0], + [sin_of_angle, 0.0, cosine_of_angle, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + } + (1, 0, 0) => { + // Rotate around x-axis + [ + [1.0, 0.0, 0.0, 0.0], + [0.0, cosine_of_angle, sin_of_angle, 0.0], + [0.0, -sin_of_angle, cosine_of_angle, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + } + _ => { + panic!("Axis must be a unit vector") + } + }; + + for i in 0..rows { + for j in 0..columns { + rotation_matrix.update(i, j, rotation[i][j]); + } + } + + return matrix_to_rotate.multiply(&rotation_matrix); +} + +/// Creates a 4x4 perspective matrix given the fov in turns (unit between +/// 0..2pi radians), aspect ratio, near clipping plane (also known as z_near), +/// and far clipping plane (also known as z_far). Enforces that the matrix being +/// created is square in both debug and release builds, but only enforces that +/// the output matrix is 4x4 in debug builds. +pub fn perspective_matrix< + V: Vector, + MatrixLike: Matrix + Default, +>( + fov: V::Scalar, + aspect_ratio: V::Scalar, + near_clipping_plane: V::Scalar, + far_clipping_plane: V::Scalar, +) -> MatrixLike { + let mut result = MatrixLike::default(); + let (rows, columns) = result.size(); + assert_eq!( + rows, columns, + "Matrix must be square to be a perspective matrix" + ); + debug_assert_eq!(rows, 4, "Matrix must be 4x4 to be a perspective matrix"); + let fov_in_radians = turns_to_radians(fov); + let f = 1.0 / (fov_in_radians / 2.0).tan(); + let range = near_clipping_plane - far_clipping_plane; + + result.update(0, 0, f / aspect_ratio); + result.update(1, 1, f); + result.update(2, 2, (near_clipping_plane + far_clipping_plane) / range); + result.update(2, 3, -1.0); + result.update( + 3, + 2, + (2.0 * near_clipping_plane * far_clipping_plane) / range, + ); + + return result; +} + +/// Create a matrix of any size that is filled with zeros. +pub fn zeroed_matrix< + V: Vector, + MatrixLike: Matrix + Default, +>( + rows: usize, + columns: usize, +) -> MatrixLike { + let mut result = MatrixLike::default(); + for i in 0..rows { + for j in 0..columns { + result.update(i, j, 0.0); + } + } + return result; +} + +/// Creates a new matrix with the given number of rows and columns, and fills it +/// with the given value. +pub fn filled_matrix< + V: Vector, + MatrixLike: Matrix + Default, +>( + rows: usize, + columns: usize, + value: V::Scalar, +) -> MatrixLike { + let mut result = MatrixLike::default(); + for i in 0..rows { + for j in 0..columns { + result.update(i, j, value); + } + } + return result; +} + +/// Creates an identity matrix of the given size. +pub fn identity_matrix< + V: Vector, + MatrixLike: Matrix + Default, +>( + rows: usize, + columns: usize, +) -> MatrixLike { + assert_eq!( + rows, columns, + "Matrix must be square to be an identity matrix" + ); + let mut result = MatrixLike::default(); + for i in 0..rows { + for j in 0..columns { + if i == j { + result.update(i, j, 1.0); + } else { + result.update(i, j, 0.0); + } + } + } + return result; +} + +// -------------------------- ARRAY IMPLEMENTATION ----------------------------- + +/// Matrix implementations for arrays of f32 arrays. Including the trait Matrix into +/// your code will allow you to use these function implementation for any array +/// of f32 arrays. +impl Matrix for Array +where + Array: AsMut<[V]> + AsRef<[V]> + Default, + V: AsMut<[f32]> + AsRef<[f32]> + Vector + Sized, +{ + fn add(&self, other: &Self) -> Self { + let mut result = Self::default(); + for (i, (a, b)) in + self.as_ref().iter().zip(other.as_ref().iter()).enumerate() + { + result.as_mut()[i] = a.add(b); + } + return result; + } + + fn subtract(&self, other: &Self) -> Self { + let mut result = Self::default(); + + for (i, (a, b)) in + self.as_ref().iter().zip(other.as_ref().iter()).enumerate() + { + result.as_mut()[i] = a.subtract(b); + } + return result; + } + + fn multiply(&self, other: &Self) -> Self { + let mut result = Self::default(); + + // We transpose the other matrix to convert the columns into rows, allowing + // us to compute the new values of each index using the dot product + // function. + let transposed = other.transpose(); + + for (i, a) in self.as_ref().iter().enumerate() { + for (j, b) in transposed.as_ref().iter().enumerate() { + result.update(i, j, a.dot(b)); + } + } + return result; + } + + /// Transposes the matrix, swapping the rows and columns. + fn transpose(&self) -> Self { + let mut result = Self::default(); + for (i, a) in self.as_ref().iter().enumerate() { + for j in 0..a.as_ref().len() { + result.update(i, j, self.at(j, i)); + } + } + return result; + } + + fn inverse(&self) -> Self { + todo!() + } + + fn transform(&self, other: &V) -> V { + todo!() + } + + /// Computes the determinant of any square matrix using Laplace expansion. + fn determinant(&self) -> f32 { + let (width, height) = + (self.as_ref()[0].as_ref().len(), self.as_ref().len()); + + if width != height { + panic!("Cannot compute determinant of non-square matrix"); + } + + return match height { + 1 => self.as_ref()[0].as_ref()[0], + 2 => { + let a = self.at(0, 0); + let b = self.at(0, 1); + let c = self.at(1, 0); + let d = self.at(1, 1); + a * d - b * c + } + _ => { + let mut result = 0.0; + for i in 0..height { + let mut submatrix: Vec> = Vec::with_capacity(height - 1); + for j in 1..height { + let mut row = Vec::new(); + for k in 0..height { + if k != i { + row.push(self.at(j, k)); + } + } + submatrix.push(row); + } + result += self.at(0, i) + * submatrix.determinant() + * (-1.0 as f32).powi(i as i32); + } + result + } + }; + } + + /// Return the size as a (rows, columns). + fn size(&self) -> (usize, usize) { + return (self.as_ref().len(), self.row(0).size()); + } + + /// Return a reference to the row. + fn row(&self, row: usize) -> &V { + return &self.as_ref()[row]; + } + + /// + fn at(&self, row: usize, column: usize) -> ::Scalar { + return self.as_ref()[row].as_ref()[column]; + } + + fn update(&mut self, row: usize, column: usize, new_value: V::Scalar) { + self.as_mut()[row].as_mut()[column] = new_value; + } +} + +// ---------------------------------- TESTS ------------------------------------ + +#[cfg(test)] +mod tests { + + use super::{ + filled_matrix, + perspective_matrix, + rotate_matrix, + submatrix, + Matrix, + }; + use crate::math::{ + matrix::translation_matrix, + turns_to_radians, + }; + + #[test] + fn square_matrix_add() { + let a = [[1.0, 2.0], [3.0, 4.0]]; + let b = [[5.0, 6.0], [7.0, 8.0]]; + let c = a.add(&b); + assert_eq!(c, [[6.0, 8.0], [10.0, 12.0]]); + } + + #[test] + fn square_matrix_subtract() { + let a = [[1.0, 2.0], [3.0, 4.0]]; + let b = [[5.0, 6.0], [7.0, 8.0]]; + let c = a.subtract(&b); + assert_eq!(c, [[-4.0, -4.0], [-4.0, -4.0]]); + } + + #[test] + // Test square matrix multiplication. + fn square_matrix_multiply() { + let m1 = [[1.0, 2.0], [3.0, 4.0]]; + let m2 = [[2.0, 0.0], [1.0, 2.0]]; + + let mut result = m1.multiply(&m2); + assert_eq!(result, [[4.0, 4.0], [10.0, 8.0]]); + + result = m2.multiply(&m1); + assert_eq!(result, [[2.0, 4.0], [7.0, 10.0]]) + } + + #[test] + fn transpose_square_matrix() { + let m = [[1.0, 2.0], [5.0, 6.0]]; + let t = m.transpose(); + assert_eq!(t, [[1.0, 5.0], [2.0, 6.0]]); + } + + #[test] + fn square_matrix_determinant() { + let m = [[3.0, 8.0], [4.0, 6.0]]; + assert_eq!(m.determinant(), -14.0); + + let m2 = [[6.0, 1.0, 1.0], [4.0, -2.0, 5.0], [2.0, 8.0, 7.0]]; + assert_eq!(m2.determinant(), -306.0); + } + + #[test] + fn non_square_matrix_determinant() { + let m = [[3.0, 8.0], [4.0, 6.0], [0.0, 1.0]]; + let result = std::panic::catch_unwind(|| m.determinant()); + assert_eq!(false, result.is_ok()); + } + + #[test] + fn submatrix_for_matrix_array() { + let matrix = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]; + + let expected_submatrix = vec![vec![2.0, 3.0], vec![8.0, 9.0]]; + let actual_submatrix = submatrix(matrix, 1, 0); + + assert_eq!(expected_submatrix, actual_submatrix); + } + + #[test] + fn translate_matrix() { + let translation: [[f32; 3]; 3] = translation_matrix([56.0, 5.0]); + assert_eq!( + translation, + [[1.0, 0.0, 56.0], [0.0, 1.0, 5.0], [0.0, 0.0, 1.0]] + ); + + let translation: [[f32; 4]; 4] = translation_matrix([10.0, 2.0, 3.0]); + let expected = [ + [1.0, 0.0, 0.0, 10.0], + [0.0, 1.0, 0.0, 2.0], + [0.0, 0.0, 1.0, 3.0], + [0.0, 0.0, 0.0, 1.0], + ]; + assert_eq!(translation, expected); + } + + #[test] + fn perspective_matrix_test() { + let perspective: [[f32; 4]; 4] = + perspective_matrix(1.0 / 4.0, 1.0, 1.0, 0.0); + + // Compute the field of view values used by the perspective matrix by hand. + let fov_radians = turns_to_radians(1.0 / 4.0); + let f = 1.0 / (fov_radians / 2.0).tan(); + + let expected: [[f32; 4]; 4] = [ + [f, 0.0, 0.0, 0.0], + [0.0, f, 0.0, 0.0], + [0.0, 0.0, 1.0, -1.0], + [0.0, 0.0, 0.0, 0.0], + ]; + + assert_eq!(perspective, expected); + } + + /// Test the rotation matrix for a 3D rotation. + #[test] + fn rotate_matrices() { + // Test a zero turn rotation. + let matrix: [[f32; 4]; 4] = filled_matrix(4, 4, 1.0); + let rotated_matrix = rotate_matrix(matrix, [0.0, 0.0, 1.0], 0.0); + assert_eq!(rotated_matrix, matrix); + + // Test a 90 degree rotation. + let matrix = [ + [1.0, 2.0, 3.0, 4.0], + [5.0, 6.0, 7.0, 8.0], + [9.0, 10.0, 11.0, 12.0], + [13.0, 14.0, 15.0, 16.0], + ]; + let rotated = rotate_matrix(matrix, [0.0, 1.0, 0.0], 0.25); + let expected = [ + [3.0, 1.9999999, -1.0000001, 4.0], + [7.0, 5.9999995, -5.0000005, 8.0], + [11.0, 9.999999, -9.000001, 12.0], + [14.999999, 13.999999, -13.000001, 16.0], + ]; + + println!("rotated: {:?}", rotated); + for i in 0..4 { + for j in 0..4 { + crate::assert_approximately_equal!( + rotated.at(i, j), + expected.at(i, j), + 0.1 + ); + } + } + } +} diff --git a/lambda/src/math/mod.rs b/lambda/src/math/mod.rs index b2550100..cfbc1380 100644 --- a/lambda/src/math/mod.rs +++ b/lambda/src/math/mod.rs @@ -1 +1,30 @@ -type Vec2 = (f32, f32); +//! Lambda Math Types and operations + +pub mod matrix; +pub mod vector; + +pub enum Angle { + Radians(f32), + Degrees(f32), + Turns(f32), +} + +/// Convert a turn into radians. +fn turns_to_radians(turns: f32) -> f32 { + return turns * std::f32::consts::PI * 2.0; +} + +#[macro_export] +/// Assert that two values are equal, with a given tolerance. +macro_rules! assert_approximately_equal { + ($a:expr, $b:expr, $eps:expr) => {{ + let (a, b, eps) = ($a, $b, $eps); + assert!( + (a - b).abs() < eps, + "{} is not approximately equal to {} with an epsilon of {}", + a, + b, + eps + ); + }}; +} diff --git a/lambda/src/math/vector.rs b/lambda/src/math/vector.rs new file mode 100644 index 00000000..d4993a2d --- /dev/null +++ b/lambda/src/math/vector.rs @@ -0,0 +1,239 @@ +//! Vector math types and functions. + +/// Generalized Vector operations that can be implemented by any vector like +/// type. +pub trait Vector { + type Scalar: Copy; + fn add(&self, other: &Self) -> Self; + fn subtract(&self, other: &Self) -> Self; + fn scale(&self, scalar: Self::Scalar) -> Self; + fn dot(&self, other: &Self) -> Self::Scalar; + fn cross(&self, other: &Self) -> Self; + fn length(&self) -> Self::Scalar; + fn normalize(&self) -> Self; + fn size(&self) -> usize; + fn at(&self, index: usize) -> Self::Scalar; + fn update(&mut self, index: usize, value: Self::Scalar); +} + +impl Vector for T +where + T: AsMut<[f32]> + AsRef<[f32]> + Default, +{ + type Scalar = f32; + + /// Add two vectors of any size together. + fn add(&self, other: &Self) -> Self { + let mut result = Self::default(); + + self + .as_ref() + .iter() + .zip(other.as_ref().iter()) + .enumerate() + .for_each(|(i, (a, b))| result.as_mut()[i] = a + b); + + return result; + } + + /// Subtract two vectors of any size. + fn subtract(&self, other: &Self) -> Self { + let mut result = Self::default(); + + self + .as_ref() + .iter() + .zip(other.as_ref().iter()) + .enumerate() + .for_each(|(i, (a, b))| result.as_mut()[i] = a - b); + + return result; + } + + fn dot(&self, other: &Self) -> Self::Scalar { + assert_eq!( + self.as_ref().len(), + other.as_ref().len(), + "Vectors must be the same length" + ); + + let mut result = 0.0; + for (a, b) in self.as_ref().iter().zip(other.as_ref().iter()) { + result += a * b; + } + return result; + } + + /// Cross product of two 3D vectors. Panics if the vectors are not 3D. + fn cross(&self, other: &Self) -> Self { + assert_eq!( + self.as_ref().len(), + other.as_ref().len(), + "Vectors must be the same length" + ); + + let mut result = Self::default(); + let a = self.as_ref(); + let b = other.as_ref(); + + // TODO: This is only for 3D vectors + match a.len() { + 3 => { + result.as_mut()[0] = a[1] * b[2] - a[2] * b[1]; + result.as_mut()[1] = a[2] * b[0] - a[0] * b[2]; + result.as_mut()[2] = a[0] * b[1] - a[1] * b[0]; + } + _ => { + panic!("Cross product is only defined for 3 dimensional vectors.") + } + } + return result; + } + + fn length(&self) -> Self::Scalar { + let mut result = 0.0; + for a in self.as_ref().iter() { + result += a * a; + } + result.sqrt() + } + + fn normalize(&self) -> Self { + assert_ne!(self.length(), 0.0, "Cannot normalize a zero length vector"); + let mut result = Self::default(); + let length = self.length(); + + self.as_ref().iter().enumerate().for_each(|(i, a)| { + result.as_mut()[i] = a / length; + }); + + return result; + } + + fn scale(&self, scalar: Self::Scalar) -> Self { + let mut result = Self::default(); + self.as_ref().iter().enumerate().for_each(|(i, a)| { + result.as_mut()[i] = a * scalar; + }); + + return result; + } + + fn size(&self) -> usize { + return self.as_ref().len(); + } + + fn at(&self, index: usize) -> Self::Scalar { + return self.as_ref()[index]; + } + + fn update(&mut self, index: usize, value: Self::Scalar) { + self.as_mut()[index] = value; + } +} + +#[cfg(test)] +mod tests { + use super::Vector; + + #[test] + fn adding_vectors() { + let a = [1.0, 2.0, 3.0]; + let b = [4.0, 5.0, 6.0]; + let c = [5.0, 7.0, 9.0]; + + let result = a.add(&b); + + assert_eq!(result, c); + } + + #[test] + fn subtracting_vectors() { + let a = [1.0, 2.0, 3.0]; + let b = [4.0, 5.0, 6.0]; + let c = [-3.0, -3.0, -3.0]; + + let result = a.subtract(&b); + + assert_eq!(result, c); + } + + #[test] + fn scaling_vectors() { + let a = [1.0, 2.0, 3.0]; + let b = [2.0, 4.0, 6.0]; + let scalar = 2.0; + + let result = a.scale(scalar); + assert_eq!(result, b); + } + + #[test] + fn dot_product() { + let a = [1.0, 2.0, 3.0]; + let b = [4.0, 5.0, 6.0]; + let c = 32.0; + + let result = a.dot(&b); + assert_eq!(result, c); + } + + #[test] + fn cross_product() { + let a = [1.0, 2.0, 3.0]; + let b = [4.0, 5.0, 6.0]; + let c = [-3.0, 6.0, -3.0]; + + let result = a.cross(&b); + assert_eq!(result, c); + } + + #[test] + fn cross_product_fails_for_non_3d_vectors() { + let a = [1.0, 2.0]; + let b = [4.0, 5.0]; + + let result = std::panic::catch_unwind(|| a.cross(&b)); + assert!(result.is_err()); + } + + #[test] + fn length() { + let a = [1.0, 2.0, 3.0]; + let b = 3.7416573867739413; + + let result = a.length(); + assert_eq!(result, b); + + let c = [1.0, 2.0, 3.0, 4.0]; + let d = 5.477225575051661; + let result = c.length(); + assert_eq!(result, d); + } + + #[test] + fn normalize() { + let a = [4.0, 3.0, 2.0]; + let b = [0.74278135, 0.55708605, 0.37139067]; + let result = a.normalize(); + assert_eq!(result, b); + } + + #[test] + fn normalize_fails_for_zero_length_vector() { + let a = [0.0, 0.0, 0.0]; + + let result = std::panic::catch_unwind(|| a.normalize()); + assert!(result.is_err()); + } + + #[test] + fn scale() { + let a = [1.0, 2.0, 3.0]; + let b = [2.0, 4.0, 6.0]; + let scalar = 2.0; + + let result = a.scale(scalar); + assert_eq!(result, b); + } +} diff --git a/lambda/src/runtimes/mod.rs b/lambda/src/runtimes/mod.rs index 0c0acceb..af03473c 100644 --- a/lambda/src/runtimes/mod.rs +++ b/lambda/src/runtimes/mod.rs @@ -148,16 +148,15 @@ impl Runtime for GenericRuntime { let mut current_frame = Instant::now(); event_loop.run_forever(move |event, _, control_flow| { - match event { + let mapped_event: Option = match event { WinitEvent::WindowEvent { event, .. } => match event { WinitWindowEvent::CloseRequested => { // Issue a Shutdown event to deallocate resources and clean up. - publisher.publish_event(Events::Runtime { + *control_flow = ControlFlow::Exit; + Some(Events::Runtime { event: RuntimeEvent::Shutdown, issued_at: Instant::now(), - }); - - *control_flow = ControlFlow::Exit; + }) } WinitWindowEvent::Resized(dims) => { active_render_context @@ -165,7 +164,7 @@ impl Runtime for GenericRuntime { .unwrap() .resize(dims.width, dims.height); - publisher.publish_event(Events::Window { + Some(Events::Window { event: WindowEvent::Resize { width: dims.width, height: dims.height, @@ -179,7 +178,7 @@ impl Runtime for GenericRuntime { .unwrap() .resize(new_inner_size.width, new_inner_size.height); - publisher.publish_event(Events::Window { + Some(Events::Window { event: WindowEvent::Resize { width: new_inner_size.width, height: new_inner_size.height, @@ -187,20 +186,20 @@ impl Runtime for GenericRuntime { issued_at: Instant::now(), }) } - WinitWindowEvent::Moved(_) => {} - WinitWindowEvent::Destroyed => {} - WinitWindowEvent::DroppedFile(_) => {} - WinitWindowEvent::HoveredFile(_) => {} - WinitWindowEvent::HoveredFileCancelled => {} - WinitWindowEvent::ReceivedCharacter(_) => {} - WinitWindowEvent::Focused(_) => {} + WinitWindowEvent::Moved(_) => {None} + WinitWindowEvent::Destroyed => {None} + WinitWindowEvent::DroppedFile(_) => {None} + WinitWindowEvent::HoveredFile(_) => {None} + WinitWindowEvent::HoveredFileCancelled => {None} + WinitWindowEvent::ReceivedCharacter(_) => {None} + WinitWindowEvent::Focused(_) => {None} WinitWindowEvent::KeyboardInput { device_id: _, input, is_synthetic, } => match (input.state, is_synthetic) { (ElementState::Pressed, false) => { - publisher.publish_event(Events::Keyboard { + Some(Events::Keyboard { event: KeyEvent::KeyPressed { scan_code: input.scancode, virtual_key: input.virtual_keycode, @@ -209,7 +208,7 @@ impl Runtime for GenericRuntime { }) } (ElementState::Released, false) => { - publisher.publish_event(Events::Keyboard { + Some(Events::Keyboard { event: KeyEvent::KeyReleased { scan_code: input.scancode, virtual_key: input.virtual_keycode, @@ -222,41 +221,42 @@ impl Runtime for GenericRuntime { "[WARN] Unhandled synthetic keyboard event: {:?}", input ); + None } }, - WinitWindowEvent::ModifiersChanged(_) => {} + WinitWindowEvent::ModifiersChanged(_) => {None} WinitWindowEvent::CursorMoved { device_id, position, modifiers, - } => {} - WinitWindowEvent::CursorEntered { device_id } => {} - WinitWindowEvent::CursorLeft { device_id } => {} + } => {None} + WinitWindowEvent::CursorEntered { device_id } => {None} + WinitWindowEvent::CursorLeft { device_id } => {None} WinitWindowEvent::MouseWheel { device_id, delta, phase, modifiers, - } => {} + } => { None } WinitWindowEvent::MouseInput { device_id, state, button, modifiers, - } => {} + } => {None} WinitWindowEvent::TouchpadPressure { device_id, pressure, stage, - } => {} + } => {None} WinitWindowEvent::AxisMotion { device_id, axis, value, - } => {} - WinitWindowEvent::Touch(_) => {} - WinitWindowEvent::ThemeChanged(_) => {} - _ => {} + } => {None} + WinitWindowEvent::Touch(_) => {None} + WinitWindowEvent::ThemeChanged(_) => {None} + _ => {None} }, WinitEvent::MainEventsCleared => { let last_frame = current_frame.clone(); @@ -268,16 +268,16 @@ impl Runtime for GenericRuntime { component.on_update(duration); } - window.redraw(); - } - WinitEvent::RedrawRequested(_) => { + let active_render_context = active_render_context.as_mut().expect("Couldn't get the active render context. "); for component in &mut component_stack { - let commands = component.on_render(active_render_context.as_mut().unwrap()); - active_render_context.as_mut().unwrap().render(commands); + let commands = component.on_render(active_render_context); + active_render_context.render(commands); } + None } - WinitEvent::NewEvents(_) => {} - WinitEvent::DeviceEvent { device_id, event } => {} + WinitEvent::RedrawRequested(_) => { None } + WinitEvent::NewEvents(_) => {None} + WinitEvent::DeviceEvent { device_id, event } => {None} WinitEvent::UserEvent(lambda_event) => match lambda_event { Events::Runtime { event, issued_at } => match event { RuntimeEvent::Initialized => { @@ -285,22 +285,20 @@ impl Runtime for GenericRuntime { for component in &mut component_stack { component.on_attach(active_render_context.as_mut().unwrap()); } + None } RuntimeEvent::Shutdown => { for component in &mut component_stack { component.on_detach(active_render_context.as_mut().unwrap()); } + None } }, - _ => { - for component in &mut component_stack { - component.on_event(lambda_event.clone()); - } - } + _ => { None} }, - WinitEvent::Suspended => {} - WinitEvent::Resumed => {} - WinitEvent::RedrawEventsCleared => {} + WinitEvent::Suspended => {None} + WinitEvent::Resumed => {None} + WinitEvent::RedrawEventsCleared => { None } WinitEvent::LoopDestroyed => { active_render_context .take() @@ -308,8 +306,23 @@ impl Runtime for GenericRuntime { .destroy(); println!("[INFO] All resources were successfully deleted."); + None } + }; + + match mapped_event { + Some(event) => { + println!("Sending event: {:?} to all components", event); + for component in &mut component_stack { + component.on_event(event.clone()); + } + } + None => {} } + + + + }); } diff --git a/tools/lambda_rs_demo/src/main.rs b/tools/lambda_rs_demo/src/main.rs index ca157f96..1bc4ec46 100644 --- a/tools/lambda_rs_demo/src/main.rs +++ b/tools/lambda_rs_demo/src/main.rs @@ -26,7 +26,7 @@ use lambda::{ }; pub struct DemoComponent { - triangle_vertex: Shader, + fragment_shader: Shader, vertex_shader: Shader, render_pass_id: Option, render_pipeline_id: Option, @@ -44,7 +44,7 @@ impl Component for DemoComponent { render_context, &render_pass, &self.vertex_shader, - &self.triangle_vertex, + Some(&self.fragment_shader), ); // Attach the render pass and pipeline to the render context @@ -102,6 +102,7 @@ impl Component for DemoComponent { println!("Component detached: {:?}", name); } }, + _ => {} } } @@ -175,7 +176,7 @@ impl Default for DemoComponent { return DemoComponent { vertex_shader: vs, - triangle_vertex: fs, + fragment_shader: fs, render_pass_id: None, render_pipeline_id: None, width: 800, diff --git a/tools/triangles_demo/src/main.rs b/tools/triangles_demo/src/main.rs index 11160878..9ab2f4ce 100644 --- a/tools/triangles_demo/src/main.rs +++ b/tools/triangles_demo/src/main.rs @@ -51,7 +51,7 @@ impl Component for TrianglesComponent { render_context, &render_pass, &self.vertex_shader, - &self.triangle_vertex, + Some(&self.triangle_vertex), ); self.render_pass = Some(render_context.attach_render_pass(render_pass));