]> git.nega.tv - josh/narcissus/commitdiff
Add blending and blitting
authorJoshua Simmons <josh@nega.tv>
Fri, 2 Dec 2022 21:24:51 +0000 (22:24 +0100)
committerJoshua Simmons <josh@nega.tv>
Fri, 2 Dec 2022 21:33:59 +0000 (22:33 +0100)
narcissus-gpu/src/backend/vulkan/mod.rs
narcissus-gpu/src/lib.rs
narcissus/src/main.rs

index 85cc1c6759f76e82ddea14c2998144a51989b104..12617e659d27e081438b58489460ef9bd9c0399a 100644 (file)
@@ -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(&copy.image_subresource_layers),
+            image_subresource: vulkan_subresource_layers(&copy.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,
index 574af5eb2b3a5d7256507006daa49ee8f0c3879b..5446aee50d54288066821aa659222087c7cb507c 100644 (file)
@@ -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<DepthBias>,
     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);
index 8609474f9dfbd9f9d09f9a97f14a48278b8bc74c..903b9cac1efc32a34f600527cc7fc43f52b8bde8 100644 (file)
@@ -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,