(&self) {
+ let type_id = TypeId::of::();
+ let pipeline = Arc::new(RwLock::new(P::default()));
+
+ let mut pipelines_id = self.pipelines_id.write().unwrap();
+ let mut pipelines_state = self.pipelines_state.write().unwrap();
+ let mut pipelines = self.pipelines.write().unwrap();
+
+ pipelines_id.push(type_id);
+ pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
+ pipelines.push(pipeline.clone());
+ }
+
+ pub fn add_material(&self) -> Result<(), MaterialError> {
+ let pipeline_id = M::pipeline_type_id();
+
+ let pipeline_result = {
+ let pipelines_id = self.pipelines_id.read().unwrap();
+ let pipelines_state = self.pipelines_state.read().unwrap();
+ let pipelines = self.pipelines.read().unwrap();
+
+ pipelines_id
+ .iter()
+ .zip(pipelines.iter())
+ .zip(pipelines_state.iter())
+ .find(|((id, _), _)| *id == &pipeline_id)
+ .map(|((_, pipeline), state)| (pipeline.clone(), state.clone()))
+ };
+
+ let (pipeline, pipeline_state) = match pipeline_result {
+ Some(pipeline) => pipeline,
+ None => {
+ tracing::error!(
+ "Pipeline with id {pipeline_id:?} not found, please add it before adding a material"
+ );
+ return Err(MaterialError::PipelineNotFound);
+ }
+ };
+
+ let type_id = TypeId::of::();
+
+ let mut materials_id = self.materials_id.write().unwrap();
+ let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap();
+ let mut materials_pipeline = self.materials_pipeline.write().unwrap();
+ let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap();
+ let mut materials_state = self.materials_state.write().unwrap();
+ let mut materials = self.materials.write().unwrap();
+
+ materials_id.push(type_id);
+ materials_pipeline_id.push(pipeline_id);
+ materials_pipeline.push(pipeline);
+ materials_pipeline_state.push(pipeline_state);
+ materials_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
+ materials.push(Arc::new(RwLock::new(M::default())));
+
+ Ok(())
+ }
+
+ pub fn update_swapchain_format(&mut self, swapchain_format: Format) {
+ if self.swapchain_format == swapchain_format {
+ return;
+ }
+
+ self.swapchain_format = swapchain_format;
+ self.mark_all_pipelines_as_loading();
+ }
+
+ fn mark_all_pipelines_as_loading(&self) {
+ let pipelines_state = self.pipelines_state.write().unwrap();
+
+ for state in pipelines_state.iter() {
+ let mut state = state.write().unwrap();
+ *state = MaterialState::Loading;
+ }
+ }
+
+ fn load_pipelines(&self) {
+ let pipelines_state = self.pipelines_state.read().unwrap();
+ let pipelines = self.pipelines.read().unwrap();
+
+ let iter = pipelines_state
+ .iter()
+ .zip(pipelines.iter())
+ .filter(|(state, _)| {
+ let state = state.read().unwrap();
+ matches!(*state, MaterialState::Loading)
+ });
+
+ for (state, pipeline) in iter {
+ let mut pipeline = pipeline.write().unwrap();
+ let result = pipeline.load(
+ &self.device,
+ &self.memory_allocator,
+ self.swapchain_format,
+ self.depth_format,
+ );
+
+ match result {
+ Ok(_) => {
+ let mut state = state.write().unwrap();
+ *state = MaterialState::Loaded;
+ }
+ Err(e) => {
+ tracing::error!("Failed to load pipeline: {e}");
+ }
+ }
+ }
+ }
+
+ fn load_materials(&self) {
+ let materials_state = self.materials_state.read().unwrap();
+ let materials = self.materials.read().unwrap();
+
+ let iter = materials_state
+ .iter()
+ .zip(materials.iter())
+ .filter(|(state, _)| {
+ let state = state.read().unwrap();
+ matches!(*state, MaterialState::Loading)
+ });
+
+ for (state, material) in iter {
+ let mut material = material.write().unwrap();
+ let result = material.load(&self.device, &self.memory_allocator);
+
+ match result {
+ Ok(_) => {
+ let mut state = state.write().unwrap();
+ *state = MaterialState::Loaded;
+ }
+ Err(e) => {
+ tracing::error!("Failed to load material: {e}");
+ }
+ }
+ }
+ }
+
+ fn render_materials(&self, f: F)
+ where
+ F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>),
+ {
+ let materials = self.materials.read().unwrap();
+ let materials_state = self.materials_state.read().unwrap();
+ let materials_pipeline = self.materials_pipeline.read().unwrap();
+ let materials_pipeline_state = self.materials_pipeline_state.read().unwrap();
+
+ materials
+ .iter()
+ .zip(materials_state.iter())
+ .zip(materials_pipeline.iter())
+ .zip(materials_pipeline_state.iter())
+ .filter(|(((_, material_state), _), pipeline_state)| {
+ let material_state = material_state.read().unwrap();
+ let pipeline_state = pipeline_state.read().unwrap();
+ matches!(*material_state, MaterialState::Loaded)
+ && matches!(*pipeline_state, MaterialState::Loaded)
+ })
+ .for_each(|(((material, _), pipeline), _)| {
+ let material = material.read().unwrap();
+ let pipeline = pipeline.read().unwrap();
+
+ f(material, pipeline);
+ });
+ }
+}
diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs
new file mode 100644
index 0000000..ec7ac54
--- /dev/null
+++ b/src/core/render/mod.rs
@@ -0,0 +1,5 @@
+pub mod material_manager;
+pub mod primitives;
+pub mod render_pass_manager;
+pub mod texture;
+pub mod vulkan_context;
diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs
new file mode 100644
index 0000000..f27622a
--- /dev/null
+++ b/src/core/render/primitives/camera.rs
@@ -0,0 +1,122 @@
+use std::{f32::consts::FRAC_PI_2, sync::Arc};
+
+use glam::{Mat4, Vec3, Vec4};
+use vulkano::{
+ Validated,
+ buffer::{AllocateBufferError, Subbuffer},
+ memory::allocator::StandardMemoryAllocator,
+};
+
+use crate::core::{input::InputManager, timer::Timer};
+
+use super::mvp::Mvp;
+
+// See docs/OPENGL_VULKAN_DIFF.md
+const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 {
+ x_axis: Vec4::new(1.0, 0.0, 0.0, 0.0),
+ y_axis: Vec4::new(0.0, -1.0, 0.0, 0.0),
+ z_axis: Vec4::new(0.0, 0.0, 1.0, 0.0),
+ w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0),
+};
+
+#[derive(Default)]
+pub struct Camera3D {
+ projection: Mat4,
+
+ position: Vec3,
+ rotation: Vec3,
+ aspect_ratio: f32,
+ fov: f32,
+ near: f32,
+ far: f32,
+}
+
+impl Camera3D {
+ pub fn new(aspect_ratio: f32, fov: f32, near: f32, far: f32) -> Self {
+ Self {
+ projection: Mat4::perspective_rh(fov, aspect_ratio, near, far),
+ position: Vec3::ZERO,
+ rotation: Vec3::ZERO,
+ aspect_ratio,
+ fov,
+ near,
+ far,
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ input_manager: &InputManager,
+ timer: &Timer,
+ movement_speed: f32,
+ camera_sensitivity: f32,
+ window_aspect_ratio: f32,
+ ) {
+ // Process camera rotation
+ let camera_delta = camera_sensitivity * timer.delta_time();
+ self.rotation += Vec3::new(
+ (input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(),
+ (input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(),
+ 0.0,
+ );
+
+ if self.rotation.x > FRAC_PI_2 {
+ self.rotation = Vec3::new(FRAC_PI_2, self.rotation.y, 0.0);
+ }
+
+ if self.rotation.x < -FRAC_PI_2 {
+ self.rotation = Vec3::new(-FRAC_PI_2, self.rotation.y, 0.0);
+ }
+
+ let movement_delta = movement_speed * timer.delta_time();
+
+ let (yaw_sin, yaw_cos) = self.rotation.y.sin_cos();
+ let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize();
+ let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize();
+
+ let tx = input_manager.get_virtual_input_state("move_right") * movement_delta;
+ self.position += tx * right;
+
+ let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta;
+ self.position += tz * forward;
+
+ if self.aspect_ratio != window_aspect_ratio {
+ self.aspect_ratio = window_aspect_ratio;
+ self.projection =
+ Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far);
+ }
+ }
+
+ pub fn set_projection(&mut self, projection: Mat4) {
+ self.projection = projection;
+ }
+
+ pub fn get_position(&self) -> Vec3 {
+ self.position
+ }
+
+ pub fn get_rotation(&self) -> Vec3 {
+ self.rotation
+ }
+
+ pub fn create_buffer(
+ &self,
+ memory_allocator: &Arc,
+ ) -> Result, Validated> {
+ let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos();
+ let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos();
+
+ let view_matrix = Mat4::look_to_rh(
+ self.position,
+ Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
+ Vec3::Y,
+ );
+
+ Mvp {
+ model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(),
+ view: view_matrix.to_cols_array_2d(),
+ projection: self.projection.to_cols_array_2d(),
+ }
+ .into_buffer(memory_allocator)
+ }
+}
diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs
new file mode 100644
index 0000000..5ab8266
--- /dev/null
+++ b/src/core/render/primitives/mod.rs
@@ -0,0 +1,25 @@
+use std::{collections::BTreeMap, sync::Arc};
+
+use vulkano::{
+ Validated, VulkanError,
+ descriptor_set::{
+ DescriptorSet,
+ allocator::StandardDescriptorSetAllocator,
+ layout::{DescriptorSetLayout, DescriptorSetLayoutBinding},
+ },
+};
+
+pub mod camera;
+pub mod mvp;
+pub mod transform;
+pub mod vertex;
+
+pub trait AsBindableDescriptorSet {
+ fn as_descriptor_set_layout_bindings() -> BTreeMap;
+
+ fn as_descriptor_set(
+ descriptor_set_allocator: &Arc,
+ layout: &Arc,
+ data: &T,
+ ) -> Result, Validated>;
+}
diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs
new file mode 100644
index 0000000..d05307e
--- /dev/null
+++ b/src/core/render/primitives/mvp.rs
@@ -0,0 +1,70 @@
+use std::collections::BTreeMap;
+use std::sync::Arc;
+
+use vulkano::buffer::{
+ AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
+};
+use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
+use vulkano::descriptor_set::layout::{
+ DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType,
+};
+use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
+use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
+use vulkano::shader::ShaderStages;
+use vulkano::{Validated, VulkanError};
+
+use crate::core::render::primitives::AsBindableDescriptorSet;
+
+#[derive(BufferContents, Clone, Copy)]
+#[repr(C)]
+pub struct Mvp {
+ pub model: [[f32; 4]; 4],
+ pub view: [[f32; 4]; 4],
+ pub projection: [[f32; 4]; 4],
+}
+
+impl Mvp {
+ pub fn into_buffer(
+ self,
+ memory_allocator: &Arc,
+ ) -> Result, Validated> {
+ Buffer::from_iter(
+ memory_allocator.clone(),
+ BufferCreateInfo {
+ usage: BufferUsage::UNIFORM_BUFFER,
+ ..Default::default()
+ },
+ AllocationCreateInfo {
+ memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+ | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+ ..Default::default()
+ },
+ [self],
+ )
+ }
+}
+
+impl AsBindableDescriptorSet> for Mvp {
+ fn as_descriptor_set_layout_bindings() -> BTreeMap {
+ BTreeMap::::from_iter([(
+ 0,
+ DescriptorSetLayoutBinding {
+ stages: ShaderStages::VERTEX,
+ ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
+ },
+ )])
+ }
+
+ fn as_descriptor_set(
+ descriptor_set_allocator: &Arc,
+ layout: &Arc,
+ data: &Subbuffer<[Mvp]>,
+ ) -> Result, Validated> {
+ DescriptorSet::new(
+ descriptor_set_allocator.clone(),
+ layout.clone(),
+ [WriteDescriptorSet::buffer(0, data.clone())],
+ [],
+ )
+ }
+}
diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs
new file mode 100644
index 0000000..4d870b0
--- /dev/null
+++ b/src/core/render/primitives/transform.rs
@@ -0,0 +1,81 @@
+use std::sync::Arc;
+
+use glam::{Mat4, Quat, Vec3};
+use vulkano::{
+ Validated,
+ buffer::{
+ AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
+ },
+ memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+ pipeline::graphics::vertex_input::Vertex,
+};
+
+pub struct Transform {
+ pub position: Vec3,
+ pub rotation: Quat,
+ pub scale: Vec3,
+}
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct TransformRaw {
+ #[format(R32G32B32A32_SFLOAT)]
+ pub model: [[f32; 4]; 4],
+}
+
+impl Default for Transform {
+ fn default() -> Self {
+ Self {
+ position: Vec3::default(),
+ rotation: Quat::default(),
+ scale: Vec3::ONE,
+ }
+ }
+}
+
+impl Transform {
+ pub fn rotate(&mut self, rotation: Quat) {
+ self.rotation *= rotation;
+ }
+
+ pub fn translate(&mut self, translation: Vec3) {
+ self.position += translation;
+ }
+
+ pub fn scale(&mut self, scale: Vec3) {
+ self.scale *= scale;
+ }
+
+ pub fn to_raw_tranform(&self) -> TransformRaw {
+ TransformRaw {
+ model: (Mat4::from_translation(self.position)
+ * Mat4::from_quat(self.rotation)
+ * Mat4::from_scale(self.scale))
+ .to_cols_array_2d(),
+ }
+ }
+
+ pub fn create_buffer(
+ memory_allocator: &Arc,
+ transforms: &[Transform],
+ ) -> Result, Validated> {
+ let transform_raws: Vec =
+ transforms.iter().map(|t| t.to_raw_tranform()).collect();
+
+ let buffer = Buffer::from_iter(
+ memory_allocator.clone(),
+ BufferCreateInfo {
+ usage: BufferUsage::VERTEX_BUFFER,
+ ..Default::default()
+ },
+ AllocationCreateInfo {
+ memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+ | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+ ..Default::default()
+ },
+ transform_raws,
+ )?;
+
+ Ok(buffer)
+ }
+}
diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs
new file mode 100644
index 0000000..166ac44
--- /dev/null
+++ b/src/core/render/primitives/vertex.rs
@@ -0,0 +1,22 @@
+use vulkano::buffer::BufferContents;
+use vulkano::pipeline::graphics::vertex_input::Vertex;
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct Vertex2D {
+ #[format(R32G32_SFLOAT)]
+ pub position: [f32; 2],
+
+ #[format(R32G32_SFLOAT)]
+ pub uv: [f32; 2],
+}
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct Vertex3D {
+ #[format(R32G32B32_SFLOAT)]
+ pub position: [f32; 3],
+
+ #[format(R32G32_SFLOAT)]
+ pub uv: [f32; 2],
+}
diff --git a/src/core/render/render_pass_manager.rs b/src/core/render/render_pass_manager.rs
new file mode 100644
index 0000000..02c3d7a
--- /dev/null
+++ b/src/core/render/render_pass_manager.rs
@@ -0,0 +1,113 @@
+use std::sync::Arc;
+use vulkano::{
+ command_buffer::{AutoCommandBufferBuilder, RenderingAttachmentInfo, RenderingInfo},
+ image::view::ImageView,
+ pipeline::graphics::viewport::Viewport,
+ render_pass::{AttachmentLoadOp, AttachmentStoreOp},
+};
+
+/// Types de render passes disponibles
+#[derive(Debug, Clone)]
+pub enum RenderPassType {
+ Standard,
+ ShadowMap,
+ PostProcess,
+}
+
+/// Configuration pour un render pass
+#[derive(Debug, Clone)]
+pub struct RenderPassConfig {
+ pub pass_type: RenderPassType,
+ pub clear_color: Option<[f32; 4]>,
+ pub clear_depth: Option,
+ pub load_op: AttachmentLoadOp,
+ pub store_op: AttachmentStoreOp,
+}
+
+impl Default for RenderPassConfig {
+ fn default() -> Self {
+ Self {
+ pass_type: RenderPassType::Standard,
+ clear_color: Some([0.0, 0.0, 0.0, 1.0]),
+ clear_depth: Some(1.0),
+ load_op: AttachmentLoadOp::Clear,
+ store_op: AttachmentStoreOp::Store,
+ }
+ }
+}
+
+/// Gestionnaire de render passes réutilisable
+pub struct RenderPassManager;
+
+impl RenderPassManager {
+ /// Commence un render pass standard avec les paramètres donnés
+ pub fn begin_standard_rendering(
+ builder: &mut AutoCommandBufferBuilder,
+ config: &RenderPassConfig,
+ color_attachment: Arc,
+ depth_attachment: Option>,
+ window_size: [f32; 2],
+ ) -> Result<(), Box> {
+ let viewport = Viewport {
+ offset: [0.0, 0.0],
+ extent: window_size,
+ depth_range: 0.0..=1.0,
+ };
+
+ let mut rendering_info = RenderingInfo {
+ color_attachments: vec![Some(RenderingAttachmentInfo {
+ load_op: config.load_op,
+ store_op: config.store_op,
+ clear_value: config.clear_color.map(|c| c.into()),
+ ..RenderingAttachmentInfo::image_view(color_attachment)
+ })],
+ depth_attachment: None,
+ ..Default::default()
+ };
+
+ if let Some(depth_view) = depth_attachment {
+ rendering_info.depth_attachment = Some(RenderingAttachmentInfo {
+ load_op: AttachmentLoadOp::Clear,
+ store_op: AttachmentStoreOp::DontCare,
+ clear_value: config.clear_depth.map(|d| [d].into()),
+ ..RenderingAttachmentInfo::image_view(depth_view)
+ });
+ }
+
+ builder
+ .begin_rendering(rendering_info)?
+ .set_viewport(0, [viewport].into_iter().collect())?;
+
+ Ok(())
+ }
+
+ /// Termine le render pass actuel
+ pub fn end_rendering(
+ builder: &mut AutoCommandBufferBuilder,
+ ) -> Result<(), Box> {
+ builder.end_rendering()?;
+ Ok(())
+ }
+
+ /// Crée une configuration pour un render pass shadow map
+ pub fn shadow_map_config() -> RenderPassConfig {
+ RenderPassConfig {
+ pass_type: RenderPassType::ShadowMap,
+ clear_color: None,
+ clear_depth: Some(1.0),
+ load_op: AttachmentLoadOp::Clear,
+ store_op: AttachmentStoreOp::Store,
+ }
+ }
+
+ /// Crée une configuration pour un render pass de post-processing
+ pub fn post_process_config() -> RenderPassConfig {
+ RenderPassConfig {
+ pass_type: RenderPassType::PostProcess,
+ clear_color: Some([0.0, 0.0, 0.0, 1.0]),
+ clear_depth: None,
+ load_op: AttachmentLoadOp::Load,
+ store_op: AttachmentStoreOp::Store,
+ }
+ }
+}
diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs
new file mode 100644
index 0000000..db0bcc5
--- /dev/null
+++ b/src/core/render/texture.rs
@@ -0,0 +1,168 @@
+use std::{collections::BTreeMap, error::Error, sync::Arc};
+
+use image::DynamicImage;
+use vulkano::{
+ Validated, VulkanError,
+ buffer::{Buffer, BufferCreateInfo, BufferUsage},
+ command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer},
+ descriptor_set::{
+ DescriptorSet, WriteDescriptorSet,
+ allocator::StandardDescriptorSetAllocator,
+ layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType},
+ },
+ device::Device,
+ format::Format,
+ image::{
+ Image, ImageCreateInfo, ImageType, ImageUsage,
+ sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo},
+ view::ImageView,
+ },
+ memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+ shader::ShaderStages,
+};
+
+use crate::core::render::primitives::AsBindableDescriptorSet;
+
+pub struct Texture {
+ texture: Arc,
+ sampler: Arc,
+}
+
+impl Texture {
+ fn new(texture: Arc, sampler: Arc) -> Self {
+ Self { texture, sampler }
+ }
+
+ pub fn from_file(
+ device: &Arc,
+ memory_allocator: &Arc,
+ builder: &mut AutoCommandBufferBuilder,
+ path: &str,
+ ) -> Result> {
+ let _span = tracing::info_span!("texture_load_from_file", path = path);
+
+ let bytes = std::fs::read(path)?;
+ Self::from_bytes(device, memory_allocator, builder, &bytes)
+ }
+
+ pub fn from_bytes(
+ device: &Arc,
+ memory_allocator: &Arc,
+ builder: &mut AutoCommandBufferBuilder,
+ bytes: &[u8],
+ ) -> Result> {
+ let image = image::load_from_memory(bytes)?;
+ Self::from_dynamic_image(device, memory_allocator, builder, image)
+ }
+
+ pub fn from_dynamic_image(
+ device: &Arc,
+ memory_allocator: &Arc,
+ builder: &mut AutoCommandBufferBuilder,
+ image: DynamicImage,
+ ) -> Result> {
+ let _span = tracing::info_span!("texture_from_dynamic_image");
+
+ let image_data = image.to_rgba8();
+ let image_dimensions = image_data.dimensions();
+ let image_data = image_data.into_raw();
+
+ let upload_buffer = Buffer::new_slice(
+ 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.len() as u64,
+ )?;
+
+ {
+ let buffer_data = &mut *upload_buffer.write()?;
+ buffer_data.copy_from_slice(&image_data);
+ }
+
+ let image = Image::new(
+ memory_allocator.clone(),
+ ImageCreateInfo {
+ image_type: ImageType::Dim2d,
+ format: Format::R8G8B8A8_SRGB,
+ extent: [image_dimensions.0, image_dimensions.1, 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)?;
+
+ tracing::trace!("Texture loaded with dimensions {:?}", image_dimensions);
+
+ Ok(Self::new(image_view, sampler))
+ }
+
+ pub fn get_texture(&self) -> &Arc {
+ &self.texture
+ }
+
+ pub fn get_sampler(&self) -> &Arc {
+ &self.sampler
+ }
+}
+
+impl AsBindableDescriptorSet for Texture {
+ fn as_descriptor_set_layout_bindings() -> BTreeMap {
+ BTreeMap::::from_iter([
+ (
+ 0,
+ DescriptorSetLayoutBinding {
+ stages: ShaderStages::FRAGMENT,
+ ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
+ },
+ ),
+ (
+ 1,
+ DescriptorSetLayoutBinding {
+ stages: ShaderStages::FRAGMENT,
+ ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage)
+ },
+ ),
+ ])
+ }
+
+ fn as_descriptor_set(
+ descriptor_set_allocator: &Arc,
+ layout: &Arc,
+ data: &Texture,
+ ) -> Result, Validated> {
+ DescriptorSet::new(
+ descriptor_set_allocator.clone(),
+ layout.clone(),
+ [
+ WriteDescriptorSet::sampler(0, data.sampler.clone()),
+ WriteDescriptorSet::image_view(1, data.texture.clone()),
+ ],
+ [],
+ )
+ }
+}
diff --git a/src/core/render/vulkan_context.rs b/src/core/render/vulkan_context.rs
new file mode 100644
index 0000000..3d91ec3
--- /dev/null
+++ b/src/core/render/vulkan_context.rs
@@ -0,0 +1,45 @@
+use std::sync::Arc;
+
+use vulkano::{
+ command_buffer::allocator::StandardCommandBufferAllocator,
+ descriptor_set::allocator::StandardDescriptorSetAllocator,
+};
+use vulkano_util::context::VulkanoContext;
+
+pub struct VulkanContext {
+ vulkano_context: VulkanoContext,
+ command_buffer_allocator: Arc,
+ descriptor_set_allocator: Arc,
+}
+
+impl VulkanContext {
+ pub fn new(vulkano_context: VulkanoContext) -> Self {
+ let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
+ vulkano_context.device().clone(),
+ Default::default(),
+ ));
+
+ let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
+ vulkano_context.device().clone(),
+ Default::default(),
+ ));
+
+ Self {
+ vulkano_context,
+ command_buffer_allocator,
+ descriptor_set_allocator,
+ }
+ }
+
+ pub fn vulkano_context(&self) -> &VulkanoContext {
+ &self.vulkano_context
+ }
+
+ pub fn command_buffer_allocator(&self) -> &Arc {
+ &self.command_buffer_allocator
+ }
+
+ pub fn descriptor_set_allocator(&self) -> &Arc {
+ &self.descriptor_set_allocator
+ }
+}
diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs
new file mode 100644
index 0000000..cf07ebc
--- /dev/null
+++ b/src/core/scene/manager.rs
@@ -0,0 +1,65 @@
+use std::error::Error;
+
+use crate::core::app::context::WindowContext;
+
+use super::Scene;
+
+pub struct SceneManager {
+ scenes: Vec>,
+ current_scene_index: Option,
+}
+
+impl SceneManager {
+ pub fn new() -> Self {
+ Self {
+ scenes: Vec::new(),
+ current_scene_index: None,
+ }
+ }
+
+ pub fn load_scene(&mut self, scene: Box) {
+ self.scenes.push(scene);
+ self.current_scene_index = Some(self.scenes.len() - 1);
+ }
+
+ pub fn replace_current_scene(&mut self, scene: Box) {
+ if let Some(index) = self.current_scene_index {
+ if index < self.scenes.len() {
+ self.scenes[index].unload();
+ self.scenes[index] = scene;
+ }
+ } else {
+ self.load_scene(scene);
+ }
+ }
+
+ pub fn current_scene(&self) -> Option<&Box> {
+ if let Some(index) = self.current_scene_index {
+ self.scenes.get(index)
+ } else {
+ None
+ }
+ }
+
+ pub fn current_scene_mut(&mut self) -> Option<&mut Box> {
+ if let Some(index) = self.current_scene_index {
+ self.scenes.get_mut(index)
+ } else {
+ None
+ }
+ }
+
+ pub fn load_scene_if_not_loaded(
+ &mut self,
+ app_context: &mut WindowContext,
+ ) -> Result<(), Box> {
+ if let Some(scene) = self.current_scene_mut() {
+ if !scene.loaded() {
+ scene.load(app_context)?;
+ }
+ } else {
+ tracing::warn!("No scene found in SceneManager!");
+ }
+ Ok(())
+ }
+}
diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs
new file mode 100644
index 0000000..5dd8a57
--- /dev/null
+++ b/src/core/scene/mod.rs
@@ -0,0 +1,19 @@
+use std::error::Error;
+
+use vulkano::sync::GpuFuture;
+
+use crate::core::app::context::WindowContext;
+
+pub mod manager;
+
+pub trait Scene {
+ fn loaded(&self) -> bool;
+ fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box>;
+ fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box>;
+ fn render(
+ &mut self,
+ acquire_future: Box,
+ app_context: &mut WindowContext,
+ ) -> Result, Box>;
+ fn unload(&mut self);
+}
diff --git a/src/core/timer.rs b/src/core/timer.rs
new file mode 100644
index 0000000..3245a4c
--- /dev/null
+++ b/src/core/timer.rs
@@ -0,0 +1,34 @@
+pub struct Timer {
+ start_time: std::time::Instant,
+ last_time: std::time::Instant,
+ current_time: std::time::Instant,
+ delta_time: f32,
+}
+
+impl Timer {
+ pub fn new() -> Self {
+ Self {
+ start_time: std::time::Instant::now(),
+ last_time: std::time::Instant::now(),
+ current_time: std::time::Instant::now(),
+ delta_time: 0.0,
+ }
+ }
+
+ pub fn update(&mut self) {
+ self.current_time = std::time::Instant::now();
+ self.delta_time = self
+ .current_time
+ .duration_since(self.last_time)
+ .as_secs_f32();
+ self.last_time = self.current_time;
+ }
+
+ pub fn delta_time(&self) -> f32 {
+ self.delta_time
+ }
+
+ pub fn start_time(&self) -> std::time::Instant {
+ self.start_time
+ }
+}
diff --git a/src/game/assets/mod.rs b/src/game/assets/mod.rs
new file mode 100644
index 0000000..d793a66
--- /dev/null
+++ b/src/game/assets/mod.rs
@@ -0,0 +1 @@
+pub mod square;
diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs
new file mode 100644
index 0000000..c4a33ad
--- /dev/null
+++ b/src/game/assets/square.rs
@@ -0,0 +1,231 @@
+use std::{collections::BTreeMap, error::Error, sync::Arc};
+
+use vulkano::{
+ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
+ command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer},
+ descriptor_set::{
+ DescriptorSet, WriteDescriptorSet,
+ allocator::StandardDescriptorSetAllocator,
+ layout::{DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType},
+ },
+ device::Device,
+ format::Format,
+ memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+ pipeline::{
+ DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
+ PipelineShaderStageCreateInfo,
+ graphics::{
+ GraphicsPipelineCreateInfo,
+ color_blend::{ColorBlendAttachmentState, ColorBlendState},
+ depth_stencil::{DepthState, DepthStencilState},
+ input_assembly::InputAssemblyState,
+ multisample::MultisampleState,
+ rasterization::RasterizationState,
+ subpass::PipelineRenderingCreateInfo,
+ vertex_input::{Vertex, VertexDefinition},
+ viewport::ViewportState,
+ },
+ layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags},
+ },
+ shader::ShaderStages,
+};
+
+use crate::core::render::{
+ primitives::{AsBindableDescriptorSet, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D},
+ texture::Texture,
+};
+
+const VERTICES: [Vertex3D; 4] = [
+ Vertex3D {
+ position: [-0.5, -0.5, 0.0],
+ uv: [0.0, 0.0],
+ },
+ Vertex3D {
+ position: [-0.5, 0.5, 0.0],
+ uv: [0.0, 1.0],
+ },
+ Vertex3D {
+ position: [0.5, -0.5, 0.0],
+ uv: [1.0, 0.0],
+ },
+ Vertex3D {
+ position: [0.5, 0.5, 0.0],
+ uv: [1.0, 1.0],
+ },
+];
+
+const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1];
+
+pub mod shaders {
+ pub mod vs {
+ vulkano_shaders::shader! {
+ ty: "vertex",
+ path: r"res/shaders/vertex.vert",
+ generate_structs: false,
+ }
+ }
+
+ pub mod fs {
+ vulkano_shaders::shader! {
+ ty: "fragment",
+ path: r"res/shaders/vertex.frag",
+ generate_structs: false,
+ }
+ }
+}
+
+pub struct Square {
+ vertex_buffer: Subbuffer<[Vertex3D]>,
+ index_buffer: Subbuffer<[u32]>,
+ pipeline: Arc,
+}
+
+impl Square {
+ pub fn new(
+ device: &Arc,
+ memory_allocator: &Arc,
+ swapchain_format: Format,
+ depth_format: Format,
+ ) -> Result> {
+ let vertex_buffer = Buffer::from_iter(
+ memory_allocator.clone(),
+ BufferCreateInfo {
+ usage: BufferUsage::VERTEX_BUFFER,
+ ..Default::default()
+ },
+ AllocationCreateInfo {
+ memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+ | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+ ..Default::default()
+ },
+ Vec::from_iter(VERTICES),
+ )?;
+ let index_buffer = Buffer::from_iter(
+ memory_allocator.clone(),
+ BufferCreateInfo {
+ usage: BufferUsage::INDEX_BUFFER,
+ ..Default::default()
+ },
+ AllocationCreateInfo {
+ memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+ | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+ ..Default::default()
+ },
+ Vec::from_iter(INDICES),
+ )?;
+
+ let vs = shaders::vs::load(device.clone())?
+ .entry_point("main")
+ .ok_or("Failed find main entry point of vertex shader".to_string())?;
+
+ let fs = shaders::fs::load(device.clone())?
+ .entry_point("main")
+ .ok_or("Failed find main entry point of fragment shader".to_string())?;
+
+ let vertex_input_state =
+ [Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?;
+
+ let stages = [
+ PipelineShaderStageCreateInfo::new(vs),
+ PipelineShaderStageCreateInfo::new(fs),
+ ];
+
+ let vertex_bindings = Mvp::as_descriptor_set_layout_bindings();
+ let texture_bindings = Texture::as_descriptor_set_layout_bindings();
+
+ let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
+ bindings: vertex_bindings,
+ ..Default::default()
+ };
+
+ let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
+ bindings: texture_bindings,
+ ..Default::default()
+ };
+
+ let create_info = PipelineDescriptorSetLayoutCreateInfo {
+ set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout],
+ flags: PipelineLayoutCreateFlags::default(),
+ push_constant_ranges: vec![],
+ }
+ .into_pipeline_layout_create_info(device.clone())?;
+
+ let layout = PipelineLayout::new(device.clone(), create_info)?;
+
+ let subpass = PipelineRenderingCreateInfo {
+ color_attachment_formats: vec![Some(swapchain_format)],
+ depth_attachment_format: Some(depth_format),
+ ..Default::default()
+ };
+
+ let pipeline = GraphicsPipeline::new(
+ device.clone(),
+ None,
+ GraphicsPipelineCreateInfo {
+ stages: stages.into_iter().collect(),
+ vertex_input_state: Some(vertex_input_state),
+ input_assembly_state: Some(InputAssemblyState::default()),
+ viewport_state: Some(ViewportState::default()),
+ rasterization_state: Some(RasterizationState::default()),
+ multisample_state: Some(MultisampleState::default()),
+ color_blend_state: Some(ColorBlendState::with_attachment_states(
+ subpass.color_attachment_formats.len() as u32,
+ ColorBlendAttachmentState::default(),
+ )),
+ depth_stencil_state: Some(DepthStencilState {
+ depth: Some(DepthState::simple()),
+ ..Default::default()
+ }),
+ dynamic_state: [DynamicState::Viewport].into_iter().collect(),
+ subpass: Some(subpass.into()),
+ ..GraphicsPipelineCreateInfo::layout(layout)
+ },
+ )?;
+
+ Ok(Self {
+ vertex_buffer,
+ index_buffer,
+ pipeline,
+ })
+ }
+
+ pub fn render(
+ &self,
+ command_buffer: &mut AutoCommandBufferBuilder,
+ descriptor_set_allocator: &Arc,
+ mvp_uniform: &Subbuffer<[Mvp]>,
+ transform_uniform: &Subbuffer<[TransformRaw]>,
+ texture: &Texture,
+ ) -> Result<(), Box> {
+ let layouts = self.pipeline.layout().set_layouts();
+
+ let uniform_descriptor_set =
+ Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], mvp_uniform)?;
+
+ let texture_descriptor_set =
+ Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?;
+
+ command_buffer.bind_pipeline_graphics(self.pipeline.clone())?;
+ command_buffer.bind_descriptor_sets(
+ PipelineBindPoint::Graphics,
+ self.pipeline.layout().clone(),
+ 0,
+ vec![uniform_descriptor_set, texture_descriptor_set],
+ )?;
+ command_buffer
+ .bind_vertex_buffers(0, (self.vertex_buffer.clone(), transform_uniform.clone()))?;
+ command_buffer.bind_index_buffer(self.index_buffer.clone())?;
+
+ unsafe {
+ command_buffer.draw_indexed(
+ INDICES.len() as u32,
+ transform_uniform.len() as u32,
+ 0,
+ 0,
+ 0,
+ )?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/game/mod.rs b/src/game/mod.rs
new file mode 100644
index 0000000..065e8e4
--- /dev/null
+++ b/src/game/mod.rs
@@ -0,0 +1,2 @@
+pub mod assets;
+pub mod scenes;
diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs
new file mode 100644
index 0000000..37fd4ed
--- /dev/null
+++ b/src/game/scenes/main_scene.rs
@@ -0,0 +1,285 @@
+use std::error::Error;
+
+use super::settings_scene::SettingsScene;
+use crate::core::app::DEPTH_IMAGE_ID;
+use crate::core::app::context::WindowContext;
+use crate::core::app::user_event::UserEvent;
+use crate::core::render::primitives::camera::Camera3D;
+use crate::core::render::primitives::transform::Transform;
+use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
+use crate::core::render::texture::Texture;
+use crate::core::scene::Scene;
+use crate::game::assets::square::Square;
+use egui_winit_vulkano::egui;
+use glam::EulerRot;
+use glam::Quat;
+use glam::Vec3;
+use vulkano::{
+ command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract},
+ sync::GpuFuture,
+};
+use winit::window::CursorGrabMode;
+
+pub struct MainSceneState {
+ square: Square,
+ instances: Vec,
+ camera: Camera3D,
+ texture: Texture,
+ speed: f32,
+}
+
+#[derive(Default)]
+pub struct MainScene {
+ state: Option,
+}
+
+impl Scene for MainScene {
+ fn loaded(&self) -> bool {
+ self.state.is_some()
+ }
+
+ fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> {
+ let depth_image_view = app_context.with_renderer_mut(|renderer| {
+ renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+ });
+
+ let swapchain_image_view =
+ app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+
+ let square = Square::new(
+ &app_context.device,
+ &app_context.memory_allocator,
+ swapchain_image_view.format(),
+ depth_image_view.format(),
+ )?;
+
+ let num_instances = 100;
+ let instance_size = 10.0;
+ let instance_spacing = 10.0;
+ let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32;
+ let instances: Vec = (0..num_instances)
+ .map(|i| Transform {
+ position: Vec3::new(
+ (i % num_instances_per_row) as f32 * (instance_spacing + instance_size),
+ 0.0,
+ (i / num_instances_per_row) as f32 * (instance_spacing + instance_size),
+ ),
+ rotation: Quat::from_euler(
+ EulerRot::XYZ,
+ 0.0,
+ rand::random_range(0.0..=360.0),
+ 0.0,
+ ),
+ scale: Vec3::new(instance_size, instance_size, instance_size),
+ })
+ .collect();
+
+ let texture = {
+ let mut uploads = AutoCommandBufferBuilder::primary(
+ app_context.command_buffer_allocator.clone(),
+ app_context.graphics_queue.queue_family_index(),
+ CommandBufferUsage::OneTimeSubmit,
+ )?;
+
+ let texture = Texture::from_file(
+ &app_context.device,
+ &app_context.memory_allocator,
+ &mut uploads,
+ "res/textures/wooden-crate.jpg",
+ )?;
+
+ let _ = uploads
+ .build()?
+ .execute(app_context.graphics_queue.clone())?;
+
+ texture
+ };
+
+ let camera = app_context.with_renderer(|renderer| {
+ Camera3D::new(
+ renderer.aspect_ratio(),
+ std::f32::consts::FRAC_PI_2,
+ 0.01,
+ 1000.0,
+ )
+ });
+
+ self.state = Some(MainSceneState {
+ square,
+ instances,
+ camera,
+ texture,
+ speed: 50.0,
+ });
+
+ Ok(())
+ }
+
+ fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box> {
+ let state = self.state.as_mut().unwrap();
+ app_context.with_input_manager(|input_manager| {
+ app_context.with_timer(|timer| {
+ state.camera.update(
+ input_manager,
+ timer,
+ state.speed,
+ 10.0,
+ app_context.get_aspect_ratio(),
+ );
+ });
+ });
+
+ if app_context
+ .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left"))
+ > 0.0
+ {
+ let _ = app_context
+ .event_loop_proxy
+ .send_event(UserEvent::CursorVisible(app_context.window_id, false));
+ let _ = app_context
+ .event_loop_proxy
+ .send_event(UserEvent::CursorGrabMode(
+ app_context.window_id,
+ CursorGrabMode::Locked,
+ ));
+ }
+
+ if app_context.with_input_manager(|input_manager| {
+ input_manager.get_virtual_input_state("mouse_right")
+ }) > 0.0
+ {
+ let _ = app_context
+ .event_loop_proxy
+ .send_event(UserEvent::CursorVisible(app_context.window_id, true));
+ let _ = app_context
+ .event_loop_proxy
+ .send_event(UserEvent::CursorGrabMode(
+ app_context.window_id,
+ CursorGrabMode::None,
+ ));
+ }
+
+ Ok(())
+ }
+
+ fn render(
+ &mut self,
+ before_future: Box,
+ app_context: &mut WindowContext,
+ ) -> Result, Box> {
+ let state = self.state.as_ref().ok_or("State not loaded")?;
+
+ let mut builder = AutoCommandBufferBuilder::primary(
+ app_context.command_buffer_allocator.clone(),
+ app_context.graphics_queue.queue_family_index(),
+ CommandBufferUsage::OneTimeSubmit,
+ )?;
+
+ {
+ let swapchain_image_view =
+ app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+ let depth_image_view = app_context.with_renderer_mut(|renderer| {
+ renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+ });
+ let config = RenderPassConfig::default();
+ RenderPassManager::begin_standard_rendering(
+ &mut builder,
+ &config,
+ swapchain_image_view,
+ Some(depth_image_view),
+ app_context.get_window_size(),
+ )?;
+ }
+
+ // Create camera uniform using the actual camera
+ let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?;
+
+ let transform_uniform =
+ Transform::create_buffer(&app_context.memory_allocator, &state.instances)?;
+
+ state
+ .square
+ .render(
+ &mut builder,
+ &app_context.descriptor_set_allocator,
+ &camera_uniform,
+ &transform_uniform,
+ &state.texture,
+ )
+ .unwrap();
+
+ RenderPassManager::end_rendering(&mut builder)?;
+
+ let command_buffer = builder.build()?;
+
+ let render_future =
+ before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
+
+ let swapchain_image_view =
+ app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+ let input_manager_status =
+ app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager));
+ let event_loop_proxy = app_context.event_loop_proxy.clone();
+ let delta_time = app_context.get_delta_time();
+ let window_id = app_context.window_id;
+ let window_size = app_context.get_window_size();
+
+ let render_future = app_context.with_gui_mut(|gui| {
+ gui.immediate_ui(|gui| {
+ let ctx = gui.context();
+ egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| {
+ ui.horizontal(|ui| {
+ ui.heading("Vulkan Test - Moteur 3D");
+ ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+ if ui.button("Paramètres").clicked() {
+ let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
+ window_id,
+ Box::new(SettingsScene::default()),
+ ));
+ }
+ if ui.button("Quitter").clicked() {
+ let _ = event_loop_proxy.send_event(UserEvent::Exit(window_id));
+ }
+ });
+ });
+ });
+
+ egui::SidePanel::left("side_panel").show(&ctx, |ui| {
+ ui.heading("Informations");
+
+ ui.separator();
+
+ ui.label(format!("Résolution: {:?}", window_size));
+ ui.label(format!("Delta Time: {:.2}ms", delta_time * 1000.0));
+
+ ui.separator();
+
+ ui.label("Position caméra:");
+ let position = state.camera.get_position();
+ ui.label(format!(" X: {:.2}", position[0]));
+ ui.label(format!(" Y: {:.2}", position[1]));
+ ui.label(format!(" Z: {:.2}", position[2]));
+
+ ui.separator();
+
+ ui.label("Rotation caméra:");
+ let rotation = state.camera.get_rotation();
+ ui.label(format!(" Yaw: {:.2}°", rotation.y.to_degrees()));
+ ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees()));
+
+ ui.separator();
+
+ ui.label(input_manager_status);
+ });
+ });
+
+ gui.draw_on_image(render_future, swapchain_image_view.clone())
+ });
+
+ Ok(render_future)
+ }
+
+ fn unload(&mut self) {
+ self.state = None;
+ }
+}
diff --git a/src/game/scenes/mod.rs b/src/game/scenes/mod.rs
new file mode 100644
index 0000000..516fa6f
--- /dev/null
+++ b/src/game/scenes/mod.rs
@@ -0,0 +1,2 @@
+pub mod main_scene;
+pub mod settings_scene;
diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs
new file mode 100644
index 0000000..466652a
--- /dev/null
+++ b/src/game/scenes/settings_scene.rs
@@ -0,0 +1,136 @@
+use std::error::Error;
+
+use crate::core::app::DEPTH_IMAGE_ID;
+use crate::core::app::context::WindowContext;
+use crate::core::app::user_event::UserEvent;
+use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
+use crate::core::scene::Scene;
+use egui_winit_vulkano::egui;
+use vulkano::{
+ command_buffer::AutoCommandBufferBuilder, command_buffer::CommandBufferUsage, sync::GpuFuture,
+};
+
+use super::main_scene::MainScene;
+
+pub struct SettingsSceneState {
+ current_resolution: [f32; 2],
+ available_resolutions: Vec<(u32, u32)>,
+}
+
+#[derive(Default)]
+pub struct SettingsScene {
+ state: Option,
+}
+
+impl Scene for SettingsScene {
+ fn loaded(&self) -> bool {
+ self.state.is_some()
+ }
+
+ fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> {
+ let current_resolution = app_context.get_window_size();
+ let available_resolutions = app_context.get_available_resolutions();
+
+ self.state = Some(SettingsSceneState {
+ current_resolution,
+ available_resolutions,
+ });
+
+ Ok(())
+ }
+
+ fn update(&mut self, _app_context: &mut WindowContext) -> Result<(), Box> {
+ Ok(())
+ }
+
+ fn render(
+ &mut self,
+ before_future: Box,
+ app_context: &mut WindowContext,
+ ) -> Result, Box> {
+ let state = self.state.as_ref().ok_or("State not found")?;
+
+ let mut builder = AutoCommandBufferBuilder::primary(
+ app_context.command_buffer_allocator.clone(),
+ app_context.graphics_queue.queue_family_index(),
+ CommandBufferUsage::OneTimeSubmit,
+ )?;
+
+ // Utiliser le RenderPassManager
+ {
+ let swapchain_image_view =
+ app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+ let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| {
+ renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+ });
+
+ let config = RenderPassConfig::default();
+ RenderPassManager::begin_standard_rendering(
+ &mut builder,
+ &config,
+ swapchain_image_view,
+ Some(depth_stencil_image_view),
+ app_context.get_window_size(),
+ )?;
+ }
+
+ // Pas de géométrie dans cette scène - juste un écran de paramètres
+ RenderPassManager::end_rendering(&mut builder)?;
+
+ let command_buffer = builder.build()?;
+
+ let render_future =
+ before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
+
+ let swapchain_image_view =
+ app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+
+ let event_loop_proxy = app_context.event_loop_proxy.clone();
+ let window_id = app_context.window_id;
+
+ let render_future = app_context.with_gui_mut(|gui| {
+ gui.immediate_ui(|gui| {
+ let ctx = gui.context();
+
+ egui::CentralPanel::default().show(&ctx, |ui| {
+ ui.heading("Paramètres");
+
+ ui.separator();
+
+ ui.label(format!(
+ "Résolution actuelle: {:?}",
+ state.current_resolution
+ ));
+
+ ui.separator();
+ ui.label("Changer la résolution:");
+
+ for &(width, height) in &state.available_resolutions {
+ if ui.button(format!("{}x{}", width, height)).clicked() {
+ let _ = event_loop_proxy.send_event(UserEvent::ChangeResolution(
+ window_id,
+ width as f32,
+ height as f32,
+ ));
+ }
+ }
+
+ ui.separator();
+
+ if ui.button("Retour au jeu").clicked() {
+ let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
+ window_id,
+ Box::new(MainScene::default()),
+ ));
+ }
+ });
+ });
+
+ gui.draw_on_image(render_future, swapchain_image_view.clone())
+ });
+
+ Ok(render_future)
+ }
+
+ fn unload(&mut self) {}
+}
diff --git a/src/main.rs b/src/main.rs
index 6c01c40..998f725 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,15 +1,108 @@
-use std::error::Error;
-use winit::event_loop::{ControlFlow, EventLoop};
+use core::input::{AxisDirection, InputManager, VirtualBinding};
+use std::collections::HashMap;
-mod renderer;
+use vulkano::device::{DeviceExtensions, DeviceFeatures};
+use vulkano_util::context::{VulkanoConfig, VulkanoContext};
+use winit::{
+ event::MouseButton,
+ event_loop::{ControlFlow, EventLoop},
+ keyboard::{KeyCode, PhysicalKey},
+};
-fn main() -> Result<(), impl Error> {
- env_logger::init();
+mod core;
+mod game;
- let event_loop = EventLoop::new().unwrap();
+fn main() {
+ use tracing_subscriber::layer::SubscriberExt;
+ use tracing_subscriber::util::SubscriberInitExt;
+
+ tracing_subscriber::registry()
+ .with(
+ tracing_subscriber::fmt::layer()
+ .with_target(true)
+ .with_thread_ids(true)
+ .with_file(true)
+ .with_line_number(true),
+ )
+ .with(tracing_tracy::TracyLayer::default())
+ .with(
+ tracing_subscriber::EnvFilter::try_from_default_env()
+ .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
+ )
+ .init();
+
+ let input_manager = InputManager::new(HashMap::from([
+ (
+ "move_forward".to_string(),
+ vec![
+ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal),
+ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert),
+ ],
+ ),
+ (
+ "move_right".to_string(),
+ vec![
+ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal),
+ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert),
+ ],
+ ),
+ (
+ "mouse_x".to_string(),
+ vec![VirtualBinding::MouseX(AxisDirection::Normal)],
+ ),
+ (
+ "mouse_y".to_string(),
+ vec![VirtualBinding::MouseY(AxisDirection::Normal)],
+ ),
+ (
+ "mouse_wheel".to_string(),
+ vec![VirtualBinding::MouseWheelY(AxisDirection::Normal)],
+ ),
+ (
+ "mouse_left".to_string(),
+ vec![VirtualBinding::MouseButton(
+ MouseButton::Left,
+ AxisDirection::Normal,
+ )],
+ ),
+ (
+ "mouse_right".to_string(),
+ vec![VirtualBinding::MouseButton(
+ MouseButton::Right,
+ AxisDirection::Normal,
+ )],
+ ),
+ ]));
+
+ let device_extensions = DeviceExtensions {
+ khr_swapchain: true,
+ ..Default::default()
+ };
+
+ let device_features = DeviceFeatures {
+ dynamic_rendering: true,
+ ..Default::default()
+ };
+
+ let vulkano_config = VulkanoConfig {
+ print_device_name: true,
+ device_extensions,
+ device_features,
+ ..Default::default()
+ };
+
+ let vulkano_context = VulkanoContext::new(vulkano_config);
+
+ let event_loop = EventLoop::with_user_event().build().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
+ let proxy = event_loop.create_proxy();
- let mut app = renderer::App::new(&event_loop);
+ let mut app = core::app::App::new(vulkano_context, input_manager, proxy);
- event_loop.run_app(&mut app)
+ match event_loop.run_app(&mut app) {
+ Ok(_) => {}
+ Err(e) => {
+ tracing::error!("Error running old app: {e}");
+ }
+ }
}
diff --git a/src/renderer/app.rs b/src/renderer/app.rs
deleted file mode 100644
index f791616..0000000
--- a/src/renderer/app.rs
+++ /dev/null
@@ -1,295 +0,0 @@
-use crate::renderer::render_context::RenderContext;
-use crate::renderer::Scene;
-use std::sync::Arc;
-use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
-use vulkano::buffer::BufferUsage;
-use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
-use vulkano::command_buffer::{
- AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo,
-};
-use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
-use vulkano::device::physical::PhysicalDeviceType;
-use vulkano::device::{
- Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
-};
-use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo};
-use vulkano::memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator};
-use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp};
-use vulkano::swapchain::{acquire_next_image, Surface, SwapchainPresentInfo};
-use vulkano::sync::GpuFuture;
-use vulkano::{sync, Validated, Version, VulkanError, VulkanLibrary};
-use winit::application::ApplicationHandler;
-use winit::event::WindowEvent;
-use winit::event_loop::{ActiveEventLoop, EventLoop};
-use winit::window::WindowId;
-
-pub struct App {
- pub instance: Arc,
- pub device: Arc,
- pub queue: Arc,
-
- pub memory_allocator: Arc,
- pub command_buffer_allocator: Arc,
- pub uniform_buffer_allocator: SubbufferAllocator,
- pub descriptor_set_allocator: Arc,
-
- pub rcx: Option,
- scene: Option,
-}
-
-impl App {
- pub fn new(event_loop: &EventLoop<()>) -> Self {
- let library = VulkanLibrary::new().unwrap();
-
- for layer in library.layer_properties().unwrap() {
- log::debug!("Available layer: {}", layer.name());
- }
-
- let required_extensions = Surface::required_extensions(event_loop).unwrap();
-
- let instance = Instance::new(
- library,
- InstanceCreateInfo {
- // Enable enumerating devices that use non-conformant Vulkan implementations.
- // (e.g. MoltenVK)
- flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
- enabled_extensions: required_extensions,
- enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")],
- ..Default::default()
- },
- )
- .unwrap();
-
- let mut device_extensions = DeviceExtensions {
- khr_swapchain: true,
- ..DeviceExtensions::empty()
- };
-
- let (physical_device, queue_family_index) = instance
- .enumerate_physical_devices()
- .unwrap()
- .filter(|p| {
- p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering
- })
- .filter(|p| p.supported_extensions().contains(&device_extensions))
- .filter_map(|p| {
- p.queue_family_properties()
- .iter()
- .enumerate()
- .position(|(i, q)| {
- q.queue_flags.intersects(QueueFlags::GRAPHICS)
- && p.presentation_support(i as u32, event_loop).unwrap()
- })
- .map(|i| (p, i as u32))
- })
- .min_by_key(|(p, _)| match p.properties().device_type {
- PhysicalDeviceType::DiscreteGpu => 0,
- PhysicalDeviceType::IntegratedGpu => 1,
- PhysicalDeviceType::VirtualGpu => 2,
- PhysicalDeviceType::Cpu => 3,
- PhysicalDeviceType::Other => 4,
- _ => 5,
- })
- .expect("no suitable physical device found");
-
- log::debug!(
- "Using device: {} (type: {:?})",
- physical_device.properties().device_name,
- physical_device.properties().device_type,
- );
-
- if physical_device.api_version() < Version::V1_3 {
- device_extensions.khr_dynamic_rendering = true;
- }
-
- log::debug!("Using device extensions: {:#?}", device_extensions);
-
- let (device, mut queues) = Device::new(
- physical_device,
- DeviceCreateInfo {
- queue_create_infos: vec![QueueCreateInfo {
- queue_family_index,
- ..Default::default()
- }],
- enabled_extensions: device_extensions,
- enabled_features: DeviceFeatures {
- dynamic_rendering: true,
- ..DeviceFeatures::empty()
- },
- ..Default::default()
- },
- )
- .unwrap();
-
- let queue = queues.next().unwrap();
- let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
-
- let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
- device.clone(),
- Default::default(),
- ));
-
- let uniform_buffer_allocator = SubbufferAllocator::new(
- memory_allocator.clone(),
- SubbufferAllocatorCreateInfo {
- buffer_usage: BufferUsage::UNIFORM_BUFFER,
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- );
-
- let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
- device.clone(),
- Default::default(),
- ));
-
- Self {
- instance,
- device,
- queue,
- memory_allocator,
- command_buffer_allocator,
- uniform_buffer_allocator,
- descriptor_set_allocator,
- rcx: None,
- scene: None,
- }
- }
-}
-
-impl ApplicationHandler for App {
- fn resumed(&mut self, event_loop: &ActiveEventLoop) {
- let window_attributes = winit::window::Window::default_attributes()
- .with_title("Rust ASH Test")
- .with_inner_size(winit::dpi::PhysicalSize::new(
- f64::from(800),
- f64::from(600),
- ));
-
- let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
-
- let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap();
-
- self.rcx = Some(RenderContext::new(window, surface, &self.device));
- self.scene = Some(Scene::load(&self).unwrap());
- }
-
- fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
- match event {
- WindowEvent::CloseRequested => {
- log::debug!("The close button was pressed; stopping");
- event_loop.exit();
- }
- WindowEvent::Resized(_) => {
- let rcx = self.rcx.as_mut().unwrap();
- rcx.recreate_swapchain = true;
- }
- WindowEvent::RedrawRequested => {
- let (image_index, acquire_future) = {
- let rcx = self.rcx.as_mut().unwrap();
- let window_size = rcx.window.inner_size();
-
- if window_size.width == 0 || window_size.height == 0 {
- return;
- }
-
- rcx.previous_frame_end.as_mut().unwrap().cleanup_finished();
- rcx.update_swapchain().unwrap();
-
- let (image_index, suboptimal, acquire_future) =
- match acquire_next_image(rcx.swapchain.clone(), None)
- .map_err(Validated::unwrap)
- {
- Ok(r) => r,
- Err(VulkanError::OutOfDate) => {
- rcx.recreate_swapchain = true;
- return;
- }
- Err(e) => panic!("failed to acquire next image: {e}"),
- };
-
- if suboptimal {
- rcx.recreate_swapchain = true;
- }
-
- (image_index, acquire_future)
- };
-
- let mut builder = AutoCommandBufferBuilder::primary(
- self.command_buffer_allocator.clone(),
- self.queue.queue_family_index(),
- CommandBufferUsage::OneTimeSubmit,
- )
- .unwrap();
-
- {
- let rcx = self.rcx.as_ref().unwrap();
- builder
- .begin_rendering(RenderingInfo {
- color_attachments: vec![Some(RenderingAttachmentInfo {
- load_op: AttachmentLoadOp::Clear,
- store_op: AttachmentStoreOp::Store,
- clear_value: Some([0.0, 0.0, 0.0, 1.0].into()),
- ..RenderingAttachmentInfo::image_view(
- rcx.attachment_image_views[image_index as usize].clone(),
- )
- })],
- ..Default::default()
- })
- .unwrap()
- .set_viewport(0, [rcx.viewport.clone()].into_iter().collect())
- .unwrap();
- }
-
- if let Some(scene) = self.scene.as_ref() {
- scene.render(&self, &mut builder).unwrap();
- }
-
- builder.end_rendering().unwrap();
-
- let command_buffer = builder.build().unwrap();
-
- {
- let rcx = self.rcx.as_mut().unwrap();
-
- let future = rcx
- .previous_frame_end
- .take()
- .unwrap()
- .join(acquire_future)
- .then_execute(self.queue.clone(), command_buffer)
- .unwrap()
- .then_swapchain_present(
- self.queue.clone(),
- SwapchainPresentInfo::swapchain_image_index(
- rcx.swapchain.clone(),
- image_index,
- ),
- )
- .then_signal_fence_and_flush();
-
- match future.map_err(Validated::unwrap) {
- Ok(future) => {
- rcx.previous_frame_end = Some(future.boxed());
- }
- Err(VulkanError::OutOfDate) => {
- rcx.recreate_swapchain = true;
- rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed());
- }
- Err(e) => {
- println!("failed to flush future: {e}");
- rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed());
- }
- }
- }
- }
- _ => {}
- }
- }
-
- fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
- let rcx = self.rcx.as_mut().unwrap();
- rcx.window.request_redraw();
- }
-}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
deleted file mode 100644
index bbcff9a..0000000
--- a/src/renderer/mod.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-mod app;
-mod pipelines;
-mod render_context;
-mod vertex;
-pub use app::App;
-
-mod scene;
-pub use scene::Scene;
-pub use vertex::Vertex2D;
diff --git a/src/renderer/pipelines/mod.rs b/src/renderer/pipelines/mod.rs
deleted file mode 100644
index e5f30a7..0000000
--- a/src/renderer/pipelines/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod triangle_pipeline;
diff --git a/src/renderer/pipelines/triangle_pipeline.rs b/src/renderer/pipelines/triangle_pipeline.rs
deleted file mode 100644
index f6001f7..0000000
--- a/src/renderer/pipelines/triangle_pipeline.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-use std::collections::BTreeMap;
-use std::error::Error;
-use std::sync::Arc;
-use vulkano::descriptor_set::layout::{
- DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
-};
-use vulkano::device::Device;
-use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState};
-use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
-use vulkano::pipeline::graphics::multisample::MultisampleState;
-use vulkano::pipeline::graphics::rasterization::RasterizationState;
-use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo;
-use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition};
-use vulkano::pipeline::graphics::viewport::ViewportState;
-use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo;
-use vulkano::pipeline::layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags};
-use vulkano::pipeline::{
- DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
-};
-use vulkano::shader::{EntryPoint, ShaderStages};
-use vulkano::swapchain::Swapchain;
-
-use crate::renderer::Vertex2D;
-
-pub mod shaders {
- pub mod vs {
- vulkano_shaders::shader! {
- ty: "vertex",
- path: r"res/shaders/vertex.vert",
- }
- }
-
- pub mod fs {
- vulkano_shaders::shader! {
- ty: "fragment",
- path: r"res/shaders/vertex.frag",
- }
- }
-}
-
-pub fn create_triangle_pipeline(
- device: &Arc,
- swapchain: &Arc,
-) -> Result, Box> {
- let (vs, fs) = load_shaders(device)?;
- let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?;
-
- let stages = [
- PipelineShaderStageCreateInfo::new(vs),
- PipelineShaderStageCreateInfo::new(fs),
- ];
-
- let mut bindings = BTreeMap::::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 descriptor_set_layout = DescriptorSetLayoutCreateInfo {
- bindings,
- ..Default::default()
- };
-
- let create_info = PipelineDescriptorSetLayoutCreateInfo {
- set_layouts: vec![descriptor_set_layout],
- flags: PipelineLayoutCreateFlags::default(),
- push_constant_ranges: vec![],
- }
- .into_pipeline_layout_create_info(device.clone())?;
-
- let layout = PipelineLayout::new(device.clone(), create_info)?;
-
- let subpass = PipelineRenderingCreateInfo {
- color_attachment_formats: vec![Some(swapchain.image_format())],
- ..Default::default()
- };
-
- let pipeline = GraphicsPipeline::new(
- device.clone(),
- None,
- GraphicsPipelineCreateInfo {
- stages: stages.into_iter().collect(),
- vertex_input_state: Some(vertex_input_state),
- input_assembly_state: Some(InputAssemblyState::default()),
- viewport_state: Some(ViewportState::default()),
- rasterization_state: Some(RasterizationState::default()),
- multisample_state: Some(MultisampleState::default()),
- color_blend_state: Some(ColorBlendState::with_attachment_states(
- subpass.color_attachment_formats.len() as u32,
- ColorBlendAttachmentState::default(),
- )),
- dynamic_state: [DynamicState::Viewport].into_iter().collect(),
- subpass: Some(subpass.into()),
- ..GraphicsPipelineCreateInfo::layout(layout)
- },
- )?;
-
- Ok(pipeline)
-}
-
-fn load_shaders(device: &Arc) -> Result<(EntryPoint, EntryPoint), Box> {
- let vs = shaders::vs::load(device.clone())?
- .entry_point("main")
- .ok_or("Failed find main entry point of vertex shader".to_string())?;
-
- let fs = shaders::fs::load(device.clone())?
- .entry_point("main")
- .ok_or("Failed find main entry point of fragment shader".to_string())?;
-
- Ok((vs, fs))
-}
diff --git a/src/renderer/render_context.rs b/src/renderer/render_context.rs
deleted file mode 100644
index dd7e840..0000000
--- a/src/renderer/render_context.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-use std::sync::Arc;
-use vulkano::device::Device;
-use vulkano::image::view::ImageView;
-use vulkano::image::{Image, ImageUsage};
-use vulkano::pipeline::graphics::viewport::Viewport;
-use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo};
-use vulkano::sync::GpuFuture;
-use vulkano::{sync, Validated, VulkanError};
-use winit::window::Window;
-
-pub struct RenderContext {
- pub(super) window: Arc,
- pub(super) swapchain: Arc,
- pub(super) attachment_image_views: Vec>,
- pub(super) viewport: Viewport,
- pub(super) recreate_swapchain: bool,
- pub(super) previous_frame_end: Option>,
-}
-
-impl RenderContext {
- pub fn new(window: Arc, surface: Arc, device: &Arc) -> Self {
- let window_size = window.inner_size();
-
- let (swapchain, images) = {
- let surface_capabilities = device
- .physical_device()
- .surface_capabilities(&surface, Default::default())
- .unwrap();
-
- let (image_format, _) = device
- .physical_device()
- .surface_formats(&surface, Default::default())
- .unwrap()[0];
-
- Swapchain::new(
- device.clone(),
- surface,
- SwapchainCreateInfo {
- // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works.
- min_image_count: surface_capabilities.min_image_count.max(2),
- image_format,
- image_extent: window_size.into(),
- image_usage: ImageUsage::COLOR_ATTACHMENT,
- composite_alpha: surface_capabilities
- .supported_composite_alpha
- .into_iter()
- .next()
- .unwrap(),
-
- ..Default::default()
- },
- )
- .unwrap()
- };
-
- let attachment_image_views = window_size_dependent_setup(&images);
-
- let viewport = Viewport {
- offset: [0.0, 0.0],
- extent: window_size.into(),
- depth_range: 0.0..=1.0,
- };
-
- let recreate_swapchain = false;
- let previous_frame_end = Some(sync::now(device.clone()).boxed());
-
- Self {
- window,
- swapchain,
- attachment_image_views,
- viewport,
- recreate_swapchain,
- previous_frame_end,
- }
- }
-
- pub fn update_swapchain(&mut self) -> Result<(), Validated> {
- if !self.recreate_swapchain {
- return Ok(());
- }
-
- let window_size = self.window.inner_size();
- let (new_swapchain, new_images) = self.swapchain.recreate(SwapchainCreateInfo {
- image_extent: window_size.into(),
- ..self.swapchain.create_info()
- })?;
-
- self.swapchain = new_swapchain;
- self.attachment_image_views = window_size_dependent_setup(&new_images);
- self.viewport.extent = window_size.into();
- self.recreate_swapchain = false;
-
- Ok(())
- }
-}
-
-fn window_size_dependent_setup(images: &[Arc]) -> Vec> {
- images
- .iter()
- .map(|image| ImageView::new_default(image.clone()).unwrap())
- .collect::>()
-}
diff --git a/src/renderer/scene.rs b/src/renderer/scene.rs
deleted file mode 100644
index e3c6b58..0000000
--- a/src/renderer/scene.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-use crate::renderer::pipelines::triangle_pipeline::shaders::vs;
-use glam::{Mat3, Mat4, Vec3};
-use std::error::Error;
-use std::sync::Arc;
-use std::time::Instant;
-use vulkano::buffer::Subbuffer;
-use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
-use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
-use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint};
-
-use crate::renderer::{pipelines::triangle_pipeline::create_triangle_pipeline, App, Vertex2D};
-
-const VERTICES: [Vertex2D; 12] = [
- // Triangle en haut à gauche
- Vertex2D {
- position: [-0.5, -0.75],
- color: [1.0, 0.0, 0.0],
- },
- Vertex2D {
- position: [-0.75, -0.25],
- color: [0.0, 1.0, 0.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],
- },
- 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],
- },
-];
-
-pub struct Scene {
- pipeline: Arc,
- vertex_buffer: Subbuffer<[Vertex2D]>,
-
- rotation_start: Instant,
-}
-
-impl Scene {
- pub fn load(app: &App) -> Result> {
- let pipeline = create_triangle_pipeline(&app.device, &app.rcx.as_ref().unwrap().swapchain)?;
- let vertex_buffer =
- Vertex2D::create_buffer(Vec::from_iter(VERTICES), &app.memory_allocator)?;
-
- Ok(Scene {
- pipeline,
- vertex_buffer,
- rotation_start: Instant::now(),
- })
- }
-
- pub fn render(
- &self,
- app: &App,
- builder: &mut AutoCommandBufferBuilder,
- ) -> Result<(), Box> {
- let vertex_count = self.vertex_buffer.len() as u32;
- let instance_count = vertex_count / 3;
-
- let uniform_buffer = self.get_uniform_buffer(app);
- let layout = &self.pipeline.layout().set_layouts()[0];
- let descriptor_set = DescriptorSet::new(
- app.descriptor_set_allocator.clone(),
- layout.clone(),
- [WriteDescriptorSet::buffer(0, uniform_buffer)],
- [],
- )
- .unwrap();
-
- unsafe {
- builder
- .bind_pipeline_graphics(self.pipeline.clone())?
- .bind_descriptor_sets(
- PipelineBindPoint::Graphics,
- self.pipeline.layout().clone(),
- 0,
- descriptor_set,
- )?
- .bind_vertex_buffers(0, self.vertex_buffer.clone())?
- .draw(vertex_count, instance_count, 0, 0)?;
- }
-
- Ok(())
- }
-
- fn get_uniform_buffer(&self, app: &App) -> Subbuffer {
- let swapchain = &app.rcx.as_ref().unwrap().swapchain;
- let elapsed = self.rotation_start.elapsed();
- let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0;
- let rotation = Mat3::from_rotation_y(rotation as f32);
-
- // NOTE: This teapot was meant for OpenGL where the origin is at the lower left
- // instead the origin is at the upper left in Vulkan, so we reverse the Y axis.
- let aspect_ratio = swapchain.image_extent()[0] as f32 / swapchain.image_extent()[1] as f32;
-
- let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0);
- let view = Mat4::look_at_rh(
- Vec3::new(0.3, 0.3, 1.0),
- Vec3::new(0.0, 0.0, 0.0),
- Vec3::new(0.0, -1.0, 0.0),
- );
- let scale = Mat4::from_scale(Vec3::splat(1.0));
-
- let uniform_data = vs::MVPData {
- world: Mat4::from_mat3(rotation).to_cols_array_2d(),
- view: (view * scale).to_cols_array_2d(),
- projection: proj.to_cols_array_2d(),
- };
-
- let buffer = app.uniform_buffer_allocator.allocate_sized().unwrap();
- *buffer.write().unwrap() = uniform_data;
-
- buffer
- }
-}
diff --git a/src/renderer/vertex.rs b/src/renderer/vertex.rs
deleted file mode 100644
index fc2ee21..0000000
--- a/src/renderer/vertex.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use std::sync::Arc;
-use vulkano::buffer::{
- AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
-};
-use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
-use vulkano::pipeline::graphics::vertex_input::Vertex;
-use vulkano::Validated;
-
-#[derive(BufferContents, Vertex)]
-#[repr(C)]
-pub struct Vertex2D {
- #[format(R32G32_SFLOAT)]
- pub position: [f32; 2],
-
- #[format(R32G32B32_SFLOAT)]
- pub color: [f32; 3],
-}
-
-impl Vertex2D {
- pub fn create_buffer(
- vertices: Vec,
- memory_allocator: &Arc,
- ) -> Result, Validated> {
- Buffer::from_iter(
- memory_allocator.clone(),
- BufferCreateInfo {
- usage: BufferUsage::VERTEX_BUFFER,
- ..Default::default()
- },
- AllocationCreateInfo {
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- vertices,
- )
- }
-}