diff --git a/Cargo.toml b/Cargo.toml index 234f37b..f2e8607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -wgpu = "0.15.1" +wgpu = "22.0.0" euclid = "0.22.7" fontdue = "0.7.2" rect_packer = "0.2.1" rand = "0.7.3" +cosmic-text = { git = "https://github.com/lapce/cosmic-text", rev = "25f260eced296296ca22d22a04cbb3026f5fe2a2" } +# cosmic-text = { path = "../cosmic-text" } [dev-dependencies] png = "0.17.6" diff --git a/src/atlas.rs b/src/atlas.rs index 0c47beb..b96b321 100644 --- a/src/atlas.rs +++ b/src/atlas.rs @@ -7,16 +7,22 @@ struct ImageData { data: Vec, } +pub enum AtlasContent { + Mask, + Color, +} + pub struct Atlas { packer: Packer, new_data: Vec, pub atlas_texture: wgpu::Texture, area_used: i32, did_clear: bool, + content: AtlasContent, } impl Atlas { - pub const ATLAS_SIZE: u32 = 1024; + pub const ATLAS_SIZE: u32 = 4096; pub const RECT_PADDING: i32 = 6; fn get_packer_config() -> rect_packer::Config { @@ -42,14 +48,37 @@ impl Atlas { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Unorm, - usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + usage: wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING, label: Some("atlas_texture"), view_formats: &[wgpu::TextureFormat::R8Unorm], } } - pub fn new(device: &wgpu::Device) -> Self { - let atlas_texture = device.create_texture(&Self::get_texture_desc()); + pub fn new(device: &wgpu::Device, content: AtlasContent) -> Self { + let texture_size = wgpu::Extent3d { + width: Atlas::ATLAS_SIZE, + height: Atlas::ATLAS_SIZE, + depth_or_array_layers: 1, + }; + let format = match content { + AtlasContent::Mask => wgpu::TextureFormat::R8Unorm, + AtlasContent::Color => wgpu::TextureFormat::Rgba8Unorm, + }; + let desc = wgpu::TextureDescriptor { + size: texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING, + label: Some("atlas_texture"), + view_formats: &[format], + }; + let atlas_texture = device.create_texture(&desc); Self { packer: Packer::new(Atlas::get_packer_config()), @@ -57,6 +86,7 @@ impl Atlas { atlas_texture, area_used: 0, did_clear: false, + content, } } @@ -81,7 +111,7 @@ impl Atlas { let sz = Atlas::ATLAS_SIZE as usize; - let data = vec![0_u8;sz * sz]; + let data = vec![0_u8; sz * sz]; let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("atlas temp buffer"), @@ -100,7 +130,7 @@ impl Atlas { buffer: &buffer, layout: wgpu::ImageDataLayout { offset: 0, - bytes_per_row: std::num::NonZeroU32::new(sz as u32), + bytes_per_row: Some((sz * 4) as u32), rows_per_image: None, }, }, @@ -119,14 +149,18 @@ impl Atlas { for data in &self.new_data { // Pad data to wgpu::COPY_BYTES_PER_ROW_ALIGNMENT let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as i32; - let padding = (align - data.rect.width % align) % align; - let padded_width = data.rect.width + padding; - let mut padded_data = vec![]; - padded_data.reserve((padded_width * data.rect.height) as usize); + let pixels = match self.content { + AtlasContent::Mask => 1, + AtlasContent::Color => 4, + }; + let width = data.rect.width * pixels; + let padding = (align - width % align) % align; + let padded_width = width + padding; + let mut padded_data = Vec::with_capacity((padded_width * data.rect.height) as usize); let mut i = 0; for _ in 0..data.rect.height { - for _ in 0..data.rect.width { + for _ in 0..width { padded_data.push(data.data[i]); i += 1; } @@ -154,7 +188,7 @@ impl Atlas { buffer: &buffer, layout: wgpu::ImageDataLayout { offset: 0, - bytes_per_row: std::num::NonZeroU32::new(padded_width as u32), + bytes_per_row: Some(padded_width as u32), rows_per_image: None, }, }, diff --git a/src/glyphs.rs b/src/glyphs.rs index 258a0cb..8b2f8be 100644 --- a/src/glyphs.rs +++ b/src/glyphs.rs @@ -1,4 +1,5 @@ -use crate::atlas::Atlas; +use crate::atlas::{Atlas, AtlasContent}; +use cosmic_text::{SubpixelBin, SwashContent, SwashImage}; use rect_packer::Rect; use std::collections::HashMap; @@ -8,10 +9,29 @@ pub struct GlyphInfo { pub metrics: fontdue::Metrics, } +#[derive(Copy, Clone, Debug)] +pub struct AtlasInfo { + pub rect: Option, + pub left: i32, + pub top: i32, + pub colored: bool, +} + pub struct GlyphCache { - pub atlas: Atlas, + pub mask_atlas: Atlas, + pub color_atlas: Atlas, pub font: fontdue::Font, info: HashMap<(char, u32), GlyphInfo>, + atlas_infos: HashMap< + ( + cosmic_text::fontdb::ID, + u16, + u32, + (SubpixelBin, SubpixelBin), + ), + AtlasInfo, + >, + svg_infos: HashMap, HashMap<(u32, u32), AtlasInfo>>, } impl GlyphCache { @@ -23,12 +43,84 @@ impl GlyphCache { let font = include_bytes!("fonts/Anodina-Regular.ttf") as &[u8]; Self { - atlas: Atlas::new(device), + mask_atlas: Atlas::new(device, AtlasContent::Mask), + color_atlas: Atlas::new(device, AtlasContent::Color), font: fontdue::Font::from_bytes(font, settings).unwrap(), info: HashMap::new(), + atlas_infos: HashMap::new(), + svg_infos: HashMap::new(), } } + pub fn get_svg_mask( + &mut self, + hash: &[u8], + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + ) -> AtlasInfo { + if !self.svg_infos.contains_key(hash) { + self.svg_infos.insert(hash.to_vec(), HashMap::new()); + } + + { + let svg_infos = self.svg_infos.get(hash).unwrap(); + if let Some(info) = svg_infos.get(&(width, height)) { + return *info; + } + } + + let data = image(); + let rect = self.color_atlas.add_region(&data, width, height); + let info = AtlasInfo { + rect, + left: 0, + top: 0, + colored: true, + }; + + let svg_infos = self.svg_infos.get_mut(hash).unwrap(); + svg_infos.insert((width, height), info); + + info + } + + pub fn get_glyph_mask( + &mut self, + font_id: cosmic_text::fontdb::ID, + glyph_id: u16, + size: u32, + subpx: (SubpixelBin, SubpixelBin), + image: impl FnOnce() -> SwashImage, + ) -> AtlasInfo { + let key = (font_id, glyph_id, size, subpx); + if let Some(rect) = self.atlas_infos.get(&key) { + return *rect; + } + + let image = image(); + let rect = match image.content { + SwashContent::Mask => self.mask_atlas.add_region( + &image.data, + image.placement.width, + image.placement.height, + ), + SwashContent::SubpixelMask | SwashContent::Color => self.color_atlas.add_region( + &image.data, + image.placement.width, + image.placement.height, + ), + }; + let info = AtlasInfo { + rect, + left: image.placement.left, + top: image.placement.top, + colored: image.content != SwashContent::Mask, + }; + self.atlas_infos.insert(key, info); + info + } + pub fn get_glyph(&mut self, c: char, size: f32) -> GlyphInfo { let factor = 65536.0; @@ -53,7 +145,7 @@ impl GlyphCache { */ let rect = - self.atlas + self.mask_atlas .add_region(&data, metrics.width as u32, metrics.height as u32); let info = GlyphInfo { rect, metrics }; @@ -65,19 +157,20 @@ impl GlyphCache { } pub fn update(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) { - self.atlas.update(device, encoder); + self.mask_atlas.update(device, encoder); + self.color_atlas.update(device, encoder); } - pub fn create_view(&self) -> wgpu::TextureView { - self.atlas.create_view() - } - - pub fn usage(&self) -> f32 { - self.atlas.usage() + pub fn check_usage(&mut self) { + if self.mask_atlas.usage() > 0.7 || self.color_atlas.usage() > 0.7 { + self.clear(); + } } pub fn clear(&mut self) { self.info.clear(); - self.atlas.clear(); + self.mask_atlas.clear(); + self.color_atlas.clear(); + self.atlas_infos.clear(); } } diff --git a/src/gpu_vec.rs b/src/gpu_vec.rs index 8f0f7c3..86a12d0 100644 --- a/src/gpu_vec.rs +++ b/src/gpu_vec.rs @@ -95,6 +95,10 @@ impl GPUVec { self.data.clear(); } + pub fn append(&mut self, values: &mut Vec) { + self.data.append(values); + } + pub fn push(&mut self, value: T) { self.data.push(value); } diff --git a/src/lib.rs b/src/lib.rs index c30e4b6..e6015ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use cosmic_text::{SubpixelBin, SwashImage}; use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; mod path; @@ -66,6 +67,7 @@ pub struct Vger { scenes: [Scene; 3], cur_scene: usize, cur_layer: usize, + cur_z_index: i32, tx_stack: Vec, scissor_stack: Vec, device_px_ratio: f32, @@ -120,6 +122,22 @@ impl Vger { wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, @@ -129,7 +147,8 @@ impl Vger { let glyph_cache = GlyphCache::new(device); - let texture_view = glyph_cache.create_view(); + let mask_texture_view = glyph_cache.mask_atlas.create_view(); + let color_texture_view = glyph_cache.color_atlas.create_view(); let uniforms = GPUVec::new_uniforms(device, "uniforms"); @@ -137,6 +156,15 @@ impl Vger { label: Some("glyph"), mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let color_glyph_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("color_glyph"), + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, ..Default::default() }); @@ -146,12 +174,20 @@ impl Vger { uniforms.bind_group_entry(0), wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&texture_view), + resource: wgpu::BindingResource::TextureView(&mask_texture_view), }, wgpu::BindGroupEntry { binding: 2, + resource: wgpu::BindingResource::TextureView(&color_texture_view), + }, + wgpu::BindGroupEntry { + binding: 3, resource: wgpu::BindingResource::Sampler(&glyph_sampler), }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::Sampler(&color_glyph_sampler), + }, ], label: Some("vger bind group"), }); @@ -178,6 +214,7 @@ impl Vger { module: &shader, entry_point: "vs_main", buffers: &[], + compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -190,6 +227,7 @@ impl Vger { }), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: Default::default(), }), primitive: wgpu::PrimitiveState { cull_mode: None, @@ -199,6 +237,7 @@ impl Vger { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); let layout = Layout::new(CoordinateSystem::PositiveYUp); @@ -207,6 +246,7 @@ impl Vger { scenes, cur_scene: 0, cur_layer: 0, + cur_z_index: 0, tx_stack: vec![], scissor_stack: vec![], device_px_ratio: 1.0, @@ -241,6 +281,7 @@ impl Vger { self.scissor_stack.push(Scissor::new()); self.paint_count = 0; self.xform_count = 0; + self.add_xform(); self.scissor_count = 0; self.pen = LocalPoint::zero(); } @@ -294,16 +335,15 @@ impl Vger { queue.submit(Some(encoder.finish())); // If we're getting close to full, reset the glyph cache. - let usage = self.glyph_cache.usage(); - // println!("glyph cache usage {}", usage); - if usage > 0.7 { - // println!("clearing glyph cache"); - self.glyph_cache.clear(); - } + self.glyph_cache.check_usage(); } fn render(&mut self, prim: Prim) { - self.scenes[self.cur_scene].prims[self.cur_layer].push(prim); + let prims = self.scenes[self.cur_scene] + .depthed_prims + .entry(self.cur_z_index) + .or_default(); + prims.push(prim); } /// Fills a circle. @@ -322,7 +362,6 @@ impl Vger { prim.paint = paint_index.index as u32; prim.quad_bounds = [c.x - radius, c.y - radius, c.x + radius, c.y + radius]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -359,7 +398,6 @@ impl Vger { c.y + radius + width, ]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -385,7 +423,6 @@ impl Vger { prim.paint = paint_index.index as u32; prim.quad_bounds = [min.x, min.y, max.x, max.y]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -411,7 +448,6 @@ impl Vger { prim.paint = paint_index.index as u32; prim.quad_bounds = [min.x - width, min.y - width, max.x + width, max.y + width]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -436,13 +472,12 @@ impl Vger { prim.width = width; prim.paint = paint_index.index as u32; prim.quad_bounds = [ - ap.x.min(bp.x), - ap.y.min(bp.y), - ap.x.max(bp.x), - ap.y.max(bp.y), + ap.x.min(bp.x) - width * 2.0, + ap.y.min(bp.y) - width * 2.0, + ap.x.max(bp.x) + width * 2.0, + ap.y.max(bp.y) + width * 2.0, ]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -477,7 +512,6 @@ impl Vger { ap.y.max(bp.y).max(cp.y) + width, ]; prim.tex_bounds = prim.quad_bounds; - prim.xform = self.add_xform() as u32; prim.scissor = self.add_scissor() as u32; self.render(prim); @@ -503,7 +537,6 @@ impl Vger { /// Fills a path. pub fn fill(&mut self, paint_index: PaintIndex) { - let xform = self.add_xform(); let scissor = self.add_scissor(); self.path_scanner.init(); @@ -512,13 +545,12 @@ impl Vger { let mut prim = Prim::default(); prim.prim_type = PrimType::PathFill as u32; prim.paint = paint_index.index as u32; - prim.xform = xform as u32; prim.scissor = scissor as u32; prim.start = self.scenes[self.cur_scene].cvs.len() as u32; let mut x_interval = Interval { - a: std::f32::MAX, - b: std::f32::MIN, + a: f32::MAX, + b: f32::MIN, }; let mut index = self.path_scanner.first; @@ -542,6 +574,8 @@ impl Vger { self.render(prim); } + + self.path_scanner.segments.clear(); } fn setup_layout(&mut self, text: &str, size: u32, max_width: Option) { @@ -560,6 +594,85 @@ impl Vger { ); } + #[allow(clippy::too_many_arguments)] + pub fn render_glyph( + &mut self, + x: f32, + y: f32, + font_id: cosmic_text::fontdb::ID, + glyph_id: u16, + size: u32, + subpx: (SubpixelBin, SubpixelBin), + image: impl FnOnce() -> SwashImage, + paint_index: PaintIndex, + ) { + let info = self + .glyph_cache + .get_glyph_mask(font_id, glyph_id, size, subpx, image); + if let Some(rect) = info.rect { + let mut prim = Prim::default(); + prim.prim_type = if info.colored { + PrimType::ColorGlyph + } else { + PrimType::Glyph + } as u32; + + let x = x + info.left as f32; + let y = y - info.top as f32; + prim.quad_bounds = [x, y, x + rect.width as f32, y + rect.height as f32]; + + prim.tex_bounds = [ + rect.x as f32, + rect.y as f32, + (rect.x + rect.width) as f32, + (rect.y + rect.height) as f32, + ]; + prim.paint = paint_index.index as u32; + prim.scissor = self.add_scissor() as u32; + + self.render(prim); + } + } + + #[allow(clippy::too_many_arguments)] + pub fn render_svg( + &mut self, + x: f32, + y: f32, + hash: &[u8], + width: u32, + height: u32, + image: impl FnOnce() -> Vec, + paint_index: Option, + ) { + let info = self.glyph_cache.get_svg_mask(hash, width, height, image); + if let Some(rect) = info.rect { + let mut prim = Prim::default(); + prim.prim_type = if paint_index.is_some() { + PrimType::OverrideColorSvg + } else { + PrimType::ColorGlyph + } as u32; + + let x = x + info.left as f32; + let y = y - info.top as f32; + prim.quad_bounds = [x, y, x + rect.width as f32, y + rect.height as f32]; + + prim.tex_bounds = [ + rect.x as f32, + rect.y as f32, + (rect.x + rect.width) as f32, + (rect.y + rect.height) as f32, + ]; + if let Some(paint_index) = paint_index { + prim.paint = paint_index.index as u32; + } + prim.scissor = self.add_scissor() as u32; + + self.render(prim); + } + } + /// Renders text. pub fn text(&mut self, text: &str, size: u32, color: Color, max_width: Option) { self.setup_layout(text, size, max_width); @@ -568,7 +681,6 @@ impl Vger { let scaled_size = size as f32 * scale; let paint = self.color_paint(color); - let xform = self.add_xform() as u32; let scissor = self.add_scissor() as u32; let mut prims = vec![]; @@ -580,7 +692,6 @@ impl Vger { if let Some(rect) = info.rect { let mut prim = Prim::default(); prim.prim_type = PrimType::Glyph as u32; - prim.xform = xform; prim.scissor = scissor; assert!(glyph.width == rect.width as usize); assert!(glyph.height == rect.height as usize); @@ -592,7 +703,7 @@ impl Vger { (glyph.y + glyph.height as f32) / scale, ]; // println!("quad_bounds: {:?}", prim.quad_bounds); - + prim.tex_bounds = [ rect.x as f32, (rect.y + rect.height) as f32, @@ -641,8 +752,7 @@ impl Vger { size: u32, max_width: Option, ) -> Vec { - let mut rects = vec![]; - rects.reserve(text.len()); + let mut rects = Vec::with_capacity(text.len()); self.setup_layout(text, size, max_width); @@ -670,8 +780,7 @@ impl Vger { self.setup_layout(text, size, max_width); let s = 1.0 / self.device_px_ratio; - let mut rects = vec![]; - rects.reserve(text.len()); + let mut rects = Vec::with_capacity(text.len()); let glyphs = self.layout.glyphs(); @@ -760,6 +869,10 @@ impl Vger { } } + pub fn set_z_index(&mut self, z_index: i32) { + self.cur_z_index = z_index; + } + /// Resets the current scissor rect. pub fn reset_scissor(&mut self) { if let Some(m) = self.scissor_stack.last_mut() { @@ -801,3 +914,44 @@ impl Vger { )) } } + +#[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Default)] +#[repr(u8)] +pub enum SubpixelOffset { + #[default] + Zero = 0, + Quarter = 1, + Half = 2, + ThreeQuarters = 3, +} + +impl SubpixelOffset { + // Skia quantizes subpixel offsets into 1/4 increments. + // Given the absolute position, return the quantized increment + pub fn quantize(pos: f32) -> Self { + // Following the conventions of Gecko and Skia, we want + // to quantize the subpixel position, such that abs(pos) gives: + // [0.0, 0.125) -> Zero + // [0.125, 0.375) -> Quarter + // [0.375, 0.625) -> Half + // [0.625, 0.875) -> ThreeQuarters, + // [0.875, 1.0) -> Zero + // The unit tests below check for this. + let apos = ((pos - pos.floor()) * 8.0) as i32; + match apos { + 1..=2 => SubpixelOffset::Quarter, + 3..=4 => SubpixelOffset::Half, + 5..=6 => SubpixelOffset::ThreeQuarters, + _ => SubpixelOffset::Zero, + } + } + + pub fn to_f32(self) -> f32 { + match self { + SubpixelOffset::Zero => 0.0, + SubpixelOffset::Quarter => 0.25, + SubpixelOffset::Half => 0.5, + SubpixelOffset::ThreeQuarters => 0.75, + } + } +} diff --git a/src/path.rs b/src/path.rs index 2bb927a..b62d379 100644 --- a/src/path.rs +++ b/src/path.rs @@ -111,7 +111,7 @@ impl PathScanner { } pub fn next(&mut self) -> bool { - let y = self.nodes[self.index as usize].coord; + let y = self.nodes[self.index].coord; self.interval.a = y; let n = self.nodes.len(); diff --git a/src/prim.rs b/src/prim.rs index 52713c9..3437b7f 100644 --- a/src/prim.rs +++ b/src/prim.rs @@ -28,8 +28,14 @@ pub enum PrimType { /// Text rendering. Glyph, + /// Colored glyph e.g. emoji. + ColorGlyph, + /// Path fills. PathFill, + + /// Svg with override color + OverrideColorSvg, } #[derive(Copy, Clone, Default)] diff --git a/src/scene.rs b/src/scene.rs index 0470033..27e179d 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::*; pub const MAX_LAYERS: usize = 4; @@ -5,6 +7,7 @@ pub const MAX_LAYERS: usize = 4; type Mat4x4 = [f32; 16]; pub(crate) struct Scene { + pub depthed_prims: HashMap>, pub prims: [GPUVec; MAX_LAYERS], pub cvs: GPUVec, pub xforms: GPUVec, @@ -40,6 +43,7 @@ impl Scene { ]; Self { + depthed_prims: HashMap::new(), prims, cvs, xforms, @@ -87,6 +91,13 @@ impl Scene { } pub fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { + let mut keys: Vec = self.depthed_prims.keys().copied().collect(); + keys.sort(); + for z_index in keys { + let mut prims = self.depthed_prims.remove(&z_index).unwrap(); + self.prims[0].append(&mut prims); + } + for i in 0..4 { self.prims[i].update(device, queue); } @@ -97,6 +108,7 @@ impl Scene { } pub fn clear(&mut self) { + self.depthed_prims.clear(); for i in 0..4 { self.prims[i].clear(); } diff --git a/src/shader.wgsl b/src/shader.wgsl index c8f2b40..483f98d 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -27,8 +27,14 @@ const vgerWire = 7; /// Text rendering. const vgerGlyph = 8; +/// Text rendering. +const vgerColorGlyph = 9; + /// Path fills. -const vgerPathFill = 9; +const vgerPathFill = 10; + +/// Svg with override color +const overrideColorSvg = 11; struct Prim { @@ -103,12 +109,13 @@ fn sdBox(p: vec2, b: vec2, r: f32) -> f32 return length(max(d,vec2(0.0, 0.0))) + min(max(d.x,d.y),0.0)-r; } -fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 +fn sdSegment(p: vec2, a: vec2, b: vec2, width: f32) -> f32 { - let pa = p-a; - let ba = b-a; - let h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); - return length( pa - ba*h ); + var dir = a-b; + let lngth = length(dir); + dir = dir / lngth; + let proj = max(0.0, min(lngth, dot((a - p), dir))) * dir; + return length( (a - p) - proj ) - (width / 2.0); } fn sdSegment2(p: vec2, a: vec2, b: vec2, width: f32) -> f32 @@ -336,13 +343,21 @@ fn sdPrimBounds(prim: Prim) -> BBox { b.min = prim.cv0; b.max = prim.cv1; } - case 9u: { // vgerPathFill + case 9u: { // vgerColorGlyph + b.min = prim.cv0; + b.max = prim.cv1; + } + case 10u: { // vgerPathFill b.min = vec2(1e10, 1e10); b.max = -b.min; for(var i: i32 = 0; i < i32(prim.count * 3u); i = i+1) { b = expand(b, cvs.cvs[i32(prim.start)+i]); } } + case 11u: { // overrideColorSvg + b.min = prim.cv0; + b.max = prim.cv1; + } default: {} } return b; @@ -406,7 +421,7 @@ fn sdPrim(prim: Prim, p: vec2, filterWidth: f32) -> f32 { d = abs(sdBox(p - center, 0.5*size, prim.radius)) - prim.width/2.0; } case 4u: { // vgerBezier - d = sdBezierApprox(p, prim.cv0, prim.cv1, prim.cv2) - prim.width; + d = sdBezierApprox(p, prim.cv0, prim.cv1, prim.cv2) - prim.width/2.0; } case 5u: { // vgerSegment d = sdSegment2(p, prim.cv0, prim.cv1, prim.width); @@ -425,7 +440,12 @@ fn sdPrim(prim: Prim, p: vec2, filterWidth: f32) -> f32 { let size = prim.cv1 - prim.cv0; d = sdBox(p - center, 0.5*size, prim.radius); } - case 9u: { // vgerPathFill + case 9u: { // vgerColorGlyph + let center = 0.5*(prim.cv1 + prim.cv0); + let size = prim.cv1 - prim.cv0; + d = sdBox(p - center, 0.5*size, prim.radius); + } + case 10u: { // vgerPathFill for(var i=0; i, filterWidth: f32) -> f32 { d = d * s; break; } + case 11u: { // overrideColorSvg + let center = 0.5*(prim.cv1 + prim.cv0); + let size = prim.cv1 - prim.cv0; + d = sdBox(p - center, 0.5*size, prim.radius); + } default: { } } return d; @@ -531,7 +556,7 @@ fn vs_main( } out.p = (xforms.xforms[prim.xform] * vec4(q, 0.0, 1.0)).xy; - out.position = vec4(2.0 * out.p / uniforms.size - 1.0, 0.0, 1.0); + out.position = vec4((2.0 * out.p / uniforms.size - 1.0) * vec2(1.0, -1.0), 0.0, 1.0); return out; } @@ -604,8 +629,16 @@ var glyph_atlas: texture_2d; @group(1) @binding(2) +var color_atlas: texture_2d; + +@group(1) +@binding(3) var samp : sampler; +@group(1) +@binding(4) +var color_samp : sampler; + // sRGB to linear conversion for one channel. fn toLinear(s: f32) -> f32 { @@ -627,17 +660,52 @@ fn fs_main( // Look up glyph alpha (if not a glyph, still have to because of wgsl). // let a = textureSample(glyph_atlas, samp, (in.t+0.5)/1024.0).r; - let a = textureLoad(glyph_atlas, vec2(in.t), 0).r; + // let mask = textureLoad(glyph_atlas, vec2(in.t), 0); + let mask = textureSample(glyph_atlas, samp, in.t/4096.0); + let color_mask = textureSample(color_atlas, color_samp, in.t/4096.0); let s = scissor_mask(scissor, in.p); if(prim.prim_type == 8u) { // vgerGlyph + if (mask.r <= 0.0) { + discard; + } + + let c = paint.inner_color; + + // XXX: using toLinear is a bit of a guess. Gets us closer + // to matching the glyph atlas in the output. + var color = vec4(c.rgb, c.a * mask.r); + + //if(glow) { + // color.a *= paint.glow; + //} + + return s * color; + } + + if(prim.prim_type == 9u) { // vgerColorGlyph + + let c = paint.inner_color; + + // XXX: using toLinear is a bit of a guess. Gets us closer + // to matching the glyph atlas in the output. + var color = vec4(color_mask.rgb, c.a * color_mask.a); + + //if(glow) { + // color.a *= paint.glow; + //} + + return s * color; + } + + if(prim.prim_type == 11u) { // overrideColorSvg let c = paint.inner_color; // XXX: using toLinear is a bit of a guess. Gets us closer // to matching the glyph atlas in the output. - var color = vec4(c.rgb, toLinear(a)); + var color = vec4(c.rgb, c.a * color_mask.a); //if(glow) { // color.a *= paint.glow; diff --git a/tests/common.rs b/tests/common.rs index 1e589d5..27dcddb 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,13 +1,14 @@ use futures::executor::block_on; use std::fs::File; use vger::*; +use wgpu::StoreOp; pub async fn setup() -> (wgpu::Device, wgpu::Queue) { let instance_desc = wgpu::InstanceDescriptor::default(); let instance = wgpu::Instance::new(instance_desc); - let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, wgpu::Backends::all(), None) + let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) .await .expect("No suitable GPU adapters found on the system!"); @@ -17,11 +18,7 @@ pub async fn setup() -> (wgpu::Device, wgpu::Queue) { let trace_dir = std::env::var("WGPU_TRACE"); adapter .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::default(), - limits: wgpu::Limits::default(), - }, + &wgpu::DeviceDescriptor::default(), trace_dir.ok().as_ref().map(std::path::Path::new), ) .await @@ -60,8 +57,8 @@ pub async fn create_png( let mut png_encoder = png::Encoder::new( File::create(png_output_path).unwrap(), - texture_extent.width as u32, - texture_extent.height as u32, + texture_extent.width, + texture_extent.height, ); png_encoder.set_depth(png::BitDepth::Eight); png_encoder.set_color(match texture_descriptor.format { @@ -115,9 +112,7 @@ fn get_texture_data( buffer: &output_buffer, layout: wgpu::ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new(texture_extent.width * bytes_per_pixel).unwrap(), - ), + bytes_per_row: Some(texture_extent.width * bytes_per_pixel), rows_per_image: None, }, }, @@ -173,10 +168,12 @@ pub fn render_test( resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, }; vger.encode(device, &desc, queue); diff --git a/tests/tests.rs b/tests/tests.rs index 9b408ce..eb107cd 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -283,7 +283,7 @@ fn text_small() { let atlas_png_name = "text_small_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue, @@ -308,7 +308,7 @@ fn text_scale() { let atlas_png_name = "text_scale_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue, @@ -342,7 +342,7 @@ fn text_box() { let atlas_png_name = "text_box_atlas.png"; save_png( - &vger.glyph_cache.atlas.atlas_texture, + &vger.glyph_cache.mask_atlas.atlas_texture, &vger::atlas::Atlas::get_texture_desc(), &device, &queue,