]> git.nega.tv - josh/narcissus/commitdiff
shark: Apply display transform in compute shader
authorJosh Simmons <josh@nega.tv>
Tue, 14 May 2024 16:54:31 +0000 (18:54 +0200)
committerJosh Simmons <josh@nega.tv>
Tue, 14 May 2024 16:54:31 +0000 (18:54 +0200)
12 files changed:
engine/narcissus-gpu/src/backend/vulkan/convert.rs
engine/narcissus-gpu/src/backend/vulkan/mod.rs
engine/narcissus-gpu/src/lib.rs
title/shark-shaders/build.rs
title/shark-shaders/shaders/display_transform.comp.glsl [new file with mode: 0644]
title/shark-shaders/shaders/ui.frag.glsl
title/shark/data/tony_mc_mapface.dds [new file with mode: 0644]
title/shark/src/main.rs
title/shark/src/pipelines/basic.rs
title/shark/src/pipelines/display_transform.rs [new file with mode: 0644]
title/shark/src/pipelines/mod.rs
title/shark/src/pipelines/ui.rs

index c1acbfb21606c831151ddaa83e9b9d98dd6e10bd..d2ee3ebc4ab3482a12e4f2b84bef3f0dc205dec9 100644 (file)
@@ -158,7 +158,8 @@ pub fn vulkan_shader_stage_flags(stage_flags: ShaderStageFlags) -> vk::ShaderSta
 pub fn vulkan_descriptor_type(binding_type: BindingType) -> vk::DescriptorType {
     match binding_type {
         BindingType::Sampler => vk::DescriptorType::Sampler,
-        BindingType::Image => vk::DescriptorType::SampledImage,
+        BindingType::StorageImage => vk::DescriptorType::StorageImage,
+        BindingType::SampledImage => vk::DescriptorType::SampledImage,
         BindingType::UniformBuffer => vk::DescriptorType::UniformBuffer,
         BindingType::StorageBuffer => vk::DescriptorType::StorageBuffer,
         BindingType::DynamicUniformBuffer => vk::DescriptorType::UniformBufferDynamic,
index 46f63aa9a348371b59e973da277958e70a735e28..b7e1b0295b2fb64299a59fe004d1f68c2b05d0f8 100644 (file)
@@ -267,12 +267,19 @@ impl VulkanTransientBufferAllocator {
     }
 }
 
+struct VulkanTouchedSwapchain {
+    image: vk::Image,
+    layout: vk::ImageLayout,
+    access_mask: vk::AccessFlags2,
+    stage_mask: vk::PipelineStageFlags2,
+}
+
 struct VulkanCmdEncoder {
     #[cfg(debug_assertions)]
     in_render_pass: bool,
     command_buffer: vk::CommandBuffer,
     bound_pipeline: Option<VulkanBoundPipeline>,
-    swapchains_touched: HashMap<vk::SurfaceKHR, (vk::Image, vk::PipelineStageFlags2)>,
+    swapchains_touched: HashMap<vk::SurfaceKHR, VulkanTouchedSwapchain>,
 }
 
 impl Default for VulkanCmdEncoder {
@@ -1571,6 +1578,60 @@ impl Device for VulkanDevice {
         }
     }
 
+    fn cmd_compute_touch_swapchain(&self, cmd_encoder: &mut CmdEncoder, image: Image) {
+        let cmd_encoder = self.cmd_encoder_mut(cmd_encoder);
+
+        match self.image_pool.lock().get(image.0) {
+            Some(VulkanImageHolder::Swapchain(image)) => {
+                assert!(
+                    !cmd_encoder.swapchains_touched.contains_key(&image.surface),
+                    "swapchain attached multiple times in a command buffer"
+                );
+                cmd_encoder.swapchains_touched.insert(
+                    image.surface,
+                    VulkanTouchedSwapchain {
+                        image: image.image,
+                        layout: vk::ImageLayout::General,
+                        access_mask: vk::AccessFlags2::SHADER_STORAGE_WRITE,
+                        stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+                    },
+                );
+
+                // Transition swapchain image to shader storage write
+                let image_memory_barriers = &[vk::ImageMemoryBarrier2 {
+                    src_stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+                    src_access_mask: vk::AccessFlags2::NONE,
+                    dst_stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+                    dst_access_mask: vk::AccessFlags2::SHADER_STORAGE_WRITE,
+                    src_queue_family_index: self.universal_queue_family_index,
+                    dst_queue_family_index: self.universal_queue_family_index,
+                    old_layout: vk::ImageLayout::Undefined,
+                    new_layout: vk::ImageLayout::General,
+                    image: image.image,
+                    subresource_range: vk::ImageSubresourceRange {
+                        aspect_mask: vk::ImageAspectFlags::COLOR,
+                        base_mip_level: 0,
+                        level_count: !0,
+                        base_array_layer: 0,
+                        layer_count: !0,
+                    },
+                    ..default()
+                }];
+
+                let dependency_info = vk::DependencyInfo {
+                    image_memory_barriers: image_memory_barriers.into(),
+                    ..default()
+                };
+
+                unsafe {
+                    self.device_fn
+                        .cmd_pipeline_barrier2(cmd_encoder.command_buffer, &dependency_info)
+                };
+            }
+            _ => panic!(),
+        }
+    }
+
     fn cmd_barrier(
         &self,
         cmd_encoder: &mut CmdEncoder,
@@ -1786,7 +1847,7 @@ impl Device for VulkanDevice {
                     ..default()
                 }
             }
-            TypedBind::Image(images) => {
+            TypedBind::SampledImage(images) => {
                 let image_infos_iter = images.iter().map(|(image_layout, image)| {
                     let image_view = self.image_pool.lock().get(image.0).unwrap().image_view();
                     vk::DescriptorImageInfo {
@@ -1809,6 +1870,29 @@ impl Device for VulkanDevice {
                     ..default()
                 }
             }
+            TypedBind::StorageImage(images) => {
+                let image_infos_iter = images.iter().map(|(image_layout, image)| {
+                    let image_view = self.image_pool.lock().get(image.0).unwrap().image_view();
+                    vk::DescriptorImageInfo {
+                        image_layout: match image_layout {
+                            ImageLayout::Optimal => vk::ImageLayout::ReadOnlyOptimal,
+                            ImageLayout::General => vk::ImageLayout::General,
+                        },
+                        image_view,
+                        sampler: vk::Sampler::null(),
+                    }
+                });
+                let image_infos = arena.alloc_slice_fill_iter(image_infos_iter);
+                vk::WriteDescriptorSet {
+                    dst_set: descriptor_set,
+                    dst_binding: bind.binding,
+                    dst_array_element: bind.array_element,
+                    descriptor_count: image_infos.len() as u32,
+                    descriptor_type: vk::DescriptorType::StorageImage,
+                    image_info: image_infos.as_ptr(),
+                    ..default()
+                }
+            }
             TypedBind::UniformBuffer(buffers) => {
                 let buffer_infos_iter = buffers.iter().map(|buffer_arg| {
                     let (buffer, offset, range) = self.unwrap_buffer_arg(buffer_arg);
@@ -1946,10 +2030,12 @@ impl Device for VulkanDevice {
                         );
                         cmd_encoder.swapchains_touched.insert(
                             image.surface,
-                            (
-                                image.image,
-                                vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
-                            ),
+                            VulkanTouchedSwapchain {
+                                image: image.image,
+                                layout: vk::ImageLayout::AttachmentOptimal,
+                                access_mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
+                                stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
+                            },
                         );
 
                         // transition swapchain image to attachment optimal
@@ -2144,16 +2230,22 @@ impl Device for VulkanDevice {
         #[cfg(debug_assertions)]
         debug_assert!(!cmd_encoder.in_render_pass);
 
-        for &(image, _) in cmd_encoder.swapchains_touched.values() {
+        for &VulkanTouchedSwapchain {
+            image,
+            layout,
+            access_mask,
+            stage_mask,
+        } in cmd_encoder.swapchains_touched.values()
+        {
             // transition swapchain image from attachment optimal to present src
             let image_memory_barriers = &[vk::ImageMemoryBarrier2 {
-                src_stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
-                src_access_mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
-                dst_stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
+                src_stage_mask: stage_mask,
+                src_access_mask: access_mask,
+                dst_stage_mask: stage_mask,
                 dst_access_mask: vk::AccessFlags2::NONE,
                 src_queue_family_index: self.universal_queue_family_index,
                 dst_queue_family_index: self.universal_queue_family_index,
-                old_layout: vk::ImageLayout::AttachmentOptimal,
+                old_layout: layout,
                 new_layout: vk::ImageLayout::PresentSrcKhr,
                 image,
                 subresource_range: vk::ImageSubresourceRange {
@@ -2183,7 +2275,16 @@ impl Device for VulkanDevice {
         let mut signal_semaphores = Vec::new();
 
         if !cmd_encoder.swapchains_touched.is_empty() {
-            for (surface, (_, stage_mask)) in cmd_encoder.swapchains_touched.drain() {
+            for (
+                surface,
+                VulkanTouchedSwapchain {
+                    image: _,
+                    layout: _,
+                    access_mask: _,
+                    stage_mask,
+                },
+            ) in cmd_encoder.swapchains_touched.drain()
+            {
                 self.touch_swapchain(
                     frame,
                     surface,
index 5ac92f48bb37294be243d5473696eafaecf23138..491c788a14c79cda62b066831a193b81444f8291 100644 (file)
@@ -458,7 +458,8 @@ pub enum IndexType {
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum BindingType {
     Sampler,
-    Image,
+    SampledImage,
+    StorageImage,
     UniformBuffer,
     StorageBuffer,
     DynamicUniformBuffer,
@@ -490,7 +491,8 @@ pub enum BufferArg<'a> {
 
 pub enum TypedBind<'a> {
     Sampler(&'a [Sampler]),
-    Image(&'a [(ImageLayout, Image)]),
+    SampledImage(&'a [(ImageLayout, Image)]),
+    StorageImage(&'a [(ImageLayout, Image)]),
     UniformBuffer(&'a [BufferArg<'a>]),
     StorageBuffer(&'a [BufferArg<'a>]),
 }
@@ -785,6 +787,8 @@ pub trait Device {
         index_type: IndexType,
     );
 
+    fn cmd_compute_touch_swapchain(&self, cmd_encoder: &mut CmdEncoder, image: Image);
+
     fn cmd_set_pipeline(&self, cmd_encoder: &mut CmdEncoder, pipeline: Pipeline);
 
     fn cmd_set_viewports(&self, cmd_encoder: &mut CmdEncoder, viewports: &[Viewport]);
index ff53b83538a771ef31a6fc323813b89e7bbe44eb..6b4848678f8c5ca674696fd636072a80ed7fbb7f 100644 (file)
@@ -7,6 +7,7 @@ const SHADER_ROOT: &str = "shaders";
 enum ShaderStage {
     Vertex,
     Fragment,
+    Compute,
 }
 
 impl ShaderStage {
@@ -14,6 +15,7 @@ impl ShaderStage {
         match self {
             ShaderStage::Vertex => "vert",
             ShaderStage::Fragment => "frag",
+            ShaderStage::Compute => "comp",
         }
     }
 }
@@ -24,7 +26,7 @@ struct Shader {
     name: &'static str,
 }
 
-const SHADERS: [Shader; 4] = [
+const SHADERS: [Shader; 5] = [
     Shader {
         stage: ShaderStage::Vertex,
         name: "basic",
@@ -33,6 +35,10 @@ const SHADERS: [Shader; 4] = [
         stage: ShaderStage::Fragment,
         name: "basic",
     },
+    Shader {
+        stage: ShaderStage::Compute,
+        name: "display_transform",
+    },
     Shader {
         stage: ShaderStage::Vertex,
         name: "ui",
diff --git a/title/shark-shaders/shaders/display_transform.comp.glsl b/title/shark-shaders/shaders/display_transform.comp.glsl
new file mode 100644 (file)
index 0000000..1b440dc
--- /dev/null
@@ -0,0 +1,30 @@
+#version 460
+
+layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout (set = 0, binding = 0) uniform sampler linear_sampler;
+
+layout (set = 0, binding = 1) uniform texture3D tony_mc_mapface_lut;
+layout (set = 0, binding = 2, rgba16f) uniform readonly image2D render_target;
+layout (set = 0, binding = 3, rgba16f) uniform writeonly image2D swapchain_image;
+
+float srgb_oetf(float a) {
+    return (.0031308f >= a) ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;
+}
+
+vec3 srgb_oetf(vec3 a) {
+       return vec3(srgb_oetf(a.r), srgb_oetf(a.g), srgb_oetf(a.b));
+}
+
+vec3 tony_mc_mapface(vec3 stimulus) {
+    const vec3 encoded = stimulus / (stimulus + 1.0);
+    const float LUT_DIMS = 48.0;
+    const vec3 uv = (encoded * ((LUT_DIMS - 1.0) / LUT_DIMS) + 0.5 / LUT_DIMS);
+    return textureLod(sampler3D(tony_mc_mapface_lut, linear_sampler), uv, 0.0).rgb;
+}
+
+void main() {
+    vec3 stimulus = imageLoad(render_target, ivec2(gl_GlobalInvocationID.xy)).rgb;
+    vec3 srgb = srgb_oetf(tony_mc_mapface(stimulus));
+    imageStore(swapchain_image, ivec2(gl_GlobalInvocationID.xy), vec4(srgb, 1.0));
+}
index e8955e973b5dca7015265bad98ba7b1e49d30c92..26af4aea478d4d25c02f2734360299e221e324f4 100644 (file)
@@ -1,6 +1,6 @@
 #version 460
 
-layout(set = 0, binding = 4) uniform sampler texSampler;
+layout(set = 0, binding = 4) uniform sampler linear_sampler;
 layout(set = 0, binding = 5) uniform texture2D tex;
 
 layout(location = 0) in vec2 texcoord;
@@ -8,6 +8,6 @@ layout(location = 1) in vec4 color;
 layout(location = 0) out vec4 outColor;
 
 void main() {
-    float coverage = texture(sampler2D(tex, texSampler), texcoord).r;
+    float coverage = texture(sampler2D(tex, linear_sampler), texcoord).r;
     outColor = color * coverage;
 }
diff --git a/title/shark/data/tony_mc_mapface.dds b/title/shark/data/tony_mc_mapface.dds
new file mode 100644 (file)
index 0000000..8d84cd9
Binary files /dev/null and b/title/shark/data/tony_mc_mapface.dds differ
index 37aef799bd36c27428d4c3933d223c60f682cb1b..624197f609642d6d4f9c2bb90b82d2596b4f1d90 100644 (file)
@@ -2,6 +2,7 @@ use std::fmt::Write;
 use std::path::Path;
 use std::time::{Duration, Instant};
 
+use narcissus_core::dds;
 use renderdoc_sys as rdoc;
 
 use fonts::{FontFamily, Fonts};
@@ -12,15 +13,18 @@ use narcissus_font::{FontCollection, GlyphCache, HorizontalMetrics};
 use narcissus_gpu::{
     create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, CmdEncoder, Device,
     DeviceExt, Extent2d, Extent3d, Frame, Image, ImageAspectFlags, ImageBarrier, ImageDesc,
-    ImageDimension, ImageFormat, ImageLayout, ImageTiling, ImageUsageFlags, IndexType, LoadOp,
-    MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment, RenderingDesc, Scissor,
-    StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport,
+    ImageDimension, ImageFormat, ImageLayout, ImageSubresourceRange, ImageTiling, ImageUsageFlags,
+    IndexType, LoadOp, MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment,
+    RenderingDesc, Scissor, StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport,
 };
 use narcissus_image as image;
 use narcissus_maths::{
     clamp, perlin_noise3, sin_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4, Point3, Vec3,
 };
-use pipelines::{BasicPipeline, BasicUniforms, PrimitiveInstance, PrimitiveVertex, UiPipeline};
+use pipelines::{
+    BasicPipeline, BasicUniforms, DisplayTransformPipeline, PrimitiveInstance, PrimitiveVertex,
+    UiPipeline,
+};
 use spring::simple_spring_damper_exact;
 
 mod fonts;
@@ -522,10 +526,10 @@ impl<'a> UiState<'a> {
     }
 }
 
-struct Model<'device> {
+struct Model<'gpu> {
     indices: u32,
-    vertex_buffer: PersistentBuffer<'device>,
-    index_buffer: PersistentBuffer<'device>,
+    vertex_buffer: PersistentBuffer<'gpu>,
+    index_buffer: PersistentBuffer<'gpu>,
 }
 
 enum ModelRes {
@@ -536,33 +540,37 @@ impl ModelRes {
     const MAX_MODELS: usize = 1;
 }
 
-struct Models<'device>([Model<'device>; ModelRes::MAX_MODELS]);
+struct Models<'gpu>([Model<'gpu>; ModelRes::MAX_MODELS]);
 
 enum ImageRes {
+    TonyMcMapfaceLut,
     Shark,
 }
 
 impl ImageRes {
-    const MAX_IMAGES: usize = 1;
+    const MAX_IMAGES: usize = 2;
 }
 
 struct Images([Image; ImageRes::MAX_IMAGES]);
 
 type Gpu = dyn Device + 'static;
 
-struct DrawState<'device> {
-    gpu: &'device Gpu,
+struct DrawState<'gpu> {
+    gpu: &'gpu Gpu,
 
     basic_pipeline: BasicPipeline,
     ui_pipeline: UiPipeline,
+    display_transform_pipeline: DisplayTransformPipeline,
 
     width: u32,
     height: u32,
 
     depth_image: Image,
+    render_target_image: Image,
+
     glyph_atlas_image: Image,
 
-    models: Models<'device>,
+    models: Models<'gpu>,
     images: Images,
 
     transforms: Vec<Affine3>,
@@ -673,6 +681,101 @@ fn load_images(gpu: &Gpu, thread_token: &ThreadToken) -> Images {
         image
     }
 
+    fn load_dds<P>(
+        gpu: &Gpu,
+        frame: &Frame,
+        thread_token: &ThreadToken,
+        cmd_encoder: &mut CmdEncoder,
+        path: P,
+    ) -> Image
+    where
+        P: AsRef<Path>,
+    {
+        let image_data = std::fs::read(path.as_ref()).unwrap();
+        let dds = dds::Dds::from_buffer(&image_data).unwrap();
+        let header_dxt10 = dds.header_dxt10.unwrap();
+
+        let width = dds.header.width;
+        let height = dds.header.height;
+        let depth = dds.header.depth;
+
+        let dimension = match header_dxt10.resource_dimension {
+            dds::D3D10ResourceDimension::Texture1d => ImageDimension::Type1d,
+            dds::D3D10ResourceDimension::Texture2d => ImageDimension::Type2d,
+            dds::D3D10ResourceDimension::Texture3d => ImageDimension::Type3d,
+            _ => panic!(),
+        };
+
+        let format = match header_dxt10.dxgi_format {
+            dds::DxgiFormat::R9G9B9E5_SHAREDEXP => ImageFormat::E5B9G9R9_UFLOAT,
+            _ => panic!(),
+        };
+
+        let image = gpu.create_image(&ImageDesc {
+            memory_location: MemoryLocation::Device,
+            host_mapped: false,
+            usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
+            dimension,
+            format,
+            tiling: ImageTiling::Optimal,
+            width,
+            height,
+            depth,
+            layer_count: 1,
+            mip_levels: 1,
+        });
+
+        gpu.cmd_barrier(
+            cmd_encoder,
+            None,
+            &[ImageBarrier {
+                prev_access: &[Access::None],
+                next_access: &[Access::TransferWrite],
+                prev_layout: ImageLayout::Optimal,
+                next_layout: ImageLayout::Optimal,
+                subresource_range: ImageSubresourceRange::default(),
+                image,
+            }],
+        );
+
+        let buffer = gpu.request_transient_buffer_with_data(
+            frame,
+            thread_token,
+            BufferUsageFlags::TRANSFER,
+            dds.data,
+        );
+
+        gpu.cmd_copy_buffer_to_image(
+            cmd_encoder,
+            buffer.to_arg(),
+            image,
+            ImageLayout::Optimal,
+            &[BufferImageCopy {
+                image_extent: Extent3d {
+                    width,
+                    height,
+                    depth,
+                },
+                ..default()
+            }],
+        );
+
+        gpu.cmd_barrier(
+            cmd_encoder,
+            None,
+            &[ImageBarrier {
+                prev_access: &[Access::TransferWrite],
+                next_access: &[Access::ShaderSampledImageRead],
+                prev_layout: ImageLayout::Optimal,
+                next_layout: ImageLayout::Optimal,
+                subresource_range: ImageSubresourceRange::default(),
+                image,
+            }],
+        );
+
+        image
+    }
+
     let images;
     let frame = gpu.begin_frame();
     {
@@ -682,13 +785,22 @@ fn load_images(gpu: &Gpu, thread_token: &ThreadToken) -> Images {
         {
             let cmd_encoder = &mut cmd_encoder;
 
-            images = Images([load_image(
-                gpu,
-                frame,
-                thread_token,
-                cmd_encoder,
-                "title/shark/data/blÃ¥haj.png",
-            )]);
+            images = Images([
+                load_dds(
+                    gpu,
+                    frame,
+                    thread_token,
+                    cmd_encoder,
+                    "title/shark/data/tony_mc_mapface.dds",
+                ),
+                load_image(
+                    gpu,
+                    frame,
+                    thread_token,
+                    cmd_encoder,
+                    "title/shark/data/blÃ¥haj.png",
+                ),
+            ]);
         }
 
         gpu.submit(frame, cmd_encoder);
@@ -698,10 +810,11 @@ fn load_images(gpu: &Gpu, thread_token: &ThreadToken) -> Images {
     images
 }
 
-impl<'device> DrawState<'device> {
-    fn new(gpu: &'device Gpu, thread_token: &ThreadToken) -> Self {
+impl<'gpu> DrawState<'gpu> {
+    fn new(gpu: &'gpu Gpu, thread_token: &ThreadToken) -> Self {
         let basic_pipeline = BasicPipeline::new(gpu);
         let ui_pipeline = UiPipeline::new(gpu);
+        let primitive_pipeline = DisplayTransformPipeline::new(gpu);
 
         let models = load_models(gpu);
         let images = load_images(gpu, thread_token);
@@ -710,9 +823,11 @@ impl<'device> DrawState<'device> {
             gpu,
             basic_pipeline,
             ui_pipeline,
+            display_transform_pipeline: primitive_pipeline,
             width: 0,
             height: 0,
             depth_image: default(),
+            render_target_image: default(),
             glyph_atlas_image: default(),
             models,
             images,
@@ -783,21 +898,23 @@ impl<'device> DrawState<'device> {
         );
         let clip_from_model = clip_from_camera * camera_from_model;
 
+        let atlas_width = ui_state.glyph_cache.width() as u32;
+        let atlas_height = ui_state.glyph_cache.height() as u32;
+
         let mut cmd_encoder = self.gpu.request_cmd_encoder(frame, thread_token);
         {
             let cmd_encoder = &mut cmd_encoder;
 
-            if width != self.width || height != self.height {
-                gpu.destroy_image(frame, self.depth_image);
-                self.depth_image = gpu.create_image(&ImageDesc {
+            if self.glyph_atlas_image.is_null() {
+                self.glyph_atlas_image = gpu.create_image(&ImageDesc {
                     memory_location: MemoryLocation::Device,
                     host_mapped: false,
-                    usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
+                    usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
                     dimension: ImageDimension::Type2d,
-                    format: ImageFormat::DEPTH_F32,
+                    format: ImageFormat::R8_SRGB,
                     tiling: ImageTiling::Optimal,
-                    width,
-                    height,
+                    width: atlas_width,
+                    height: atlas_height,
                     depth: 1,
                     layer_count: 1,
                     mip_levels: 1,
@@ -808,26 +925,40 @@ impl<'device> DrawState<'device> {
                     None,
                     &[ImageBarrier::layout_optimal(
                         &[Access::None],
-                        &[Access::DepthStencilAttachmentWrite],
-                        self.depth_image,
-                        ImageAspectFlags::DEPTH,
+                        &[Access::FragmentShaderSampledImageRead],
+                        self.glyph_atlas_image,
+                        ImageAspectFlags::COLOR,
                     )],
                 );
-
-                self.width = width;
-                self.height = height;
             }
 
-            if self.glyph_atlas_image.is_null() {
-                let image = gpu.create_image(&ImageDesc {
+            if width != self.width || height != self.height {
+                gpu.destroy_image(frame, self.depth_image);
+                gpu.destroy_image(frame, self.render_target_image);
+
+                self.depth_image = gpu.create_image(&ImageDesc {
                     memory_location: MemoryLocation::Device,
-                    usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
                     host_mapped: false,
+                    usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
                     dimension: ImageDimension::Type2d,
-                    format: ImageFormat::R8_SRGB,
+                    format: ImageFormat::DEPTH_F32,
                     tiling: ImageTiling::Optimal,
-                    width: ui_state.glyph_cache.width() as u32,
-                    height: ui_state.glyph_cache.height() as u32,
+                    width,
+                    height,
+                    depth: 1,
+                    layer_count: 1,
+                    mip_levels: 1,
+                });
+
+                self.render_target_image = gpu.create_image(&ImageDesc {
+                    memory_location: MemoryLocation::Device,
+                    host_mapped: false,
+                    usage: ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::STORAGE,
+                    dimension: ImageDimension::Type2d,
+                    format: ImageFormat::RGBA16_FLOAT,
+                    tiling: ImageTiling::Optimal,
+                    width,
+                    height,
                     depth: 1,
                     layer_count: 1,
                     mip_levels: 1,
@@ -838,24 +969,20 @@ impl<'device> DrawState<'device> {
                     None,
                     &[ImageBarrier::layout_optimal(
                         &[Access::None],
-                        &[Access::ShaderSampledImageRead],
-                        image,
-                        ImageAspectFlags::COLOR,
+                        &[Access::DepthStencilAttachmentWrite],
+                        self.depth_image,
+                        ImageAspectFlags::DEPTH,
                     )],
                 );
 
-                self.glyph_atlas_image = image;
+                self.width = width;
+                self.height = height;
             }
 
-            let atlas_width = ui_state.glyph_cache.width() as u32;
-            let atlas_height = ui_state.glyph_cache.height() as u32;
-
             let (touched_glyphs, glyph_texture) = ui_state.glyph_cache.update_atlas();
 
             // If the atlas has been updated, we need to upload it to the GPU.
             if let Some(texture) = glyph_texture {
-                let image = self.glyph_atlas_image;
-
                 let buffer = gpu.request_transient_buffer_with_data(
                     frame,
                     thread_token,
@@ -869,7 +996,7 @@ impl<'device> DrawState<'device> {
                     &[ImageBarrier::layout_optimal(
                         &[Access::ShaderSampledImageRead],
                         &[Access::TransferWrite],
-                        image,
+                        self.glyph_atlas_image,
                         ImageAspectFlags::COLOR,
                     )],
                 );
@@ -877,7 +1004,7 @@ impl<'device> DrawState<'device> {
                 gpu.cmd_copy_buffer_to_image(
                     cmd_encoder,
                     buffer.to_arg(),
-                    image,
+                    self.glyph_atlas_image,
                     ImageLayout::Optimal,
                     &[BufferImageCopy {
                         image_extent: Extent3d {
@@ -895,12 +1022,23 @@ impl<'device> DrawState<'device> {
                     &[ImageBarrier::layout_optimal(
                         &[Access::TransferWrite],
                         &[Access::FragmentShaderSampledImageRead],
-                        image,
+                        self.glyph_atlas_image,
                         ImageAspectFlags::COLOR,
                     )],
                 );
             }
 
+            gpu.cmd_barrier(
+                cmd_encoder,
+                None,
+                &[ImageBarrier::layout_optimal(
+                    &[Access::None],
+                    &[Access::ColorAttachmentWrite],
+                    self.render_target_image,
+                    ImageAspectFlags::COLOR,
+                )],
+            );
+
             gpu.cmd_begin_rendering(
                 cmd_encoder,
                 &RenderingDesc {
@@ -909,7 +1047,7 @@ impl<'device> DrawState<'device> {
                     width,
                     height,
                     color_attachments: &[RenderingAttachment {
-                        image: swapchain_image,
+                        image: self.render_target_image,
                         load_op: LoadOp::Clear(ClearValue::ColorF32([1.0, 1.0, 1.0, 1.0])),
                         store_op: StoreOp::Store,
                     }],
@@ -1005,7 +1143,7 @@ impl<'device> DrawState<'device> {
                             Bind {
                                 binding: 3,
                                 array_element: 0,
-                                typed: TypedBind::Image(&[(ImageLayout::Optimal, image)]),
+                                typed: TypedBind::SampledImage(&[(ImageLayout::Optimal, image)]),
                             },
                         ],
                     );
@@ -1104,7 +1242,7 @@ impl<'device> DrawState<'device> {
                             Bind {
                                 binding: 5,
                                 array_element: 0,
-                                typed: TypedBind::Image(&[(
+                                typed: TypedBind::SampledImage(&[(
                                     ImageLayout::Optimal,
                                     self.glyph_atlas_image,
                                 )]),
@@ -1126,6 +1264,60 @@ impl<'device> DrawState<'device> {
             }
 
             gpu.cmd_end_rendering(cmd_encoder);
+
+            gpu.cmd_barrier(
+                cmd_encoder,
+                None,
+                &[ImageBarrier {
+                    prev_access: &[Access::ColorAttachmentWrite],
+                    prev_layout: ImageLayout::Optimal,
+                    next_access: &[Access::ShaderOtherRead],
+                    next_layout: ImageLayout::General,
+                    image: self.render_target_image,
+                    subresource_range: ImageSubresourceRange::default(),
+                }],
+            );
+
+            gpu.cmd_compute_touch_swapchain(cmd_encoder, swapchain_image);
+
+            gpu.cmd_set_pipeline(cmd_encoder, self.display_transform_pipeline.pipeline);
+
+            gpu.cmd_set_bind_group(
+                frame,
+                cmd_encoder,
+                self.display_transform_pipeline.bind_group_layout,
+                0,
+                &[
+                    Bind {
+                        binding: 0,
+                        array_element: 0,
+                        typed: TypedBind::Sampler(&[self.display_transform_pipeline.sampler]),
+                    },
+                    Bind {
+                        binding: 1,
+                        array_element: 0,
+                        typed: TypedBind::SampledImage(&[(
+                            ImageLayout::Optimal,
+                            self.images.0[ImageRes::TonyMcMapfaceLut as usize],
+                        )]),
+                    },
+                    Bind {
+                        binding: 2,
+                        array_element: 0,
+                        typed: TypedBind::StorageImage(&[(
+                            ImageLayout::General,
+                            self.render_target_image,
+                        )]),
+                    },
+                    Bind {
+                        binding: 3,
+                        array_element: 0,
+                        typed: TypedBind::StorageImage(&[(ImageLayout::General, swapchain_image)]),
+                    },
+                ],
+            );
+
+            gpu.cmd_dispatch(cmd_encoder, (self.width + 7) / 8, (self.height + 7) / 8, 1);
         }
         gpu.submit(frame, cmd_encoder);
     }
@@ -1177,7 +1369,7 @@ pub fn main() {
         {
             let frame = &frame;
 
-            let formats = &[ImageFormat::RGBA8_SRGB];
+            let formats = &[ImageFormat::A2R10G10B10_UNORM];
             let mut swapchain_images = [Image::default(); 1];
 
             let SwapchainImage { width, height } = loop {
@@ -1191,7 +1383,7 @@ pub fn main() {
                     window.upcast(),
                     drawable_width,
                     drawable_height,
-                    ImageUsageFlags::COLOR_ATTACHMENT,
+                    ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::STORAGE,
                     formats,
                     &mut swapchain_images,
                 ) {
@@ -1199,7 +1391,7 @@ pub fn main() {
                 }
             };
 
-            let [swapchain_image_srgb] = swapchain_images;
+            let [swapchain_image_unorm] = swapchain_images;
 
             let tick_start = Instant::now();
             'tick: loop {
@@ -1283,7 +1475,7 @@ pub fn main() {
                 &game_state,
                 width,
                 height,
-                swapchain_image_srgb,
+                swapchain_image_unorm,
             );
         }
 
index ac04836fd0384d61586c606a05043fe794f6649b..08df2a32ac5db471a9b9050ff16235c2f90ae824 100644 (file)
@@ -64,7 +64,7 @@ impl BasicPipeline {
                 BindGroupLayoutEntryDesc {
                     slot: 3,
                     stages: ShaderStageFlags::ALL,
-                    binding_type: BindingType::Image,
+                    binding_type: BindingType::SampledImage,
                     count: 1,
                 },
             ],
@@ -90,7 +90,7 @@ impl BasicPipeline {
             },
             bind_group_layouts: &[uniforms_bind_group_layout, storage_bind_group_layout],
             layout: GraphicsPipelineLayout {
-                color_attachment_formats: &[ImageFormat::RGBA8_SRGB],
+                color_attachment_formats: &[ImageFormat::RGBA16_FLOAT],
                 depth_attachment_format: Some(ImageFormat::DEPTH_F32),
                 stencil_attachment_format: None,
             },
diff --git a/title/shark/src/pipelines/display_transform.rs b/title/shark/src/pipelines/display_transform.rs
new file mode 100644 (file)
index 0000000..8891fb6
--- /dev/null
@@ -0,0 +1,76 @@
+use narcissus_gpu::{
+    BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType,
+    ComputePipelineDesc, Pipeline, Sampler, SamplerAddressMode, SamplerDesc, SamplerFilter,
+    ShaderDesc, ShaderStageFlags,
+};
+
+use crate::Gpu;
+
+#[allow(unused)]
+#[repr(C)]
+pub struct DisplayTransformUniforms {
+    pub width: u32,
+    pub height: u32,
+}
+
+pub struct DisplayTransformPipeline {
+    pub bind_group_layout: BindGroupLayout,
+    pub pipeline: Pipeline,
+    pub sampler: Sampler,
+}
+
+impl DisplayTransformPipeline {
+    pub fn new(gpu: &Gpu) -> Self {
+        let bind_group_layout = gpu.create_bind_group_layout(&BindGroupLayoutDesc {
+            entries: &[
+                BindGroupLayoutEntryDesc {
+                    slot: 0,
+                    stages: ShaderStageFlags::COMPUTE,
+                    binding_type: BindingType::Sampler,
+                    count: 1,
+                },
+                BindGroupLayoutEntryDesc {
+                    slot: 1,
+                    stages: ShaderStageFlags::COMPUTE,
+                    binding_type: BindingType::SampledImage,
+                    count: 1,
+                },
+                BindGroupLayoutEntryDesc {
+                    slot: 2,
+                    stages: ShaderStageFlags::COMPUTE,
+                    binding_type: BindingType::StorageImage,
+                    count: 1,
+                },
+                BindGroupLayoutEntryDesc {
+                    slot: 3,
+                    stages: ShaderStageFlags::COMPUTE,
+                    binding_type: BindingType::StorageImage,
+                    count: 1,
+                },
+            ],
+        });
+
+        let sampler = gpu.create_sampler(&SamplerDesc {
+            filter: SamplerFilter::Bilinear,
+            address_mode: SamplerAddressMode::Clamp,
+            compare_op: None,
+            mip_lod_bias: 0.0,
+            min_lod: 0.0,
+            max_lod: 0.0,
+        });
+
+        let pipeline = gpu.create_compute_pipeline(&ComputePipelineDesc {
+            shader: ShaderDesc {
+                entry: c"main",
+                code: shark_shaders::DISPLAY_TRANSFORM_COMP_SPV,
+            },
+            bind_group_layouts: &[bind_group_layout],
+        });
+
+        Self {
+            bind_group_layout,
+            pipeline,
+            sampler,
+        }
+    }
+}
index 2626abbd23cb5f5b95d7df58d88b6878b580a1bf..02b802fede76affa24b8be5b68b6e7ddfaab019e 100644 (file)
@@ -1,6 +1,9 @@
 mod basic;
+mod display_transform;
 mod ui;
 
 pub use basic::{BasicPipeline, BasicUniforms, Vertex};
 
 pub use ui::{PrimitiveInstance, PrimitiveVertex, UiPipeline, UiUniforms};
+
+pub use display_transform::DisplayTransformPipeline;
index 03ef8f3fa79f30281d6a48a9645198c48dfd234e..11053ea77fa7e83e07e587582d00985074efa002 100644 (file)
@@ -87,7 +87,7 @@ impl UiPipeline {
                 BindGroupLayoutEntryDesc {
                     slot: 5,
                     stages: ShaderStageFlags::ALL,
-                    binding_type: BindingType::Image,
+                    binding_type: BindingType::SampledImage,
                     count: 1,
                 },
             ],
@@ -113,7 +113,7 @@ impl UiPipeline {
             },
             bind_group_layouts: &[bind_group_layout],
             layout: GraphicsPipelineLayout {
-                color_attachment_formats: &[ImageFormat::RGBA8_SRGB],
+                color_attachment_formats: &[ImageFormat::RGBA16_FLOAT],
                 depth_attachment_format: Some(ImageFormat::DEPTH_F32),
                 stencil_attachment_format: None,
             },