Re-organize the pipeline structures as well.
--- /dev/null
+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<P: AsRef<Path>>(path: P) -> (Vec<Vertex>, Vec<u16>) {
+ #[derive(Default)]
+ struct ObjVisitor {
+ positions: Vec<Vec3>,
+ normals: Vec<Vec3>,
+ texcoords: Vec<Vec2>,
+ 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<P: AsRef<Path>>(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<T>(
+ device: &dyn Device,
+ usage: BufferUsageFlags,
+ data: &[T],
+) -> Buffer
+where
+ T: Blittable,
+{
+ let len = data.len() * std::mem::size_of::<T>();
+ 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
+}
-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;
/// # 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<P: AsRef<Path>>(path: P) -> (Vec<Vertex>, Vec<u16>) {
- #[derive(Default)]
- struct ObjVisitor {
- positions: Vec<Vec3>,
- normals: Vec<Vec3>,
- texcoords: Vec<Vec2>,
- 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<P: AsRef<Path>>(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<T>(device: &dyn Device, usage: BufferUsageFlags, data: &[T]) -> Buffer
-where
- T: Blittable,
-{
- let len = data.len() * std::mem::size_of::<T>();
- 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<T>(&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::<T>(),
- );
- self.slice.copy_from_slice(src)
- }
- }
-
- pub fn write_slice<T>(&mut self, values: &[T])
- where
- T: Blittable,
- {
- unsafe {
- let len = values.len() * std::mem::size_of::<T>();
- 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();
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::<BasicUniforms>(),
);
- let mut basic_transforms = MappedBuffer::new(
+ let mut basic_transform_buffer = MappedBuffer::new(
device.as_ref(),
BufferUsageFlags::STORAGE,
std::mem::size_of::<Affine3>() * 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::<TextUniforms>(),
std::mem::size_of::<GlyphInstance>() * 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::<CachedGlyph>() * MAX_GLYPHS,
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();
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;
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. ½½½½";
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,
});
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
{
);
// 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);
--- /dev/null
+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<T>(&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::<T>(),
+ );
+ self.slice.copy_from_slice(src)
+ }
+ }
+
+ pub fn write_slice<T>(&mut self, values: &[T])
+ where
+ T: Blittable,
+ {
+ unsafe {
+ let len = values.len() * std::mem::size_of::<T>();
+ 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);
+ }
+ }
+}
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,
}
],
});
+ 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"),
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);
+ }
}
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;
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 {
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"),
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),
});
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)]),
+ },
+ ],
+ );
+ }
}
#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;
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[];
};