From e8bcaae2a768b3a7abfa4ddc79dc943ae2b2413d Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 31 Dec 2024 11:19:23 +0100 Subject: [PATCH] render egui to the screen (finally) --- Cargo.toml | 2 +- crates/game/src/main.rs | 6 +- crates/renderer/Cargo.toml | 8 +- crates/renderer/shaders/egui.slang | 13 +- crates/renderer/shaders/egui_frag.spv | Bin 688 -> 1196 bytes crates/renderer/shaders/egui_vert.spv | Bin 1236 -> 1448 bytes crates/renderer/src/buffers.rs | 49 +- crates/renderer/src/commands.rs | 115 +++- crates/renderer/src/device.rs | 100 +++- crates/renderer/src/images.rs | 139 +++-- crates/renderer/src/lib.rs | 787 ++++++++++++++++++++++++-- crates/renderer/src/pipeline.rs | 321 +++++++++-- crates/renderer/src/render_graph.rs | 13 +- crates/renderer/src/util.rs | 25 + 14 files changed, 1386 insertions(+), 192 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e02c17..60ff277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ anyhow = "1.0.89" ash = "0.38.0" ash-window = "0.13.0" -glam = "0.29.0" +glam = {version = "0.29.0", features = ["bytemuck"]} thiserror = "2.0" tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/crates/game/src/main.rs b/crates/game/src/main.rs index 657730a..d6585dd 100644 --- a/crates/game/src/main.rs +++ b/crates/game/src/main.rs @@ -66,8 +66,10 @@ impl WinitState { window.demo_app.ui(&window.egui_platform.context()); let output = window.egui_platform.end_pass(Some(&window.window)); - self.renderer - .draw_egui(&window.egui_platform.context(), output); + let egui_state = self + .renderer + .draw_egui(&window.egui_platform.context(), output) + .unwrap(); // rendering self.renderer diff --git a/crates/renderer/Cargo.toml b/crates/renderer/Cargo.toml index a058c47..30d8faa 100644 --- a/crates/renderer/Cargo.toml +++ b/crates/renderer/Cargo.toml @@ -11,7 +11,7 @@ dyn-clone = "1" anyhow = "1.0.89" ash = "0.38.0" ash-window = "0.13.0" -glam = "0.29.0" +glam = {workspace = true} thiserror = {workspace = true} tracing = "0.1.40" tracing-subscriber = "0.3.18" @@ -23,5 +23,7 @@ smol.workspace = true tracing-test = "0.2.5" raw-window-handle = { workspace = true } -egui = { workspace = true } -egui_winit_platform = { workspace = true } \ No newline at end of file +egui = { workspace = true , features = ["bytemuck"]} +egui_winit_platform = { workspace = true } +bytemuck = { version = "1.21.0", features = ["derive"] } +indexmap = "2.7.0" diff --git a/crates/renderer/shaders/egui.slang b/crates/renderer/shaders/egui.slang index 886e09f..ede6d28 100644 --- a/crates/renderer/shaders/egui.slang +++ b/crates/renderer/shaders/egui.slang @@ -10,6 +10,7 @@ struct VertexIn { struct VertexOut { [[vk::layout(0)]] float4 color; [[vk::layout(1)]] float2 uv; + [[vk::layout(2), flat]] uint draw_id; float4 position : SV_Position; } @@ -21,7 +22,7 @@ struct PushConstant { ConstantBuffer push_constant; [shader("vertex")] -VertexOut vertex(VertexIn vertex) { +VertexOut vertex(VertexIn vertex, uint draw_id : SV_DrawIndex) { VertexOut output; output.position = float4( @@ -32,17 +33,23 @@ VertexOut vertex(VertexIn vertex) { ); output.color = vertex.color; output.uv = vertex.uv; + output.draw_id = draw_id; return output; } [[vk::binding(0)]] -Sampler2D texture; +Sampler2D texture[]; + + +[[vk::binding(1)]] +StructuredBuffer texture_ids; [shader("fragment")] Fragment fragment(VertexOut input) { Fragment output; - output.color = input.color * texture.Sample(input.uv); + uint texture_id = texture_ids[input.draw_id]; + output.color = input.color * texture[texture_id].Sample(input.uv); return output; } diff --git a/crates/renderer/shaders/egui_frag.spv b/crates/renderer/shaders/egui_frag.spv index b79d54ffe2ef1d9ddbe43e3383f74043c877ed0e..4e76cdde11c3ad12ed3e654d03c30c6c216141be 100644 GIT binary patch literal 1196 zcmYL|Z%-3J5XPtNL9r-*L{LPq1qq@_^%Ws8nm}UkWfP40?sDl}$tCpKZ0{64fM1Ev z#v2o#-(ELNGo5{Ao|)a5-D@=u+SY2=TQmD8Mb@opch73u5boHLT@HT4Uw?g%(`+_$ z`Lvw4I4{!dCNHjR#accO?y5bqNVX{*CwW2OgY;PXM*2j$CO+}Dw58v&EV&D|=;L`Y zt;&Atl0R{tdVkxV%3hYP8kUvI((`IG%A8!vS+GsnGP@z7gqb{%?LPQChrR2%)o(35 z=TLT*jHlOG>N%XT<_ss-6DMECpFJx|cRQHmML9@ZGLASr1J?+VPS#N2@eqHHv@wwN~+oo`59+MF#vd6OI z;U~^ax>9`a=Xh0X1pb;VGlajZy_g;AJ*}A)7@nT2=i%GdJA5h2IjBp15HPZ%{2f_% z)Maj9YQ2(0@PR}B#ObjoOKs{B=dK!3{9wK(FtY&nWtkWFlQ1*jJ-`8TcJ7dRVD{&p z5HR(5!+}qI?kQmEb5HDXqBn#;^o;Lbh`07x|IChWj{VQn1@m2(vOjYt7I<4)6FXB) zX7F0dx$wiO=T-Z`J^A;gob{(_(=XpySBf8jhyO@Q4}Vn~{BG`p|3#h9559lu=2ZG0 D+Gtie literal 688 zcmXw$OH0E*6oqdSYkh!yX-ln0q8nFTicoaxwmX*+G)kaJN=!t5fdAQz;Q1z*TTbts zGxs?OgX@8XJ-acpD*+o@U_JR03mxY?v;*6d4}?6)3NB({Da?eCV!0a#p?=5m6Bj9Kj3`5+-c+7?HQc+oYM&6Eam~_>KI_hwdPBea z(KrG0C2u9$lJ}B%$%f>SWLDDDcUn8~llt%<#aR)5esGZH*gEctY=gsm?{%8wMU)g_ zUW}eRUUzMM_*nTci{m8B$6sT`wB0q?4ipm(eULDA?)Ch`^kbR{%05}<UswV0sUx-(cqRkFQnMQumX0J)Rko59_>G;aMuDSaHrv zZ_5@)7R2At4sLmT%471mDC?B=K*9o_(H=k<}z>cU^=x{1>rzcTLoC9cjtlulmMM0`&>uix++kL~^~kGbQy&QlBX zgab@1%+oOW@^0-s_c1^7bDsGb?n&=U$N@g?=f7r{`?qVjC!P7>f2#Ka4|8B)`z6Gj zUg(!z@oy&IZ^Gns&fD-);{^%%e-IaQSnxJ&?uWNslFoea^<|^yHzd@_EQ~9Vj@`JH zq+?_K^jyo*;luac+q%-RGdJV9CB2^4iZC|nwY+XiCog;!w<^7!!yRF4#90n^r4x(K za#)j&og4;w4ZMeY5^}@G8?yZHtxK4z-TO>BbA72P^tIzhXO{TTnIrv2XNK_qR%RQL EKd+B(`2YX_ literal 1236 zcmZ{i+e;f!5XQ$OCThV;t+!TfVtNs@Qfg7bwkmzFkMS=EAucpfcV%}K+b5wE`X~ER z@cW&dp#kwMvuD1!o$t(?YNg$<>X>bs*|CH*XB8XMK5f;2(P!*Q&@1|1^ad&5K=Mg) zB$<=!O5RDT`iLfPTpzycBrlVjoy)Ai*02THI#NT{3YY|UG%=rtlQmZN9{HE}1)QQ^?Cij^{ z>v%oh4Rb|*M@X>;q$z |this| unsafe { + this.device().clone().alloc().destroy_buffer(this.handle(), &mut this.alloc); } } @@ -34,6 +27,7 @@ impl Buffer { queue_families: &[u32], memory_usage: vk_mem::MemoryUsage, alloc_flags: vk_mem::AllocationCreateFlags, + name: Option>, ) -> VkResult> { let sharing_mode = if queue_families.len() > 1 { vk::SharingMode::CONCURRENT @@ -56,20 +50,23 @@ impl Buffer { )? }; - let buffer = Self { + Ok(Arc::new(Self::construct( device, buffer, + name, allocation, usage, - size: size as u64, - }; + size as u64, + )?)) + } - Ok(Arc::new(buffer)) + pub fn map_arc(self: &mut Arc) -> VkResult> { + Arc::get_mut(self).map(Self::map).unwrap() } pub fn map(&mut self) -> VkResult> { let bytes = unsafe { - let data = self.device.alloc().map_memory(&mut self.allocation)?; + let data = self.inner.dev().alloc().map_memory(&mut self.alloc)?; let slice = core::slice::from_raw_parts_mut(data, self.size as usize); slice @@ -78,7 +75,10 @@ impl Buffer { Ok(MappedBuffer { inner: self, bytes }) } pub fn buffer(&self) -> vk::Buffer { - self.buffer + self.handle() + } + pub fn len(&self) -> u64 { + self.size } } @@ -91,9 +91,10 @@ impl Drop for MappedBuffer<'_> { fn drop(&mut self) { unsafe { self.inner - .device + .inner + .dev() .alloc() - .unmap_memory(&mut self.inner.allocation); + .unmap_memory(&mut self.inner.alloc); } } } diff --git a/crates/renderer/src/commands.rs b/crates/renderer/src/commands.rs index acc1f14..df7cf70 100644 --- a/crates/renderer/src/commands.rs +++ b/crates/renderer/src/commands.rs @@ -2,7 +2,9 @@ use std::{future::Future, marker::PhantomData, sync::Arc}; use crate::{ buffers::Buffer, + device::DeviceOwned, images::{Image2D, QueueOwnership}, + pipeline::{Pipeline, PipelineLayout}, sync::{self, FenceFuture}, util::{self, FormatExt, MutexExt}, }; @@ -196,6 +198,14 @@ impl SingleUseCommand { } } + pub fn copy_buffers(&self, src: vk::Buffer, dst: vk::Buffer, regions: &[vk::BufferCopy]) { + unsafe { + self.device + .dev() + .cmd_copy_buffer(self.buffer, src, dst, regions); + } + } + pub fn clear_color_image( &self, image: vk::Image, @@ -235,7 +245,110 @@ impl SingleUseCommand { } } - // pub fn + pub fn set_viewport(&self, viewports: &[vk::Viewport]) { + unsafe { + self.device + .dev() + .cmd_set_viewport(self.buffer(), 0, viewports); + } + } + pub fn set_scissors(&self, scissors: &[vk::Rect2D]) { + unsafe { + self.device + .dev() + .cmd_set_scissor(self.buffer(), 0, scissors); + } + } + pub fn push_constants( + &self, + layout: &PipelineLayout, + stage: vk::ShaderStageFlags, + offset: u32, + bytes: &[u8], + ) { + unsafe { + self.device.dev().cmd_push_constants( + self.buffer, + layout.handle(), + stage, + offset, + bytes, + ); + } + } + pub fn bind_pipeline(&self, pipeline: &Pipeline) { + unsafe { + self.device.dev().cmd_bind_pipeline( + self.buffer(), + pipeline.bind_point(), + pipeline.handle(), + ); + } + } + pub fn bind_vertices(&self, buffer: vk::Buffer, offset: u64) { + unsafe { + self.device + .dev() + .cmd_bind_vertex_buffers(self.buffer(), 0, &[buffer], &[offset]); + } + } + pub fn bind_indices(&self, buffer: vk::Buffer, offset: u64, kind: vk::IndexType) { + unsafe { + self.device + .dev() + .cmd_bind_index_buffer(self.buffer(), buffer, offset, kind); + } + } + + pub fn bind_descriptor_sets( + &self, + layout: &PipelineLayout, + bind_point: vk::PipelineBindPoint, + descriptor_sets: &[vk::DescriptorSet], + ) { + use crate::device::DeviceOwned; + unsafe { + self.device.dev().cmd_bind_descriptor_sets( + self.buffer(), + bind_point, + layout.handle(), + 0, + descriptor_sets, + &[], + ); + } + } + pub fn draw_indexed( + &self, + indices: u32, + instances: u32, + index_offset: u32, + vertex_offset: i32, + instance_offset: u32, + ) { + unsafe { + self.device.dev().cmd_draw_indexed( + self.buffer(), + indices, + instances, + index_offset, + vertex_offset, + instance_offset, + ); + } + } + + pub fn draw_indexed_indirect(&self, buffer: vk::Buffer, offset: u64, count: u32, stride: u32) { + unsafe { + self.device.dev().cmd_draw_indexed_indirect( + self.buffer(), + buffer, + offset, + count, + stride, + ); + } + } pub fn end_rendering(&self) { unsafe { diff --git a/crates/renderer/src/device.rs b/crates/renderer/src/device.rs index b101987..fadf799 100644 --- a/crates/renderer/src/device.rs +++ b/crates/renderer/src/device.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, collections::BTreeMap, ops::Deref, sync::Arc}; +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, + ops::Deref, + sync::Arc, +}; use ash::{ khr, @@ -78,6 +83,7 @@ pub struct DeviceInner { transfer_queue: Queue, present_queue: Queue, sync_threadpool: sync::SyncThreadpool, + features: crate::PhysicalDeviceFeatures, } impl core::fmt::Debug for DeviceInner { @@ -166,6 +172,7 @@ impl Device { present_queue, compute_queue, transfer_queue, + features, sync_threadpool: sync::SyncThreadpool::new(), } }; @@ -196,6 +203,12 @@ impl Device { pub fn phy(&self) -> vk::PhysicalDevice { self.0.physical.pdev } + pub fn features(&self) -> &crate::PhysicalDeviceFeatures { + &self.0.features + } + pub fn physical_device(&self) -> &PhysicalDevice { + &self.0.physical + } pub fn graphics_queue(&self) -> &Queue { &self.0.main_queue } @@ -237,6 +250,18 @@ impl Device { tracing::warn!("finished waiting: unlocking all queues."); Ok(()) } + + pub fn debug_name_object(&self, handle: T, name: &str) -> VkResult<()> { + let name = std::ffi::CString::new(name.as_bytes()).unwrap_or(c"invalid name".to_owned()); + unsafe { + self.debug_utils().set_debug_utils_object_name( + &vk::DebugUtilsObjectNameInfoEXT::default() + .object_handle(handle) + .object_name(&name), + )?; + } + Ok(()) + } } impl AsRef for Device { @@ -290,25 +315,16 @@ impl std::fmt::Debug for DeviceOwnedDebu } impl DeviceOwnedDebugObject { - pub fn new>>( + pub fn new( device: crate::Device, object: T, - name: Option, + name: Option>, ) -> ash::prelude::VkResult where T: vk::Handle + Copy, { - let name = name.map(Into::>::into); if let Some(name) = name.as_ref() { - let name = - std::ffi::CString::new(name.as_bytes()).unwrap_or(c"invalid name".to_owned()); - unsafe { - device.debug_utils().set_debug_utils_object_name( - &vk::DebugUtilsObjectNameInfoEXT::default() - .object_handle(object) - .object_name(&name), - )?; - } + device.debug_name_object(object, name); } Ok(Self { @@ -328,3 +344,61 @@ impl DeviceOwnedDebugObject { self.object } } + +pub trait DeviceOwned { + fn device(&self) -> &Device; + fn handle(&self) -> T; +} + +#[macro_export] +macro_rules! define_device_owned_handle { + ($(#[$attr:meta])* + $ty_vis:vis $ty:ident($handle:ty) { + $($field_vis:vis $field_name:ident : $field_ty:ty),* + $(,)? + } $(=> |$this:ident| $dtor:stmt)?) => { + $(#[$attr])* + $ty_vis struct $ty { + inner: crate::device::DeviceOwnedDebugObject<$handle>, + $( + $field_vis $field_name: $field_ty, + )* + } + + impl crate::device::DeviceOwned<$handle> for $ty { + fn device(&self) -> &Device { + self.inner.dev() + } + fn handle(&self) -> $handle { + self.inner.handle() + } + } + + impl $ty { + fn construct( + device: crate::device::Device, + handle: $handle, + name: Option<::std::borrow::Cow<'static, str>>, + $($field_name: $field_ty,)* + ) -> ::ash::prelude::VkResult { + Ok(Self { + inner: crate::device::DeviceOwnedDebugObject::new( + device, + handle, + name, + )?, + $($field_name,)* + }) + } + } + + $( + impl Drop for $ty { + fn drop(&mut self) { + let mut $this = self; + $dtor + } + } + )? + }; +} diff --git a/crates/renderer/src/images.rs b/crates/renderer/src/images.rs index 0bc643d..6378616 100644 --- a/crates/renderer/src/images.rs +++ b/crates/renderer/src/images.rs @@ -1,11 +1,96 @@ -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; -use crate::buffers::Buffer; +use crate::{buffers::Buffer, define_device_owned_handle, device::DeviceOwned}; use super::{Device, Queue}; use ash::{prelude::*, vk}; use vk_mem::Alloc; +#[derive(Debug, Default, Clone)] +pub struct ImageViewDesc { + pub flags: vk::ImageViewCreateFlags, + pub name: Option>, + pub kind: vk::ImageViewType, + pub format: vk::Format, + pub components: vk::ComponentMapping, + pub aspect: vk::ImageAspectFlags, + pub mip_range: MipRange, + pub layer_range: MipRange, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MipRange(u32, u32); + +impl Default for MipRange { + fn default() -> Self { + Self(0, vk::REMAINING_ARRAY_LAYERS) + } +} + +impl MipRange { + fn count(&self) -> u32 { + self.1 + } +} + +impl> From for MipRange { + fn from(value: R) -> Self { + let start = match value.start_bound() { + std::ops::Bound::Included(v) => *v, + std::ops::Bound::Excluded(v) => *v + 1, + std::ops::Bound::Unbounded => 0, + }; + let count = match value.end_bound() { + std::ops::Bound::Included(v) => *v + 1 - start, + std::ops::Bound::Excluded(v) => *v - start, + std::ops::Bound::Unbounded => vk::REMAINING_MIP_LEVELS, + }; + + Self(start, count) + } +} + +impl std::hash::Hash for ImageViewDesc { + fn hash(&self, state: &mut H) { + self.flags.hash(state); + self.kind.hash(state); + self.format.hash(state); + ( + self.components.r, + self.components.g, + self.components.b, + self.components.a, + ) + .hash(state); + self.aspect.hash(state); + self.layer_range.hash(state); + self.mip_range.hash(state); + } +} + +impl Eq for ImageViewDesc {} +impl PartialEq for ImageViewDesc { + fn eq(&self, other: &Self) -> bool { + self.flags == other.flags + && self.kind == other.kind + && self.format == other.format + && ( + self.components.r, + self.components.g, + self.components.b, + self.components.a, + ) == ( + other.components.r, + other.components.g, + other.components.b, + other.components.a, + ) + && self.aspect == other.aspect + && self.mip_range == other.mip_range + && self.layer_range == other.layer_range + } +} + #[derive(Debug)] pub struct Image2D { device: Device, @@ -94,32 +179,29 @@ impl Image2D { self.format } - pub fn view( - self: &Arc, - device: &Device, - aspect: vk::ImageAspectFlags, - ) -> VkResult> { + pub fn device(&self) -> Device { + self.device.clone() + } + + pub fn view(&self, desc: ImageViewDesc) -> VkResult { let create_info = vk::ImageViewCreateInfo::default() + .flags(desc.flags) .image(self.image()) .view_type(vk::ImageViewType::TYPE_2D) - .format(self.format) - .components(vk::ComponentMapping::default()) + .format(desc.format) + .components(desc.components) .subresource_range( vk::ImageSubresourceRange::default() - .aspect_mask(aspect) - .base_mip_level(0) - .level_count(self.mip_levels) - .base_array_layer(0) - .layer_count(1), + .aspect_mask(desc.aspect) + .base_mip_level(desc.mip_range.0) + .level_count(desc.mip_range.count()) + .base_array_layer(desc.layer_range.0) + .layer_count(desc.layer_range.count()), ); - let view = unsafe { device.dev().create_image_view(&create_info, None)? }; + let view = unsafe { self.device.dev().create_image_view(&create_info, None)? }; - Ok(Arc::new(ImageView2D { - view, - image: self.clone(), - aspect, - })) + ImageView::construct(self.device.clone(), view, desc.name) } pub fn image(&self) -> vk::Image { @@ -136,22 +218,13 @@ impl Image2D { } } -pub struct ImageView2D { - view: vk::ImageView, - aspect: vk::ImageAspectFlags, - image: Arc, -} - -impl Drop for ImageView2D { - fn drop(&mut self) { - unsafe { - self.image.device.dev().destroy_image_view(self.view, None); - } +define_device_owned_handle! { + #[derive(Debug)] + pub ImageView(vk::ImageView) {} => |this| unsafe { + this.device().dev().destroy_image_view(this.handle(), None); } } -impl ImageView2D {} - pub struct QueueOwnership { pub src: u32, pub dst: u32, diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index d67b897..c292645 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -12,6 +12,8 @@ use std::{ collections::{BTreeMap, BTreeSet, HashMap}, ffi::{CStr, CString}, fmt::Debug, + hash::Hash, + hint::black_box, marker::PhantomData, ops::Deref, sync::{ @@ -20,7 +22,9 @@ use std::{ }, }; +use bytemuck::Contiguous; use egui::Color32; +use indexmap::IndexMap; use parking_lot::{Mutex, MutexGuard, RwLock}; use ash::{ @@ -44,17 +48,51 @@ mod render_graph; mod sync; mod util; -use device::{Device, DeviceAndQueues, DeviceQueueFamilies, WeakDevice}; +use device::{Device, DeviceAndQueues, DeviceOwned, DeviceQueueFamilies, WeakDevice}; mod texture { - use std::{collections::BTreeMap, sync::Arc}; + use std::{ + collections::{hash_map::Entry, BTreeMap, HashMap}, + sync::Arc, + }; - use crate::{def_monotonic_id, images::Image2D, Device}; + use ash::prelude::VkResult; + use parking_lot::Mutex; + + use crate::{ + def_monotonic_id, + images::{Image2D, ImageView}, + Device, + }; def_monotonic_id!(TextureId); + pub struct Texture { + id: TextureId, + image: Arc, + views: Mutex>>, + } + + impl Texture { + pub fn image(&self) -> Arc { + self.image.clone() + } + pub fn view(&self, desc: crate::images::ImageViewDesc) -> VkResult> { + let mut views = self.views.lock(); + let view = match views.entry(desc) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + let view = Arc::new(self.image.view(entry.key().clone())?); + entry.insert(view).clone() + } + }; + + Ok(view) + } + } + pub struct TextureManager { - pub textures: BTreeMap>, + pub textures: BTreeMap>, dev: Device, } @@ -65,6 +103,25 @@ mod texture { textures: BTreeMap::new(), } } + + pub fn insert_image_with_id(&mut self, id: TextureId, image: Arc) { + self.textures.insert( + id, + Arc::new(Texture { + id, + image, + views: Mutex::new(HashMap::new()), + }), + ); + } + + pub fn remove_texture(&mut self, id: TextureId) -> Option> { + self.textures.remove(&id) + } + + pub fn get_texture(&self, id: TextureId) -> Option> { + self.textures.get(&id).cloned() + } } } @@ -84,6 +141,8 @@ pub enum Error { NulError(#[from] std::ffi::NulError), #[error("No Physical Device found.")] NoPhysicalDevice, + #[error(transparent)] + Io(#[from] std::io::Error), } type Result = core::result::Result; @@ -164,13 +223,16 @@ impl Queue { } } -trait ExtendsDeviceFeatures2Debug: vk::ExtendsPhysicalDeviceFeatures2 + Debug {} +trait ExtendsDeviceFeatures2Debug: vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync {} trait ExtendsDeviceProperties2Debug: vk::ExtendsPhysicalDeviceProperties2 + Debug + DynClone + Send + Sync { } -impl ExtendsDeviceFeatures2Debug for T {} +impl ExtendsDeviceFeatures2Debug + for T +{ +} impl ExtendsDeviceProperties2Debug for T { @@ -178,13 +240,13 @@ impl #[derive(Default, Debug)] struct PhysicalDeviceFeatures { - version: u32, - physical_features_10: vk::PhysicalDeviceFeatures, - physical_features_11: Option>, - physical_features_12: Option>, - physical_features_13: Option>, - extra_features: Vec>, - device_extensions: Vec, + pub version: u32, + pub physical_features_10: vk::PhysicalDeviceFeatures, + pub physical_features_11: Option>, + pub physical_features_12: Option>, + pub physical_features_13: Option>, + pub extra_features: Vec>, + pub device_extensions: Vec, } impl PhysicalDeviceFeatures { @@ -291,6 +353,16 @@ impl PhysicalDeviceFeatures { features2 } + fn supports_extension(&self, e: &vk::ExtensionProperties) -> bool { + self.device_extensions + .iter() + .find(|ext| { + ext.extension_name_as_c_str() == e.extension_name_as_c_str() + && ext.spec_version >= e.spec_version + }) + .is_some() + } + fn compatible_with(&self, device: &Self) -> bool { let sort_exts = |a: &vk::ExtensionProperties, b: &vk::ExtensionProperties| { (a.extension_name_as_c_str().unwrap(), a.spec_version) @@ -1001,9 +1073,37 @@ impl Drop for Surface { } } +struct SamplerCache { + device: Device, + samplers: HashMap, +} + +impl SamplerCache { + pub fn new(device: Device) -> SamplerCache { + Self { + device, + samplers: HashMap::new(), + } + } + + pub fn get_sampler(&mut self, desc: pipeline::SamplerDesc) -> VkResult { + use std::collections::hash_map::Entry; + let entry = match self.samplers.entry(desc) { + Entry::Occupied(entry) => entry.get().handle(), + Entry::Vacant(entry) => { + let sampler = pipeline::Sampler::new(self.device.clone(), entry.key())?; + entry.insert(sampler).handle() + } + }; + + Ok(entry) + } +} + pub struct Vulkan { instance: Arc, device: Device, + samplers: SamplerCache, } impl Drop for Vulkan { @@ -1069,7 +1169,8 @@ impl Vulkan { let extensions = Self::get_extensions( &entry, &layers, - &[ash::ext::debug_utils::NAME, ash::ext::layer_settings::NAME], + // &[ash::ext::debug_utils::NAME, ash::ext::layer_settings::NAME], + &[ash::ext::debug_utils::NAME], display_handle, ) .unwrap(); @@ -1121,6 +1222,10 @@ impl Vulkan { .features12( vk::PhysicalDeviceVulkan12Features::default() .shader_int8(true) + .runtime_descriptor_array(true) + .descriptor_binding_partially_bound(true) + .shader_sampled_image_array_non_uniform_indexing(true) + .descriptor_binding_sampled_image_update_after_bind(true) .storage_buffer8_bit_access(true), ) .features13( @@ -1180,7 +1285,11 @@ impl Vulkan { tracing::debug!("pdev: {pdev:?}"); let device = Device::new(instance.clone(), pdev, features)?; - Ok(Self { instance, device }) + Ok(Self { + instance, + samplers: SamplerCache::new(device.clone()), + device, + }) } fn queue_family_supports_presentation( @@ -1191,17 +1300,28 @@ impl Vulkan { ) -> bool { unsafe { match display_handle { - RawDisplayHandle::Xlib(_xlib_display_handle) => { - todo!() + RawDisplayHandle::Xlib(display) => { + let surface = + ash::khr::xlib_surface::Instance::new(&instance.entry, &instance.instance); + surface.get_physical_device_xlib_presentation_support( + pdev, + queue_family, + display.display.unwrap().as_ptr() as _, + display.screen as _, + ) + //todo!("xlib") } - RawDisplayHandle::Xcb(_xcb_display_handle) => todo!(), + RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"), RawDisplayHandle::Wayland(wayland_display_handle) => { - ash::khr::wayland_surface::Instance::new(&instance.entry, &instance.instance) - .get_physical_device_wayland_presentation_support( - pdev, - queue_family, - wayland_display_handle.display.cast().as_mut(), - ) + let surface = ash::khr::wayland_surface::Instance::new( + &instance.entry, + &instance.instance, + ); + surface.get_physical_device_wayland_presentation_support( + pdev, + queue_family, + wayland_display_handle.display.cast().as_mut(), + ) } RawDisplayHandle::Drm(_) => { todo!() @@ -1656,9 +1776,258 @@ impl WindowContext { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct EguiState { - pub textures: HashMap, + textures: HashMap, + descriptor_pool: pipeline::DescriptorPool, + descriptor_set: vk::DescriptorSet, + descriptor_layout: pipeline::DescriptorSetLayout, + pipeline_layout: pipeline::PipelineLayout, + pipeline: pipeline::Pipeline, + render_state: Option, +} + +#[derive(Debug)] +struct EguiRenderState { + vertices: Arc, + indices: Arc, + draw_calls: Arc, + texture_ids: Arc, + textures_to_free: Vec, + num_draw_calls: usize, +} + +#[derive(Debug, Clone, Copy)] +struct EguiTextureInfo { + id: texture::TextureId, + options: egui::epaint::textures::TextureOptions, +} + +impl EguiTextureInfo { + fn into_sampler_desc(&self) -> pipeline::SamplerDesc { + let address_mode = match self.options.wrap_mode { + egui::TextureWrapMode::ClampToEdge => vk::SamplerAddressMode::CLAMP_TO_EDGE, + egui::TextureWrapMode::Repeat => vk::SamplerAddressMode::REPEAT, + egui::TextureWrapMode::MirroredRepeat => vk::SamplerAddressMode::MIRRORED_REPEAT, + }; + pipeline::SamplerDesc { + min_filter: match self.options.minification { + egui::TextureFilter::Nearest => vk::Filter::NEAREST, + egui::TextureFilter::Linear => vk::Filter::LINEAR, + }, + mag_filter: match self.options.magnification { + egui::TextureFilter::Nearest => vk::Filter::NEAREST, + egui::TextureFilter::Linear => vk::Filter::LINEAR, + }, + mipmap_mode: match self.options.mipmap_mode { + Some(egui::TextureFilter::Linear) => vk::SamplerMipmapMode::LINEAR, + Some(egui::TextureFilter::Nearest) => vk::SamplerMipmapMode::NEAREST, + None => Default::default(), + }, + address_u: address_mode, + address_v: address_mode, + address_w: address_mode, + max_lod: vk::LOD_CLAMP_NONE, + ..Default::default() + } + } +} + +impl EguiState { + const TEXTURE_BINDING: u32 = 0; + const UNIFORM_BINDING: u32 = 1; + fn new(device: Device) -> Result { + let descriptor_pool = pipeline::DescriptorPool::new( + device.clone(), + pipeline::DescriptorPoolDesc { + flags: vk::DescriptorPoolCreateFlags::UPDATE_AFTER_BIND, + name: Some("egui-descriptorpool".into()), + sizes: &[ + vk::DescriptorPoolSize { + ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, + descriptor_count: device + .physical_device() + .properties + .base + .limits + .max_descriptor_set_sampled_images, + }, + vk::DescriptorPoolSize { + ty: vk::DescriptorType::STORAGE_BUFFER, + descriptor_count: 1, + }, + ], + max_sets: 1, + ..Default::default() + }, + )?; + let descriptor_layout = pipeline::DescriptorSetLayout::new( + device.clone(), + pipeline::DescriptorSetLayoutDesc { + flags: vk::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL, + name: Some("egui-descriptor-layout".into()), + bindings: &[ + pipeline::DescriptorSetLayoutBindingDesc { + binding: Self::TEXTURE_BINDING, + count: device + .physical_device() + .properties + .base + .limits + .max_descriptor_set_sampled_images, + kind: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, + stage: vk::ShaderStageFlags::FRAGMENT, + flags: Some( + vk::DescriptorBindingFlags::PARTIALLY_BOUND + | vk::DescriptorBindingFlags::UPDATE_AFTER_BIND, + ), + }, + pipeline::DescriptorSetLayoutBindingDesc { + binding: Self::UNIFORM_BINDING, + count: 1, + kind: vk::DescriptorType::STORAGE_BUFFER, + stage: vk::ShaderStageFlags::FRAGMENT, + flags: None, + }, + ], + }, + )?; + + let sets = descriptor_pool.allocate(&[pipeline::DescriptorSetAllocDesc { + name: None, + layout: &descriptor_layout, + }])?; + + let pipeline_layout = pipeline::PipelineLayout::new( + device.clone(), + pipeline::PipelineLayoutDesc { + descriptor_set_layouts: &[&descriptor_layout], + push_constant_ranges: &[vk::PushConstantRange { + offset: 0, + size: 128, + stage_flags: vk::ShaderStageFlags::VERTEX, + }], + name: Some("egui-pipeline-layout".into()), + }, + )?; + + let frag_shader = pipeline::ShaderModule::new_from_path( + device.clone(), + "crates/renderer/shaders/egui_frag.spv", + )?; + let vert_shader = pipeline::ShaderModule::new_from_path( + device.clone(), + "crates/renderer/shaders/egui_vert.spv", + )?; + + let pipeline = pipeline::Pipeline::new( + device.clone(), + pipeline::PipelineDesc::Graphics(pipeline::GraphicsPipelineDesc { + flags: Default::default(), + name: Some("egui-pipeline".into()), + shader_stages: &[ + pipeline::ShaderStageDesc { + flags: vk::PipelineShaderStageCreateFlags::empty(), + module: &frag_shader, + stage: vk::ShaderStageFlags::FRAGMENT, + entry: c"main".into(), + }, + pipeline::ShaderStageDesc { + flags: vk::PipelineShaderStageCreateFlags::empty(), + module: &vert_shader, + stage: vk::ShaderStageFlags::VERTEX, + entry: c"main".into(), + }, + ], + render_pass: None, + layout: &pipeline_layout, + subpass: None, + base_pipeline: None, + vertex_input: Some(pipeline::VertexInputState { + bindings: &[vk::VertexInputBindingDescription { + binding: 0, + stride: 20, + input_rate: vk::VertexInputRate::VERTEX, + }], + attributes: &[ + vk::VertexInputAttributeDescription { + location: 0, + binding: 0, + format: vk::Format::R32G32_SFLOAT, + offset: 0, + }, + vk::VertexInputAttributeDescription { + location: 1, + binding: 0, + format: vk::Format::R32G32_SFLOAT, + offset: 8, + }, + vk::VertexInputAttributeDescription { + location: 2, + binding: 0, + format: vk::Format::R8G8B8A8_UNORM, + offset: 16, + }, + ], + }), + input_assembly: Some(pipeline::InputAssemblyState { + topology: vk::PrimitiveTopology::TRIANGLE_LIST, + primitive_restart: false, + }), + tessellation: None, + viewport: Some(pipeline::ViewportState { + num_viewports: 1, + num_scissors: 1, + ..Default::default() + }), + rasterization: Some(pipeline::RasterizationState { + cull_mode: vk::CullModeFlags::NONE, + ..Default::default() + }), + multisample: Some(pipeline::MultisampleState { + ..Default::default() + }), + depth_stencil: Some(pipeline::DepthStencilState { + depth: Some(pipeline::DepthState { + write_enable: false, + compare_op: Some(vk::CompareOp::LESS), + bounds: Some(pipeline::DepthBounds { min: 0.0, max: 1.0 }), + }), + ..Default::default() + }), + color_blend: Some(pipeline::ColorBlendState { + attachments: &[vk::PipelineColorBlendAttachmentState { + color_write_mask: vk::ColorComponentFlags::RGBA, + blend_enable: 0, + ..Default::default() + }], + ..Default::default() + }), + rendering: Some(pipeline::RenderingState { + color_formats: &[vk::Format::R8G8B8A8_UNORM], + ..Default::default() + }), + dynamic: Some(pipeline::DynamicState { + dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR], + ..Default::default() + }), + }), + )?; + + Ok(Self { + textures: HashMap::new(), + descriptor_pool, + descriptor_layout, + descriptor_set: sets[0], + pipeline, + pipeline_layout, + render_state: None, + }) + } + + fn lookup_texture(&self, id: egui::epaint::TextureId) -> Option { + self.textures.get(&id).map(|entry| entry.id) + } } pub struct Renderer { @@ -1677,14 +2046,14 @@ impl Renderer { let vulkan = Vulkan::new("Vidya", &[], &[], Some(display))?; Ok(Self { texture_handler: texture::TextureManager::new(vulkan.device.clone()), + egui_state: EguiState::new(vulkan.device.clone())?, vulkan, - egui_state: Default::default(), display, window_contexts: HashMap::new(), }) } - pub fn draw_egui(&mut self, ctx: &egui::Context, output: egui::FullOutput) { + pub fn draw_egui(&mut self, ctx: &egui::Context, output: egui::FullOutput) -> Result<()> { let pool = commands::SingleUseCommandPool::new( self.vulkan.device.clone(), self.vulkan.device.graphics_queue().clone(), @@ -1708,6 +2077,7 @@ impl Renderer { vk_mem::AllocationCreateFlags::MAPPED | vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE | vk_mem::AllocationCreateFlags::STRATEGY_FIRST_FIT, + Some(format!("egui-{egui_id:?}-staging-buf").into()), ) .expect("staging buffer"); { @@ -1790,21 +2160,29 @@ impl Renderer { }], ); - let id = self - .egui_state - .textures - .get(egui_id) - .cloned() - .unwrap_or_else(|| { - let id = texture::TextureId::new(); - self.egui_state.textures.insert(*egui_id, id); + let id = self.egui_state.lookup_texture(*egui_id).unwrap_or_else(|| { + let id = texture::TextureId::new(); - id - }); + self.egui_state.textures.insert( + *egui_id, + EguiTextureInfo { + id, + options: delta.options, + }, + ); + + id + }); if let Some(pos) = delta.pos { // SAFETY: must exist because image is not whole. - let existing_texture = self.texture_handler.textures.get(&id).cloned().unwrap(); + let existing_texture = self + .texture_handler + .textures + .get(&id) + .cloned() + .unwrap() + .image(); cmd.image_barrier( texture.image(), @@ -1860,7 +2238,8 @@ impl Renderer { vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, None, ); - self.texture_handler.textures.insert(id, texture.clone()); + self.texture_handler + .insert_image_with_id(id, texture.clone()); info!("new texture for egui: {egui_id:?} -> {id:?}"); } @@ -1868,25 +2247,252 @@ impl Renderer { }) .collect::>(); - let fence = Arc::new(sync::Fence::create(self.vulkan.device.clone()).unwrap()); + let draw_data = ctx.tessellate(output.shapes, output.pixels_per_point); + + #[repr(C)] + #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] + struct Vertex { + pos: glam::Vec2, + uv: glam::Vec2, + color: egui::epaint::Color32, + } + + #[repr(transparent)] + #[derive(Debug, Clone, Copy)] + struct DrawCall(vk::DrawIndexedIndirectCommand); + unsafe impl bytemuck::Zeroable for DrawCall {} + unsafe impl bytemuck::Pod for DrawCall {} + + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + let mut draw_calls = Vec::new(); + let mut textures = IndexMap::new(); + let mut textures_indices = Vec::new(); + for draw in draw_data { + let egui::epaint::Primitive::Mesh(mesh) = draw.primitive else { + continue; + }; + + draw_calls.push(DrawCall(vk::DrawIndexedIndirectCommand { + index_count: mesh.indices.len() as u32, + instance_count: 1, + first_index: indices.len() as u32, + vertex_offset: vertices.len() as i32, + first_instance: 0, + })); + + vertices.extend(mesh.vertices.iter().map(|v| Vertex { + pos: glam::vec2(v.pos.x, v.pos.y), + uv: glam::vec2(v.uv.x, v.uv.y), + color: v.color, + })); + + indices.extend(mesh.indices); + let texture = self + .egui_state + .textures + .get(&mesh.texture_id) + .cloned() + .unwrap(); + if !textures.contains_key(&texture.id) { + textures.insert(texture.id, texture); + } + let idx = textures.get_index_of(&texture.id).unwrap(); + textures_indices.push(idx as u32); + } + + eprintln!("{:?}", self.egui_state.textures); + eprintln!("{draw_calls:?}"); + + let num_draw_calls = draw_calls.len(); + let device = self.vulkan.device.clone(); + let (draw_staging, vertices, indices, draw_calls, texture_ids) = { + let vertices_size = vertices.len() * size_of::(); + let indices_size = indices.len() * size_of::(); + let draw_calls_size = draw_calls.len() * size_of::(); + + let staging_size = vertices_size + indices_size + draw_calls_size; + + let mut staging = buffers::Buffer::new( + device.clone(), + staging_size, + vk::BufferUsageFlags::TRANSFER_SRC, + &[device.queue_families().graphics_familty()], + vk_mem::MemoryUsage::AutoPreferHost, + vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE + | vk_mem::AllocationCreateFlags::MAPPED, + Some("egui-draw-staging".into()), + )?; + + { + let mut map = staging.map_arc()?; + + let (st_vertices, rest) = map.split_at_mut(vertices_size); + let (st_indices, st_drawcalls) = rest.split_at_mut(indices_size); + st_vertices.copy_from_slice(bytemuck::cast_slice(&vertices)); + st_indices.copy_from_slice(bytemuck::cast_slice(&indices)); + st_drawcalls.copy_from_slice(bytemuck::cast_slice(&draw_calls)); + } + + let vertices = buffers::Buffer::new( + device.clone(), + vertices_size, + vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, + &[device.queue_families().graphics_familty()], + vk_mem::MemoryUsage::AutoPreferDevice, + vk_mem::AllocationCreateFlags::empty(), + Some("egui-draw-vertices".into()), + )?; + let indices = buffers::Buffer::new( + device.clone(), + indices_size, + vk::BufferUsageFlags::INDEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, + &[device.queue_families().graphics_familty()], + vk_mem::MemoryUsage::AutoPreferDevice, + vk_mem::AllocationCreateFlags::empty(), + Some("egui-draw-indices".into()), + )?; + let draw_calls = buffers::Buffer::new( + device.clone(), + draw_calls_size, + vk::BufferUsageFlags::INDIRECT_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, + &[device.queue_families().graphics_familty()], + vk_mem::MemoryUsage::AutoPreferDevice, + vk_mem::AllocationCreateFlags::empty(), + Some("egui-draw-draw_calls".into()), + )?; + + cmd.copy_buffers( + staging.buffer(), + vertices.buffer(), + &[vk::BufferCopy { + src_offset: 0, + dst_offset: 0, + size: vertices_size as u64, + }], + ); + cmd.copy_buffers( + staging.buffer(), + indices.buffer(), + &[vk::BufferCopy { + src_offset: vertices_size as u64, + dst_offset: 0, + size: indices_size as u64, + }], + ); + cmd.copy_buffers( + staging.buffer(), + draw_calls.buffer(), + &[vk::BufferCopy { + src_offset: (vertices_size + indices_size) as u64, + dst_offset: 0, + size: draw_calls_size as u64, + }], + ); + + let mut texture_ids = buffers::Buffer::new( + device.clone(), + textures_indices.len() * size_of::(), + vk::BufferUsageFlags::STORAGE_BUFFER, + &[device.queue_families().graphics_familty()], + vk_mem::MemoryUsage::AutoPreferDevice, + vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE, + Some("egui-draw-texture_ids".into()), + )?; + { + let mut map = texture_ids.map_arc()?; + map.copy_from_slice(bytemuck::cast_slice(&textures_indices)); + } + + (staging, vertices, indices, draw_calls, texture_ids) + }; + + let descriptor_infos = textures + .values() + .map(|entry| { + let texture = self.texture_handler.get_texture(entry.id).unwrap(); + let info = vk::DescriptorImageInfo { + sampler: self + .vulkan + .samplers + .get_sampler(entry.into_sampler_desc()) + .unwrap(), + image_view: texture + .view(images::ImageViewDesc { + kind: vk::ImageViewType::TYPE_2D, + format: texture.image().format(), + aspect: vk::ImageAspectFlags::COLOR, + mip_range: (0..1).into(), + layer_range: (0..1).into(), + ..Default::default() + }) + .unwrap() + .handle(), + image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + }; + + info + }) + .collect::>(); + + let uniform_info = vk::DescriptorBufferInfo { + buffer: texture_ids.buffer(), + offset: 0, + range: texture_ids.len(), + }; + + let descriptor_writes = descriptor_infos + .iter() + .enumerate() + .map(|(i, info)| { + vk::WriteDescriptorSet::default() + .image_info(core::slice::from_ref(info)) + .descriptor_count(1) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .dst_set(self.egui_state.descriptor_set) + .dst_binding(EguiState::TEXTURE_BINDING) + .dst_array_element(i as u32) + }) + .chain(core::iter::once({ + vk::WriteDescriptorSet::default() + .buffer_info(core::slice::from_ref(&uniform_info)) + .descriptor_count(1) + .dst_binding(EguiState::UNIFORM_BINDING) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .dst_array_element(0) + .dst_set(self.egui_state.descriptor_set) + })) + .collect::>(); + + unsafe { + device.dev().update_descriptor_sets(&descriptor_writes, &[]); + } + + let to_remove_tex_ids = output + .textures_delta + .free + .iter() + .filter_map(|id| self.egui_state.textures.get(id).cloned()) + .map(|entry| entry.id) + .collect::>(); + + self.egui_state.render_state = Some(EguiRenderState { + vertices, + indices, + draw_calls, + num_draw_calls, + texture_ids, + textures_to_free: to_remove_tex_ids, + }); + + let fence = Arc::new(sync::Fence::create(device.clone()).unwrap()); let future = cmd.submit_async(None, None, fence).unwrap(); future.block(); - let draw_data = ctx.tessellate(output.shapes, output.pixels_per_point); - - // do drawing stuff - let cmd = pool.alloc().unwrap(); - + black_box((cmd_objects, draw_staging)); // free after drawing - let texture_deltas = &output.textures_delta; - texture_deltas - .free - .iter() - .filter_map(|id| self.egui_state.textures.get(id).cloned()) - .for_each(|id| { - self.texture_handler.textures.remove(&id); - }); + Ok(()) } pub fn debug_draw(&mut self, window: &K, pre_present_cb: F) -> Result<()> @@ -1974,9 +2580,78 @@ impl Renderer { vk::ImageAspectFlags::COLOR, vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE, - vk::PipelineStageFlags2::TRANSFER, - vk::AccessFlags2::empty(), + vk::PipelineStageFlags2::FRAGMENT_SHADER, + vk::AccessFlags2::SHADER_WRITE, vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + None, + ); + + let egui_ctx = self.egui_state.render_state.take(); + if let Some(ctx) = egui_ctx.as_ref() { + let color_attachment = &vk::RenderingAttachmentInfo::default() + .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .image_view(frame.view) + .load_op(vk::AttachmentLoadOp::LOAD) + .store_op(vk::AttachmentStoreOp::STORE); + + cmd.begin_rendering( + vk::RenderingInfo::default() + .color_attachments(core::slice::from_ref(color_attachment)) + .layer_count(1) + .render_area(vk::Rect2D::default().extent(frame.swapchain.extent)), + ); + + cmd.set_scissors(&[vk::Rect2D::default() + .offset(vk::Offset2D::default()) + .extent(frame.swapchain.extent)]); + + cmd.set_viewport(&[vk::Viewport::default() + .x(0.0) + .y(0.0) + .min_depth(0.0) + .max_depth(1.0) + .width(frame.swapchain.extent.width as f32) + .height(frame.swapchain.extent.height as f32)]); + + cmd.bind_pipeline(&self.egui_state.pipeline); + cmd.bind_indices(ctx.indices.buffer(), 0, vk::IndexType::UINT32); + cmd.bind_vertices(ctx.vertices.buffer(), 0); + cmd.push_constants( + &self.egui_state.pipeline_layout, + vk::ShaderStageFlags::VERTEX, + 0, + bytemuck::cast_slice( + &[ + frame.swapchain.extent.width as f32, + frame.swapchain.extent.height as f32, + ] + .map(|f| f.to_bits()), + ), + ); + cmd.bind_descriptor_sets( + &self.egui_state.pipeline_layout, + vk::PipelineBindPoint::GRAPHICS, + &[self.egui_state.descriptor_set], + ); + cmd.draw_indexed_indirect( + ctx.draw_calls.buffer(), + 0, + ctx.num_draw_calls as u32, + size_of::() as u32, + ); + + cmd.end_rendering(); + } + + cmd.image_barrier( + frame.image, + vk::ImageAspectFlags::COLOR, + vk::PipelineStageFlags2::FRAGMENT_SHADER, + vk::AccessFlags2::SHADER_WRITE, + vk::PipelineStageFlags2::BOTTOM_OF_PIPE, + vk::AccessFlags2::empty(), + vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, vk::ImageLayout::PRESENT_SRC_KHR, None, ); diff --git a/crates/renderer/src/pipeline.rs b/crates/renderer/src/pipeline.rs index 9bc5a6d..a0aae4c 100644 --- a/crates/renderer/src/pipeline.rs +++ b/crates/renderer/src/pipeline.rs @@ -2,12 +2,15 @@ use std::{borrow::Cow, path::Path, sync::Arc}; use ash::{prelude::*, vk}; -use crate::device::{Device, DeviceOwnedDebugObject}; +use crate::{ + define_device_owned_handle, + device::{Device, DeviceOwnedDebugObject}, +}; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct ShaderStageDesc<'a> { pub flags: vk::PipelineShaderStageCreateFlags, - pub module: vk::ShaderModule, + pub module: &'a ShaderModule, pub stage: vk::ShaderStageFlags, pub entry: Cow<'a, std::ffi::CStr>, // specialization: Option @@ -19,6 +22,7 @@ pub struct DescriptorSetLayoutBindingDesc { pub count: u32, pub kind: vk::DescriptorType, pub stage: vk::ShaderStageFlags, + pub flags: Option, } #[derive(Debug, Default)] @@ -30,7 +34,7 @@ pub struct DescriptorSetLayoutDesc<'a> { #[derive(Debug, Default)] pub struct PipelineLayoutDesc<'a> { - pub descriptor_set_layouts: &'a [Arc], + pub descriptor_set_layouts: &'a [&'a DescriptorSetLayout], pub push_constant_ranges: &'a [vk::PushConstantRange], pub name: Option>, } @@ -117,7 +121,7 @@ impl Default for RasterizationState { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct MultisampleState<'a> { pub flags: vk::PipelineMultisampleStateCreateFlags, pub sample_shading_enable: bool, @@ -128,6 +132,20 @@ pub struct MultisampleState<'a> { pub alpha_to_one_enable: bool, } +impl<'a> Default for MultisampleState<'a> { + fn default() -> Self { + Self { + flags: Default::default(), + sample_shading_enable: Default::default(), + rasterization_samples: vk::SampleCountFlags::TYPE_1, + min_sample_shading: 1.0, + sample_mask: Default::default(), + alpha_to_coverage_enable: Default::default(), + alpha_to_one_enable: Default::default(), + } + } +} + #[derive(Debug)] pub struct DepthBounds { pub min: f32, @@ -164,6 +182,13 @@ pub struct ColorBlendState<'a> { pub blend_constants: [f32; 4], } +#[derive(Debug, Default)] +pub struct RenderingState<'a> { + pub color_formats: &'a [vk::Format], + pub depth_format: Option, + pub stencil_format: Option, +} + #[derive(Debug, Default)] pub struct DynamicState<'a> { pub flags: vk::PipelineDynamicStateCreateFlags, @@ -176,7 +201,7 @@ pub struct GraphicsPipelineDesc<'a> { pub name: Option>, pub shader_stages: &'a [ShaderStageDesc<'a>], pub render_pass: Option, - pub layout: Arc, + pub layout: &'a PipelineLayout, pub subpass: Option, pub base_pipeline: Option>, @@ -189,63 +214,120 @@ pub struct GraphicsPipelineDesc<'a> { pub depth_stencil: Option, pub color_blend: Option>, pub dynamic: Option>, + pub rendering: Option>, +} + +#[derive(Debug, Default)] +pub struct DescriptorPoolDesc<'a> { + pub flags: vk::DescriptorPoolCreateFlags, + pub name: Option>, + pub sizes: &'a [vk::DescriptorPoolSize], + pub max_sets: u32, } #[derive(Debug)] -pub struct DescriptorSetLayout { - set_layout: DeviceOwnedDebugObject, +pub struct DescriptorSetAllocDesc<'a> { + pub name: Option>, + pub layout: &'a DescriptorSetLayout, } -impl Drop for DescriptorSetLayout { - fn drop(&mut self) { +define_device_owned_handle! { + #[derive(Debug)] + pub DescriptorPool(vk::DescriptorPool) {} => |this| unsafe { + this.device().dev().destroy_descriptor_pool(this.handle(), None); + } +} + +impl DescriptorPool { + pub fn new(device: Device, desc: DescriptorPoolDesc) -> VkResult { + let info = &vk::DescriptorPoolCreateInfo::default() + .flags(desc.flags) + .max_sets(desc.max_sets) + .pool_sizes(desc.sizes); + + let handle = unsafe { device.dev().create_descriptor_pool(info, None)? }; + + Self::construct(device, handle, desc.name) + } + + pub fn allocate(&self, descs: &[DescriptorSetAllocDesc]) -> VkResult> { + let layouts = descs + .iter() + .map(|desc| desc.layout.handle()) + .collect::>(); + + let info = &vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(self.handle()) + .set_layouts(&layouts); + let sets = unsafe { self.device().dev().allocate_descriptor_sets(&info)? }; + + Ok(sets) + } + + // pub fn free(&self) {} + pub fn reset(&self) -> VkResult<()> { unsafe { - self.set_layout + self.device() .dev() - .dev() - .destroy_descriptor_set_layout(self.set_layout.handle(), None); + .reset_descriptor_pool(self.handle(), vk::DescriptorPoolResetFlags::empty()) } } } +define_device_owned_handle! { + #[derive(Debug)] + pub DescriptorSetLayout(vk::DescriptorSetLayout) {} => |this| unsafe { + this.device().dev().destroy_descriptor_set_layout(this.handle(), None); + } +} + impl DescriptorSetLayout { pub fn new(device: Device, desc: DescriptorSetLayoutDesc) -> VkResult { - let bindings = desc + let (flags, bindings): (Vec<_>, Vec<_>) = desc .bindings .iter() .map(|binding| { - vk::DescriptorSetLayoutBinding::default() + let flag = binding.flags.unwrap_or_default(); + let binding = vk::DescriptorSetLayoutBinding::default() .binding(binding.binding) .descriptor_count(binding.count) .descriptor_type(binding.kind) - .stage_flags(binding.stage) - }) - .collect::>(); + .stage_flags(binding.stage); - let info = &vk::DescriptorSetLayoutCreateInfo::default() + (flag, binding) + }) + .unzip(); + + let flags = + &mut vk::DescriptorSetLayoutBindingFlagsCreateInfo::default().binding_flags(&flags); + + let mut info = vk::DescriptorSetLayoutCreateInfo::default() .bindings(&bindings) .flags(desc.flags); - let layout = unsafe { device.dev().create_descriptor_set_layout(info, None)? }; + if device.features().version >= vk::API_VERSION_1_2 + || device + .features() + .supports_extension(&crate::make_extention_properties( + ash::ext::descriptor_indexing::NAME, + ash::ext::descriptor_indexing::SPEC_VERSION, + )) + { + info = info.push_next(flags); + } - Ok(Self { - set_layout: DeviceOwnedDebugObject::new(device, layout, desc.name)?, - }) + let layout = unsafe { device.dev().create_descriptor_set_layout(&info, None)? }; + + Self::construct(device, layout, desc.name) } } -#[derive(Debug)] -pub struct PipelineLayout { - pipeline_layout: DeviceOwnedDebugObject, -} +use crate::device::DeviceOwned; -impl Drop for PipelineLayout { - fn drop(&mut self) { - unsafe { - self.pipeline_layout - .dev() - .dev() - .destroy_pipeline_layout(self.pipeline_layout.handle(), None); - } +define_device_owned_handle! { + #[derive(Debug)] + pub PipelineLayout(vk::PipelineLayout) {} => |this| unsafe { + this.device().dev().destroy_pipeline_layout(this.handle(), None); } } @@ -254,22 +336,149 @@ impl PipelineLayout { let set_layouts = desc .descriptor_set_layouts .iter() - .map(|desc| desc.set_layout.handle()) + .map(|desc| desc.handle()) .collect::>(); let info = &vk::PipelineLayoutCreateInfo::default() .set_layouts(&set_layouts) .push_constant_ranges(desc.push_constant_ranges); let layout = unsafe { device.dev().create_pipeline_layout(info, None)? }; - Ok(Self { - pipeline_layout: DeviceOwnedDebugObject::new(device, layout, desc.name)?, - }) + Self::construct(device, layout, desc.name) + } +} + +#[derive(Debug, Default)] +pub struct SamplerDesc { + pub flags: vk::SamplerCreateFlags, + pub min_filter: vk::Filter, + pub mag_filter: vk::Filter, + pub mipmap_mode: vk::SamplerMipmapMode, + pub address_u: vk::SamplerAddressMode, + pub address_v: vk::SamplerAddressMode, + pub address_w: vk::SamplerAddressMode, + pub mip_lod_bias: f32, + pub anisotropy_enable: bool, + pub max_anisotropy: f32, + pub compare_op: Option, + pub min_lod: f32, + pub max_lod: f32, + pub border_color: vk::BorderColor, + pub unnormalized_coordinates: bool, +} + +impl Eq for SamplerDesc {} +impl PartialEq for SamplerDesc { + fn eq(&self, other: &Self) -> bool { + use crate::util::eq_f32; + self.flags == other.flags + && self.min_filter == other.min_filter + && self.mag_filter == other.mag_filter + && self.mipmap_mode == other.mipmap_mode + && self.address_u == other.address_u + && self.address_v == other.address_v + && self.address_w == other.address_w + && self.anisotropy_enable == other.anisotropy_enable + && self.compare_op == other.compare_op + && eq_f32(self.mip_lod_bias, other.mip_lod_bias) + && eq_f32(self.max_anisotropy, other.max_anisotropy) + && eq_f32(self.min_lod, other.min_lod) + && eq_f32(self.max_lod, other.max_lod) + && self.border_color == other.border_color + && self.unnormalized_coordinates == other.unnormalized_coordinates + } +} + +impl std::hash::Hash for SamplerDesc { + fn hash(&self, state: &mut H) { + use crate::util::hash_f32; + self.flags.hash(state); + self.min_filter.hash(state); + self.mag_filter.hash(state); + self.mipmap_mode.hash(state); + self.address_u.hash(state); + self.address_v.hash(state); + self.address_w.hash(state); + hash_f32(state, self.mip_lod_bias); + hash_f32(state, self.max_anisotropy); + hash_f32(state, self.min_lod); + hash_f32(state, self.max_lod); + self.anisotropy_enable.hash(state); + self.compare_op.hash(state); + self.border_color.hash(state); + self.unnormalized_coordinates.hash(state); + } +} + +define_device_owned_handle! { + #[derive(Debug)] + pub Sampler(vk::Sampler) {} => |this| unsafe { + this.device().dev().destroy_sampler(this.handle(), None); + } +} + +impl Sampler { + pub fn new(device: Device, desc: &SamplerDesc) -> VkResult { + let info = &vk::SamplerCreateInfo::default() + .flags(desc.flags) + .min_filter(desc.min_filter) + .mag_filter(desc.mag_filter) + .mip_lod_bias(desc.mip_lod_bias) + .mipmap_mode(desc.mipmap_mode) + .address_mode_u(desc.address_u) + .address_mode_v(desc.address_v) + .address_mode_w(desc.address_w) + .anisotropy_enable(desc.anisotropy_enable) + .max_anisotropy(desc.max_anisotropy) + .compare_enable(desc.compare_op.is_some()) + .compare_op(desc.compare_op.unwrap_or_default()) + .min_lod(desc.min_lod) + .max_lod(desc.max_lod) + .border_color(desc.border_color) + .unnormalized_coordinates(desc.unnormalized_coordinates); + + let handle = unsafe { device.dev().create_sampler(info, None)? }; + + Self::construct(device, handle, None) + } +} + +define_device_owned_handle! { + #[derive(Debug)] + pub ShaderModule(vk::ShaderModule) {} => |this| unsafe { + this.device().dev().destroy_shader_module(this.handle(), None); + } +} + +impl ShaderModule { + pub fn new_from_path>(device: Device, path: P) -> crate::Result { + use std::io::{BufReader, Read, Seek}; + + let mut file = std::fs::File::open(path)?; + let size = file.seek(std::io::SeekFrom::End(0))? / 4; + file.seek(std::io::SeekFrom::Start(0))?; + let mut reader = BufReader::new(file); + + let mut buffer = Vec::::with_capacity(size as usize); + buffer.resize(size as usize, 0); + let size = reader.read(bytemuck::cast_slice_mut(buffer.as_mut_slice()))?; + buffer.resize(size / 4, 0); + + Ok(Self::new_from_memory(device, &buffer)?) + } + + pub fn new_from_memory(device: Device, buffer: &[u32]) -> VkResult { + let info = &vk::ShaderModuleCreateInfo::default().code(buffer); + + let module = unsafe { device.dev().create_shader_module(info, None)? }; + + Self::construct(device, module, None) } } #[derive(Debug)] pub struct Pipeline { pipeline: DeviceOwnedDebugObject, + bind_point: vk::PipelineBindPoint, } impl Drop for Pipeline { @@ -286,7 +495,7 @@ impl Drop for Pipeline { impl ShaderStageDesc<'_> { fn into_create_info(&self) -> vk::PipelineShaderStageCreateInfo { vk::PipelineShaderStageCreateInfo::default() - .module(self.module) + .module(self.module.handle()) .flags(self.flags) .stage(self.stage) .name(&self.entry) @@ -296,11 +505,13 @@ impl ShaderStageDesc<'_> { impl Pipeline { pub fn new(device: Device, desc: PipelineDesc) -> VkResult { let name: Option>; + let bind_point: vk::PipelineBindPoint; let result = match desc { PipelineDesc::Compute(desc) => { name = desc.name; + bind_point = vk::PipelineBindPoint::COMPUTE; let info = &vk::ComputePipelineCreateInfo::default() - .layout(desc.layout.pipeline_layout.handle()) + .layout(desc.layout.handle()) .stage(desc.shader_stage.into_create_info()); unsafe { @@ -313,6 +524,7 @@ impl Pipeline { } PipelineDesc::Graphics(desc) => { name = desc.name; + bind_point = vk::PipelineBindPoint::GRAPHICS; let stages = desc .shader_stages @@ -426,6 +638,15 @@ impl Pipeline { info }); + let mut rendering = desc.rendering.map(|state| { + let mut info = vk::PipelineRenderingCreateInfo::default() + .color_attachment_formats(state.color_formats) + .depth_attachment_format(state.depth_format.unwrap_or_default()) + .stencil_attachment_format(state.stencil_format.unwrap_or_default()); + + info + }); + fn option_to_ptr(option: &Option) -> *const T { option .as_ref() @@ -433,7 +654,7 @@ impl Pipeline { .unwrap_or(core::ptr::null()) } - let info = &vk::GraphicsPipelineCreateInfo { + let mut info = vk::GraphicsPipelineCreateInfo { flags: desc.flags, stage_count: stages.len() as u32, p_stages: stages.as_ptr(), @@ -446,7 +667,7 @@ impl Pipeline { p_depth_stencil_state: option_to_ptr(&depth_stencil), p_color_blend_state: option_to_ptr(&color_blend), p_dynamic_state: option_to_ptr(&dynamic), - layout: desc.layout.pipeline_layout.handle(), + layout: desc.layout.handle(), render_pass: desc.render_pass.unwrap_or(vk::RenderPass::null()), subpass: desc.subpass.unwrap_or(0), base_pipeline_handle: desc @@ -456,10 +677,14 @@ impl Pipeline { ..Default::default() }; + if let Some(rendering) = rendering.as_mut() { + info = info.push_next(rendering) + } + unsafe { device.dev().create_graphics_pipelines( vk::PipelineCache::null(), - core::slice::from_ref(info), + core::slice::from_ref(&info), None, ) } @@ -481,6 +706,14 @@ impl Pipeline { Ok(Self { pipeline: DeviceOwnedDebugObject::new(device, pipeline, name)?, + bind_point, }) } + + pub fn handle(&self) -> vk::Pipeline { + self.pipeline.handle() + } + pub fn bind_point(&self) -> vk::PipelineBindPoint { + self.bind_point + } } diff --git a/crates/renderer/src/render_graph.rs b/crates/renderer/src/render_graph.rs index d068c27..7b1c724 100644 --- a/crates/renderer/src/render_graph.rs +++ b/crates/renderer/src/render_graph.rs @@ -1,5 +1,6 @@ use std::hash::Hash; +use crate::util::hash_f32; use ash::vk; #[derive(Debug, Clone, Copy)] @@ -7,18 +8,6 @@ pub struct Rgba(pub [f32; 4]); impl std::hash::Hash for Rgba { fn hash(&self, state: &mut H) { - fn hash_f32(state: &mut H, f: f32) { - let classify = f.classify(); - match classify { - std::num::FpCategory::Nan => (classify as u8).hash(state), - std::num::FpCategory::Infinite | std::num::FpCategory::Zero => { - (classify as u8, f.signum() as i8).hash(state) - } - std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => { - f.to_bits().hash(state) - } - } - } self.0.map(|f| hash_f32(state, f)); } } diff --git a/crates/renderer/src/util.rs b/crates/renderer/src/util.rs index 166669b..5d4a5ee 100644 --- a/crates/renderer/src/util.rs +++ b/crates/renderer/src/util.rs @@ -19,6 +19,10 @@ macro_rules! def_monotonic_id { .expect(&format!("integer overwarp for {}", stringify!($ty))), ) } + + pub fn as_u32(&self) -> u32 { + self.0.get() + } } }; } @@ -298,3 +302,24 @@ impl Rect2D { ] } } + +pub fn eq_f32(lhs: f32, rhs: f32) -> bool { + use core::num::FpCategory::*; + match (lhs.classify(), rhs.classify()) { + (Zero, Zero) | (Nan, Nan) => true, + (Infinite, Infinite) => lhs.signum() == rhs.signum(), + _ => lhs == rhs, + } +} + +pub fn hash_f32(state: &mut H, f: f32) { + use std::hash::Hash; + let classify = f.classify(); + match classify { + std::num::FpCategory::Nan => (classify as u8).hash(state), + std::num::FpCategory::Infinite | std::num::FpCategory::Zero => { + (classify as u8, f.signum() as i8).hash(state) + } + std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => f.to_bits().hash(state), + } +}