(&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 ac0d7c9..998f725 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,108 @@
-use winit::event_loop::EventLoop;
+use core::input::{AxisDirection, InputManager, VirtualBinding};
+use std::collections::HashMap;
-mod renderer;
-mod vulkan;
+use vulkano::device::{DeviceExtensions, DeviceFeatures};
+use vulkano_util::context::{VulkanoConfig, VulkanoContext};
+use winit::{
+ event::MouseButton,
+ event_loop::{ControlFlow, EventLoop},
+ keyboard::{KeyCode, PhysicalKey},
+};
-use renderer::app::App;
-use vulkan::context::VulkanContext;
+mod core;
+mod game;
fn main() {
- let event_loop = EventLoop::new().unwrap();
- let vulkan_context = VulkanContext::new(&event_loop).unwrap();
- let mut app = App::new(vulkan_context);
- event_loop.run_app(&mut app).unwrap();
+ 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 = core::app::App::new(vulkano_context, input_manager, proxy);
+
+ 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 923d70c..0000000
--- a/src/renderer/app.rs
+++ /dev/null
@@ -1,262 +0,0 @@
-use crate::renderer::components::{Entity, Material, Mesh, Transform};
-use crate::vulkan::context::VulkanContext;
-use crate::vulkan::pipeline::{Pipeline, triangle::TrianglePipeline};
-use crate::vulkan::renderer::VulkanRenderer;
-use crate::vulkan::resources::vertex::{MVPData, Vertex2D};
-use std::error::Error;
-use std::sync::Arc;
-use vulkano::VulkanError;
-use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage};
-use vulkano::command_buffer::{
- AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer,
- RenderingAttachmentInfo, RenderingInfo,
-};
-use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
-use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter};
-use vulkano::pipeline::{Pipeline as VulkanPipeline, PipelineBindPoint};
-use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp};
-use vulkano::swapchain::Surface;
-use winit::application::ApplicationHandler;
-use winit::event::WindowEvent;
-use winit::event_loop::ActiveEventLoop;
-use winit::window::WindowId;
-
-pub struct App {
- pub vulkan_context: VulkanContext,
- pub renderer: Option,
- pub entities: Vec,
-}
-
-impl App {
- pub fn new(vulkan_context: VulkanContext) -> Self {
- Self {
- vulkan_context,
- renderer: None,
- entities: Vec::new(),
- }
- }
-
- pub fn setup_test_entities(&mut self) -> Result<(), Box> {
- // Créer un pipeline de test
- let pipeline = TrianglePipeline::new(&self.vulkan_context.device);
-
- // Créer un buffer de vertex pour un triangle
- let vertices = [
- Vertex2D {
- position: [-0.5, -0.5],
- color: [1.0, 0.0, 0.0], // Rouge
- },
- Vertex2D {
- position: [0.5, -0.5],
- color: [0.0, 1.0, 0.0], // Vert
- },
- Vertex2D {
- position: [0.0, 0.5],
- color: [0.0, 0.0, 1.0], // Bleu
- },
- ];
-
- let vertex_buffer =
- Vertex2D::create_buffer(vertices.to_vec(), &self.vulkan_context.memory_allocator)
- .unwrap();
-
- // Créer un buffer uniform pour les matrices MVP
- let mvp_data = MVPData {
- world: [
- [1.0, 0.0, 0.0, 0.0],
- [0.0, 1.0, 0.0, 0.0],
- [0.0, 0.0, 1.0, 0.0],
- [0.0, 0.0, 0.0, 1.0],
- ],
- view: [
- [1.0, 0.0, 0.0, 0.0],
- [0.0, 1.0, 0.0, 0.0],
- [0.0, 0.0, 1.0, 0.0],
- [0.0, 0.0, 0.0, 1.0],
- ],
- projection: [
- [1.0, 0.0, 0.0, 0.0],
- [0.0, 1.0, 0.0, 0.0],
- [0.0, 0.0, 1.0, 0.0],
- [0.0, 0.0, 0.0, 1.0],
- ],
- };
-
- let uniform_buffer = Buffer::from_data(
- self.vulkan_context.memory_allocator.clone(),
- BufferCreateInfo {
- usage: BufferUsage::UNIFORM_BUFFER,
- ..Default::default()
- },
- AllocationCreateInfo {
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- mvp_data,
- )
- .unwrap();
-
- // Créer un descriptor set de test
- let descriptor_set = DescriptorSet::new(
- self.vulkan_context.descriptor_set_allocator.clone(),
- pipeline
- .get_pipeline()
- .layout()
- .set_layouts()
- .get(0)
- .unwrap()
- .clone(),
- [WriteDescriptorSet::buffer(0, uniform_buffer)],
- [],
- )?;
-
- let material = Material {
- pipeline: pipeline.get_pipeline().clone(),
- descriptor_set,
- };
-
- // Créer quelques entités de test
- let mut entities = Vec::new();
- for i in 0..3 {
- entities.push(Entity {
- mesh: Mesh {
- vertex_buffer: vertex_buffer.clone(),
- vertex_count: 3,
- instance_count: 1,
- },
- material: material.clone(),
- transform: Transform {
- position: [i as f32 * 0.5 - 0.5, 0.0, 0.0],
- rotation: [0.0, 0.0, 0.0],
- scale: [1.0, 1.0, 1.0],
- },
- });
- }
- self.entities = entities;
-
- Ok(())
- }
-
- pub fn render(
- &self,
- command_buffer: &mut AutoCommandBufferBuilder,
- ) -> Result<(), Box> {
- for entity in &self.entities {
- command_buffer
- .bind_pipeline_graphics(entity.material.pipeline.clone())
- .unwrap()
- .bind_descriptor_sets(
- PipelineBindPoint::Graphics,
- entity.material.pipeline.layout().clone(),
- 0,
- entity.material.descriptor_set.clone(),
- )
- .unwrap()
- .bind_vertex_buffers(0, entity.mesh.vertex_buffer.clone())
- .unwrap();
- unsafe {
- command_buffer
- .draw(entity.mesh.vertex_count, 1, 0, 0)
- .unwrap();
- }
- }
- Ok(())
- }
-}
-
-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.vulkan_context.instance.clone(), window.clone()).unwrap();
-
- self.renderer = Some(VulkanRenderer::new(
- window,
- surface,
- self.vulkan_context.device.clone(),
- self.vulkan_context.queue.clone(),
- ));
-
- self.setup_test_entities().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 renderer = self.renderer.as_mut().unwrap();
- renderer.recreate_swapchain = true;
- }
- WindowEvent::RedrawRequested => {
- let (image_index, acquire_future) =
- match self.renderer.as_mut().unwrap().begin_frame() {
- Ok(r) => r,
- Err(VulkanError::OutOfDate) => return,
- Err(e) => panic!("failed to acquire next image: {e}"),
- };
-
- let mut builder = AutoCommandBufferBuilder::primary(
- self.vulkan_context.command_buffer_allocator.clone(),
- self.vulkan_context.queue.queue_family_index(),
- CommandBufferUsage::OneTimeSubmit,
- )
- .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(
- self.renderer.as_ref().unwrap().attachment_image_views
- [image_index as usize]
- .clone(),
- )
- })],
- ..Default::default()
- })
- .unwrap()
- .set_viewport(
- 0,
- [self.renderer.as_ref().unwrap().viewport.clone()]
- .into_iter()
- .collect(),
- )
- .unwrap();
- }
-
- self.render(&mut builder).unwrap();
-
- {
- builder.end_rendering().unwrap();
- }
-
- self.renderer
- .as_mut()
- .unwrap()
- .end_frame(image_index, acquire_future, builder)
- .unwrap();
- }
- _ => {}
- }
- }
-
- fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
- let renderer = self.renderer.as_mut().unwrap();
- renderer.window.request_redraw();
- }
-}
diff --git a/src/renderer/components.rs b/src/renderer/components.rs
deleted file mode 100644
index 968539e..0000000
--- a/src/renderer/components.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use std::sync::Arc;
-use vulkano::buffer::Subbuffer;
-use vulkano::buffer::allocator::SubbufferAllocator;
-use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
-use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
-use vulkano::descriptor_set::DescriptorSet;
-use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
-use vulkano::device::Device;
-use vulkano::memory::allocator::StandardMemoryAllocator;
-use vulkano::pipeline::GraphicsPipeline;
-use vulkano::pipeline::Pipeline;
-
-use crate::vulkan::resources::vertex::Vertex2D;
-
-#[derive(Clone)]
-pub struct Mesh {
- pub vertex_buffer: Subbuffer<[Vertex2D]>,
- pub vertex_count: u32,
- pub instance_count: u32,
-}
-
-#[derive(Clone)]
-pub struct Material {
- pub pipeline: Arc,
- pub descriptor_set: Arc,
-}
-
-#[derive(Clone)]
-pub struct Transform {
- pub position: [f32; 3],
- pub rotation: [f32; 3],
- pub scale: [f32; 3],
-}
-
-#[derive(Clone)]
-pub struct RenderResources {
- pub device: Arc,
- pub memory_allocator: Arc,
- pub command_buffer_allocator: Arc,
- pub uniform_buffer_allocator: Arc,
- pub descriptor_set_allocator: Arc,
-}
-
-pub struct Entity {
- pub mesh: Mesh,
- pub material: Material,
- pub transform: Transform,
-}
-
-pub fn render_system(
- _resources: &RenderResources,
- builder: &mut AutoCommandBufferBuilder,
- entities: &[Entity],
-) -> Result<(), Box> {
- for entity in entities {
- unsafe {
- builder
- .bind_pipeline_graphics(entity.material.pipeline.clone())?
- .bind_descriptor_sets(
- vulkano::pipeline::PipelineBindPoint::Graphics,
- entity.material.pipeline.layout().clone(),
- 0,
- entity.material.descriptor_set.clone(),
- )?
- .bind_vertex_buffers(0, entity.mesh.vertex_buffer.clone())?
- .draw(entity.mesh.vertex_count, entity.mesh.instance_count, 0, 0)?;
- }
- }
-
- Ok(())
-}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
deleted file mode 100644
index 13545c4..0000000
--- a/src/renderer/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod app;
-pub mod components;
diff --git a/src/vulkan/context.rs b/src/vulkan/context.rs
deleted file mode 100644
index bd1aeba..0000000
--- a/src/vulkan/context.rs
+++ /dev/null
@@ -1,135 +0,0 @@
-use std::sync::Arc;
-use vulkano::buffer::BufferUsage;
-use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
-use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
-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::swapchain::Surface;
-use vulkano::{Version, VulkanLibrary};
-use winit::event_loop::EventLoop;
-
-pub struct VulkanContext {
- pub instance: Arc,
- pub device: Arc,
- pub queue: Arc,
- pub memory_allocator: Arc,
- pub command_buffer_allocator: Arc,
- pub uniform_buffer_allocator: Arc,
- pub descriptor_set_allocator: Arc,
-}
-
-impl VulkanContext {
- pub fn new(event_loop: &EventLoop<()>) -> Result> {
- 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 {
- 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))
- .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 = Arc::new(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(),
- ));
-
- Ok(Self {
- instance,
- device,
- queue,
- memory_allocator,
- command_buffer_allocator,
- uniform_buffer_allocator,
- descriptor_set_allocator,
- })
- }
-}
diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs
deleted file mode 100644
index 8a4bdd9..0000000
--- a/src/vulkan/mod.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-pub mod context;
-pub mod pipeline;
-pub mod renderer;
-pub mod resources;
diff --git a/src/vulkan/pipeline/mod.rs b/src/vulkan/pipeline/mod.rs
deleted file mode 100644
index f9ba6d5..0000000
--- a/src/vulkan/pipeline/mod.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-pub mod triangle;
-
-use std::sync::Arc;
-use vulkano::device::Device;
-use vulkano::pipeline::GraphicsPipeline;
-
-pub trait Pipeline {
- fn create_pipeline(device: &Arc) -> Arc;
- fn get_pipeline(&self) -> &Arc;
-}
-
-pub struct PipelineManager {
- pipelines: std::collections::HashMap>,
-}
-
-impl PipelineManager {
- pub fn new() -> Self {
- Self {
- pipelines: std::collections::HashMap::new(),
- }
- }
-
- pub fn register_pipeline(&mut self, name: String, pipeline: Arc) {
- self.pipelines.insert(name, pipeline);
- }
-
- pub fn get_pipeline(&self, name: &str) -> Option<&Arc> {
- self.pipelines.get(name)
- }
-}
diff --git a/src/vulkan/pipeline/triangle.rs b/src/vulkan/pipeline/triangle.rs
deleted file mode 100644
index c625e66..0000000
--- a/src/vulkan/pipeline/triangle.rs
+++ /dev/null
@@ -1,127 +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::GraphicsPipelineCreateInfo;
-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::layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags};
-use vulkano::pipeline::{
- DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
-};
-use vulkano::shader::{EntryPoint, ShaderStages};
-
-use crate::vulkan::resources::vertex::Vertex2D;
-
-use super::Pipeline;
-
-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 struct TrianglePipeline {
- pipeline: Arc,
-}
-
-impl super::Pipeline for TrianglePipeline {
- fn create_pipeline(device: &Arc) -> Arc {
- let (vs, fs) = load_shaders(device).unwrap();
- let vertex_input_state = Vertex2D::per_vertex().definition(&vs).unwrap();
-
- 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())
- .unwrap();
-
- let layout = PipelineLayout::new(device.clone(), create_info).unwrap();
-
- let subpass = PipelineRenderingCreateInfo {
- color_attachment_formats: vec![Some(vulkano::format::Format::B8G8R8A8_UNORM)],
- ..Default::default()
- };
-
- 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)
- },
- )
- .unwrap()
- }
-
- fn get_pipeline(&self) -> &Arc {
- &self.pipeline
- }
-}
-
-impl TrianglePipeline {
- pub fn new(device: &Arc) -> Self {
- Self {
- pipeline: Self::create_pipeline(device),
- }
- }
-}
-
-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/vulkan/renderer.rs b/src/vulkan/renderer.rs
deleted file mode 100644
index 079276b..0000000
--- a/src/vulkan/renderer.rs
+++ /dev/null
@@ -1,167 +0,0 @@
-use std::sync::Arc;
-use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer};
-use vulkano::device::Queue;
-use vulkano::image::view::ImageView;
-use vulkano::pipeline::graphics::viewport::Viewport;
-use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo};
-use vulkano::sync::{self, GpuFuture};
-use vulkano::{Validated, VulkanError};
-use winit::window::Window;
-
-pub struct VulkanRenderer {
- pub window: Arc,
- pub surface: Arc,
- pub swapchain: Arc,
- pub queue: Arc,
- pub attachment_image_views: Vec>,
- pub previous_frame_end: Option>,
- pub recreate_swapchain: bool,
- pub viewport: Viewport,
-}
-
-impl VulkanRenderer {
- pub fn new(
- window: Arc,
- surface: Arc,
- device: Arc,
- queue: Arc,
- ) -> Self {
- let window_size = window.inner_size();
- let surface_formats = device
- .physical_device()
- .surface_formats(&surface, Default::default())
- .unwrap();
- let surface_format = surface_formats[0];
-
- let (swapchain, images) = Swapchain::new(
- device.clone(),
- surface.clone(),
- SwapchainCreateInfo {
- image_extent: window_size.into(),
- image_usage: vulkano::image::ImageUsage::COLOR_ATTACHMENT,
- image_format: surface_format.0,
- ..Default::default()
- },
- )
- .unwrap();
-
- let attachment_image_views = images
- .into_iter()
- .map(|image| ImageView::new_default(image).unwrap())
- .collect();
-
- let viewport = Viewport {
- offset: [0.0, 0.0],
- extent: window_size.into(),
- depth_range: 0.0..=1.0,
- };
-
- Self {
- window,
- surface,
- swapchain,
- queue,
- attachment_image_views,
- previous_frame_end: Some(sync::now(device).boxed()),
- recreate_swapchain: false,
- viewport,
- }
- }
-
- pub fn begin_frame(&mut self) -> Result<(u32, Box), VulkanError> {
- self.previous_frame_end.as_mut().unwrap().cleanup_finished();
-
- if self.recreate_swapchain {
- self.recreate_swapchain();
- self.recreate_swapchain = false;
- }
-
- let (image_index, suboptimal, acquire_future) =
- match vulkano::swapchain::acquire_next_image(self.swapchain.clone(), None)
- .map_err(Validated::unwrap)
- {
- Ok(r) => r,
- Err(VulkanError::OutOfDate) => {
- self.recreate_swapchain = true;
- return Err(VulkanError::OutOfDate);
- }
- Err(e) => panic!("failed to acquire next image: {e}"),
- };
-
- if suboptimal {
- self.recreate_swapchain = true;
- }
-
- Ok((image_index, acquire_future.boxed()))
- }
-
- pub fn end_frame(
- &mut self,
- image_index: u32,
- acquire_future: Box,
- command_buffer: AutoCommandBufferBuilder,
- ) -> Result<(), VulkanError> {
- let command_buffer = command_buffer.build().unwrap();
- let future = self
- .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(self.swapchain.clone(), image_index),
- )
- .then_signal_fence_and_flush();
-
- match future.map_err(Validated::unwrap) {
- Ok(future) => {
- self.previous_frame_end = Some(future.boxed());
- Ok(())
- }
- Err(VulkanError::OutOfDate) => {
- self.recreate_swapchain = true;
- self.previous_frame_end = Some(sync::now(self.queue.device().clone()).boxed());
- Ok(())
- }
- Err(e) => {
- println!("failed to flush future: {e}");
- self.previous_frame_end = Some(sync::now(self.queue.device().clone()).boxed());
- Ok(())
- }
- }
- }
-
- fn recreate_swapchain(&mut self) {
- let image_extent: [u32; 2] = self.window.inner_size().into();
- if image_extent.contains(&0) {
- return;
- }
-
- let surface_formats = self
- .queue
- .device()
- .physical_device()
- .surface_formats(&self.surface, Default::default())
- .unwrap();
- let surface_format = surface_formats[0];
-
- let (new_swapchain, new_images) = self
- .swapchain
- .recreate(SwapchainCreateInfo {
- image_extent,
- image_usage: vulkano::image::ImageUsage::COLOR_ATTACHMENT,
- image_format: surface_format.0,
- ..self.swapchain.create_info()
- })
- .expect("failed to recreate swapchain");
-
- self.swapchain = new_swapchain;
- self.attachment_image_views = new_images
- .into_iter()
- .map(|image| ImageView::new_default(image).unwrap())
- .collect();
- self.viewport.extent = [image_extent[0] as f32, image_extent[1] as f32];
- }
-}
diff --git a/src/vulkan/resources/buffer.rs b/src/vulkan/resources/buffer.rs
deleted file mode 100644
index 318d96e..0000000
--- a/src/vulkan/resources/buffer.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use std::sync::Arc;
-use vulkano::buffer::BufferContents;
-use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
-use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
-
-pub struct BufferManager {
- memory_allocator: Arc,
-}
-
-impl BufferManager {
- pub fn new(memory_allocator: Arc) -> Self {
- Self { memory_allocator }
- }
-
- pub fn create_vertex_buffer(&self, data: &[T]) -> Subbuffer<[T]> {
- Buffer::from_iter(
- self.memory_allocator.clone(),
- BufferCreateInfo {
- usage: BufferUsage::VERTEX_BUFFER,
- ..Default::default()
- },
- AllocationCreateInfo {
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- data.iter().cloned(),
- )
- .unwrap()
- }
-
- pub fn create_index_buffer(&self, data: &[u32]) -> Subbuffer<[u32]> {
- Buffer::from_iter(
- self.memory_allocator.clone(),
- BufferCreateInfo {
- usage: BufferUsage::INDEX_BUFFER,
- ..Default::default()
- },
- AllocationCreateInfo {
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- data.iter().cloned(),
- )
- .unwrap()
- }
-
- pub fn create_uniform_buffer(&self, data: &T) -> Subbuffer {
- Buffer::from_data(
- self.memory_allocator.clone(),
- BufferCreateInfo {
- usage: BufferUsage::UNIFORM_BUFFER,
- ..Default::default()
- },
- AllocationCreateInfo {
- memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
- | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
- ..Default::default()
- },
- *data,
- )
- .unwrap()
- }
-}
diff --git a/src/vulkan/resources/descriptor.rs b/src/vulkan/resources/descriptor.rs
deleted file mode 100644
index 6b7af8a..0000000
--- a/src/vulkan/resources/descriptor.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::sync::Arc;
-use vulkano::descriptor_set::DescriptorSet;
-use vulkano::descriptor_set::WriteDescriptorSet;
-use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
-use vulkano::descriptor_set::layout::{
- DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
-};
-use vulkano::device::Device;
-use vulkano::shader::ShaderStages;
-
-pub struct DescriptorManager {
- device: Arc,
- allocator: Arc,
-}
-
-impl DescriptorManager {
- pub fn new(device: Arc, allocator: Arc) -> Self {
- Self { device, allocator }
- }
-
- pub fn create_descriptor_set_layout(
- &self,
- bindings: &[(u32, DescriptorType, u32)],
- ) -> Arc {
- let mut bindings_map = std::collections::BTreeMap::new();
- for (binding_index, ty, _count) in bindings {
- let mut binding = DescriptorSetLayoutBinding::descriptor_type(*ty);
- binding.stages = ShaderStages::all_graphics();
- bindings_map.insert(*binding_index, binding);
- }
-
- let create_info = DescriptorSetLayoutCreateInfo {
- bindings: bindings_map,
- ..Default::default()
- };
-
- DescriptorSetLayout::new(self.device.clone(), create_info).unwrap()
- }
-
- pub fn create_descriptor_set(
- &self,
- layout: &Arc,
- writes: Vec,
- ) -> Arc {
- DescriptorSet::new(self.allocator.clone(), layout.clone(), writes, []).unwrap()
- }
-}
diff --git a/src/vulkan/resources/mod.rs b/src/vulkan/resources/mod.rs
deleted file mode 100644
index 0c2b51c..0000000
--- a/src/vulkan/resources/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod buffer;
-pub mod descriptor;
-pub mod vertex;
diff --git a/src/vulkan/resources/vertex.rs b/src/vulkan/resources/vertex.rs
deleted file mode 100644
index cc25b1b..0000000
--- a/src/vulkan/resources/vertex.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::sync::Arc;
-use vulkano::Validated;
-use vulkano::buffer::{
- AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
-};
-use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
-use vulkano::pipeline::graphics::vertex_input::Vertex;
-
-#[derive(BufferContents, Vertex, Clone)]
-#[repr(C)]
-pub struct Vertex2D {
- #[format(R32G32_SFLOAT)]
- pub position: [f32; 2],
-
- #[format(R32G32B32_SFLOAT)]
- pub color: [f32; 3],
-}
-
-#[derive(BufferContents, Clone)]
-#[repr(C)]
-pub struct MVPData {
- pub world: [[f32; 4]; 4],
- pub view: [[f32; 4]; 4],
- pub projection: [[f32; 4]; 4],
-}
-
-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.into_iter(),
- )
- }
-}