From 526d6cca59e034b6aa28e930d00f8d66524c08af Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sat, 5 Aug 2023 11:00:20 +0200 Subject: [PATCH] narcissus: Avoid using draw indexed for glyphs 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. --- bins/narcissus/src/helpers.rs | 4 +- bins/narcissus/src/main.rs | 65 ++++++++++++++----- bins/narcissus/src/mapped_buffer.rs | 4 +- bins/narcissus/src/pipelines/basic.rs | 7 +- bins/narcissus/src/pipelines/mod.rs | 2 +- bins/narcissus/src/pipelines/text.rs | 60 +++++++++++++---- bins/narcissus/src/shaders/text.frag.glsl | 4 +- bins/narcissus/src/shaders/text.frag.spv | Bin 700 -> 700 bytes bins/narcissus/src/shaders/text.vert.glsl | 19 ++++-- bins/narcissus/src/shaders/text.vert.spv | Bin 3076 -> 3300 bytes libs/narcissus-gpu/src/backend/vulkan/mod.rs | 2 + libs/narcissus-gpu/src/lib.rs | 1 + 12 files changed, 125 insertions(+), 43 deletions(-) diff --git a/bins/narcissus/src/helpers.rs b/bins/narcissus/src/helpers.rs index dfb4560..8ab78df 100644 --- a/bins/narcissus/src/helpers.rs +++ b/bins/narcissus/src/helpers.rs @@ -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>(path: P) -> (Vec, Vec) { #[derive(Default)] @@ -95,7 +95,7 @@ pub fn create_host_buffer_with_data( data: &[T], ) -> Buffer where - T: Blittable, + T: Blit, { // SAFETY: T: Blittable which implies it's freely convertable to a byte slice. unsafe { diff --git a/bins/narcissus/src/main.rs b/bins/narcissus/src/main.rs index eb7e182..31a07d7 100644 --- a/bins/narcissus/src/main.rs +++ b/bins/narcissus/src/main.rs @@ -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 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::()) + 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 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); diff --git a/bins/narcissus/src/mapped_buffer.rs b/bins/narcissus/src/mapped_buffer.rs index f25b1b5..fedfcf6 100644 --- a/bins/narcissus/src/mapped_buffer.rs +++ b/bins/narcissus/src/mapped_buffer.rs @@ -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(&mut self, values: &[T]) where - T: Blittable, + T: Blit, { unsafe { let len = std::mem::size_of_val(values); diff --git a/bins/narcissus/src/pipelines/basic.rs b/bins/narcissus/src/pipelines/basic.rs index 50e56ba..41fa7d9 100644 --- a/bins/narcissus/src/pipelines/basic.rs +++ b/bins/narcissus/src/pipelines/basic.rs @@ -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, diff --git a/bins/narcissus/src/pipelines/mod.rs b/bins/narcissus/src/pipelines/mod.rs index 6f5c2e6..f5241c0 100644 --- a/bins/narcissus/src/pipelines/mod.rs +++ b/bins/narcissus/src/pipelines/mod.rs @@ -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}; diff --git a/bins/narcissus/src/pipelines/text.rs b/bins/narcissus/src/pipelines/text.rs index 3651fc7..f4ed102 100644 --- a/bins/narcissus/src/pipelines/text.rs +++ b/bins/narcissus/src/pipelines/text.rs @@ -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::(), + 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)]), }, ], diff --git a/bins/narcissus/src/shaders/text.frag.glsl b/bins/narcissus/src/shaders/text.frag.glsl index f241618..e8955e9 100644 --- a/bins/narcissus/src/shaders/text.frag.glsl +++ b/bins/narcissus/src/shaders/text.frag.glsl @@ -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; diff --git a/bins/narcissus/src/shaders/text.frag.spv b/bins/narcissus/src/shaders/text.frag.spv index 3bb4aa05dca613a4a2ed6c6c9023c2176a6bccc0..d858dfd50168222d16b6a5b592395712f75d216a 100644 GIT binary patch delta 17 ZcmdnPx`%bb1V+}06Xh9MHg4o*0suAl1zrFE delta 17 ZcmdnPx`%bb1V)yL6XhA1H*Vx+0suAW1zZ3C diff --git a/bins/narcissus/src/shaders/text.vert.glsl b/bins/narcissus/src/shaders/text.vert.glsl index 0efddaf..314f917 100644 --- a/bins/narcissus/src/shaders/text.vert.glsl +++ b/bins/narcissus/src/shaders/text.vert.glsl @@ -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; diff --git a/bins/narcissus/src/shaders/text.vert.spv b/bins/narcissus/src/shaders/text.vert.spv index 4f9060d4c25848441d0057d66c7f5b444f4a67f5..3dd51db63943630100d91a9cd15b08d9e49c4ef5 100644 GIT binary patch literal 3300 zcmZve+ly6o6vw~koPEyZjMwb;po1oqsZD{GBGYMl83REhdZ|FmOIaC0;zJZ^CfN;y zdI$=7=&hg&l~ym-Q@6+N(dFOhC6LeB-|w(FxMA3Peb?=~erv7YnL&TughoA0X^H+- z`>ar3JxNm<)q8&TeYbsG2^x-25QaLBz+@kUX2z>8cF51ON}NgigcZ%T^cUP>x|E9P*~so&TWpNv8y_H=?ix01@R_~t#bWaT>l!^zjdts$ldzEQa^Litsj~C z(ZlzV%hV4xUO(7){b1etIs4erJLa*Q8=2fZoHx0_ zQa3v=?YQXJhowGKzR?kXrTXQwR)dO%|J7=$H=H@oY7G|lXxjV1Uae76`I%MrYI{cp zBMw_j9CB^63=basmsy7${>aP?_UNzlE-)+cXEvCb$g5rVH5$};lW$t1nhDOIKm7Rv zXGh0zYGl{YQ6oEq42PS{0Su1F>=8Ah-{kMscT!wl$(MKXowLe!Aa&i{lC(WBV5ARd9hxZHCgTaeEhz~mc z$oryq20grx`MqF|h)dp=+R-fgSFIyfc)n$fcpv+l^B^8Fdq+HE{Mj|+O&XTnU#^Y% zlJoC2=WJ~LkqQIfreU2Mwet|`KlSTAB?mpa+kJ`ry5$|-w-w&0IgM(cR(dYjLv+~D z3+`rgKWbDmcLW&w#*PK&4g<#@9dX%#o4ps{*btYUn6d6$uc@89#n`sZ#$HX-J@HD#Zgt!x>^b#v7rf^ChBM-vk+Dy$=cJm!$ZtqBYpLl? zscgVw^R`s87n^r%10I`qJ;$+mPb!Q!!QMB9AGL7je;`#(V&a1hyt!Y{Wj;QX3hU0t zM^fSG^{4JN_egsEnN)aqel8UjpY!(N&W+CnsqpxGAr+Q7zLY8sczXPmRD96EpF0|C ou}0Oii~cR4X6}-3q29VZTVJ2wBTr`y%jC73JOF8}}l literal 3076 zcmZve+l!TD6vlsh?{DuZ$3tcw0-Ze2(Mh^c38ZvxoS{Y>(~i)E7=)2Em8AxP5`sY& z-W6z3YIUPsmED$y!owfrO%T7e*P1>yeK36A`>ezJthJu^-P7xC8e`C5oF&|2_$;SO z2N-7%@9Ek7vs*qn`p%ZE+n&>7DZOyiXBkW4y8(_IJp6%+WxxtxJ@68+5BM1P4)_I_ zq0e|k!KQo{W==8l71mAcffgnM{T2h$H1Qq)4~0*YtYA&X3|aoS-!#3z%x%1!Hzt=_ zZ-526^~ia1=3Dze{KZE3i;eOZYxB2GF>AJ_VGTp9pV%X$0D& z-S@HFS4>{6t;4#ijyj|IKb|&ot)}7W+|OC)@BZr5Ij^OOv#gJp@snAXXWaEnuJx(D zsC)b3oYZK$HouLVOZ{j!&b6AAhxS+UTmP!#wbp#>&3iGQQ#-BWNY)|ms>6#`9@g<; z+QsChek(?sfA0HH>R!9NTzfC8fAhNU(UvZbr_Ef&^Gssq`!>I;KdwD5<}<(Xo)OoR z^uzCV)@FUR=bho6JIm)T1SYGoVdBwY@sdES&2!aW=gx#rDyU-u#+(u1{gVCFU%|oawd1oOfZrCnkS+sMRN8 zE(8x_^r-Ug9V|H5`8rhj2m5c{i{4RL+c=^hANAJ5_bsAV_-@D0M+u6hpL*VB4BC+=&aL-uH=Tiv0wBqbOOH8fIF&5|h z7=*7j?*PxI^?HU+u_@KJxZn&{q054}rDyaRCCaS%06i_IIXc{R0GEJTF3E z6>}+Lyeli_G6Y^RKSE$t$4?M>#B0VsL-5fifA3bY#SCI*SMoPt7sUOtmj?sC3Srak U*>}!9o_X_lC+qVM9iFGdzb^!~`2YX_ diff --git a/libs/narcissus-gpu/src/backend/vulkan/mod.rs b/libs/narcissus-gpu/src/backend/vulkan/mod.rs index f430ffb..0c83b3b 100644 --- a/libs/narcissus-gpu/src/backend/vulkan/mod.rs +++ b/libs/narcissus-gpu/src/backend/vulkan/mod.rs @@ -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(); diff --git a/libs/narcissus-gpu/src/lib.rs b/libs/narcissus-gpu/src/lib.rs index 41a9b30..071fb8a 100644 --- a/libs/narcissus-gpu/src/lib.rs +++ b/libs/narcissus-gpu/src/lib.rs @@ -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, -- 2.49.0