diff --git a/.gitignore b/.gitignore index 878b808..adac069 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ /blade-asset/cooked /_failure.wgsl +libEGL.dylib +libGLESv2.dylib + .DS_Store /.vs /.vscode diff --git a/Cargo.toml b/Cargo.toml index 061ebe6..d1cc0f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ profiling = "1" slab = "0.4" strum = { version = "0.25", features = ["derive"] } web-sys = "0.3.60" -winit = "0.30" +winit = { version = "0.30" } [lib] @@ -90,9 +90,11 @@ del-geo = "=0.1.29" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] # see https://github.com/emilk/egui/issues/4270 -egui-winit = "0.29" +egui-winit = { version="0.29", default-features=false, features=["links"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] +# see https://github.com/emilk/egui/issues/4270 +egui-winit = { version="0.29", default-features=false, features=["links"] } console_error_panic_hook = "0.1.7" console_log = "1" web-sys = { workspace = true, features = ["Window"] } diff --git a/blade-egui/src/lib.rs b/blade-egui/src/lib.rs index 7af3d5b..cb8b500 100644 --- a/blade-egui/src/lib.rs +++ b/blade-egui/src/lib.rs @@ -71,6 +71,7 @@ impl GuiTexture { mip_level_count: 1, dimension: blade_graphics::TextureDimension::D2, usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE, + sample_count: 1, }); let view = context.create_texture_view( allocation, @@ -161,6 +162,7 @@ impl GuiPainter { }), write_mask: blade_graphics::ColorWrites::all(), }], + multisample_state: Default::default(), }); let belt = BufferBelt::new(BufferBeltDescriptor { diff --git a/blade-graphics/src/gles/command.rs b/blade-graphics/src/gles/command.rs index 03001e6..1f140c9 100644 --- a/blade-graphics/src/gles/command.rs +++ b/blade-graphics/src/gles/command.rs @@ -185,6 +185,10 @@ impl super::CommandEncoder { if let crate::FinishOp::Discard = rt.finish_op { invalidate_attachments.push(attachment); } + if let crate::FinishOp::ResolveTo(to) = rt.finish_op { + self.commands + .push(super::Command::BlitFramebuffer { from: rt.view, to }); + } } if let Some(ref rt) = targets.depth_stencil { let attachment = match rt.view.aspects { @@ -806,6 +810,83 @@ impl super::Command { None, ); } + + Self::BlitFramebuffer { from, to } => { + /* + TODO: Framebuffers could be re-used instead of being created on the fly. + Currently deleted down below + */ + let framebuf_from = gl.create_framebuffer().unwrap(); + let framebuf_to = gl.create_framebuffer().unwrap(); + + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuf_from)); + match from.inner { + super::TextureInner::Renderbuffer { raw } => { + gl.framebuffer_renderbuffer( + glow::READ_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, // NOTE: Assuming color attachment + glow::RENDERBUFFER, + Some(raw), + ); + } + super::TextureInner::Texture { raw, target } => { + gl.framebuffer_texture_2d( + glow::READ_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + target, + Some(raw), + 0, + ); + } + } + + gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, Some(framebuf_to)); + match to.inner { + super::TextureInner::Renderbuffer { raw } => { + gl.framebuffer_renderbuffer( + glow::DRAW_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, // NOTE: Assuming color attachment + glow::RENDERBUFFER, + Some(raw), + ); + } + super::TextureInner::Texture { raw, target } => { + gl.framebuffer_texture_2d( + glow::DRAW_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + target, + Some(raw), + 0, + ); + } + } + + debug_assert_eq!( + gl.check_framebuffer_status(glow::DRAW_FRAMEBUFFER), + glow::FRAMEBUFFER_COMPLETE, + "DRAW_FRAMEBUFFER is not complete" + ); + + gl.blit_framebuffer( + 0, + 0, + from.target_size[0] as _, + from.target_size[1] as _, + 0, + 0, + to.target_size[0] as _, + to.target_size[1] as _, + glow::COLOR_BUFFER_BIT, // NOTE: Assuming color + glow::NEAREST, + ); + + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None); + gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None); + + gl.delete_framebuffer(framebuf_from); + gl.delete_framebuffer(framebuf_to); + } + Self::BindAttachment { attachment, ref view, diff --git a/blade-graphics/src/gles/egl.rs b/blade-graphics/src/gles/egl.rs index 9e9e5ce..70d6567 100644 --- a/blade-graphics/src/gles/egl.rs +++ b/blade-graphics/src/gles/egl.rs @@ -357,7 +357,8 @@ impl super::Context { let window_ptr = unsafe { use objc::{msg_send, runtime::Object, sel, sel_impl}; // ns_view always have a layer and don't need to verify that it exists. - let layer: *mut Object = msg_send![handle.ns_view.as_ptr(), layer]; + let layer: *mut Object = + msg_send![handle.ns_view.as_ptr() as *mut Object, layer]; layer as *mut ffi::c_void }; window_ptr diff --git a/blade-graphics/src/gles/mod.rs b/blade-graphics/src/gles/mod.rs index a56c9b2..3ea9c3a 100644 --- a/blade-graphics/src/gles/mod.rs +++ b/blade-graphics/src/gles/mod.rs @@ -274,6 +274,10 @@ enum Command { size: crate::Extent, }, ResetFramebuffer, + BlitFramebuffer { + from: TextureView, + to: TextureView, + }, BindAttachment { attachment: u32, view: TextureView, diff --git a/blade-graphics/src/gles/resource.rs b/blade-graphics/src/gles/resource.rs index 426a775..bad82b8 100644 --- a/blade-graphics/src/gles/resource.rs +++ b/blade-graphics/src/gles/resource.rs @@ -128,12 +128,24 @@ impl crate::traits::ResourceDevice for super::Context { let raw = unsafe { gl.create_renderbuffer().unwrap() }; unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(raw)); - gl.renderbuffer_storage( - glow::RENDERBUFFER, - format_desc.internal, - desc.size.width as i32, - desc.size.height as i32, - ); + + if desc.sample_count <= 1 { + gl.renderbuffer_storage( + glow::RENDERBUFFER, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + ); + } else { + gl.renderbuffer_storage_multisample( + glow::RENDERBUFFER, + desc.sample_count as i32, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + ); + } + gl.bind_renderbuffer(glow::RENDERBUFFER, None); #[cfg(not(target_arch = "wasm32"))] if !desc.name.is_empty() && gl.supports_debug() { @@ -144,11 +156,16 @@ impl crate::traits::ResourceDevice for super::Context { ); } } + super::TextureInner::Renderbuffer { raw } } else { let raw = unsafe { gl.create_texture().unwrap() }; + let target = match desc.dimension { crate::TextureDimension::D1 => { + if desc.sample_count > 1 { + log::warn!("Sample count is ignored: not supported for 1D textures",); + } if desc.array_layer_count > 1 { glow::TEXTURE_1D_ARRAY } else { @@ -157,12 +174,25 @@ impl crate::traits::ResourceDevice for super::Context { } crate::TextureDimension::D2 => { if desc.array_layer_count > 1 { - glow::TEXTURE_2D_ARRAY + if desc.sample_count <= 1 { + glow::TEXTURE_2D_ARRAY + } else { + glow::TEXTURE_2D_MULTISAMPLE_ARRAY + } } else { - glow::TEXTURE_2D + if desc.sample_count <= 1 { + glow::TEXTURE_2D + } else { + glow::TEXTURE_2D_MULTISAMPLE + } + } + } + crate::TextureDimension::D3 => { + if desc.sample_count > 1 { + log::warn!("Sample count is ignored: not supported for 3D textures",); } + glow::TEXTURE_3D } - crate::TextureDimension::D3 => glow::TEXTURE_3D, }; unsafe { @@ -184,13 +214,25 @@ impl crate::traits::ResourceDevice for super::Context { ); } crate::TextureDimension::D2 => { - gl.tex_storage_2d( - target, - desc.mip_level_count as i32, - format_desc.internal, - desc.size.width as i32, - desc.size.height as i32, - ); + if desc.sample_count <= 1 { + gl.tex_storage_2d( + target, + desc.mip_level_count as i32, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + ); + } else { + assert_eq!(desc.mip_level_count, 1); + gl.tex_storage_2d_multisample( + target, + desc.sample_count as i32, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + true, + ); + } } crate::TextureDimension::D1 => { gl.tex_storage_1d( diff --git a/blade-graphics/src/lib.rs b/blade-graphics/src/lib.rs index b85baf6..6719137 100644 --- a/blade-graphics/src/lib.rs +++ b/blade-graphics/src/lib.rs @@ -402,6 +402,7 @@ pub struct TextureDesc<'a> { pub size: Extent, pub array_layer_count: u32, pub mip_level_count: u32, + pub sample_count: u32, pub dimension: TextureDimension, pub usage: TextureUsage, } @@ -1018,18 +1019,39 @@ pub struct RenderPipelineDesc<'a> { pub depth_stencil: Option, pub fragment: ShaderFunction<'a>, pub color_targets: &'a [ColorTargetState], + pub multisample_state: MultisampleState, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct MultisampleState { + pub sample_count: u32, + pub sample_mask: u64, + pub alpha_to_coverage: bool, +} + +impl Default for MultisampleState { + fn default() -> Self { + Self { + sample_count: 1, + sample_mask: !0, + alpha_to_coverage: false, + } + } } #[derive(Clone, Copy, Debug)] pub enum InitOp { Load, Clear(TextureColor), + DontCare, } #[derive(Clone, Copy, Debug)] pub enum FinishOp { Store, Discard, + /// The texture specified here will be stored but it is undefined what + /// happens to the original render target ResolveTo(TextureView), Ignore, } diff --git a/blade-graphics/src/metal/command.rs b/blade-graphics/src/metal/command.rs index 4f148d6..ebd5dad 100644 --- a/blade-graphics/src/metal/command.rs +++ b/blade-graphics/src/metal/command.rs @@ -217,6 +217,7 @@ impl super::CommandEncoder { at_descriptor.set_clear_color(clear_color); metal::MTLLoadAction::Clear } + crate::InitOp::DontCare => metal::MTLLoadAction::DontCare, }; at_descriptor.set_load_action(load_action); @@ -247,6 +248,7 @@ impl super::CommandEncoder { at_descriptor.set_clear_depth(clear_depth); metal::MTLLoadAction::Clear } + crate::InitOp::DontCare => metal::MTLLoadAction::DontCare, }; let store_action = match rt.finish_op { crate::FinishOp::Store | crate::FinishOp::Ignore => { diff --git a/blade-graphics/src/metal/pipeline.rs b/blade-graphics/src/metal/pipeline.rs index 904412a..643cebd 100644 --- a/blade-graphics/src/metal/pipeline.rs +++ b/blade-graphics/src/metal/pipeline.rs @@ -420,6 +420,8 @@ impl crate::traits::ShaderDevice for super::Context { }, ); descriptor.set_vertex_function(Some(&vs.function)); + descriptor.set_raster_sample_count(desc.multisample_state.sample_count as _); + descriptor.set_alpha_to_coverage_enabled(desc.multisample_state.alpha_to_coverage); // Fragment shader let fs = self.load_shader( diff --git a/blade-graphics/src/metal/resource.rs b/blade-graphics/src/metal/resource.rs index 2b725b0..84e2a84 100644 --- a/blade-graphics/src/metal/resource.rs +++ b/blade-graphics/src/metal/resource.rs @@ -22,14 +22,26 @@ fn map_texture_usage(usage: crate::TextureUsage) -> metal::MTLTextureUsage { mtl_usage } -fn map_view_dimension(dimension: crate::ViewDimension) -> metal::MTLTextureType { +fn map_view_dimension(dimension: crate::ViewDimension, sample_count: u64) -> metal::MTLTextureType { use crate::ViewDimension as Vd; use metal::MTLTextureType::*; match dimension { Vd::D1 => D1, Vd::D1Array => D1Array, - Vd::D2 => D2, - Vd::D2Array => D2Array, + Vd::D2 => { + if sample_count <= 1 { + D2 + } else { + D2Multisample + } + } + Vd::D2Array => { + if sample_count <= 1 { + D2Array + } else { + D2MultisampleArray + } + } Vd::D3 => D3, Vd::Cube => Cube, Vd::CubeArray => CubeArray, @@ -179,9 +191,17 @@ impl crate::traits::ResourceDevice for super::Context { } crate::TextureDimension::D2 => { if desc.array_layer_count > 1 { - metal::MTLTextureType::D2Array + if desc.sample_count <= 1 { + metal::MTLTextureType::D2Array + } else { + metal::MTLTextureType::D2MultisampleArray + } } else { - metal::MTLTextureType::D2 + if desc.sample_count <= 1 { + metal::MTLTextureType::D2 + } else { + metal::MTLTextureType::D2Multisample + } } } crate::TextureDimension::D3 => metal::MTLTextureType::D3, @@ -198,6 +218,7 @@ impl crate::traits::ResourceDevice for super::Context { descriptor.set_array_length(desc.array_layer_count as u64); descriptor.set_mipmap_level_count(desc.mip_level_count as u64); descriptor.set_pixel_format(mtl_format); + descriptor.set_sample_count(desc.sample_count as _); descriptor.set_usage(mtl_usage); descriptor.set_storage_mode(metal::MTLStorageMode::Private); @@ -225,7 +246,7 @@ impl crate::traits::ResourceDevice for super::Context { ) -> super::TextureView { let texture = texture.as_ref(); let mtl_format = super::map_texture_format(desc.format); - let mtl_type = map_view_dimension(desc.dimension); + let mtl_type = map_view_dimension(desc.dimension, texture.sample_count()); let mip_level_count = match desc.subresources.mip_level_count { Some(count) => count.get() as u64, None => texture.mipmap_level_count() - desc.subresources.base_mip_level as u64, diff --git a/blade-graphics/src/vulkan/command.rs b/blade-graphics/src/vulkan/command.rs index b778686..c74b75e 100644 --- a/blade-graphics/src/vulkan/command.rs +++ b/blade-graphics/src/vulkan/command.rs @@ -190,10 +190,34 @@ fn map_render_target(rt: &crate::RenderTarget) -> vk::RenderingAttachmentInfo<'s }, } }; + vk_info.load_op = vk::AttachmentLoadOp::CLEAR; vk_info.clear_value = cv; } + if let crate::FinishOp::ResolveTo(resolve_view) = rt.finish_op { + vk_info = vk_info + .resolve_image_view(resolve_view.raw) + .resolve_image_layout(vk::ImageLayout::GENERAL) + .resolve_mode(vk::ResolveModeFlags::AVERAGE); + } + + vk_info.store_op = match rt.finish_op { + crate::FinishOp::Store => vk::AttachmentStoreOp::STORE, + crate::FinishOp::Discard => vk::AttachmentStoreOp::DONT_CARE, + crate::FinishOp::Ignore => vk::AttachmentStoreOp::DONT_CARE, + crate::FinishOp::ResolveTo(..) => { + /* + TODO: DONT_CARE is most optimal in many cases where the msaa texture itself is never read afterwards but only the resolved, + but how can the user specify this in blade? + https://docs.vulkan.org/samples/latest/samples/performance/msaa/README.html#_best_practice_summary + */ + + // vk::AttachmentStoreOp::STORE + vk::AttachmentStoreOp::DONT_CARE + } + }; + vk_info } diff --git a/blade-graphics/src/vulkan/pipeline.rs b/blade-graphics/src/vulkan/pipeline.rs index 8182aa8..fa07096 100644 --- a/blade-graphics/src/vulkan/pipeline.rs +++ b/blade-graphics/src/vulkan/pipeline.rs @@ -503,9 +503,16 @@ impl crate::traits::ShaderDevice for super::Context { .scissor_count(1) .viewport_count(1); - let vk_sample_mask = [1u32, 0]; + let vk_sample_mask = [ + desc.multisample_state.sample_mask as u32, + (desc.multisample_state.sample_mask >> 32) as u32, + ]; + let vk_multisample = vk::PipelineMultisampleStateCreateInfo::default() - .rasterization_samples(vk::SampleCountFlags::TYPE_1) + .rasterization_samples(vk::SampleCountFlags::from_raw( + desc.multisample_state.sample_count, + )) + .alpha_to_coverage_enable(desc.multisample_state.alpha_to_coverage) .sample_mask(&vk_sample_mask); let mut d_format = vk::Format::UNDEFINED; diff --git a/blade-graphics/src/vulkan/resource.rs b/blade-graphics/src/vulkan/resource.rs index 089219b..02b08b7 100644 --- a/blade-graphics/src/vulkan/resource.rs +++ b/blade-graphics/src/vulkan/resource.rs @@ -252,7 +252,7 @@ impl crate::traits::ResourceDevice for super::Context { let mut create_flags = vk::ImageCreateFlags::empty(); if desc.dimension == crate::TextureDimension::D2 && desc.size.depth % 6 == 0 - //&& desc.sample_count == 1 + && desc.sample_count == 1 && desc.size.width == desc.size.height { create_flags |= vk::ImageCreateFlags::CUBE_COMPATIBLE; @@ -265,13 +265,17 @@ impl crate::traits::ResourceDevice for super::Context { extent: super::map_extent_3d(&desc.size), mip_levels: desc.mip_level_count, array_layers: desc.array_layer_count, - samples: vk::SampleCountFlags::from_raw(1), // desc.sample_count + samples: vk::SampleCountFlags::from_raw(desc.sample_count), tiling: vk::ImageTiling::OPTIMAL, usage: map_texture_usage(desc.usage, desc.format.aspects()), sharing_mode: vk::SharingMode::EXCLUSIVE, ..Default::default() }; + /* + TODO(ErikWDev): Support lazily allocated texture with transient allocation for efficient msaa? + Measure bandwidth usage! + */ let raw = unsafe { self.device.core.create_image(&vk_info, None).unwrap() }; let requirements = unsafe { self.device.core.get_image_memory_requirements(raw) }; let allocation = self.allocate_memory(requirements, crate::Memory::Device); diff --git a/blade-render/src/render/debug.rs b/blade-render/src/render/debug.rs index 06da080..027158c 100644 --- a/blade-render/src/render/debug.rs +++ b/blade-render/src/render/debug.rs @@ -95,6 +95,7 @@ fn create_draw_pipeline( blend: Some(blade_graphics::BlendState::ALPHA_BLENDING), write_mask: blade_graphics::ColorWrites::all(), }], + multisample_state: blade_graphics::MultisampleState::default(), }) } @@ -117,6 +118,7 @@ fn create_blit_pipeline( depth_stencil: None, fragment: shader.at("blit_fs"), color_targets: &[format.into()], + multisample_state: blade_graphics::MultisampleState::default(), }) } diff --git a/blade-render/src/render/dummy.rs b/blade-render/src/render/dummy.rs index f4c9fb4..7e6e75c 100644 --- a/blade-render/src/render/dummy.rs +++ b/blade-render/src/render/dummy.rs @@ -29,6 +29,7 @@ impl DummyResources { mip_level_count: 1, dimension: blade_graphics::TextureDimension::D2, usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE, + sample_count: 1, }); let white_view = gpu.create_texture_view( white_texture, @@ -47,6 +48,7 @@ impl DummyResources { mip_level_count: 1, dimension: blade_graphics::TextureDimension::D2, usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE, + sample_count: 1, }); let black_view = gpu.create_texture_view( black_texture, @@ -65,6 +67,7 @@ impl DummyResources { mip_level_count: 1, dimension: blade_graphics::TextureDimension::D2, usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE, + sample_count: 1, }); let red_view = gpu.create_texture_view( red_texture, diff --git a/blade-render/src/render/env_map.rs b/blade-render/src/render/env_map.rs index 979dfae..a87bf42 100644 --- a/blade-render/src/render/env_map.rs +++ b/blade-render/src/render/env_map.rs @@ -112,6 +112,7 @@ impl EnvironmentMap { array_layer_count: 1, mip_level_count, usage: blade_graphics::TextureUsage::RESOURCE | blade_graphics::TextureUsage::STORAGE, + sample_count: 1, }); self.weight_view = gpu.create_texture_view( self.weight_texture, diff --git a/blade-render/src/render/mod.rs b/blade-render/src/render/mod.rs index 43f92da..569c1cf 100644 --- a/blade-render/src/render/mod.rs +++ b/blade-render/src/render/mod.rs @@ -178,6 +178,7 @@ impl RenderTarget { array_layer_count: N as u32, mip_level_count: 1, usage: blade_graphics::TextureUsage::RESOURCE | blade_graphics::TextureUsage::STORAGE, + sample_count: 1, }); encoder.init_texture(texture); @@ -605,6 +606,7 @@ impl ShaderPipelines { fragment: shader.at("postfx_fs"), color_targets: &[info.format.into()], depth_stencil: None, + multisample_state: blade_graphics::MultisampleState::default(), }) } diff --git a/blade-render/src/texture/mod.rs b/blade-render/src/texture/mod.rs index 6e5192c..4a5b052 100644 --- a/blade-render/src/texture/mod.rs +++ b/blade-render/src/texture/mod.rs @@ -396,6 +396,7 @@ impl blade_asset::Baker for Baker { mip_level_count: image.mips.len() as u32, dimension: blade_graphics::TextureDimension::D2, usage: blade_graphics::TextureUsage::COPY | blade_graphics::TextureUsage::RESOURCE, + sample_count: 1, }); let view = self.gpu_context.create_texture_view( texture, diff --git a/examples/bunnymark/main.rs b/examples/bunnymark/main.rs index b9e8d32..e027c7e 100644 --- a/examples/bunnymark/main.rs +++ b/examples/bunnymark/main.rs @@ -123,6 +123,7 @@ impl Example { blend: Some(gpu::BlendState::ALPHA_BLENDING), write_mask: gpu::ColorWrites::default(), }], + multisample_state: gpu::MultisampleState::default(), }); let extent = gpu::Extent { @@ -138,6 +139,7 @@ impl Example { array_layer_count: 1, mip_level_count: 1, usage: gpu::TextureUsage::RESOURCE | gpu::TextureUsage::COPY, + sample_count: 1, }); let view = context.create_texture_view( texture, diff --git a/examples/init/main.rs b/examples/init/main.rs index a192139..5ffabc2 100644 --- a/examples/init/main.rs +++ b/examples/init/main.rs @@ -29,6 +29,7 @@ impl EnvMapSampler { mip_level_count: 1, dimension: gpu::TextureDimension::D2, usage: gpu::TextureUsage::TARGET, + sample_count: 1, }); let accum_view = context.create_texture_view( accum_texture, @@ -57,6 +58,7 @@ impl EnvMapSampler { blend: None, write_mask: gpu::ColorWrites::ALL, }], + multisample_state: gpu::MultisampleState::default(), }); let accum_pipeline = context.create_render_pipeline(gpu::RenderPipelineDesc { name: "env-accum", @@ -74,6 +76,7 @@ impl EnvMapSampler { blend: Some(gpu::BlendState::ADDITIVE), write_mask: gpu::ColorWrites::RED, }], + multisample_state: gpu::MultisampleState::default(), }); Self { diff --git a/examples/mini/main.rs b/examples/mini/main.rs index 223df8d..11299ae 100644 --- a/examples/mini/main.rs +++ b/examples/mini/main.rs @@ -59,6 +59,7 @@ fn main() { array_layer_count: 1, mip_level_count, usage: gpu::TextureUsage::RESOURCE | gpu::TextureUsage::STORAGE | gpu::TextureUsage::COPY, + sample_count: 1, }); let views = (0..mip_level_count) .map(|i| { diff --git a/examples/particle/main.rs b/examples/particle/main.rs index e6a24c7..d6a8659 100644 --- a/examples/particle/main.rs +++ b/examples/particle/main.rs @@ -10,10 +10,88 @@ struct Example { context: gpu::Context, surface: gpu::Surface, gui_painter: blade_egui::GuiPainter, + particle_system: particle::System, + + sample_count: u32, + msaa_texture: Option, + msaa_view: Option, } +// pub const INITIAL_SAMPLE_COUNT: u32 = 1; +// pub const INITIAL_SAMPLE_COUNT: u32 = 2; +pub const INITIAL_SAMPLE_COUNT: u32 = 4; +// pub const INITIAL_SAMPLE_COUNT: u32 = 8; + impl Example { + fn resize(&mut self, size: winit::dpi::PhysicalSize) { + let config = Self::make_surface_config(size); + self.context.reconfigure_surface(&mut self.surface, config); + + let surface_info = self.surface.info(); + + if let Some(sp) = self.prev_sync_point.take() { + self.context.wait_for(&sp, !0); + } + + if let Some(msaa_view) = self.msaa_view.take() { + self.context.destroy_texture_view(msaa_view); + } + if let Some(msaa_texture) = self.msaa_texture.take() { + self.context.destroy_texture(msaa_texture); + } + self.recreate_msaa_texutres_if_needed((size.width, size.height), surface_info.format); + } + + fn recreate_msaa_texutres_if_needed( + &mut self, + (width, height): (u32, u32), + format: gpu::TextureFormat, + ) { + if self.sample_count > 1 && self.msaa_texture.is_none() { + let msaa_texture = self.context.create_texture(gpu::TextureDesc { + name: "msaa texture", + format, + size: gpu::Extent { + width, + height, + depth: 1, + }, + sample_count: self.sample_count, + dimension: gpu::TextureDimension::D2, + usage: gpu::TextureUsage::TARGET, + array_layer_count: 1, + mip_level_count: 1, + }); + let msaa_view = self.context.create_texture_view( + msaa_texture, + gpu::TextureViewDesc { + name: "msaa texture view", + format, + dimension: gpu::ViewDimension::D2, + subresources: &Default::default(), + }, + ); + + self.msaa_texture = Some(msaa_texture); + self.msaa_view = Some(msaa_view); + } + } + + fn make_surface_config(size: winit::dpi::PhysicalSize) -> gpu::SurfaceConfig { + log::info!("Window size: {:?}", size); + gpu::SurfaceConfig { + size: gpu::Extent { + width: size.width, + height: size.height, + depth: 1, + }, + usage: gpu::TextureUsage::TARGET, + display_sync: gpu::DisplaySync::Block, + ..Default::default() + } + } + fn new(window: &winit::window::Window) -> Self { let window_size = window.inner_size(); let context = unsafe { @@ -21,23 +99,14 @@ impl Example { presentation: true, validation: cfg!(debug_assertions), timing: true, - capture: true, + capture: false, + ..Default::default() }) .unwrap() }; - let surface_config = gpu::SurfaceConfig { - size: gpu::Extent { - width: window_size.width, - height: window_size.height, - depth: 1, - }, - usage: gpu::TextureUsage::TARGET, - display_sync: gpu::DisplaySync::Block, - ..Default::default() - }; let surface = context - .create_surface_configured(window, surface_config) + .create_surface_configured(window, Self::make_surface_config(window_size)) .unwrap(); let surface_info = surface.info(); @@ -49,6 +118,7 @@ impl Example { capacity: 100_000, draw_format: surface_info.format, }, + INITIAL_SAMPLE_COUNT, ); let mut command_encoder = context.create_command_encoder(gpu::CommandEncoderDesc { @@ -66,6 +136,9 @@ impl Example { surface, gui_painter, particle_system, + sample_count: INITIAL_SAMPLE_COUNT, + msaa_texture: None, + msaa_view: None, } } @@ -78,6 +151,13 @@ impl Example { self.gui_painter.destroy(&self.context); self.particle_system.destroy(&self.context); self.context.destroy_surface(&mut self.surface); + + if let Some(msaa_view) = self.msaa_view.take() { + self.context.destroy_texture_view(msaa_view); + } + if let Some(msaa_texture) = self.msaa_texture.take() { + self.context.destroy_texture(msaa_texture); + } } fn render( @@ -86,30 +166,71 @@ impl Example { gui_textures: &egui::TexturesDelta, screen_desc: &blade_egui::ScreenDescriptor, ) { + self.recreate_msaa_texutres_if_needed( + screen_desc.physical_size, + self.surface.info().format, + ); + let frame = self.surface.acquire_frame(); + let frame_view = frame.texture_view(); self.command_encoder.start(); + if let Some(msaa_texture) = self.msaa_texture { + self.command_encoder.init_texture(msaa_texture); + } self.command_encoder.init_texture(frame.texture()); self.gui_painter .update_textures(&mut self.command_encoder, gui_textures, &self.context); - self.particle_system.update(&mut self.command_encoder); - if let mut pass = self.command_encoder.render( - "draw", - gpu::RenderTargetSet { - colors: &[gpu::RenderTarget { - view: frame.texture_view(), - init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack), - finish_op: gpu::FinishOp::Store, - }], - depth_stencil: None, - }, - ) { - self.particle_system.draw(&mut pass); - self.gui_painter - .paint(&mut pass, gui_primitives, screen_desc, &self.context); + if self.sample_count <= 1 { + if let mut pass = self.command_encoder.render( + "draw particles and ui", + gpu::RenderTargetSet { + colors: &[gpu::RenderTarget { + view: frame_view, + init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack), + finish_op: gpu::FinishOp::Store, + }], + depth_stencil: None, + }, + ) { + self.particle_system + .draw(&mut pass, screen_desc.physical_size); + self.gui_painter + .paint(&mut pass, gui_primitives, screen_desc, &self.context); + } + } else { + if let mut pass = self.command_encoder.render( + "draw particles with msaa resolve", + gpu::RenderTargetSet { + colors: &[gpu::RenderTarget { + view: self.msaa_view.unwrap(), + init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack), + finish_op: gpu::FinishOp::ResolveTo(frame_view), + }], + depth_stencil: None, + }, + ) { + self.particle_system + .draw(&mut pass, screen_desc.physical_size); + } + if let mut pass = self.command_encoder.render( + "draw ui", + gpu::RenderTargetSet { + colors: &[gpu::RenderTarget { + view: frame_view, + init_op: gpu::InitOp::Load, + finish_op: gpu::FinishOp::Store, + }], + depth_stencil: None, + }, + ) { + self.gui_painter + .paint(&mut pass, gui_primitives, screen_desc, &self.context); + } } + self.command_encoder.present(frame); let sync_point = self.context.submit(&mut self.command_encoder); self.gui_painter.after_submit(&sync_point); @@ -123,6 +244,45 @@ impl Example { fn add_gui(&mut self, ui: &mut egui::Ui) { ui.heading("Particle System"); self.particle_system.add_gui(ui); + + ui.add_space(5.0); + ui.heading("Rendering Settings"); + egui::ComboBox::new("msaa dropdown", "MSAA samples") + .selected_text(format!("x{}", self.sample_count)) + .show_ui(ui, |ui| { + for i in [1, 2, 4] { + if ui + .selectable_value(&mut self.sample_count, i, format!("x{i}")) + .changed() + { + if let Some(sp) = self.prev_sync_point.take() { + self.context.wait_for(&sp, !0); + } + + let old_params = self.particle_system.params; + self.particle_system.destroy(&self.context); + self.particle_system = particle::System::new( + &self.context, + particle::SystemDesc { + name: "particle system", + capacity: 100_000, + draw_format: self.surface.info().format, + }, + self.sample_count, + ); + self.particle_system.params = old_params; + + if let Some(msaa_view) = self.msaa_view.take() { + self.context.destroy_texture_view(msaa_view); + } + if let Some(msaa_texture) = self.msaa_texture.take() { + self.context.destroy_texture(msaa_texture); + } + } + } + }); + + ui.add_space(5.0); ui.heading("Timings"); for (name, time) in self.command_encoder.timings() { let millis = time.as_secs_f32() * 1000.0; @@ -156,6 +316,7 @@ fn main() { winit::event::Event::AboutToWait => { window.request_redraw(); } + winit::event::Event::WindowEvent { event, .. } => { let response = egui_winit.on_window_event(&window, &event); if response.consumed { @@ -183,11 +344,19 @@ fn main() { winit::event::WindowEvent::CloseRequested => { target.exit(); } + + winit::event::WindowEvent::Resized(size) => { + example.resize(size); + } + winit::event::WindowEvent::RedrawRequested => { let raw_input = egui_winit.take_egui_input(&window); let egui_output = egui_winit.egui_ctx().run(raw_input, |egui_ctx| { egui::SidePanel::left("info").show(egui_ctx, |ui| { + ui.add_space(5.0); example.add_gui(ui); + + ui.add_space(5.0); if ui.button("Quit").clicked() { target.exit(); } @@ -207,7 +376,9 @@ fn main() { let control_flow = if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_delay) { - winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + winit::event_loop::ControlFlow::WaitUntil( + repaint_after_instant.into(), + ) } else { winit::event_loop::ControlFlow::Wait }; diff --git a/examples/particle/particle.rs b/examples/particle/particle.rs index 7314514..376950d 100644 --- a/examples/particle/particle.rs +++ b/examples/particle/particle.rs @@ -71,7 +71,7 @@ struct DrawData { } impl System { - pub fn new(context: &gpu::Context, desc: SystemDesc) -> Self { + pub fn new(context: &gpu::Context, desc: SystemDesc, sample_count: u32) -> Self { let source = std::fs::read_to_string("examples/particle/particle.wgsl").unwrap(); let shader = context.create_shader(gpu::ShaderDesc { source: &source }); let particle_size = shader.get_struct_size("Particle"); @@ -112,6 +112,10 @@ impl System { write_mask: gpu::ColorWrites::default(), }], depth_stencil: None, + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }); let wg_width = reset_pipeline.get_workgroup_size()[0] as usize; @@ -198,7 +202,7 @@ impl System { } } - pub fn draw(&self, pass: &mut gpu::RenderCommandEncoder) { + pub fn draw(&self, pass: &mut gpu::RenderCommandEncoder, size: (u32, u32)) { let mut pc = pass.with(&self.draw_pipeline); pc.bind( 0, @@ -211,7 +215,11 @@ impl System { scale: 1.0, }, screen_center: [0.0; 2], - screen_extent: [1000.0; 2], + screen_extent: { + let ratio = size.0 as f32 / size.1 as f32; + + [1000.0 * ratio, 1000.0] + }, }, }, ); @@ -219,8 +227,8 @@ impl System { } pub fn add_gui(&mut self, ui: &mut egui::Ui) { - ui.add(egui::Slider::new(&mut self.params.life, 0.1..=10.0).text("life")); + ui.add(egui::Slider::new(&mut self.params.life, 0.1..=20.0).text("life")); ui.add(egui::Slider::new(&mut self.params.velocity, 1.0..=500.0).text("velocity")); - ui.add(egui::Slider::new(&mut self.params.scale, 0.1..=50.0).text("scale")); + ui.add(egui::Slider::new(&mut self.params.scale, 0.1..=70.0).text("scale")); } } diff --git a/examples/ray-query/main.rs b/examples/ray-query/main.rs index 3fab8f2..b5c1039 100644 --- a/examples/ray-query/main.rs +++ b/examples/ray-query/main.rs @@ -82,6 +82,7 @@ impl Example { dimension: gpu::TextureDimension::D2, array_layer_count: 1, mip_level_count: 1, + sample_count: 1, usage: gpu::TextureUsage::RESOURCE | gpu::TextureUsage::STORAGE, }); let target_view = context.create_texture_view( @@ -115,6 +116,7 @@ impl Example { fragment: shader.at("draw_fs"), color_targets: &[surface.info().format.into()], depth_stencil: None, + multisample_state: Default::default(), }); let (indices, vertex_values) =