]> git.nega.tv - josh/narcissus/commitdiff
narcissus: Avoid using draw indexed for glyphs
authorJoshua Simmons <josh@nega.tv>
Sat, 5 Aug 2023 09:00:20 +0000 (11:00 +0200)
committerJoshua Simmons <josh@nega.tv>
Sat, 5 Aug 2023 09:00:20 +0000 (11:00 +0200)
In preparation for being able to draw different types of primitive from
the same system, stop using indexed drawing for glyphs. Instead pack
information into the index buffer.

12 files changed:
bins/narcissus/src/helpers.rs
bins/narcissus/src/main.rs
bins/narcissus/src/mapped_buffer.rs
bins/narcissus/src/pipelines/basic.rs
bins/narcissus/src/pipelines/mod.rs
bins/narcissus/src/pipelines/text.rs
bins/narcissus/src/shaders/text.frag.glsl
bins/narcissus/src/shaders/text.frag.spv
bins/narcissus/src/shaders/text.vert.glsl
bins/narcissus/src/shaders/text.vert.spv
libs/narcissus-gpu/src/backend/vulkan/mod.rs
libs/narcissus-gpu/src/lib.rs

index dfb45605a124877e19f8b3157d4be1c893bf3783..8ab78df3773204673352bf6f7f929761ea72419a 100644 (file)
@@ -5,7 +5,7 @@ use narcissus_gpu::{Buffer, BufferDesc, BufferUsageFlags, Device, MemoryLocation
 use narcissus_image as image;
 use narcissus_maths::{vec2, vec3, vec4, Vec2, Vec3};
 
-use crate::{pipelines::Vertex, Blittable};
+use crate::{pipelines::Vertex, Blit};
 
 pub fn load_obj<P: AsRef<Path>>(path: P) -> (Vec<Vertex>, Vec<u16>) {
     #[derive(Default)]
@@ -95,7 +95,7 @@ pub fn create_host_buffer_with_data<T>(
     data: &[T],
 ) -> Buffer
 where
-    T: Blittable,
+    T: Blit,
 {
     // SAFETY: T: Blittable which implies it's freely convertable to a byte slice.
     unsafe {
index eb7e182bda92e148b764fbc481959aea02acd06c..31a07d7a949b1b87ab21f32f4cc18127036cbc9e 100644 (file)
@@ -16,7 +16,7 @@ use narcissus_gpu::{
     RenderingDesc, Scissor, StoreOp, ThreadToken, Viewport,
 };
 use narcissus_maths::{sin_cos_pi_f32, vec3, Affine3, HalfTurn, Mat3, Mat4, Point3, Vec3};
-use pipelines::{BasicUniforms, GlyphInstance, TextUniforms};
+use pipelines::{BasicUniforms, GlyphInstance, PrimitiveVertex, TextUniforms};
 
 mod fonts;
 mod helpers;
@@ -36,19 +36,40 @@ const MAX_GLYPHS: usize = 8192;
 /// # Safety
 ///
 /// Must not be applied to any types with padding
-pub unsafe trait Blittable: Sized {
+pub unsafe trait Blit {}
+
+unsafe impl Blit for u8 {}
+unsafe impl Blit for u16 {}
+unsafe impl Blit for Affine3 {}
+unsafe impl Blit for TouchedGlyph {}
+
+trait AsBytes {
+    fn as_bytes(&self) -> &[u8];
+}
+
+impl<T> AsBytes for T
+where
+    T: Blit,
+{
     fn as_bytes(&self) -> &[u8] {
-        // SAFETY: Safe whilst trait is correctly applied.
+        // SAFETY: Safe while `Blit` trait is correctly applied.
         unsafe {
-            std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of::<Self>())
+            std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of_val(self))
         }
     }
 }
 
-unsafe impl Blittable for u8 {}
-unsafe impl Blittable for u16 {}
-unsafe impl Blittable for Affine3 {}
-unsafe impl Blittable for TouchedGlyph {}
+impl<T> AsBytes for [T]
+where
+    T: Blit,
+{
+    fn as_bytes(&self) -> &[u8] {
+        // SAFETY: Safe while `Blit` trait is correctly applied.
+        unsafe {
+            std::slice::from_raw_parts(self as *const _ as *const u8, std::mem::size_of_val(self))
+        }
+    }
+}
 
 pub fn main() {
     let app = create_app();
@@ -238,7 +259,8 @@ pub fn main() {
     }
 
     let mut font_size_str = String::new();
-    let mut glyph_instances = Vec::new();
+    let mut primitive_instances = Vec::new();
+    let mut primitive_vertices = Vec::new();
     let mut line_glyph_indices = Vec::new();
     let mut line_kern_advances = Vec::new();
 
@@ -368,7 +390,8 @@ pub fn main() {
 
         let mut rng = Pcg64::new();
 
-        glyph_instances.clear();
+        primitive_instances.clear();
+        primitive_vertices.clear();
 
         for line in 0.. {
             let (font_family, font_size_px, text) = if line & 1 == 0 {
@@ -428,16 +451,25 @@ pub fn main() {
                         x += advance * scale;
                     }
 
-                    let color = *rng
-                        .select(&[0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63])
-                        .unwrap();
+                    let color =
+                        *rng.array_select(&[0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]);
 
-                    glyph_instances.push(GlyphInstance {
+                    let instance_index = primitive_instances.len() as u32;
+                    primitive_instances.push(GlyphInstance {
                         x,
                         y,
                         touched_glyph_index,
                         color,
                     });
+                    let glyph_vertices = &[
+                        PrimitiveVertex::glyph(0, instance_index),
+                        PrimitiveVertex::glyph(1, instance_index),
+                        PrimitiveVertex::glyph(2, instance_index),
+                        PrimitiveVertex::glyph(2, instance_index),
+                        PrimitiveVertex::glyph(1, instance_index),
+                        PrimitiveVertex::glyph(3, instance_index),
+                    ];
+                    primitive_vertices.extend_from_slice(glyph_vertices);
 
                     x += advance_width * scale;
                 }
@@ -447,7 +479,7 @@ pub fn main() {
         let atlas_width = glyph_cache.width() as u32;
         let atlas_height = glyph_cache.height() as u32;
 
-        glyph_instance_buffer.write_slice(&glyph_instances);
+        glyph_instance_buffer.write_slice(&primitive_instances);
 
         let (touched_glyphs, texture) = glyph_cache.update_atlas();
 
@@ -587,12 +619,13 @@ pub fn main() {
                 atlas_width,
                 atlas_height,
             },
+            primitive_vertices.as_slice(),
             glyph_buffer.buffer(),
             glyph_instance_buffer.buffer(),
             glyph_atlas,
         );
 
-        device.cmd_draw(&mut cmd_buffer, 4, glyph_instances.len() as u32, 0, 0);
+        device.cmd_draw(&mut cmd_buffer, primitive_vertices.len() as u32, 1, 0, 0);
 
         device.cmd_end_rendering(&mut cmd_buffer);
 
index f25b1b56abd667d59499b87c9557f5a3aafb142e..fedfcf637a31250bda853ed7b545e06c9ad4e3fd 100644 (file)
@@ -1,6 +1,6 @@
 use narcissus_gpu::{Buffer, BufferDesc, BufferUsageFlags, Device, MemoryLocation};
 
-use crate::Blittable;
+use crate::Blit;
 
 pub struct MappedBuffer<'a> {
     device: &'a dyn Device,
@@ -33,7 +33,7 @@ impl<'a> MappedBuffer<'a> {
 
     pub fn write_slice<T>(&mut self, values: &[T])
     where
-        T: Blittable,
+        T: Blit,
     {
         unsafe {
             let len = std::mem::size_of_val(values);
index 50e56ba3b3358e9f9d2865af4d52c05087bf2d42..41fa7d9b8277b446a0474ce7794059d328047925 100644 (file)
@@ -8,7 +8,7 @@ use narcissus_gpu::{
 };
 use narcissus_maths::Mat4;
 
-use crate::Blittable;
+use crate::{AsBytes, Blit};
 
 const VERT_SPV: &[u8] = include_bytes_align!(4, "../shaders/basic.vert.spv");
 const FRAG_SPV: &[u8] = include_bytes_align!(4, "../shaders/basic.frag.spv");
@@ -27,8 +27,8 @@ pub struct Vertex {
     pub texcoord: [f32; 4],
 }
 
-unsafe impl Blittable for BasicUniforms {}
-unsafe impl Blittable for Vertex {}
+unsafe impl Blit for BasicUniforms {}
+unsafe impl Blit for Vertex {}
 
 pub struct BasicPipeline {
     pub uniforms_bind_group_layout: BindGroupLayout,
@@ -102,6 +102,7 @@ impl BasicPipeline {
                 stencil_attachment_format: None,
             },
             topology: Topology::Triangles,
+            primitive_restart: false,
             polygon_mode: PolygonMode::Fill,
             culling_mode: CullingMode::Back,
             front_face: FrontFace::CounterClockwise,
index 6f5c2e6afd72fe3bb77513fff96612e3b3613088..f5241c0671bf5ec7410d62792d50f409a33ddcb6 100644 (file)
@@ -3,4 +3,4 @@ mod text;
 
 pub use basic::{BasicPipeline, BasicUniforms, Vertex};
 
-pub use text::{GlyphInstance, TextPipeline, TextUniforms};
+pub use text::{GlyphInstance, PrimitiveVertex, TextPipeline, TextUniforms};
index 3651fc7cb6e4610326739800b409e5734fc27c27..f4ed102a05bdf19dd59626b27f94893b09cde6a0 100644 (file)
@@ -8,7 +8,7 @@ use narcissus_gpu::{
     ShaderStageFlags, ThreadToken, Topology, TypedBind,
 };
 
-use crate::Blittable;
+use crate::{AsBytes, Blit};
 
 const VERT_SPV: &[u8] = include_bytes_align!(4, "../shaders/text.vert.spv");
 const FRAG_SPV: &[u8] = include_bytes_align!(4, "../shaders/text.frag.spv");
@@ -22,6 +22,23 @@ pub struct TextUniforms {
     pub atlas_height: u32,
 }
 
+#[repr(u32)]
+pub enum PrimitiveKind {
+    Glyph,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct PrimitiveVertex(u32);
+
+impl PrimitiveVertex {
+    #[inline(always)]
+    pub fn glyph(corner: u32, index: u32) -> Self {
+        let kind = PrimitiveKind::Glyph as u32;
+        Self(kind << 26 | corner << 24 | index)
+    }
+}
+
 #[allow(unused)]
 #[repr(C)]
 pub struct GlyphInstance {
@@ -31,8 +48,9 @@ pub struct GlyphInstance {
     pub color: u32,
 }
 
-unsafe impl Blittable for TextUniforms {}
-unsafe impl Blittable for GlyphInstance {}
+unsafe impl Blit for TextUniforms {}
+unsafe impl Blit for PrimitiveVertex {}
+unsafe impl Blit for GlyphInstance {}
 
 pub struct TextPipeline {
     bind_group_layout: BindGroupLayout,
@@ -65,12 +83,18 @@ impl TextPipeline {
                 BindGroupLayoutEntryDesc {
                     slot: 3,
                     stages: ShaderStageFlags::ALL,
-                    binding_type: BindingType::Sampler,
+                    binding_type: BindingType::StorageBuffer,
                     count: 1,
                 },
                 BindGroupLayoutEntryDesc {
                     slot: 4,
                     stages: ShaderStageFlags::ALL,
+                    binding_type: BindingType::Sampler,
+                    count: 1,
+                },
+                BindGroupLayoutEntryDesc {
+                    slot: 5,
+                    stages: ShaderStageFlags::ALL,
                     binding_type: BindingType::Image,
                     count: 1,
                 },
@@ -101,7 +125,8 @@ impl TextPipeline {
                 depth_attachment_format: Some(ImageFormat::DEPTH_F32),
                 stencil_attachment_format: None,
             },
-            topology: Topology::TriangleStrip,
+            topology: Topology::Triangles,
+            primitive_restart: false,
             polygon_mode: PolygonMode::Fill,
             culling_mode: CullingMode::None,
             front_face: FrontFace::CounterClockwise,
@@ -129,18 +154,24 @@ impl TextPipeline {
         thread_token: &ThreadToken,
         cmd_buffer: &mut CmdBuffer,
         text_uniforms: &TextUniforms,
+        primitive_vertices: &[PrimitiveVertex],
         cached_glyphs: Buffer,
         glyph_instances: Buffer,
         atlas: Image,
     ) {
-        let mut uniforms = device.request_transient_buffer(
+        let uniforms_buffer = device.request_transient_buffer_with_data(
             frame,
             thread_token,
             BufferUsageFlags::UNIFORM,
-            std::mem::size_of::<TextUniforms>(),
+            text_uniforms.as_bytes(),
         );
 
-        uniforms.copy_from_slice(text_uniforms.as_bytes());
+        let primitive_vertex_buffer = device.request_transient_buffer_with_data(
+            frame,
+            thread_token,
+            BufferUsageFlags::STORAGE,
+            primitive_vertices.as_bytes(),
+        );
 
         device.cmd_set_pipeline(cmd_buffer, self.pipeline);
         device.cmd_set_bind_group(
@@ -152,26 +183,31 @@ impl TextPipeline {
                 Bind {
                     binding: 0,
                     array_element: 0,
-                    typed: TypedBind::UniformBuffer(&[uniforms.into()]),
+                    typed: TypedBind::UniformBuffer(&[uniforms_buffer.into()]),
                 },
                 Bind {
                     binding: 1,
                     array_element: 0,
-                    typed: TypedBind::StorageBuffer(&[cached_glyphs.into()]),
+                    typed: TypedBind::StorageBuffer(&[primitive_vertex_buffer.into()]),
                 },
                 Bind {
                     binding: 2,
                     array_element: 0,
-                    typed: TypedBind::StorageBuffer(&[glyph_instances.into()]),
+                    typed: TypedBind::StorageBuffer(&[cached_glyphs.into()]),
                 },
                 Bind {
                     binding: 3,
                     array_element: 0,
-                    typed: TypedBind::Sampler(&[self.sampler]),
+                    typed: TypedBind::StorageBuffer(&[glyph_instances.into()]),
                 },
                 Bind {
                     binding: 4,
                     array_element: 0,
+                    typed: TypedBind::Sampler(&[self.sampler]),
+                },
+                Bind {
+                    binding: 5,
+                    array_element: 0,
                     typed: TypedBind::Image(&[(ImageLayout::Optimal, atlas)]),
                 },
             ],
index f241618408ec0c649a171ac458b58cbce039289b..e8955e973b5dca7015265bad98ba7b1e49d30c92 100644 (file)
@@ -1,7 +1,7 @@
 #version 460
 
-layout(set = 0, binding = 3) uniform sampler texSampler;
-layout(set = 0, binding = 4) uniform texture2D tex;
+layout(set = 0, binding = 4) uniform sampler texSampler;
+layout(set = 0, binding = 5) uniform texture2D tex;
 
 layout(location = 0) in vec2 texcoord;
 layout(location = 1) in vec4 color;
index 3bb4aa05dca613a4a2ed6c6c9023c2176a6bccc0..d858dfd50168222d16b6a5b592395712f75d216a 100644 (file)
Binary files a/bins/narcissus/src/shaders/text.frag.spv and b/bins/narcissus/src/shaders/text.frag.spv differ
index 0efddaf1f937332dcb0899ad9b95a8ea955f7abc..314f9172c52d0d7c6541ed47b31b076274bf096c 100644 (file)
@@ -28,11 +28,15 @@ layout(std430, set = 0, binding = 0) uniform uniformBuffer {
     uint atlasHeight;
 };
 
-layout(std430, set = 0, binding = 1) readonly buffer glyphBuffer {
+layout(std430, set = 0, binding = 1) readonly buffer primitiveBuffer {
+    uint primitiveVertices[];
+};
+
+layout(std430, set = 0, binding = 2) readonly buffer glyphBuffer {
     CachedGlyph cachedGlyphs[];
 };
 
-layout(std430, set = 0, binding = 2) readonly buffer glyphInstanceBuffer {
+layout(std430, set = 0, binding = 3) readonly buffer glyphInstanceBuffer {
     GlyphInstance glyphInstances[];
 };
 
@@ -40,7 +44,12 @@ layout(location = 0) out vec2 outTexcoord;
 layout(location = 1) out flat vec4 outColor;
 
 void main() {
-    GlyphInstance gi = glyphInstances[gl_InstanceIndex];
+    uint primitivePacked = primitiveVertices[gl_VertexIndex];
+    uint primitiveKind = bitfieldExtract(primitivePacked, 26, 6);
+    uint primitiveData = bitfieldExtract(primitivePacked, 24, 2);
+    uint instanceIndex = bitfieldExtract(primitivePacked, 0, 24);
+
+    GlyphInstance gi = glyphInstances[instanceIndex];
     CachedGlyph cg = cachedGlyphs[gi.index];
 
     vec2 positions[4] = {
@@ -50,7 +59,7 @@ void main() {
         vec2(cg.offset_x1, cg.offset_y1)
     };
 
-    vec2 position = positions[gl_VertexIndex];
+    vec2 position = positions[primitiveData];
     vec2 halfScreenSize = vec2(screenWidth, screenHeight) / 2.0;
     vec2 glyphPosition = vec2(gi.x, gi.y);
     vec2 vertexPosition = (position + glyphPosition) / halfScreenSize - 1.0;
@@ -63,7 +72,7 @@ void main() {
         vec2(cg.x1, cg.y1)
     };
 
-    vec2 texcoord = texcoords[gl_VertexIndex];
+    vec2 texcoord = texcoords[primitiveData];
     outTexcoord = texcoord / vec2(atlasWidth, atlasHeight);
 
     vec4 color = unpackUnorm4x8(gi.color).bgra;
index 4f9060d4c25848441d0057d66c7f5b444f4a67f5..3dd51db63943630100d91a9cd15b08d9e49c4ef5 100644 (file)
Binary files a/bins/narcissus/src/shaders/text.vert.spv and b/bins/narcissus/src/shaders/text.vert.spv differ
index f430ffb538b086286e6a12ad07abc16fb16bc5ad..0c83b3b964b1b83163dc05d7a6791d8c61167d5c 100644 (file)
@@ -1214,6 +1214,7 @@ impl Device for VulkanDevice {
         ];
 
         let topology = vulkan_primitive_topology(desc.topology);
+        let primitive_restart_enable = vulkan_bool32(desc.primitive_restart);
         let polygon_mode = vulkan_polygon_mode(desc.polygon_mode);
         let cull_mode = vulkan_cull_mode(desc.culling_mode);
         let front_face = vulkan_front_face(desc.front_face);
@@ -1242,6 +1243,7 @@ impl Device for VulkanDevice {
         let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default();
         let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo {
             topology,
+            primitive_restart_enable,
             ..default()
         };
         let viewport_state = vk::PipelineViewportStateCreateInfo::default();
index 41a9b30e44bfd3cf6b9ce81108361ac89b244acb..071fb8ad9ca01818ed891bd0d54368ff3d432f91 100644 (file)
@@ -391,6 +391,7 @@ pub struct GraphicsPipelineDesc<'a> {
     pub bind_group_layouts: &'a [BindGroupLayout],
     pub layout: GraphicsPipelineLayout<'a>,
     pub topology: Topology,
+    pub primitive_restart: bool,
     pub polygon_mode: PolygonMode,
     pub culling_mode: CullingMode,
     pub front_face: FrontFace,