From 221228ec15dc5837bc2e21bdffdd12c49d2e6dbe Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Fri, 2 Dec 2022 22:24:51 +0100 Subject: [PATCH] Add blending and blitting --- narcissus-gpu/src/backend/vulkan/mod.rs | 151 ++++++++++++++++++++---- narcissus-gpu/src/lib.rs | 47 +++++++- narcissus/src/main.rs | 18 +-- 3 files changed, 183 insertions(+), 33 deletions(-) diff --git a/narcissus-gpu/src/backend/vulkan/mod.rs b/narcissus-gpu/src/backend/vulkan/mod.rs index 85cc1c6..12617e6 100644 --- a/narcissus-gpu/src/backend/vulkan/mod.rs +++ b/narcissus-gpu/src/backend/vulkan/mod.rs @@ -18,14 +18,14 @@ use vulkan_sys as vk; use crate::{ delay_queue::DelayQueue, frame_counter::FrameCounter, Access, Bind, BindGroupLayout, - BindGroupLayoutDesc, BindingType, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, - ClearValue, CmdBuffer, CompareOp, ComputePipelineDesc, CullingMode, Device, Extent2d, Extent3d, - Frame, FrontFace, GlobalBarrier, GpuConcurrent, GraphicsPipelineDesc, Image, ImageAspectFlags, - ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageSubresourceLayers, - ImageSubresourceRange, ImageUsageFlags, ImageViewDesc, IndexType, LoadOp, MemoryLocation, - Offset2d, Offset3d, Pipeline, PolygonMode, Sampler, SamplerAddressMode, SamplerCompareOp, - SamplerDesc, SamplerFilter, ShaderStageFlags, StencilOp, StencilOpState, StoreOp, - SwapchainOutOfDateError, ThreadToken, Topology, TypedBind, + BindGroupLayoutDesc, BindingType, BlendMode, Buffer, BufferDesc, BufferImageCopy, + BufferUsageFlags, ClearValue, CmdBuffer, CompareOp, ComputePipelineDesc, CullingMode, Device, + Extent2d, Extent3d, Frame, FrontFace, GlobalBarrier, GpuConcurrent, GraphicsPipelineDesc, + Image, ImageAspectFlags, ImageBarrier, ImageBlit, ImageDesc, ImageDimension, ImageFormat, + ImageLayout, ImageSubresourceLayers, ImageSubresourceRange, ImageUsageFlags, ImageViewDesc, + IndexType, LoadOp, MemoryLocation, Offset2d, Offset3d, Pipeline, PolygonMode, Sampler, + SamplerAddressMode, SamplerCompareOp, SamplerDesc, SamplerFilter, ShaderStageFlags, StencilOp, + StencilOpState, StoreOp, SwapchainOutOfDateError, ThreadToken, Topology, TypedBind, }; const NUM_FRAMES: usize = 2; @@ -303,6 +303,48 @@ fn vulkan_stencil_op_state(stencil_op_state: StencilOpState) -> vk::StencilOpSta } } +#[must_use] +fn vulkan_blend_mode(blend_mode: BlendMode) -> vk::PipelineColorBlendAttachmentState { + match blend_mode { + BlendMode::Opaque => vk::PipelineColorBlendAttachmentState { + color_write_mask: vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + ..default() + }, + BlendMode::Mask => todo!(), + BlendMode::Translucent => vk::PipelineColorBlendAttachmentState { + blend_enable: vk::Bool32::True, + src_color_blend_factor: vk::BlendFactor::SrcAlpha, + dst_color_blend_factor: vk::BlendFactor::OneMinusSrcAlpha, + color_blend_op: vk::BlendOp::Add, + src_alpha_blend_factor: vk::BlendFactor::One, + dst_alpha_blend_factor: vk::BlendFactor::Zero, + alpha_blend_op: vk::BlendOp::Add, + color_write_mask: vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + }, + BlendMode::Premultiplied => vk::PipelineColorBlendAttachmentState { + blend_enable: vk::Bool32::True, + src_color_blend_factor: vk::BlendFactor::One, + dst_color_blend_factor: vk::BlendFactor::OneMinusSrcAlpha, + color_blend_op: vk::BlendOp::Add, + src_alpha_blend_factor: vk::BlendFactor::One, + dst_alpha_blend_factor: vk::BlendFactor::Zero, + alpha_blend_op: vk::BlendOp::Add, + color_write_mask: vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + }, + BlendMode::Additive => todo!(), + BlendMode::Modulate => todo!(), + } +} + #[must_use] fn vulkan_image_view_type(layer_count: u32, image_dimension: ImageDimension) -> vk::ImageViewType { match (layer_count, image_dimension) { @@ -698,7 +740,7 @@ impl VulkanImageHolder { match self { VulkanImageHolder::Unique(x) => x.image.image, VulkanImageHolder::Shared(_) => panic!(), - VulkanImageHolder::Swapchain(_) => panic!(), + VulkanImageHolder::Swapchain(x) => x.image, } } @@ -1549,10 +1591,13 @@ impl Device for VulkanDevice { if desc.usage.contains(ImageUsageFlags::STORAGE) { usage |= vk::ImageUsageFlags::STORAGE; } - if desc.usage.contains(ImageUsageFlags::DEPTH_STENCIL) { + if desc + .usage + .contains(ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) + { usage |= vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT; } - if desc.usage.contains(ImageUsageFlags::RENDER_TARGET) { + if desc.usage.contains(ImageUsageFlags::COLOR_ATTACHMENT) { usage |= vk::ImageUsageFlags::COLOR_ATTACHMENT; } if desc.usage.contains(ImageUsageFlags::TRANSFER_DST) { @@ -1888,13 +1933,7 @@ impl Device for VulkanDevice { front, ..default() }; - let color_blend_attachments = &[vk::PipelineColorBlendAttachmentState { - color_write_mask: vk::ColorComponentFlags::R - | vk::ColorComponentFlags::G - | vk::ColorComponentFlags::B - | vk::ColorComponentFlags::A, - ..default() - }]; + let color_blend_attachments = &[vulkan_blend_mode(desc.blend_mode)]; let color_blend_state = vk::PipelineColorBlendStateCreateInfo { attachments: color_blend_attachments.into(), ..default() @@ -2150,7 +2189,7 @@ impl Device for VulkanDevice { buffer_offset: copy.buffer_offset, buffer_row_length: copy.buffer_row_length, buffer_image_height: copy.buffer_image_height, - image_subresource: vulkan_subresource_layers(©.image_subresource_layers), + image_subresource: vulkan_subresource_layers(©.image_subresource), image_offset: copy.image_offset.into(), image_extent: copy.image_extent.into(), })); @@ -2186,6 +2225,62 @@ impl Device for VulkanDevice { } } + fn cmd_blit_image( + &self, + cmd_buffer: &mut CmdBuffer, + src_image: Image, + src_image_layout: ImageLayout, + dst_image: Image, + dst_image_layout: ImageLayout, + regions: &[ImageBlit], + ) { + let arena = HybridArena::<4096>::new(); + + let regions = arena.alloc_slice_fill_iter(regions.iter().map(|blit| vk::ImageBlit { + src_subresource: vulkan_subresource_layers(&blit.src_subresource), + src_offsets: [blit.src_offset_min.into(), blit.src_offset_max.into()], + dst_subresource: vulkan_subresource_layers(&blit.dst_subresource), + dst_offsets: [blit.dst_offset_min.into(), blit.dst_offset_max.into()], + })); + + let src_image = self + .image_pool + .lock() + .get(src_image.0) + .expect("invalid src image handle") + .image(); + + let src_image_layout = match src_image_layout { + ImageLayout::Optimal => vk::ImageLayout::TransferSrcOptimal, + ImageLayout::General => vk::ImageLayout::General, + }; + + let dst_image = self + .image_pool + .lock() + .get(dst_image.0) + .expect("invalid dst image handle") + .image(); + + let dst_image_layout = match dst_image_layout { + ImageLayout::Optimal => vk::ImageLayout::TransferDstOptimal, + ImageLayout::General => vk::ImageLayout::General, + }; + + let command_buffer = self.cmd_buffer_mut(cmd_buffer).command_buffer; + unsafe { + self.device_fn.cmd_blit_image( + command_buffer, + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + vk::Filter::Linear, + ); + } + } + fn cmd_set_bind_group( &self, frame: &Frame, @@ -2626,7 +2721,10 @@ impl Device for VulkanDevice { .get_mut(&swapchain) .expect("presenting a swapchain that hasn't been acquired this frame"); - assert!(!present_swapchain.acquire.is_null()); + assert!( + !present_swapchain.acquire.is_null(), + "acquiring a swapchain image multiple times" + ); present_swapchain.release = self.request_transient_semaphore(frame); wait_semaphores.push(vk::SemaphoreSubmitInfo { @@ -2734,8 +2832,17 @@ impl Device for VulkanDevice { { let frame = self.frame_mut(&mut frame); + self.swapchains.lock(); + let present_swapchains = frame.present_swapchains.get_mut(); if !present_swapchains.is_empty() { + for present_info in present_swapchains.values() { + assert!( + !present_info.release.is_null(), + "swapchain image was acquired, but not consumed" + ); + } + let windows = arena.alloc_slice_fill_iter(present_swapchains.keys().copied()); let wait_semaphores = arena.alloc_slice_fill_iter(present_swapchains.values().map(|x| x.release)); @@ -2984,7 +3091,9 @@ impl VulkanDevice { image_format: vulkan_swapchain.surface_format.format, image_color_space: vulkan_swapchain.surface_format.color_space, image_extent: vk::Extent2d { width, height }, - image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, + image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT + | vk::ImageUsageFlags::TRANSFER_SRC + | vk::ImageUsageFlags::TRANSFER_DST, image_array_layers: 1, image_sharing_mode: vk::SharingMode::Exclusive, pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, diff --git a/narcissus-gpu/src/lib.rs b/narcissus-gpu/src/lib.rs index 574af5e..5446aee 100644 --- a/narcissus-gpu/src/lib.rs +++ b/narcissus-gpu/src/lib.rs @@ -60,7 +60,7 @@ pub struct BindGroupLayout(Handle); #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Pipeline(Handle); -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum MemoryLocation { HostMapped, Device, @@ -82,6 +82,15 @@ pub struct Scissor { pub extent: Extent2d, } +impl Scissor { + pub const fn new(x: i32, y: i32, width: u32, height: u32) -> Self { + Self { + offset: Offset2d { x, y }, + extent: Extent2d { width, height }, + } + } +} + flags_def!(ShaderStageFlags); impl ShaderStageFlags { pub const VERTEX: Self = Self(1 << 0); @@ -119,8 +128,8 @@ flags_def!(ImageUsageFlags); impl ImageUsageFlags { pub const SAMPLED: Self = Self(1 << 0); pub const STORAGE: Self = Self(1 << 1); - pub const DEPTH_STENCIL: Self = Self(1 << 2); - pub const RENDER_TARGET: Self = Self(1 << 3); + pub const COLOR_ATTACHMENT: Self = Self(1 << 2); + pub const DEPTH_STENCIL_ATTACHMENT: Self = Self(1 << 3); pub const TRANSFER_SRC: Self = Self(1 << 4); pub const TRANSFER_DST: Self = Self(1 << 5); } @@ -208,11 +217,20 @@ pub struct BufferImageCopy { pub buffer_offset: u64, pub buffer_row_length: u32, pub buffer_image_height: u32, - pub image_subresource_layers: ImageSubresourceLayers, + pub image_subresource: ImageSubresourceLayers, pub image_offset: Offset3d, pub image_extent: Extent3d, } +pub struct ImageBlit { + pub src_subresource: ImageSubresourceLayers, + pub src_offset_min: Offset3d, + pub src_offset_max: Offset3d, + pub dst_subresource: ImageSubresourceLayers, + pub dst_offset_min: Offset3d, + pub dst_offset_max: Offset3d, +} + pub struct ShaderDesc<'a> { pub entry: &'a CStr, pub code: &'a [u8], @@ -278,6 +296,16 @@ pub enum FrontFace { CounterClockwise, } +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum BlendMode { + Opaque, + Mask, + Translucent, + Premultiplied, + Additive, + Modulate, +} + #[derive(Copy, Clone, PartialEq, Eq)] pub enum CompareOp { Never, @@ -348,6 +376,7 @@ pub struct GraphicsPipelineDesc<'a> { pub polygon_mode: PolygonMode, pub culling_mode: CullingMode, pub front_face: FrontFace, + pub blend_mode: BlendMode, pub depth_bias: Option, pub depth_compare_op: CompareOp, pub depth_test_enable: bool, @@ -721,6 +750,16 @@ pub trait Device { copies: &[BufferImageCopy], ); + fn cmd_blit_image( + &self, + cmd_buffer: &mut CmdBuffer, + src_image: Image, + src_image_layout: ImageLayout, + dst_image: Image, + dst_image_layout: ImageLayout, + regions: &[ImageBlit], + ); + fn cmd_begin_rendering(&self, cmd_buffer: &mut CmdBuffer, desc: &RenderingDesc); fn cmd_end_rendering(&self, cmd_buffer: &mut CmdBuffer); diff --git a/narcissus/src/main.rs b/narcissus/src/main.rs index 8609474..903b9ca 100644 --- a/narcissus/src/main.rs +++ b/narcissus/src/main.rs @@ -4,12 +4,13 @@ use narcissus_app::{create_app, Event, Key, WindowDesc}; use narcissus_core::{cstr, default, obj, rand::Pcg64}; use narcissus_gpu::{ create_device, Access, Bind, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, - Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, CompareOp, CullingMode, - Device, Extent2d, Extent3d, FrontFace, GraphicsPipelineDesc, GraphicsPipelineLayout, Image, - ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, ImageUsageFlags, IndexType, - LoadOp, MemoryLocation, Offset2d, Offset3d, PolygonMode, RenderingAttachment, RenderingDesc, - SamplerAddressMode, SamplerDesc, SamplerFilter, Scissor, ShaderDesc, ShaderStageFlags, StoreOp, - ThreadToken, Topology, TypedBind, Viewport, + BlendMode, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, CompareOp, + CullingMode, Device, Extent2d, Extent3d, FrontFace, GraphicsPipelineDesc, + GraphicsPipelineLayout, Image, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, + ImageLayout, ImageUsageFlags, IndexType, LoadOp, MemoryLocation, Offset2d, Offset3d, + PolygonMode, RenderingAttachment, RenderingDesc, SamplerAddressMode, SamplerDesc, + SamplerFilter, Scissor, ShaderDesc, ShaderStageFlags, StoreOp, ThreadToken, Topology, + TypedBind, Viewport, }; use narcissus_image as image; use narcissus_maths::{ @@ -192,7 +193,7 @@ fn create_image_with_data( buffer_offset: 0, buffer_row_length: 0, buffer_image_height: 0, - image_subresource_layers: default(), + image_subresource: default(), image_offset: Offset3d { x: 0, y: 0, z: 0 }, image_extent: Extent3d { width, @@ -357,6 +358,7 @@ pub fn main() { polygon_mode: PolygonMode::Fill, culling_mode: CullingMode::Back, front_face: FrontFace::CounterClockwise, + blend_mode: BlendMode::Opaque, depth_bias: None, depth_compare_op: CompareOp::GreaterOrEqual, depth_test_enable: true, @@ -499,7 +501,7 @@ pub fn main() { device.destroy_image(&frame, depth_image); depth_image = device.create_image(&ImageDesc { location: MemoryLocation::HostMapped, - usage: ImageUsageFlags::DEPTH_STENCIL, + usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, dimension: ImageDimension::Type2d, format: ImageFormat::DEPTH_F32, initial_layout: ImageLayout::Optimal, -- 2.49.0