From 28a7218b5d314d24a884d4b60d24d7cf2a98b44d Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Mon, 27 Feb 2023 21:15:43 +0100 Subject: [PATCH] Move a bunch of helper code out of main.rs Re-organize the pipeline structures as well. --- bins/narcissus/src/helpers.rs | 194 +++++++++ bins/narcissus/src/main.rs | 471 +++------------------- bins/narcissus/src/mapped_buffer.rs | 65 +++ bins/narcissus/src/pipelines/basic.rs | 96 ++++- bins/narcissus/src/pipelines/mod.rs | 5 + bins/narcissus/src/pipelines/text.rs | 120 +++++- bins/narcissus/src/shaders/text.frag.glsl | 4 +- bins/narcissus/src/shaders/text.frag.spv | Bin 840 -> 840 bytes bins/narcissus/src/shaders/text.vert.glsl | 4 +- bins/narcissus/src/shaders/text.vert.spv | Bin 3076 -> 3076 bytes 10 files changed, 513 insertions(+), 446 deletions(-) create mode 100644 bins/narcissus/src/helpers.rs create mode 100644 bins/narcissus/src/mapped_buffer.rs diff --git a/bins/narcissus/src/helpers.rs b/bins/narcissus/src/helpers.rs new file mode 100644 index 0000000..08c03a3 --- /dev/null +++ b/bins/narcissus/src/helpers.rs @@ -0,0 +1,194 @@ +use std::path::Path; + +use narcissus_core::{default, obj}; +use narcissus_gpu::{ + Access, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, Device, Extent3d, Image, + ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, + ImageUsageFlags, MemoryLocation, Offset3d, ThreadToken, +}; +use narcissus_image as image; +use narcissus_maths::{vec2, vec3, vec4, Vec2, Vec3}; + +use crate::{pipelines::Vertex, Blittable}; + +pub fn load_obj>(path: P) -> (Vec, Vec) { + #[derive(Default)] + struct ObjVisitor { + positions: Vec, + normals: Vec, + texcoords: Vec, + indices: Vec<[(i32, i32, i32); 3]>, + } + + impl obj::Visitor for ObjVisitor { + fn visit_position(&mut self, x: f32, y: f32, z: f32, _w: f32) { + self.positions.push(vec3(x, y, z)) + } + + fn visit_texcoord(&mut self, u: f32, v: f32, _w: f32) { + self.texcoords.push(vec2(u, v)); + } + + fn visit_normal(&mut self, x: f32, y: f32, z: f32) { + self.normals.push(vec3(x, y, z)) + } + + fn visit_face(&mut self, indices: &[(i32, i32, i32)]) { + self.indices + .push(indices.try_into().expect("not a triangle")); + } + + fn visit_object(&mut self, _name: &str) {} + fn visit_group(&mut self, _name: &str) {} + fn visit_smooth_group(&mut self, _group: i32) {} + } + + let start = std::time::Instant::now(); + let path = path.as_ref(); + let file = std::fs::File::open(path).expect("couldn't open file"); + let mut visitor = ObjVisitor::default(); + + obj::Parser::new(file) + .visit(&mut visitor) + .expect("failed to parse obj file"); + + let (vertices, indices): (Vec<_>, Vec<_>) = visitor + .indices + .iter() + .flatten() + .enumerate() + .map(|(index, &(position_index, texcoord_index, normal_index))| { + let position = visitor.positions[position_index as usize - 1]; + let normal = visitor.normals[normal_index as usize - 1]; + let texcoord = visitor.texcoords[texcoord_index as usize - 1]; + ( + Vertex { + position: vec4(position.x, position.y, position.z, 0.0).into(), + normal: vec4(normal.x, normal.y, normal.z, 0.0).into(), + texcoord: vec4(texcoord.x, texcoord.y, 0.0, 0.0).into(), + }, + index as u16, + ) + }) + .unzip(); + + println!( + "parsing obj {path:?} took {:?}", + std::time::Instant::now() - start + ); + + (vertices, indices) +} + +pub fn load_image>(path: P) -> image::Image { + let start = std::time::Instant::now(); + let path = path.as_ref(); + let texture = + image::Image::from_buffer(std::fs::read(path).expect("failed to read file").as_slice()) + .expect("failed to load image"); + println!( + "loading image {path:?} took {:?}", + std::time::Instant::now() - start + ); + texture +} + +pub fn create_buffer_with_data( + device: &dyn Device, + usage: BufferUsageFlags, + data: &[T], +) -> Buffer +where + T: Blittable, +{ + let len = data.len() * std::mem::size_of::(); + let buffer = device.create_buffer(&BufferDesc { + location: MemoryLocation::HostMapped, + usage, + size: len, + }); + // Safety: T: Blittable which implies it's freely convertable to a byte slice. + unsafe { + let dst = std::slice::from_raw_parts_mut(device.map_buffer(buffer), len); + let src = std::slice::from_raw_parts(data.as_ptr() as *const u8, len); + dst.copy_from_slice(src); + device.unmap_buffer(buffer); + } + buffer +} + +pub fn create_image_with_data( + device: &dyn Device, + thread_token: &ThreadToken, + width: u32, + height: u32, + data: &[u8], +) -> Image { + let frame = device.begin_frame(); + + let buffer = create_buffer_with_data(device, BufferUsageFlags::TRANSFER_SRC, data); + + let image = device.create_image(&ImageDesc { + location: MemoryLocation::Device, + usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + dimension: ImageDimension::Type2d, + format: ImageFormat::RGBA8_SRGB, + initial_layout: ImageLayout::Optimal, + width, + height, + depth: 1, + layer_count: 1, + mip_levels: 1, + }); + + let mut cmd_buffer = device.create_cmd_buffer(&frame, thread_token); + + device.cmd_barrier( + &mut cmd_buffer, + None, + &[ImageBarrier::layout_optimal( + &[Access::None], + &[Access::TransferWrite], + image, + ImageAspectFlags::COLOR, + )], + ); + + device.cmd_copy_buffer_to_image( + &mut cmd_buffer, + buffer, + image, + ImageLayout::Optimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: default(), + image_offset: Offset3d { x: 0, y: 0, z: 0 }, + image_extent: Extent3d { + width, + height, + depth: 1, + }, + }], + ); + + device.cmd_barrier( + &mut cmd_buffer, + None, + &[ImageBarrier::layout_optimal( + &[Access::TransferWrite], + &[Access::FragmentShaderSampledImageRead], + image, + ImageAspectFlags::COLOR, + )], + ); + + device.submit(&frame, cmd_buffer); + + device.destroy_buffer(&frame, buffer); + + device.end_frame(frame); + + image +} diff --git a/bins/narcissus/src/main.rs b/bins/narcissus/src/main.rs index 7848f73..42e3835 100644 --- a/bins/narcissus/src/main.rs +++ b/bins/narcissus/src/main.rs @@ -1,26 +1,28 @@ -use std::{path::Path, time::Instant}; +use std::time::Instant; +use helpers::{create_buffer_with_data, create_image_with_data, load_image, load_obj}; +use mapped_buffer::MappedBuffer; use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc}; -use narcissus_core::{default, obj, rand::Pcg64}; -use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache}; +use narcissus_core::{default, rand::Pcg64}; +use narcissus_font::{CachedGlyph, FontCollection, GlyphCache}; use narcissus_gpu::{ - create_device, Access, Bind, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, - Device, Extent2d, Extent3d, Image, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, - ImageFormat, ImageLayout, ImageUsageFlags, IndexType, LoadOp, MemoryLocation, Offset2d, - Offset3d, RenderingAttachment, RenderingDesc, SamplerAddressMode, SamplerDesc, SamplerFilter, - Scissor, StoreOp, ThreadToken, TypedBind, Viewport, -}; -use narcissus_image as image; -use narcissus_maths::{ - sin_cos_pi_f32, vec2, vec3, vec4, Affine3, Deg, HalfTurn, Mat3, Mat4, Point3, Vec2, Vec3, + create_device, Access, BufferImageCopy, BufferUsageFlags, ClearValue, Extent2d, Extent3d, + ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, + ImageUsageFlags, LoadOp, MemoryLocation, Offset2d, Offset3d, RenderingAttachment, + RenderingDesc, Scissor, StoreOp, ThreadToken, Viewport, }; +use narcissus_maths::{sin_cos_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4, Point3, Vec3}; +use pipelines::{BasicUniforms, GlyphInstance, TextUniforms}; + use crate::{ fonts::{FontFamily, Fonts}, pipelines::{BasicPipeline, TextPipeline}, }; mod fonts; +mod helpers; +mod mapped_buffer; mod pipelines; const MAX_SHARKS: usize = 262_144; @@ -36,291 +38,12 @@ const MAX_GLYPHS: usize = 8192; /// # Safety /// /// Must not be applied to any types with padding -unsafe trait Blittable: Sized {} - -#[allow(unused)] -#[repr(C)] -struct BasicUniforms { - clip_from_model: Mat4, -} +pub unsafe trait Blittable: Sized {} -unsafe impl Blittable for BasicUniforms {} - -#[allow(unused)] -#[repr(C)] -struct Vertex { - position: [f32; 4], - normal: [f32; 4], - texcoord: [f32; 4], -} - -#[allow(unused)] -#[repr(C)] -struct TextUniforms { - screen_width: u32, - screen_height: u32, - atlas_width: u32, - atlas_height: u32, -} - -unsafe impl Blittable for TextUniforms {} - -#[allow(unused)] -#[repr(C)] -struct GlyphInstance { - cached_glyph_index: CachedGlyphIndex, - x: f32, - y: f32, - color: u32, -} - -unsafe impl Blittable for CachedGlyph {} -unsafe impl Blittable for GlyphInstance {} - -unsafe impl Blittable for Vertex {} unsafe impl Blittable for u8 {} unsafe impl Blittable for u16 {} unsafe impl Blittable for Affine3 {} - -fn load_obj>(path: P) -> (Vec, Vec) { - #[derive(Default)] - struct ObjVisitor { - positions: Vec, - normals: Vec, - texcoords: Vec, - indices: Vec<[(i32, i32, i32); 3]>, - } - - impl obj::Visitor for ObjVisitor { - fn visit_position(&mut self, x: f32, y: f32, z: f32, _w: f32) { - self.positions.push(vec3(x, y, z)) - } - - fn visit_texcoord(&mut self, u: f32, v: f32, _w: f32) { - self.texcoords.push(vec2(u, v)); - } - - fn visit_normal(&mut self, x: f32, y: f32, z: f32) { - self.normals.push(vec3(x, y, z)) - } - - fn visit_face(&mut self, indices: &[(i32, i32, i32)]) { - self.indices - .push(indices.try_into().expect("not a triangle")); - } - - fn visit_object(&mut self, _name: &str) {} - fn visit_group(&mut self, _name: &str) {} - fn visit_smooth_group(&mut self, _group: i32) {} - } - - let start = std::time::Instant::now(); - let path = path.as_ref(); - let file = std::fs::File::open(path).expect("couldn't open file"); - let mut visitor = ObjVisitor::default(); - - obj::Parser::new(file) - .visit(&mut visitor) - .expect("failed to parse obj file"); - - let (vertices, indices): (Vec<_>, Vec<_>) = visitor - .indices - .iter() - .flatten() - .enumerate() - .map(|(index, &(position_index, texcoord_index, normal_index))| { - let position = visitor.positions[position_index as usize - 1]; - let normal = visitor.normals[normal_index as usize - 1]; - let texcoord = visitor.texcoords[texcoord_index as usize - 1]; - ( - Vertex { - position: vec4(position.x, position.y, position.z, 0.0).into(), - normal: vec4(normal.x, normal.y, normal.z, 0.0).into(), - texcoord: vec4(texcoord.x, texcoord.y, 0.0, 0.0).into(), - }, - index as u16, - ) - }) - .unzip(); - - println!( - "parsing obj {path:?} took {:?}", - std::time::Instant::now() - start - ); - - (vertices, indices) -} - -fn load_image>(path: P) -> image::Image { - let start = std::time::Instant::now(); - let path = path.as_ref(); - let texture = - image::Image::from_buffer(std::fs::read(path).expect("failed to read file").as_slice()) - .expect("failed to load image"); - println!( - "loading image {path:?} took {:?}", - std::time::Instant::now() - start - ); - texture -} - -fn create_buffer_with_data(device: &dyn Device, usage: BufferUsageFlags, data: &[T]) -> Buffer -where - T: Blittable, -{ - let len = data.len() * std::mem::size_of::(); - let buffer = device.create_buffer(&BufferDesc { - location: MemoryLocation::HostMapped, - usage, - size: len, - }); - // Safety: T: Blittable which implies it's freely convertable to a byte slice. - unsafe { - let dst = std::slice::from_raw_parts_mut(device.map_buffer(buffer), len); - let src = std::slice::from_raw_parts(data.as_ptr() as *const u8, len); - dst.copy_from_slice(src); - device.unmap_buffer(buffer); - } - buffer -} - -fn create_image_with_data( - device: &dyn Device, - thread_token: &ThreadToken, - width: u32, - height: u32, - data: &[u8], -) -> Image { - let frame = device.begin_frame(); - - let buffer = create_buffer_with_data(device, BufferUsageFlags::TRANSFER_SRC, data); - - let image = device.create_image(&ImageDesc { - location: MemoryLocation::Device, - usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, - dimension: ImageDimension::Type2d, - format: ImageFormat::RGBA8_SRGB, - initial_layout: ImageLayout::Optimal, - width, - height, - depth: 1, - layer_count: 1, - mip_levels: 1, - }); - - let mut cmd_buffer = device.create_cmd_buffer(&frame, thread_token); - - device.cmd_barrier( - &mut cmd_buffer, - None, - &[ImageBarrier::layout_optimal( - &[Access::None], - &[Access::TransferWrite], - image, - ImageAspectFlags::COLOR, - )], - ); - - device.cmd_copy_buffer_to_image( - &mut cmd_buffer, - buffer, - image, - ImageLayout::Optimal, - &[BufferImageCopy { - buffer_offset: 0, - buffer_row_length: 0, - buffer_image_height: 0, - image_subresource: default(), - image_offset: Offset3d { x: 0, y: 0, z: 0 }, - image_extent: Extent3d { - width, - height, - depth: 1, - }, - }], - ); - - device.cmd_barrier( - &mut cmd_buffer, - None, - &[ImageBarrier::layout_optimal( - &[Access::TransferWrite], - &[Access::FragmentShaderSampledImageRead], - image, - ImageAspectFlags::COLOR, - )], - ); - - device.submit(&frame, cmd_buffer); - - device.destroy_buffer(&frame, buffer); - - device.end_frame(frame); - - image -} - -struct MappedBuffer<'a> { - device: &'a dyn Device, - buffer: Buffer, - slice: &'a mut [u8], -} - -impl<'a> MappedBuffer<'a> { - pub fn new(device: &'a dyn Device, usage: BufferUsageFlags, len: usize) -> Self { - let buffer = device.create_buffer(&BufferDesc { - location: MemoryLocation::HostMapped, - usage, - size: len, - }); - unsafe { - let ptr = device.map_buffer(buffer); - let slice = std::slice::from_raw_parts_mut(ptr, len); - Self { - device, - buffer, - slice, - } - } - } - - pub fn buffer(&self) -> Buffer { - self.buffer - } - - pub fn write(&mut self, value: T) - where - T: Blittable, - { - unsafe { - let src = std::slice::from_raw_parts( - &value as *const T as *const u8, - std::mem::size_of::(), - ); - self.slice.copy_from_slice(src) - } - } - - pub fn write_slice(&mut self, values: &[T]) - where - T: Blittable, - { - unsafe { - let len = values.len() * std::mem::size_of::(); - let src = std::slice::from_raw_parts(values.as_ptr() as *const u8, len); - self.slice[..len].copy_from_slice(src) - } - } -} - -impl<'a> Drop for MappedBuffer<'a> { - fn drop(&mut self) { - // Safety: Make sure we don't have the slice outlive the mapping. - unsafe { - self.device.unmap_buffer(self.buffer); - } - } -} +unsafe impl Blittable for CachedGlyph {} pub fn main() { let app = create_app(); @@ -362,28 +85,19 @@ pub fn main() { blåhaj_image.as_slice(), ); - let mut basic_uniforms = MappedBuffer::new( + let mut basic_uniform_buffer = MappedBuffer::new( device.as_ref(), BufferUsageFlags::UNIFORM, std::mem::size_of::(), ); - let mut basic_transforms = MappedBuffer::new( + let mut basic_transform_buffer = MappedBuffer::new( device.as_ref(), BufferUsageFlags::STORAGE, std::mem::size_of::() * MAX_SHARKS, ); - let basic_sampler = device.create_sampler(&SamplerDesc { - filter: SamplerFilter::Point, - address_mode: SamplerAddressMode::Clamp, - compare_op: None, - mip_lod_bias: 0.0, - min_lod: 0.0, - max_lod: 1000.0, - }); - - let mut text_uniforms = MappedBuffer::new( + let mut text_uniform_buffer = MappedBuffer::new( device.as_ref(), BufferUsageFlags::UNIFORM, std::mem::size_of::(), @@ -395,7 +109,7 @@ pub fn main() { std::mem::size_of::() * MAX_GLYPH_INSTANCES, ); - let mut cached_glyph_buffer = MappedBuffer::new( + let mut glyph_buffer = MappedBuffer::new( device.as_ref(), BufferUsageFlags::STORAGE, std::mem::size_of::() * MAX_GLYPHS, @@ -431,15 +145,6 @@ pub fn main() { device.end_frame(frame); } - let text_sampler = device.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 mut depth_width = 0; let mut depth_height = 0; let mut depth_image = default(); @@ -549,7 +254,7 @@ pub fn main() { transform.matrix *= Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(0.002 * direction)) } - basic_transforms.write_slice(&shark_transforms); + basic_transform_buffer.write_slice(&shark_transforms); let (s, c) = sin_cos_pi_f32(frame_start * 0.2); let camera_height = c * 8.0; @@ -561,7 +266,7 @@ pub fn main() { Mat4::perspective_rev_inf_zo(Deg::new(45.0).into(), width as f32 / height as f32, 0.01); let clip_from_model = clip_from_camera * camera_from_model; - basic_uniforms.write(BasicUniforms { clip_from_model }); + basic_uniform_buffer.write(BasicUniforms { clip_from_model }); // Do some Font Shit.' let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½"; @@ -624,7 +329,7 @@ pub fn main() { let atlas_width = glyph_cache.width() as u32; let atlas_height = glyph_cache.height() as u32; - text_uniforms.write(TextUniforms { + text_uniform_buffer.write(TextUniforms { screen_width: width, screen_height: height, atlas_width, @@ -632,8 +337,9 @@ pub fn main() { }); glyph_instance_buffer.write_slice(&glyph_instances); + // If the atlas has been updated, we need to upload it to the GPU if let Some((cached_glyphs, texture)) = glyph_cache.update_atlas() { - cached_glyph_buffer.write_slice(cached_glyphs); + glyph_buffer.write_slice(cached_glyphs); // upload atlas { @@ -739,109 +445,38 @@ pub fn main() { ); // Render basic stuff. - { - device.cmd_set_pipeline(&mut cmd_buffer, basic_pipeline.pipeline); - - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - basic_pipeline.uniforms_bind_group_layout, - 0, - &[Bind { - binding: 0, - array_element: 0, - typed: TypedBind::UniformBuffer(&[basic_uniforms.buffer()]), - }], - ); - - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - basic_pipeline.storage_bind_group_layout, - 1, - &[ - Bind { - binding: 0, - array_element: 0, - typed: TypedBind::StorageBuffer(&[blåhaj_vertex_buffer]), - }, - Bind { - binding: 1, - array_element: 0, - typed: TypedBind::StorageBuffer(&[basic_transforms.buffer()]), - }, - Bind { - binding: 2, - array_element: 0, - typed: TypedBind::Sampler(&[basic_sampler]), - }, - Bind { - binding: 3, - array_element: 0, - typed: TypedBind::Image(&[(ImageLayout::Optimal, blåhaj_image)]), - }, - ], - ); - - device.cmd_set_index_buffer(&mut cmd_buffer, blåhaj_index_buffer, 0, IndexType::U16); + basic_pipeline.bind( + device.as_ref(), + &frame, + &mut cmd_buffer, + basic_uniform_buffer.buffer(), + blåhaj_vertex_buffer, + blåhaj_index_buffer, + basic_transform_buffer.buffer(), + blåhaj_image, + ); - device.cmd_draw_indexed( - &mut cmd_buffer, - blåhaj_indices.len() as u32, - shark_transforms.len() as u32, - 0, - 0, - 0, - ); - } + device.cmd_draw_indexed( + &mut cmd_buffer, + blåhaj_indices.len() as u32, + shark_transforms.len() as u32, + 0, + 0, + 0, + ); // Render text stuff. - { - device.cmd_set_pipeline(&mut cmd_buffer, text_pipeline.pipeline); - - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - text_pipeline.uniforms_bind_group_layout, - 0, - &[Bind { - binding: 0, - array_element: 0, - typed: TypedBind::UniformBuffer(&[text_uniforms.buffer()]), - }], - ); - - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - basic_pipeline.storage_bind_group_layout, - 1, - &[ - Bind { - binding: 0, - array_element: 0, - typed: TypedBind::StorageBuffer(&[cached_glyph_buffer.buffer()]), - }, - Bind { - binding: 1, - array_element: 0, - typed: TypedBind::StorageBuffer(&[glyph_instance_buffer.buffer()]), - }, - Bind { - binding: 2, - array_element: 0, - typed: TypedBind::Sampler(&[text_sampler]), - }, - Bind { - binding: 3, - array_element: 0, - typed: TypedBind::Image(&[(ImageLayout::Optimal, glyph_atlas)]), - }, - ], - ); + text_pipeline.bind( + device.as_ref(), + &frame, + &mut cmd_buffer, + text_uniform_buffer.buffer(), + 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, 4, glyph_instances.len() as u32, 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 new file mode 100644 index 0000000..1b7575c --- /dev/null +++ b/bins/narcissus/src/mapped_buffer.rs @@ -0,0 +1,65 @@ +use narcissus_gpu::{Buffer, BufferDesc, BufferUsageFlags, Device, MemoryLocation}; + +use crate::Blittable; + +pub struct MappedBuffer<'a> { + device: &'a dyn Device, + buffer: Buffer, + slice: &'a mut [u8], +} + +impl<'a> MappedBuffer<'a> { + pub fn new(device: &'a dyn Device, usage: BufferUsageFlags, len: usize) -> Self { + let buffer = device.create_buffer(&BufferDesc { + location: MemoryLocation::HostMapped, + usage, + size: len, + }); + unsafe { + let ptr = device.map_buffer(buffer); + let slice = std::slice::from_raw_parts_mut(ptr, len); + Self { + device, + buffer, + slice, + } + } + } + + pub fn buffer(&self) -> Buffer { + self.buffer + } + + pub fn write(&mut self, value: T) + where + T: Blittable, + { + unsafe { + let src = std::slice::from_raw_parts( + &value as *const T as *const u8, + std::mem::size_of::(), + ); + self.slice.copy_from_slice(src) + } + } + + pub fn write_slice(&mut self, values: &[T]) + where + T: Blittable, + { + unsafe { + let len = values.len() * std::mem::size_of::(); + let src = std::slice::from_raw_parts(values.as_ptr() as *const u8, len); + self.slice[..len].copy_from_slice(src) + } + } +} + +impl<'a> Drop for MappedBuffer<'a> { + fn drop(&mut self) { + // Safety: Make sure we don't have the slice outlive the mapping. + unsafe { + self.device.unmap_buffer(self.buffer); + } + } +} diff --git a/bins/narcissus/src/pipelines/basic.rs b/bins/narcissus/src/pipelines/basic.rs index 47fa486..bd94ef0 100644 --- a/bins/narcissus/src/pipelines/basic.rs +++ b/bins/narcissus/src/pipelines/basic.rs @@ -1,16 +1,39 @@ use narcissus_core::{cstr, default, include_bytes_align}; use narcissus_gpu::{ - BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, - CompareOp, CullingMode, Device, FrontFace, GraphicsPipelineDesc, GraphicsPipelineLayout, - ImageFormat, Pipeline, PolygonMode, ShaderDesc, ShaderStageFlags, Topology, + Bind, BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, + Buffer, CmdBuffer, CompareOp, CullingMode, Device, Frame, FrontFace, GraphicsPipelineDesc, + GraphicsPipelineLayout, Image, ImageFormat, ImageLayout, IndexType, Pipeline, PolygonMode, + Sampler, SamplerAddressMode, SamplerDesc, SamplerFilter, ShaderDesc, ShaderStageFlags, + Topology, TypedBind, }; +use narcissus_maths::Mat4; + +use crate::Blittable; const VERT_SPV: &'static [u8] = include_bytes_align!(4, "../shaders/basic.vert.spv"); const FRAG_SPV: &'static [u8] = include_bytes_align!(4, "../shaders/basic.frag.spv"); +#[allow(unused)] +#[repr(C)] +pub struct BasicUniforms { + pub clip_from_model: Mat4, +} + +#[allow(unused)] +#[repr(C)] +pub struct Vertex { + pub position: [f32; 4], + pub normal: [f32; 4], + pub texcoord: [f32; 4], +} + +unsafe impl Blittable for BasicUniforms {} +unsafe impl Blittable for Vertex {} + pub struct BasicPipeline { pub uniforms_bind_group_layout: BindGroupLayout, pub storage_bind_group_layout: BindGroupLayout, + pub sampler: Sampler, pub pipeline: Pipeline, } @@ -54,6 +77,15 @@ impl BasicPipeline { ], }); + let sampler = device.create_sampler(&SamplerDesc { + filter: SamplerFilter::Point, + address_mode: SamplerAddressMode::Clamp, + compare_op: None, + mip_lod_bias: 0.0, + min_lod: 0.0, + max_lod: 1000.0, + }); + let pipeline = device.create_graphics_pipeline(&GraphicsPipelineDesc { vertex_shader: ShaderDesc { entry: cstr!("main"), @@ -86,7 +118,65 @@ impl BasicPipeline { Self { uniforms_bind_group_layout, storage_bind_group_layout, + sampler, pipeline, } } + + pub fn bind( + &self, + device: &dyn Device, + frame: &Frame, + cmd_buffer: &mut CmdBuffer, + uniform_buffer: Buffer, + vertex_buffer: Buffer, + index_buffer: Buffer, + transform_buffer: Buffer, + texture: Image, + ) { + device.cmd_set_pipeline(cmd_buffer, self.pipeline); + + device.cmd_set_bind_group( + frame, + cmd_buffer, + self.uniforms_bind_group_layout, + 0, + &[Bind { + binding: 0, + array_element: 0, + typed: TypedBind::UniformBuffer(&[uniform_buffer]), + }], + ); + + device.cmd_set_bind_group( + frame, + cmd_buffer, + self.storage_bind_group_layout, + 1, + &[ + Bind { + binding: 0, + array_element: 0, + typed: TypedBind::StorageBuffer(&[vertex_buffer]), + }, + Bind { + binding: 1, + array_element: 0, + typed: TypedBind::StorageBuffer(&[transform_buffer]), + }, + Bind { + binding: 2, + array_element: 0, + typed: TypedBind::Sampler(&[self.sampler]), + }, + Bind { + binding: 3, + array_element: 0, + typed: TypedBind::Image(&[(ImageLayout::Optimal, texture)]), + }, + ], + ); + + device.cmd_set_index_buffer(cmd_buffer, index_buffer, 0, IndexType::U16); + } } diff --git a/bins/narcissus/src/pipelines/mod.rs b/bins/narcissus/src/pipelines/mod.rs index ecc2c49..3a2f9d9 100644 --- a/bins/narcissus/src/pipelines/mod.rs +++ b/bins/narcissus/src/pipelines/mod.rs @@ -2,4 +2,9 @@ mod basic; mod text; pub use basic::BasicPipeline; +pub use basic::BasicUniforms; +pub use basic::Vertex; + +pub use text::GlyphInstance; pub use text::TextPipeline; +pub use text::TextUniforms; diff --git a/bins/narcissus/src/pipelines/text.rs b/bins/narcissus/src/pipelines/text.rs index 203011b..48e5f71 100644 --- a/bins/narcissus/src/pipelines/text.rs +++ b/bins/narcissus/src/pipelines/text.rs @@ -1,36 +1,53 @@ use narcissus_core::{cstr, default, include_bytes_align}; +use narcissus_font::CachedGlyphIndex; use narcissus_gpu::{ - BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, - CompareOp, CullingMode, Device, FrontFace, GraphicsPipelineDesc, GraphicsPipelineLayout, - ImageFormat, Pipeline, PolygonMode, ShaderDesc, ShaderStageFlags, Topology, + Bind, BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, + Buffer, CmdBuffer, CompareOp, CullingMode, Device, Frame, FrontFace, GraphicsPipelineDesc, + GraphicsPipelineLayout, Image, ImageFormat, ImageLayout, Pipeline, PolygonMode, Sampler, + SamplerAddressMode, SamplerDesc, SamplerFilter, ShaderDesc, ShaderStageFlags, Topology, + TypedBind, }; +use crate::Blittable; + const VERT_SPV: &'static [u8] = include_bytes_align!(4, "../shaders/text.vert.spv"); const FRAG_SPV: &'static [u8] = include_bytes_align!(4, "../shaders/text.frag.spv"); +#[allow(unused)] +#[repr(C)] +pub struct TextUniforms { + pub screen_width: u32, + pub screen_height: u32, + pub atlas_width: u32, + pub atlas_height: u32, +} + +#[allow(unused)] +#[repr(C)] +pub struct GlyphInstance { + pub cached_glyph_index: CachedGlyphIndex, + pub x: f32, + pub y: f32, + pub color: u32, +} + +unsafe impl Blittable for TextUniforms {} +unsafe impl Blittable for GlyphInstance {} + pub struct TextPipeline { - pub uniforms_bind_group_layout: BindGroupLayout, - pub storage_bind_group_layout: BindGroupLayout, - pub pipeline: Pipeline, + bind_group_layout: BindGroupLayout, + sampler: Sampler, + pipeline: Pipeline, } impl TextPipeline { pub fn new(device: &dyn Device) -> Self { - let uniforms_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDesc { - entries: &[BindGroupLayoutEntryDesc { - slot: 0, - stages: ShaderStageFlags::ALL, - binding_type: BindingType::UniformBuffer, - count: 1, - }], - }); - - let storage_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDesc { + let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDesc { entries: &[ BindGroupLayoutEntryDesc { slot: 0, stages: ShaderStageFlags::ALL, - binding_type: BindingType::StorageBuffer, + binding_type: BindingType::UniformBuffer, count: 1, }, BindGroupLayoutEntryDesc { @@ -42,18 +59,33 @@ impl TextPipeline { BindGroupLayoutEntryDesc { slot: 2, stages: ShaderStageFlags::ALL, - binding_type: BindingType::Sampler, + binding_type: BindingType::StorageBuffer, count: 1, }, BindGroupLayoutEntryDesc { slot: 3, stages: ShaderStageFlags::ALL, + binding_type: BindingType::Sampler, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 4, + stages: ShaderStageFlags::ALL, binding_type: BindingType::Image, count: 1, }, ], }); + let sampler = device.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 = device.create_graphics_pipeline(&GraphicsPipelineDesc { vertex_shader: ShaderDesc { entry: cstr!("main"), @@ -63,7 +95,7 @@ impl TextPipeline { entry: cstr!("main"), code: FRAG_SPV, }, - bind_group_layouts: &[uniforms_bind_group_layout, storage_bind_group_layout], + bind_group_layouts: &[bind_group_layout], layout: GraphicsPipelineLayout { color_attachment_formats: &[ImageFormat::BGRA8_SRGB], depth_attachment_format: Some(ImageFormat::DEPTH_F32), @@ -84,9 +116,55 @@ impl TextPipeline { }); Self { - uniforms_bind_group_layout, - storage_bind_group_layout, + bind_group_layout, + sampler, pipeline, } } + + pub fn bind( + &self, + device: &dyn Device, + frame: &Frame, + cmd_buffer: &mut CmdBuffer, + uniforms: Buffer, + cached_glyphs: Buffer, + glyph_instances: Buffer, + atlas: Image, + ) { + device.cmd_set_pipeline(cmd_buffer, self.pipeline); + device.cmd_set_bind_group( + frame, + cmd_buffer, + self.bind_group_layout, + 0, + &[ + Bind { + binding: 0, + array_element: 0, + typed: TypedBind::UniformBuffer(&[uniforms]), + }, + Bind { + binding: 1, + array_element: 0, + typed: TypedBind::StorageBuffer(&[cached_glyphs]), + }, + Bind { + binding: 2, + array_element: 0, + typed: TypedBind::StorageBuffer(&[glyph_instances]), + }, + Bind { + binding: 3, + array_element: 0, + typed: TypedBind::Sampler(&[self.sampler]), + }, + Bind { + binding: 4, + 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 a946192..0163eda 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 = 1, binding = 2) uniform sampler texSampler; -layout(set = 1, binding = 3) uniform texture2D tex; +layout(set = 0, binding = 3) uniform sampler texSampler; +layout(set = 0, binding = 4) 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 d15569a0af9b1a3b4b06ecd5d158a0196802a9ec..c54b72753fa2755e08fda86ebadd2ef387a0872d 100644 GIT binary patch delta 60 xcmX@Xc7km}gCPS01A{vQ3j;R;1A`(10|N_`&(FZXpahac;)CRwH*TEH1OV1H22KC~ delta 60 zcmX@Xc7km}gCQdW1A{vQ3j;R;1A`(10|PUZ&(FZXpv1ty0FwvtLGnx+H%?~)0M=Fp AP5=M^ diff --git a/bins/narcissus/src/shaders/text.vert.glsl b/bins/narcissus/src/shaders/text.vert.glsl index 44519e5..146096e 100644 --- a/bins/narcissus/src/shaders/text.vert.glsl +++ b/bins/narcissus/src/shaders/text.vert.glsl @@ -26,11 +26,11 @@ layout(set = 0, binding = 0) uniform uniformBuffer { uint atlasHeight; }; -layout(std430, set = 1, binding = 0) readonly buffer glyphBuffer { +layout(std430, set = 0, binding = 1) readonly buffer glyphBuffer { CachedGlyph cachedGlyphs[]; }; -layout(std430, set = 1, binding = 1) readonly buffer glyphInstanceBuffer { +layout(std430, set = 0, binding = 2) readonly buffer glyphInstanceBuffer { GlyphInstance glyphInstances[]; }; diff --git a/bins/narcissus/src/shaders/text.vert.spv b/bins/narcissus/src/shaders/text.vert.spv index 5a948e74a70fd7372b0ec74488d6263c5914f252..ed9eb1ce047c2bebecc5218d1c9b6250a55cebbd 100644 GIT binary patch literal 3076 zcmZve+pAS&6vfBhd+mM9<0bPN=utB@Q=|urP)vQOBPD7G74#5-c+JujdWy6QD4H5w z)aY)$SWlI&YGri!i+l;>H}@E}*tp=Z);H#D%=wM)+o#uEHl{&GXTo7C2_e^8b31>J>(A;^F*= zxn#Xmx@V^!IIniTh4qkjDO24ntki66DlY-WWG{mw8CzOj*iwz`#jsd|-< zI&;eW6m%Lxri&+H|8#~MnL&FnF=1kM#Yhzloi%Mvr-p%$`1}xyt*xP)+FtA29NBmf?YpGM5*uk*s%FD_OtN zn#+F2laqZz!|5|`>Kt-T?DWAJO~VuWVSf{o`J?gd7aUL5ex2AvpJ!bcy{6BP>`$NP zYzKoEdQJ>B{-}Gwy)s z_d-9Z@%OOCruh6L6$bv4`ZXtd1{boJs^+s!*yAwTc(V(^r3;;=&L(#y*o*4d-u&!4mj{gb{9=r~3ihip_BXNDjlmxtXm@$dQN@GY z@IcEeY=Z}9ZKbi~x?wCl(9KO_;Q@BbSewUh#^4dYzZ+x5!TvDD?@zGX#>gG)Ph-qt zr}ucBR5|evw%!>2!8S`(r|tKwv7z5soz3T@%1I7l@OehP+IMV6tkvhGnoZW*J%iNS zA(an!d|r}jCh>XMKH%|r)$@(dYf@q43AWc5aXZa>pHwx;Nen*lMxWTWs9$HFf10=t z&?6dPzHdr}hrlz)Co8N^K z{y&cT+^1DZA=!h^{`B3XjIo*JsAiI5XxR z`dlhsY}{4cD{$oQx#atdxiWXw_@{?2>vw$ETYC6PDm-)k+H>Z2A#?slDm-)kRw^tp z-#Lcgj>LQ~6`q(YQem0-4^rg;&#bOW#RnVwxm&@eX;8gi^PSI`;C|Vm2MYgDDjRmr UKJ((?%#(*Z8K3{u(bMYaE-qTV#{d8T literal 3076 zcmZve$&Zy)5XFDpZ)jx{5zqu$5N8N+fyQVQS!koS(uj!*6C(}_j3_FOi3!AzVB&(E zL!5_=x^md!9EZQijfwfyJB2SVeo5#1>fEZjr>f4quLtux7Z}g6nzj7JGcb&6TDHBD-mV54A{Ozmr(`gLu_E(G&i{@bAZoWTKx zEQW8#v~3JK2ZPFI{3eFy zXW+A$LCwitE;24XJ4s;D2=qK4(iE0<+&^N zhV;z%i?gPm@!INLf7dr9*JSTiTo!(E?7yy|-;1m&E%ti9d%fS*rB>Cgb=gmC_q{y( zipfjcI=b#F^KI-FhFqII+I#DC&+9VRtqe;q`iPm|vn&sFq`eedALpPZn|HtUyC?c^ zU0&L1)_q-H&bKk$@0FSUKOCR#x0>$x$myw?&&hq(bu7HpXV>LLtG{(Un{_dHtKW`s z%|CfP7u~z=zS`c)>fgNXdtyo#uVl?!UB~HQ=6g5a_RD-p#1|eot-I z*L(gk*>hj}{0W7L-^8f;YVAJe`kVQ>7u~}d?oRz}zZUzpXx+P0J@02+THc)BD9?av z`l;R-X70v*3uZ4? zGUL5aIrVSsmSFlfb_dix(|&gboA@o%*}MxXr#Xz#=XM4)ch~kasxxS>cQOJuSfF#ko$Yz zJQn?m>*HB^Jou^PelnQ(&ICIOg}d(?!JdY~J!3te7oqUhigS7?m|9t3q0aZqP`<8t z2Y5c^=ox-B_k?}5@7nrzPhW@9WZNgBz3#&)C?E0mIRmA|_IWct;_dTRblpB@p)m6_ z_I5Diti`+h9jHCcX^cMd_FizUd;dNZHr@LVpzzkmhtY@kb?f6JD7<-o425;fCyDW{ z?3hoX@Q(Qm3hO#Phsq<~GyVdKk8ASxZWUX@xMuccegixc-Y*Bau<);-)?D}OJ7*uy Qym`En_4$W6Zf1^u0n=i(mjD0& -- 2.49.0