diff --git a/Cargo.lock b/Cargo.lock index d461d0b..54175c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2110,6 +2110,7 @@ dependencies = [ "egui_winit_vulkano", "env_logger", "glam", + "image", "log", "thiserror 2.0.12", "vulkano", diff --git a/Cargo.toml b/Cargo.toml index 372b1a5..50be745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ vulkano-shaders = "0.35" vulkano-util = "0.35" egui_winit_vulkano = { version = "0.28" } +image = { version = "0.25", features = ["png", "jpeg"] } + # Math glam = { version = "0.30" } diff --git a/res/shaders/vertex.frag b/res/shaders/vertex.frag index 720d192..67831a9 100644 --- a/res/shaders/vertex.frag +++ b/res/shaders/vertex.frag @@ -1,9 +1,12 @@ #version 450 -layout (location = 0) in vec3 color; +layout (location = 0) in vec2 tex_coords; layout (location = 0) out vec4 f_color; +layout(set = 1, binding = 0) uniform sampler mySampler; +layout(set = 1, binding = 1) uniform texture2D myTexture; + void main() { - f_color = vec4(color, 1.0); -} \ No newline at end of file + f_color = texture(sampler2D(myTexture, mySampler), tex_coords); +} diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert index bb1c261..0445e54 100644 --- a/res/shaders/vertex.vert +++ b/res/shaders/vertex.vert @@ -1,9 +1,9 @@ #version 450 layout (location = 0) in vec2 position; -layout (location = 1) in vec3 color; +layout (location = 1) in vec2 uv; -layout (location = 0) out vec3 fragColor; +layout (location = 0) out vec2 fragUv; layout (set = 0, binding = 0) uniform MVP { mat4 world; @@ -14,5 +14,5 @@ layout (set = 0, binding = 0) uniform MVP { void main() { mat4 worldview = uniforms.view * uniforms.world; gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); - fragColor = color; + fragUv = uv; } diff --git a/res/textures/wooden-crate.jpg b/res/textures/wooden-crate.jpg new file mode 100644 index 0000000..d1c8734 Binary files /dev/null and b/res/textures/wooden-crate.jpg differ diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index c550198..0a718e4 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,5 @@ pub mod pipelines; pub mod primitives; pub mod render_context; +pub mod texture; pub mod vulkan_context; diff --git a/src/core/render/pipelines/triangle_pipeline.rs b/src/core/render/pipelines/triangle_pipeline.rs index b7ffce4..43d00d5 100644 --- a/src/core/render/pipelines/triangle_pipeline.rs +++ b/src/core/render/pipelines/triangle_pipeline.rs @@ -8,7 +8,7 @@ use vulkano::device::Device; use vulkano::format::Format; use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; -use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; +use vulkano::pipeline::graphics::input_assembly::{InputAssemblyState, PrimitiveTopology}; use vulkano::pipeline::graphics::multisample::MultisampleState; use vulkano::pipeline::graphics::rasterization::RasterizationState; use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo; @@ -52,19 +52,42 @@ pub fn create_triangle_pipeline( PipelineShaderStageCreateInfo::new(fs), ]; - let mut bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::new(); - let mut descriptor_set_layout_binding = - DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer); - descriptor_set_layout_binding.stages = ShaderStages::VERTEX; - bindings.insert(0, descriptor_set_layout_binding); + let vertex_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::VERTEX, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) + }, + )]); + let fragment_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) + }, + ), + ]); - let descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings, + let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: vertex_bindings, + ..Default::default() + }; + + let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: fragment_bindings, ..Default::default() }; let create_info = PipelineDescriptorSetLayoutCreateInfo { - set_layouts: vec![descriptor_set_layout], + set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], flags: PipelineLayoutCreateFlags::default(), push_constant_ranges: vec![], } @@ -83,7 +106,10 @@ pub fn create_triangle_pipeline( GraphicsPipelineCreateInfo { stages: stages.into_iter().collect(), vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState::default()), + input_assembly_state: Some(InputAssemblyState { + topology: PrimitiveTopology::TriangleStrip, + ..Default::default() + }), viewport_state: Some(ViewportState::default()), rasterization_state: Some(RasterizationState::default()), multisample_state: Some(MultisampleState::default()), diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index 9bf133e..b35588c 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -12,8 +12,8 @@ pub struct Vertex2D { #[format(R32G32_SFLOAT)] pub position: [f32; 2], - #[format(R32G32B32_SFLOAT)] - pub color: [f32; 3], + #[format(R32G32_SFLOAT)] + pub uv: [f32; 2], } impl Vertex2D { diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs new file mode 100644 index 0000000..680ddbf --- /dev/null +++ b/src/core/render/texture.rs @@ -0,0 +1,113 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Error; +use image::{DynamicImage, EncodableLayout}; +use vulkano::{ + buffer::{Buffer, BufferCreateInfo, BufferUsage}, + command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer}, + device::Device, + format::Format, + image::{ + Image, ImageCreateInfo, ImageType, ImageUsage, + sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, + view::ImageView, + }, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, +}; + +pub struct Texture { + texture: Arc<ImageView>, + sampler: Arc<Sampler>, +} + +impl Texture { + fn new(texture: Arc<ImageView>, sampler: Arc<Sampler>) -> Self { + Self { texture, sampler } + } + + pub fn from_file( + device: &Arc<Device>, + memory_allocator: &Arc<StandardMemoryAllocator>, + builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, + path: &str, + ) -> Result<Self, Error> { + let image = image::open(path)?; + Self::from_dynamic_image(device, memory_allocator, builder, image) + } + + pub fn from_bytes( + device: &Arc<Device>, + memory_allocator: &Arc<StandardMemoryAllocator>, + builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, + bytes: &[u8], + ) -> Result<Self, Error> { + let image = image::load_from_memory(bytes)?; + Self::from_dynamic_image(device, memory_allocator, builder, image) + } + + pub fn from_dynamic_image( + device: &Arc<Device>, + memory_allocator: &Arc<StandardMemoryAllocator>, + builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, + image: DynamicImage, + ) -> Result<Self, Error> { + let image_data = image.to_rgba8(); + let image_dimensions = image_data.dimensions(); + + let upload_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_HOST + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + image_data.to_vec(), + )?; + + let image = Image::new( + memory_allocator.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format: Format::R8G8B8A8_SRGB, + extent: [image_dimensions.0 as u32, image_dimensions.1 as u32, 1], + array_layers: 1, + usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, + ..Default::default() + }, + AllocationCreateInfo::default(), + )?; + + builder.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image( + upload_buffer, + image.clone(), + ))?; + + let sampler = Sampler::new( + device.clone(), + SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + address_mode: [SamplerAddressMode::Repeat; 3], + ..Default::default() + }, + )?; + + let image_view = ImageView::new_default(image)?; + + log::trace!("Texture loaded with dimensions {:?}", image_dimensions); + + Ok(Self::new(image_view, sampler)) + } + + pub fn get_texture(&self) -> &Arc<ImageView> { + &self.texture + } + + pub fn get_sampler(&self) -> &Arc<Sampler> { + &self.sampler + } +} diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 1f27f0b..c5a56a0 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -3,67 +3,35 @@ use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; use crate::core::render::primitives::camera::Camera; use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; +use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::timer::Timer; use glam::{Mat4, Quat, Vec3}; use std::sync::Arc; use vulkano::buffer::Subbuffer; -use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; +use vulkano::command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, + PrimaryCommandBufferAbstract, +}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -const VERTICES: [Vertex2D; 12] = [ - // Triangle en haut à gauche +const VERTICES: [Vertex2D; 4] = [ Vertex2D { - position: [-0.5, -0.75], - color: [1.0, 0.0, 0.0], + position: [-0.5, -0.5], + uv: [0.0, 0.0], }, Vertex2D { - position: [-0.75, -0.25], - color: [0.0, 1.0, 0.0], + position: [-0.5, 0.5], + uv: [0.0, 1.0], }, Vertex2D { - position: [-0.25, -0.25], - color: [0.0, 0.0, 1.0], - }, - // Triangle en bas à gauche - Vertex2D { - position: [-0.5, 0.25], - color: [0.5, 0.5, 0.5], + position: [0.5, -0.5], + uv: [1.0, 0.0], }, Vertex2D { - position: [-0.75, 0.75], - color: [0.2, 0.8, 0.2], - }, - Vertex2D { - position: [-0.25, 0.75], - color: [0.8, 0.2, 0.2], - }, - // Triangle en haut à droite - Vertex2D { - position: [0.5, -0.75], - color: [1.0, 1.0, 0.0], - }, - Vertex2D { - position: [0.25, -0.25], - color: [0.0, 1.0, 1.0], - }, - Vertex2D { - position: [0.75, -0.25], - color: [1.0, 0.0, 1.0], - }, - // Triangle en bas à droite - Vertex2D { - position: [0.5, 0.25], - color: [0.1, 0.5, 0.8], - }, - Vertex2D { - position: [0.25, 0.75], - color: [0.8, 0.6, 0.1], - }, - Vertex2D { - position: [0.75, 0.75], - color: [0.3, 0.4, 0.6], + position: [0.5, 0.5], + uv: [1.0, 1.0], }, ]; @@ -71,6 +39,7 @@ pub struct MainSceneState { pipeline: Arc<GraphicsPipeline>, vertex_buffer: Subbuffer<[Vertex2D]>, camera: Camera, + texture: Texture, } #[derive(Default)] @@ -105,11 +74,33 @@ impl Scene for MainScene { ), ); + let mut uploads = AutoCommandBufferBuilder::primary( + render_context.command_buffer_allocator().clone(), + render_context.graphics_queue().queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + let texture = Texture::from_file( + render_context.device(), + render_context.memory_allocator(), + &mut uploads, + "res/textures/wooden-crate.jpg", + ) + .unwrap(); + + let _ = uploads + .build() + .unwrap() + .execute(render_context.graphics_queue().clone()) + .unwrap(); + self.state = Some(MainSceneState { pipeline, vertex_buffer, camera, - }) + texture, + }); } fn update( @@ -157,21 +148,32 @@ impl Scene for MainScene { ) { let state = self.state.as_ref().unwrap(); let vertex_count = state.vertex_buffer.len() as u32; - let instance_count = vertex_count / 3; + let instance_count = vertex_count / 4; - let layout = &state.pipeline.layout().set_layouts()[0]; + let layouts = state.pipeline.layout().set_layouts(); let uniform_buffer = state .camera .create_buffer(render_context.memory_allocator()) .unwrap(); - let descriptor_set = DescriptorSet::new( + let uniform_descriptor_set = DescriptorSet::new( render_context.descriptor_set_allocator().clone(), - layout.clone(), + layouts[0].clone(), [WriteDescriptorSet::buffer(0, uniform_buffer)], [], ) .unwrap(); + let texture_descriptor_set = DescriptorSet::new( + render_context.descriptor_set_allocator().clone(), + layouts[1].clone(), + [ + WriteDescriptorSet::sampler(0, state.texture.get_sampler().clone()), + WriteDescriptorSet::image_view(1, state.texture.get_texture().clone()), + ], + [], + ) + .unwrap(); + unsafe { builder .bind_pipeline_graphics(state.pipeline.clone()) @@ -180,7 +182,7 @@ impl Scene for MainScene { PipelineBindPoint::Graphics, state.pipeline.layout().clone(), 0, - descriptor_set, + vec![uniform_descriptor_set, texture_descriptor_set], ) .unwrap() .bind_vertex_buffers(0, state.vertex_buffer.clone()) diff --git a/src/main.rs b/src/main.rs index 6a4984b..7a4284c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert), - VirtualBinding::Axis(0, AxisDirection::Normal, 1.0), + VirtualBinding::Axis(0, AxisDirection::Normal, 0.0), ], ), ( @@ -29,7 +29,7 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert), - VirtualBinding::Axis(1, AxisDirection::Normal, 1.0), + VirtualBinding::Axis(1, AxisDirection::Normal, 0.0), ], ), (