From 94428013a07c0569638e7f0b76093848188ee899 Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sun, 26 Feb 2023 19:23:07 +0100 Subject: [PATCH] Add support for basic text drawing Move pipelines out of main.rs and into their own module. Add new text pipeline. Add new text shader. Update main.rs to draw nice text. :) --- bins/narcissus/Cargo.toml | 1 + bins/narcissus/src/fonts.rs | 36 ++ bins/narcissus/src/main.rs | 459 +++++++++++++++------ bins/narcissus/src/pipelines/basic.rs | 92 +++++ bins/narcissus/src/pipelines/mod.rs | 5 + bins/narcissus/src/pipelines/text.rs | 92 +++++ bins/narcissus/src/shaders/basic.frag.glsl | 2 +- bins/narcissus/src/shaders/basic.frag.spv | Bin 1036 -> 1036 bytes bins/narcissus/src/shaders/basic.vert.glsl | 16 +- bins/narcissus/src/shaders/basic.vert.spv | Bin 2464 -> 2464 bytes bins/narcissus/src/shaders/build.sh | 2 + bins/narcissus/src/shaders/text.frag.glsl | 13 + bins/narcissus/src/shaders/text.frag.spv | Bin 0 -> 840 bytes bins/narcissus/src/shaders/text.vert.glsl | 67 +++ bins/narcissus/src/shaders/text.vert.spv | Bin 0 -> 3076 bytes 15 files changed, 643 insertions(+), 142 deletions(-) create mode 100644 bins/narcissus/src/fonts.rs create mode 100644 bins/narcissus/src/pipelines/basic.rs create mode 100644 bins/narcissus/src/pipelines/mod.rs create mode 100644 bins/narcissus/src/pipelines/text.rs create mode 100644 bins/narcissus/src/shaders/text.frag.glsl create mode 100644 bins/narcissus/src/shaders/text.frag.spv create mode 100644 bins/narcissus/src/shaders/text.vert.glsl create mode 100644 bins/narcissus/src/shaders/text.vert.spv diff --git a/bins/narcissus/Cargo.toml b/bins/narcissus/Cargo.toml index 4768c83..c17ab4c 100644 --- a/bins/narcissus/Cargo.toml +++ b/bins/narcissus/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] narcissus-core = { path = "../../libs/narcissus-core" } +narcissus-font = { path = "../../libs/narcissus-font" } narcissus-maths = { path = "../../libs/narcissus-maths" } narcissus-image = { path = "../../libs/narcissus-image" } narcissus-app = { path = "../../libs/narcissus-app" } diff --git a/bins/narcissus/src/fonts.rs b/bins/narcissus/src/fonts.rs new file mode 100644 index 0000000..0b107ce --- /dev/null +++ b/bins/narcissus/src/fonts.rs @@ -0,0 +1,36 @@ +use narcissus_font::{Font, FontCollection}; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum FontFamily { + NotoSansJapanese, + RobotoRegular, +} + +pub struct Fonts<'a> { + noto_sans_japanese: Font<'a>, + roboto_regular: Font<'a>, +} + +impl<'a> Fonts<'a> { + pub fn new() -> Self { + // Safety: Safe because Roboto-Regular.ttf is a valid ttf font embedded in the application. + let roboto_regular = + unsafe { Font::from_bytes(include_bytes!("fonts/Roboto-Regular.ttf")) }; + let noto_sans_japanese = + unsafe { Font::from_bytes(include_bytes!("fonts/NotoSansJP-Medium.otf")) }; + Self { + noto_sans_japanese, + roboto_regular, + } + } +} + +impl<'a> FontCollection<'a> for Fonts<'a> { + type Family = FontFamily; + fn font(&self, family: Self::Family) -> &Font<'a> { + match family { + FontFamily::NotoSansJapanese => &self.noto_sans_japanese, + FontFamily::RobotoRegular => &self.roboto_regular, + } + } +} diff --git a/bins/narcissus/src/main.rs b/bins/narcissus/src/main.rs index 1f18062..f8a0226 100644 --- a/bins/narcissus/src/main.rs +++ b/bins/narcissus/src/main.rs @@ -1,25 +1,34 @@ use std::{path::Path, time::Instant}; use narcissus_app::{create_app, Event, Key, WindowDesc}; -use narcissus_core::{cstr, default, include_bytes_align, obj, rand::Pcg64}; +use narcissus_core::{default, obj, rand::Pcg64}; +use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache, Oversample}; use narcissus_gpu::{ - create_device, Access, Bind, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, - BlendMode, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, CompareOp, - CullingMode, Device, Extent2d, Extent3d, FrontFace, GraphicsPipelineDesc, - GraphicsPipelineLayout, Image, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, + 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, PolygonMode, RenderingAttachment, RenderingDesc, SamplerAddressMode, SamplerDesc, - SamplerFilter, Scissor, ShaderDesc, ShaderStageFlags, StoreOp, ThreadToken, Topology, - TypedBind, Viewport, + 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, }; +use crate::{ + fonts::{FontFamily, Fonts}, + pipelines::{BasicPipeline, TextPipeline}, +}; + +mod fonts; +mod pipelines; + const MAX_SHARKS: usize = 262_144; const NUM_SHARKS: usize = 50; +const MAX_GLYPH_INSTANCES: usize = 262_144; +const MAX_GLYPHS: usize = 1024; + /// Marker trait indicates it's safe to convert a given type directly to an array of bytes. /// /// # Safety @@ -28,19 +37,44 @@ const NUM_SHARKS: usize = 50; unsafe trait Blittable: Sized {} #[allow(unused)] -struct Uniforms { +#[repr(C)] +struct BasicUniforms { clip_from_model: Mat4, } -unsafe impl Blittable for Uniforms {} +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 {} @@ -297,75 +331,11 @@ pub fn main() { let thread_token = ThreadToken::new(); - let vert_spv = include_bytes_align!(4, "shaders/basic.vert.spv"); - let frag_spv = include_bytes_align!(4, "shaders/basic.frag.spv"); - - let uniform_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 { - entries: &[ - BindGroupLayoutEntryDesc { - slot: 0, - stages: ShaderStageFlags::ALL, - binding_type: BindingType::StorageBuffer, - count: 1, - }, - BindGroupLayoutEntryDesc { - slot: 1, - stages: ShaderStageFlags::ALL, - binding_type: BindingType::StorageBuffer, - count: 1, - }, - BindGroupLayoutEntryDesc { - slot: 2, - stages: ShaderStageFlags::ALL, - binding_type: BindingType::Sampler, - count: 1, - }, - BindGroupLayoutEntryDesc { - slot: 3, - stages: ShaderStageFlags::ALL, - binding_type: BindingType::Image, - count: 1, - }, - ], - }); + let basic_pipeline = BasicPipeline::new(device.as_ref()); + let text_pipeline = TextPipeline::new(device.as_ref()); - let pipeline = device.create_graphics_pipeline(&GraphicsPipelineDesc { - vertex_shader: ShaderDesc { - entry: cstr!("main"), - code: vert_spv, - }, - fragment_shader: ShaderDesc { - entry: cstr!("main"), - code: frag_spv, - }, - bind_group_layouts: &[uniform_bind_group_layout, storage_bind_group_layout], - layout: GraphicsPipelineLayout { - color_attachment_formats: &[ImageFormat::BGRA8_SRGB], - depth_attachment_format: Some(ImageFormat::DEPTH_F32), - stencil_attachment_format: None, - }, - topology: Topology::Triangles, - polygon_mode: PolygonMode::Fill, - culling_mode: CullingMode::Back, - front_face: FrontFace::CounterClockwise, - blend_mode: BlendMode::Opaque, - depth_bias: None, - depth_compare_op: CompareOp::GreaterOrEqual, - depth_test_enable: true, - depth_write_enable: true, - stencil_test_enable: false, - stencil_back: default(), - stencil_front: default(), - }); + let fonts = Fonts::new(); + let mut glyph_cache = GlyphCache::new(&fonts, 512, 512, Oversample::X2, Oversample::X2); let blåhaj_image = load_image("bins/narcissus/data/blåhaj.png"); let (blåhaj_vertices, blåhaj_indices) = load_obj("bins/narcissus/data/blåhaj.obj"); @@ -390,19 +360,19 @@ pub fn main() { blåhaj_image.as_slice(), ); - let mut uniforms = MappedBuffer::new( + let mut basic_uniforms = MappedBuffer::new( device.as_ref(), BufferUsageFlags::UNIFORM, - std::mem::size_of::(), + std::mem::size_of::(), ); - let mut transforms = MappedBuffer::new( + let mut basic_transforms = MappedBuffer::new( device.as_ref(), BufferUsageFlags::STORAGE, std::mem::size_of::() * MAX_SHARKS, ); - let sampler = device.create_sampler(&SamplerDesc { + let basic_sampler = device.create_sampler(&SamplerDesc { filter: SamplerFilter::Point, address_mode: SamplerAddressMode::Clamp, compare_op: None, @@ -411,6 +381,33 @@ pub fn main() { max_lod: 1000.0, }); + let mut text_uniforms = MappedBuffer::new( + device.as_ref(), + BufferUsageFlags::UNIFORM, + std::mem::size_of::(), + ); + + let mut glyph_instance_buffer = MappedBuffer::new( + device.as_ref(), + BufferUsageFlags::STORAGE, + std::mem::size_of::() * MAX_GLYPH_INSTANCES, + ); + + let mut cached_glyph_buffer = MappedBuffer::new( + device.as_ref(), + BufferUsageFlags::STORAGE, + std::mem::size_of::() * MAX_GLYPHS, + ); + + 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(); @@ -514,7 +511,7 @@ pub fn main() { transform.matrix *= Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(0.002 * direction)) } - transforms.write_slice(&shark_transforms); + basic_transforms.write_slice(&shark_transforms); let (s, c) = sin_cos_pi_f32(frame_start * 0.2); let camera_height = c * 8.0; @@ -526,52 +523,150 @@ 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; - uniforms.write(Uniforms { clip_from_model }); + basic_uniforms.write(BasicUniforms { clip_from_model }); - device.cmd_set_pipeline(&mut cmd_buffer, pipeline); + // 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. ½½½½"; + let line1 = "加盟国は、国際連合と協力して"; - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - uniform_bind_group_layout, - 0, - &[Bind { - binding: 0, - array_element: 0, - typed: TypedBind::UniformBuffer(&[uniforms.buffer()]), - }], - ); + let mut glyph_instances = Vec::new(); + let mut glyphs = Vec::new(); - device.cmd_set_bind_group( - &frame, - &mut cmd_buffer, - 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(&[transforms.buffer()]), - }, - Bind { - binding: 2, - array_element: 0, - typed: TypedBind::Sampler(&[sampler]), - }, - Bind { - binding: 3, - array_element: 0, - typed: TypedBind::Image(&[(ImageLayout::Optimal, blåhaj_image)]), - }, - ], - ); + let mut y = 0.0; + + let mut rng = Pcg64::new(); + + for line in 0..100 { + let font_family = if line & 1 == 0 { + FontFamily::RobotoRegular + } else { + FontFamily::NotoSansJapanese + }; + let font = fonts.font(font_family); + + let v_metrics = font.vertical_metrics(); + let font_scale = font.scale_for_pixel_height(if line & 1 == 0 { 25.0 } else { 40.0 }); + + y += v_metrics.ascent * font_scale - v_metrics.descent * font_scale + + v_metrics.line_gap * font_scale; + y = y.trunc(); + + let mut x = 0.0; + + glyphs.clear(); + + let text = if line & 1 == 0 { line0 } else { line1 }; + + glyphs.extend(text.chars().map(|c| { + font.glyph_id(c) + .unwrap_or_else(|| font.glyph_id('□').unwrap()) + })); + + let mut prev_glyph_index = None; + for glyph_index in glyphs.iter().copied() { + let cached_glyph_index = + glyph_cache.cache_glyph(font_family, glyph_index, font_scale); + + if let Some(prev_glyph_index) = prev_glyph_index { + x += font.kerning_advance(prev_glyph_index, glyph_index) * font_scale; + } + + const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]; + + glyph_instances.push(GlyphInstance { + cached_glyph_index, + x, + y, + color: COLOR_SERIES[rng.next_bound_u64(4) as usize], + }); + + let h_metrics = font.horizontal_metrics(glyph_index); + x += h_metrics.advance_width * font_scale; + prev_glyph_index = Some(glyph_index); + } + } + + let atlas_width = glyph_cache.width() as u32; + let atlas_height = glyph_cache.height() as u32; - device.cmd_set_index_buffer(&mut cmd_buffer, blåhaj_index_buffer, 0, IndexType::U16); + let (cached_glyphs, texture) = glyph_cache.update_atlas(); + + text_uniforms.write(TextUniforms { + screen_width: width, + screen_height: height, + atlas_width, + atlas_height, + }); + cached_glyph_buffer.write_slice(cached_glyphs); + glyph_instance_buffer.write_slice(&glyph_instances); + + // upload atlas + let glyph_atlas = { + let width = atlas_width; + let height = atlas_height; + let data = texture; + + let buffer = + create_buffer_with_data(device.as_ref(), BufferUsageFlags::TRANSFER_SRC, data); + + let image = device.create_image(&ImageDesc { + location: MemoryLocation::Device, + usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + dimension: ImageDimension::Type2d, + format: ImageFormat::R8_UNORM, + initial_layout: ImageLayout::Optimal, + width, + height, + depth: 1, + layer_count: 1, + mip_levels: 1, + }); + + 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.destroy_buffer(&frame, buffer); + + image + }; device.cmd_begin_rendering( &mut cmd_buffer, @@ -619,17 +714,115 @@ pub fn main() { }], ); - device.cmd_draw_indexed( - &mut cmd_buffer, - blåhaj_indices.len() as u32, - shark_transforms.len() as u32, - 0, - 0, - 0, - ); + // 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); + + 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)]), + }, + ], + ); + + device.cmd_draw(&mut cmd_buffer, 4, glyph_instances.len() as u32, 0, 0); + } device.cmd_end_rendering(&mut cmd_buffer); + device.destroy_image(&frame, glyph_atlas); + device.submit(&frame, cmd_buffer); device.end_frame(frame); diff --git a/bins/narcissus/src/pipelines/basic.rs b/bins/narcissus/src/pipelines/basic.rs new file mode 100644 index 0000000..47fa486 --- /dev/null +++ b/bins/narcissus/src/pipelines/basic.rs @@ -0,0 +1,92 @@ +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, +}; + +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"); + +pub struct BasicPipeline { + pub uniforms_bind_group_layout: BindGroupLayout, + pub storage_bind_group_layout: BindGroupLayout, + pub pipeline: Pipeline, +} + +impl BasicPipeline { + 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 { + entries: &[ + BindGroupLayoutEntryDesc { + slot: 0, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::StorageBuffer, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 1, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::StorageBuffer, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 2, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::Sampler, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 3, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::Image, + count: 1, + }, + ], + }); + + let pipeline = device.create_graphics_pipeline(&GraphicsPipelineDesc { + vertex_shader: ShaderDesc { + entry: cstr!("main"), + code: VERT_SPV, + }, + fragment_shader: ShaderDesc { + entry: cstr!("main"), + code: FRAG_SPV, + }, + bind_group_layouts: &[uniforms_bind_group_layout, storage_bind_group_layout], + layout: GraphicsPipelineLayout { + color_attachment_formats: &[ImageFormat::BGRA8_SRGB], + depth_attachment_format: Some(ImageFormat::DEPTH_F32), + stencil_attachment_format: None, + }, + topology: Topology::Triangles, + polygon_mode: PolygonMode::Fill, + culling_mode: CullingMode::Back, + front_face: FrontFace::CounterClockwise, + blend_mode: BlendMode::Opaque, + depth_bias: None, + depth_compare_op: CompareOp::GreaterOrEqual, + depth_test_enable: true, + depth_write_enable: true, + stencil_test_enable: false, + stencil_back: default(), + stencil_front: default(), + }); + + Self { + uniforms_bind_group_layout, + storage_bind_group_layout, + pipeline, + } + } +} diff --git a/bins/narcissus/src/pipelines/mod.rs b/bins/narcissus/src/pipelines/mod.rs new file mode 100644 index 0000000..ecc2c49 --- /dev/null +++ b/bins/narcissus/src/pipelines/mod.rs @@ -0,0 +1,5 @@ +mod basic; +mod text; + +pub use basic::BasicPipeline; +pub use text::TextPipeline; diff --git a/bins/narcissus/src/pipelines/text.rs b/bins/narcissus/src/pipelines/text.rs new file mode 100644 index 0000000..203011b --- /dev/null +++ b/bins/narcissus/src/pipelines/text.rs @@ -0,0 +1,92 @@ +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, +}; + +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"); + +pub struct TextPipeline { + pub uniforms_bind_group_layout: BindGroupLayout, + pub storage_bind_group_layout: BindGroupLayout, + pub 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 { + entries: &[ + BindGroupLayoutEntryDesc { + slot: 0, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::StorageBuffer, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 1, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::StorageBuffer, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 2, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::Sampler, + count: 1, + }, + BindGroupLayoutEntryDesc { + slot: 3, + stages: ShaderStageFlags::ALL, + binding_type: BindingType::Image, + count: 1, + }, + ], + }); + + let pipeline = device.create_graphics_pipeline(&GraphicsPipelineDesc { + vertex_shader: ShaderDesc { + entry: cstr!("main"), + code: VERT_SPV, + }, + fragment_shader: ShaderDesc { + entry: cstr!("main"), + code: FRAG_SPV, + }, + bind_group_layouts: &[uniforms_bind_group_layout, storage_bind_group_layout], + layout: GraphicsPipelineLayout { + color_attachment_formats: &[ImageFormat::BGRA8_SRGB], + depth_attachment_format: Some(ImageFormat::DEPTH_F32), + stencil_attachment_format: None, + }, + topology: Topology::TriangleStrip, + polygon_mode: PolygonMode::Fill, + culling_mode: CullingMode::None, + front_face: FrontFace::CounterClockwise, + blend_mode: BlendMode::Premultiplied, + depth_bias: None, + depth_compare_op: CompareOp::Always, + depth_test_enable: false, + depth_write_enable: false, + stencil_test_enable: false, + stencil_back: default(), + stencil_front: default(), + }); + + Self { + uniforms_bind_group_layout, + storage_bind_group_layout, + pipeline, + } + } +} diff --git a/bins/narcissus/src/shaders/basic.frag.glsl b/bins/narcissus/src/shaders/basic.frag.glsl index 5b23cbd..c09e330 100644 --- a/bins/narcissus/src/shaders/basic.frag.glsl +++ b/bins/narcissus/src/shaders/basic.frag.glsl @@ -11,4 +11,4 @@ void main() { float NdotL = max(dot(normal, vec3(0.0, 1.0, 0.0)), 0.1f); vec3 rgb = texture(sampler2D(tex, texSampler), vec2(texcoord.x, texcoord.y)).rgb; outColor = vec4(rgb * NdotL, 1.0); -} \ No newline at end of file +} diff --git a/bins/narcissus/src/shaders/basic.frag.spv b/bins/narcissus/src/shaders/basic.frag.spv index a297aed4d56b8d95aa8742e01b35bd86ea3a47cb..aabdd66eaf0b8deb0faff787824003c68c2fc62e 100644 GIT binary patch delta 18 ZcmeC-=;7dGW>RKnU}I$9-pI+!0st9>0yF>s delta 18 ZcmeC-=;7dGW>RKnU}I$9+Q`Yw0st9+0y6*r diff --git a/bins/narcissus/src/shaders/basic.vert.glsl b/bins/narcissus/src/shaders/basic.vert.glsl index f7f36ac..569def4 100644 --- a/bins/narcissus/src/shaders/basic.vert.glsl +++ b/bins/narcissus/src/shaders/basic.vert.glsl @@ -1,9 +1,5 @@ #version 460 -layout(set = 0, binding = 0) uniform uniformBuffer { - mat4 viewProj; -}; - struct VertexData { vec4 position; vec4 normal; @@ -14,6 +10,10 @@ struct TransformData { vec4 transform[3]; }; +layout(set = 0, binding = 0) uniform uniformBuffer { + mat4 viewProj; +}; + layout(std430, set = 1, binding = 0) readonly buffer vertexBuffer { VertexData vertices[]; }; @@ -22,8 +22,8 @@ layout(std430, set = 1, binding = 1) readonly buffer transformBuffer { TransformData transforms[]; }; -layout(location = 0) out vec2 texcoord; -layout(location = 1) out vec3 normal; +layout(location = 0) out vec2 outTexcoord; +layout(location = 1) out vec3 outNormal; void main() { TransformData td = transforms[gl_InstanceIndex]; @@ -39,6 +39,6 @@ void main() { vec4 posClip = transpose(viewProj) * vec4(posWorld, 1.0); gl_Position = posClip; - normal = vd.normal.xyz; - texcoord = vec2(vd.texcoord.x, 1.0 - vd.texcoord.y); + outNormal = vd.normal.xyz; + outTexcoord = vec2(vd.texcoord.x, 1.0 - vd.texcoord.y); } diff --git a/bins/narcissus/src/shaders/basic.vert.spv b/bins/narcissus/src/shaders/basic.vert.spv index f9260ed64f9501b8e21323e0c4a02fb9c386bb7f..f22e6089d9b0ae3f67572dd1cfddd5812c048728 100644 GIT binary patch delta 18 ZcmZ1=yg-Ud0}B8E delta 18 ZcmZ1=yg-UY0}22D diff --git a/bins/narcissus/src/shaders/build.sh b/bins/narcissus/src/shaders/build.sh index e870edb..4d379fc 100755 --- a/bins/narcissus/src/shaders/build.sh +++ b/bins/narcissus/src/shaders/build.sh @@ -3,5 +3,7 @@ pushd "${0%/*}" glslc --target-env=vulkan1.3 -O -fshader-stage=vert -o basic.vert.spv basic.vert.glsl glslc --target-env=vulkan1.3 -O -fshader-stage=frag -o basic.frag.spv basic.frag.glsl +glslc --target-env=vulkan1.3 -O -fshader-stage=vert -o text.vert.spv text.vert.glsl +glslc --target-env=vulkan1.3 -O -fshader-stage=frag -o text.frag.spv text.frag.glsl echo "built shaders" popd diff --git a/bins/narcissus/src/shaders/text.frag.glsl b/bins/narcissus/src/shaders/text.frag.glsl new file mode 100644 index 0000000..a946192 --- /dev/null +++ b/bins/narcissus/src/shaders/text.frag.glsl @@ -0,0 +1,13 @@ +#version 460 + +layout(set = 1, binding = 2) uniform sampler texSampler; +layout(set = 1, binding = 3) uniform texture2D tex; + +layout(location = 0) in vec2 texcoord; +layout(location = 1) in vec4 color; +layout(location = 0) out vec4 outColor; + +void main() { + float coverage = texture(sampler2D(tex, texSampler), vec2(texcoord.x, texcoord.y)).r; + outColor = color * coverage; +} diff --git a/bins/narcissus/src/shaders/text.frag.spv b/bins/narcissus/src/shaders/text.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..d15569a0af9b1a3b4b06ecd5d158a0196802a9ec GIT binary patch literal 840 zcmY+Cxk^M~428eB%ZQH4xbN$zD8gW&B8Y;OjfEm;E!bFSqtD^<+6eOHW?TZpa86E= zll-?VdPB5ZbQoiwCMGFp0Ug@$e|3F#-G6(3>K`2+su-sXp_&Oss33kXkI%0rI>Ey% z&;xdWE@fzjfGeudY(djM@g_uP@dW(Ki_I5VZ|}9nZs?u!7dum|$7zbF+oX*C_L!rj z4Qv353^NRY)&GqF-k@S66Kc;AyQ{H8!C3UG7*9^DPON`pnUYC}9`jU8=UhEGW9G+m zQ!92?eauoZm!6G-#Pflg`i0cG+aSN3ob%Rmo^R$n-@wfEZjr>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& literal 0 HcmV?d00001 -- 2.49.0