From b13a834475ca552eafc60fb806b4cf7f08958610 Mon Sep 17 00:00:00 2001 From: mitchellhansen Date: Wed, 11 Sep 2019 23:39:07 -0700 Subject: [PATCH] fiddling with text rendering --- Cargo.toml | 3 +- src/canvas.rs | 47 +++++++++++- src/canvas_buffer.rs | 52 ++++++++++++- src/canvas_text.rs | 177 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 6 +- 5 files changed, 274 insertions(+), 11 deletions(-) create mode 100644 src/canvas_text.rs diff --git a/Cargo.toml b/Cargo.toml index 27366eb5..b41d139a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,5 @@ shaderc = "0.5.0" shade_runner = {path = "../shade_runner"} winit = "0.19.1" #criterion = "0.3.0" -hprof = "0.1.3" \ No newline at end of file +hprof = "0.1.3" +rusttype = { version = "0.7", features = ["gpu_cache"] } \ No newline at end of file diff --git a/src/canvas.rs b/src/canvas.rs index d6133006..d0fd346e 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -2,12 +2,12 @@ use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState}; use std::collections::HashMap; use vulkano::buffer::{BufferAccess, BufferUsage, ImmutableBuffer, CpuAccessibleBuffer}; use std::sync::Arc; -use vulkano::format::{ClearValue, Format}; +use vulkano::format::{ClearValue, Format, R8Unorm}; use vulkano::framebuffer::{FramebufferAbstract, Framebuffer, RenderPass, RenderPassAbstract}; use vulkano::device::{Device, Queue}; use vulkano::instance::PhysicalDevice; use vulkano::image::immutable::ImmutableImage; -use vulkano::image::{Dimensions, ImageAccess, ImageDimensions, SwapchainImage, ImageUsage, AttachmentImage}; +use vulkano::image::{Dimensions, ImageAccess, ImageDimensions, SwapchainImage, ImageUsage, AttachmentImage, ImageLayout}; use vulkano::sampler::{Sampler, SamplerAddressMode, MipmapMode, Filter}; use vulkano::descriptor::DescriptorSet; use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; @@ -21,7 +21,7 @@ use vulkano::descriptor::descriptor::DescriptorDescTy::TexelBuffer; use crate::canvas_frame::CanvasFrame; use std::hash::Hash; use crate::canvas_shader::{CanvasShader, CanvasShaderHandle}; -use crate::canvas_buffer::{CanvasImage, CanvasTexture}; +use crate::canvas_buffer::{CanvasImage, CanvasTexture, CanvasText, CanvasTextCache}; use crate::vertex_3d::Vertex3D; /// A drawable object can be passed into a CanvasFrame to be rendered @@ -83,6 +83,7 @@ pub struct CanvasState { image_buffers: Vec>, texture_buffers: Vec>, shader_buffers: Vec>, + text_buffers: Vec>, // Hold onto the vertices we get from the Compu and Canvas Frames // When the run comes around, push the vertices to the GPU @@ -183,6 +184,7 @@ impl CanvasState { texture_buffers: vec![], shader_buffers: vec![], + text_buffers: vec![], colored_drawables: vec![], colored_vertex_buffer: vec![], textured_drawables: HashMap::default(), @@ -196,6 +198,45 @@ impl CanvasState { } } + /// Using the dimensions and suggested usage, load a CanvasImage and return it's handle + pub fn create_text_buffers(&mut self, dimensions: (u32, u32)) -> Arc { + + let handle = Arc::new(CanvasTextHandle { handle: self.text_buffers.len() as u32 }); + + let text = CanvasText { + handle: handle.clone(), + buffer: ImmutableImage::uninitialized( + self.device.clone(), + Dimensions::Dim2d { width: CACHE_WIDTH as u32, height: CACHE_HEIGHT as u32 }, + R8Unorm, + 1, + ImageUsage { + sampled: true, + transfer_destination: true, + .. ImageUsage::none() + }, + ImageLayout::General, + Some(self.queue.family()) + ).unwrap().0, + size: dimensions, + }; + + let text_cache = CanvasTextCache { + handle: handle.clone(), + buffer: CpuAccessibleBuffer::<[u8]>::from_iter( + self.device.clone(), + BufferUsage::all(), + cache_pixel_buffer.iter().cloned() + ).unwrap(), + size: dimensions, + }; + + self.text_buffers.push(Arc::new((text, text_cache))); + + handle + } + + /// Using the dimensions and suggested usage, load a CanvasImage and return it's handle pub fn create_image(&mut self, dimensions: (u32, u32), usage: ImageUsage) -> Arc { let handle = Arc::new(CanvasImageHandle { handle: self.image_buffers.len() as u32 }); diff --git a/src/canvas_buffer.rs b/src/canvas_buffer.rs index 86776a63..1546d02d 100644 --- a/src/canvas_buffer.rs +++ b/src/canvas_buffer.rs @@ -1,11 +1,12 @@ use crate::canvas::{CanvasTextureHandle, CanvasImageHandle}; use vulkano::image::{ImmutableImage, AttachmentImage}; use std::sync::Arc; -use vulkano::format::Format; +use vulkano::format::{Format, R8Unorm}; use crate::canvas_shader::CanvasShader; use vulkano::sampler::Sampler; use vulkano::descriptor::DescriptorSet; use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; +use vulkano::buffer::CpuAccessibleBuffer; #[derive(Clone)] pub struct CanvasTexture { @@ -17,8 +18,8 @@ pub struct CanvasTexture { impl CanvasTexture { pub fn get_descriptor_set(&self, - shader: Arc, - sampler: Arc) -> Box { + shader: Arc, + sampler: Arc) -> Box { let o: Box = Box::new( PersistentDescriptorSet::start( shader.clone().get_pipeline().clone(), 0, @@ -38,7 +39,7 @@ pub struct CanvasImage { impl CanvasImage { pub fn get_descriptor_set(&self, shader: Arc) - -> Box { + -> Box { let o: Box = Box::new( PersistentDescriptorSet::start( shader.clone().get_pipeline().clone(), 0, @@ -47,4 +48,47 @@ impl CanvasImage { .build().unwrap()); o } +} + +#[derive(Clone)] +pub struct CanvasTextCache { + pub(crate) handle: Arc, + pub(crate) buffer: Arc>, + pub(crate) size: (u32, u32), +} + +impl CanvasTextCache { + pub fn get_descriptor_set(&self, + shader: Arc, + sampler: Arc) -> Box { + let o: Box = Box::new( + PersistentDescriptorSet::start( + shader.clone().get_pipeline().clone(), 0, + ) + .add_sampled_image(self.buffer.clone(), sampler.clone()).unwrap() + .build().unwrap()); + o + } +} + + +#[derive(Clone)] +pub struct CanvasText { + pub(crate) handle: Arc, + pub(crate) buffer: Arc>, + pub(crate) size: (u32, u32), +} + +impl CanvasText { + pub fn get_descriptor_set(&self, + shader: Arc, + sampler: Arc) -> Box { + let o: Box = Box::new( + PersistentDescriptorSet::start( + shader.clone().get_pipeline().clone(), 0, + ) + .add_sampled_image(self.buffer.clone(), sampler.clone()).unwrap() + .build().unwrap()); + o + } } \ No newline at end of file diff --git a/src/canvas_text.rs b/src/canvas_text.rs new file mode 100644 index 00000000..f1b0a59a --- /dev/null +++ b/src/canvas_text.rs @@ -0,0 +1,177 @@ + +use rusttype::{Font, PositionedGlyph, Scale, Rect, point}; +use rusttype::gpu_cache::Cache; + +const CACHE_WIDTH: usize = 1000; +const CACHE_HEIGHT: usize = 1000; + +/// So currently, I'm using these as container classes which vkprocessor owns +/// I then use a CanvasFrame which accumulates lists of handles and vertices. +pub struct CanvasText { + device: Arc, + queue: Arc, + font: Font<'static>, + cache: Cache<'static>, + cache_pixel_buffer: Vec, + texts: Vec, +} + +impl CanvasText { + + pub fn new() -> CanvasText { + + let cache = Cache::builder().dimensions(CACHE_WIDTH as u32, CACHE_HEIGHT as u32).build(); + let cache_pixel_buffer = vec!(0; CACHE_WIDTH * CACHE_HEIGHT); + + + let font_data = include_bytes!("DejaVuSans.ttf"); + let font = Font::from_bytes(font_data as &[u8]).unwrap(); + + CanvasText { + device: (), + queue: (), + font: font, + cache: (), + cache_pixel_buffer: (), + texts: () + } + + } + + pub fn queue_text(&mut self, + x: f32, y: f32, + size: f32, color: [f32; 4], + text: &str) { + let glyphs: Vec = self.font.layout(text, Scale::uniform(size), point(x, y)).map(|x| x.standalone()).collect(); + for glyph in &glyphs { + self.cache.queue_glyph(0, glyph.clone()); + } + self.texts.push(TextData { + glyphs: glyphs.clone(), + color: color, + }); + } + + pub fn draw_text(&mut self, + command_buffer: AutoCommandBufferBuilder, + image_num: usize + ) -> AutoCommandBufferBuilder { + + let screen_width = 0; + let screen_height = 0; + + let cache_pixel_buffer = &mut self.cache_pixel_buffer; + let cache = &mut self.cache; + + // update texture cache + cache.cache_queued( + |rect, src_data| { + let width = (rect.max.x - rect.min.x) as usize; + let height = (rect.max.y - rect.min.y) as usize; + let mut dst_index = rect.min.y as usize * CACHE_WIDTH + rect.min.x as usize; + let mut src_index = 0; + + for _ in 0..height { + let dst_slice = &mut cache_pixel_buffer[dst_index..dst_index+width]; + let src_slice = &src_data[src_index..src_index+width]; + dst_slice.copy_from_slice(src_slice); + + dst_index += CACHE_WIDTH; + src_index += width; + } + } + ).unwrap(); + + // need to get a hold of the cache buffer handle after I create it + // will then get swapped into this texture buffer + // Hmmm so this uninit call returns the texture and then a handle for whatever fills it up + let (cache_texture, cache_texture_write) = ImmutableImage::uninitialized( + self.device.clone(), + Dimensions::Dim2d { width: CACHE_WIDTH as u32, height: CACHE_HEIGHT as u32 }, + R8Unorm, + 1, + ImageUsage { + sampled: true, + transfer_destination: true, + .. ImageUsage::none() + }, + ImageLayout::General, + Some(self.queue.family()) + ).unwrap(); + + + + let set = Arc::new( + PersistentDescriptorSet::start(self.pipeline.clone(), 0) + .add_sampled_image(cache_texture.clone(), sampler).unwrap() + .build().unwrap() + ); + + let mut command_buffer = command_buffer + .copy_buffer_to_image( + buffer.clone(), + cache_texture_write, + ).unwrap() + .begin_render_pass(self.framebuffers[image_num].clone(), false, vec!(ClearValue::None)).unwrap(); + + // draw + for text in &mut self.texts.drain(..) { + let vertices: Vec = text.glyphs.iter().flat_map(|g| { + if let Ok(Some((uv_rect, screen_rect))) = cache.rect_for(0, g) { + let gl_rect = Rect { + min: point( + (screen_rect.min.x as f32 / screen_width as f32 - 0.5) * 2.0, + (screen_rect.min.y as f32 / screen_height as f32 - 0.5) * 2.0 + ), + max: point( + (screen_rect.max.x as f32 / screen_width as f32 - 0.5) * 2.0, + (screen_rect.max.y as f32 / screen_height as f32 - 0.5) * 2.0 + ) + }; + vec!( + Vertex { + position: [gl_rect.min.x, gl_rect.max.y], + tex_position: [uv_rect.min.x, uv_rect.max.y], + color: text.color, + }, + Vertex { + position: [gl_rect.min.x, gl_rect.min.y], + tex_position: [uv_rect.min.x, uv_rect.min.y], + color: text.color, + }, + Vertex { + position: [gl_rect.max.x, gl_rect.min.y], + tex_position: [uv_rect.max.x, uv_rect.min.y], + color: text.color, + }, + + Vertex { + position: [gl_rect.max.x, gl_rect.min.y], + tex_position: [uv_rect.max.x, uv_rect.min.y], + color: text.color, + }, + Vertex { + position: [gl_rect.max.x, gl_rect.max.y], + tex_position: [uv_rect.max.x, uv_rect.max.y], + color: text.color, + }, + Vertex { + position: [gl_rect.min.x, gl_rect.max.y], + tex_position: [uv_rect.min.x, uv_rect.max.y], + color: text.color, + }, + ).into_iter() + } + else { + vec!().into_iter() + } + }).collect(); + + let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::all(), vertices.into_iter()).unwrap(); + command_buffer = command_buffer.draw(self.pipeline.clone(), &DynamicState::none(), vertex_buffer.clone(), set.clone(), ()).unwrap(); + } + + command_buffer.end_render_pass().unwrap() + } + +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 91f5cd39..c7642a3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,7 +90,7 @@ pub fn main() { let image_data = load_raw(String::from("funky-bird.jpg")); let image_dimensions_f = ((image_data.1).0 as f32, (image_data.1).1 as f32); let image_dimensions_u = image_data.1; - let compu_sprite1 = CompuSprite::new((0.0, -0.5), (0.4, 0.4), 3, image_dimensions_f, + let compu_sprite1 = CompuSprite::new((0.0, -0.5), (0.4, 0.4), 0, image_dimensions_f, // This swap image needs to match the size of the compute processor.new_swap_image(image_dimensions_u)); @@ -102,8 +102,8 @@ pub fn main() { let funky_handle = processor.get_texture_handle(String::from("funky-bird.jpg")).unwrap(); let sfml_handle = processor.get_texture_handle(String::from("sfml.png")).unwrap(); - let funky_sprite = Sprite::new_with_texture((0.0, -0.5), (0.5, 0.5), 2, funky_handle.clone()); - let sfml_sprite = Sprite::new_with_texture((0.0, -0.5), (0.5, 0.5), 1, sfml_handle.clone()); + let funky_sprite = Sprite::new_with_texture((0.0, -0.5), (0.5, 0.5), 0, funky_handle.clone()); + let sfml_sprite = Sprite::new_with_texture((0.0, -0.5), (0.5, 0.5), 0, sfml_handle.clone()); drop(q2);