From 165b3a083bde37820105d77851251cc9dc0772fc Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Thu, 9 Mar 2023 17:18:03 -0500 Subject: [PATCH 1/6] Let's add images --- Cargo.toml | 1 + examples/assets/piet-logo.png | Bin 0 -> 12484 bytes examples/headless/src/main.rs | 4 +- examples/scenes/Cargo.toml | 1 + examples/scenes/src/images.rs | 53 +++++++++++++++ examples/scenes/src/lib.rs | 3 + examples/scenes/src/test_scenes.rs | 15 ++++- examples/with_winit/src/lib.rs | 4 +- shader/coarse.wgsl | 18 ++++++ shader/draw_leaf.wgsl | 19 +++++- shader/fine.wgsl | 44 +++++++++++++ shader/shared/drawtag.wgsl | 2 +- shader/shared/ptcl.wgsl | 8 +++ src/encoding.rs | 1 + src/encoding/draw.rs | 10 +-- src/encoding/encoding.rs | 40 ++++++++++-- src/encoding/image_cache.rs | 92 ++++++++++++++++++++++++++ src/encoding/resolve.rs | 100 +++++++++++++++++++++++++++-- src/engine.rs | 96 +++++++++++++++++++++++---- src/render.rs | 21 +++++- src/scene.rs | 13 +++- src/shaders.rs | 1 + 22 files changed, 508 insertions(+), 38 deletions(-) create mode 100644 examples/assets/piet-logo.png create mode 100644 examples/scenes/src/images.rs create mode 100644 src/encoding/image_cache.rs diff --git a/Cargo.toml b/Cargo.toml index 550674b..1d8c700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ bytemuck = { version = "1.12.1", features = ["derive"] } smallvec = "1.8.0" moscato = { git = "https://github.com/dfrg/pinot", rev = "59db153" } peniko = { git = "https://github.com/linebender/peniko", rev = "cafdac9a211a0fb2fec5656bd663d1ac770bcc81" } +guillotiere = "0.6.2" [workspace.dependencies] wgpu = "0.15" diff --git a/examples/assets/piet-logo.png b/examples/assets/piet-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1f5a48f4d7a15479216ca87485a365e95ff75522 GIT binary patch literal 12484 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 Result<()> { let mut builder = SceneBuilder::for_fragment(&mut fragment); let example_scene = &mut scenes.scenes[index]; let mut text = SimpleText::new(); + let mut images = ImageCache::new(); let mut scene_params = SceneParams { time: args.time.unwrap_or(0.), text: &mut text, + images: &mut images, resolution: None, base_color: None, }; diff --git a/examples/scenes/Cargo.toml b/examples/scenes/Cargo.toml index 9a21a5b..7037d28 100644 --- a/examples/scenes/Cargo.toml +++ b/examples/scenes/Cargo.toml @@ -15,6 +15,7 @@ vello = { path = "../../" } vello_svg = { path = "../../integrations/vello_svg" } anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } +image = "0.24.5" # Used for the `download` command byte-unit = "4.0" diff --git a/examples/scenes/src/images.rs b/examples/scenes/src/images.rs new file mode 100644 index 0000000..2086351 --- /dev/null +++ b/examples/scenes/src/images.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use vello::peniko::{Blob, Format, Image}; + +/// Simple hack to support loading images for examples. +#[derive(Default)] +pub struct ImageCache { + files: HashMap, + bytes: HashMap, +} + +impl ImageCache { + pub fn new() -> Self { + Self::default() + } + + pub fn from_file(&mut self, path: impl AsRef) -> Option { + let path = path.as_ref(); + if let Some(image) = self.files.get(path) { + Some(image.clone()) + } else { + let data = std::fs::read(path).ok()?; + let image = decode_image(&data)?; + self.files.insert(path.to_owned(), image.clone()); + Some(image) + } + } + + pub fn from_bytes(&mut self, key: usize, bytes: &[u8]) -> Option { + if let Some(image) = self.bytes.get(&key) { + Some(image.clone()) + } else { + let image = decode_image(bytes)?; + self.bytes.insert(key, image.clone()); + Some(image) + } + } +} + +fn decode_image(data: &[u8]) -> Option { + let image = image::io::Reader::new(std::io::Cursor::new(data)) + .with_guessed_format() + .ok()? + .decode() + .ok()?; + let width = image.width(); + let height = image.height(); + let data = Arc::new(image.into_rgba8().into_vec()); + let blob = Blob::new(data); + Some(Image::new(blob, Format::Rgba8, width, height)) +} diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs index 242cbe1..a1bf160 100644 --- a/examples/scenes/src/lib.rs +++ b/examples/scenes/src/lib.rs @@ -1,4 +1,5 @@ pub mod download; +mod images; mod simple_text; mod svg; mod test_scenes; @@ -7,6 +8,7 @@ use std::path::PathBuf; use anyhow::{anyhow, Result}; use clap::{Args, Subcommand}; use download::Download; +pub use images::ImageCache; pub use simple_text::SimpleText; pub use svg::{default_scene, scene_from_files}; pub use test_scenes::test_scenes; @@ -16,6 +18,7 @@ use vello::{kurbo::Vec2, peniko::Color, SceneBuilder}; pub struct SceneParams<'a> { pub time: f64, pub text: &'a mut SimpleText, + pub images: &'a mut ImageCache, pub resolution: Option, pub base_color: Option, } diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 499e2a4..721b921 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -1,8 +1,12 @@ +use std::sync::Arc; + use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet}; -use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; +use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect, Vec2}; use vello::peniko::*; use vello::*; +const PIET_LOGO_IMAGE: &[u8] = include_bytes!("../../assets/piet-logo.png"); + macro_rules! scene { ($name: ident) => { scene!($name: false) @@ -97,6 +101,11 @@ fn cardioid_and_friends(sb: &mut SceneBuilder, _: &mut SceneParams) { } fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) { + let piet_logo = params + .images + .from_bytes(PIET_LOGO_IMAGE.as_ptr() as usize, PIET_LOGO_IMAGE) + .unwrap(); + use PathEl::*; let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0)); let star = [ @@ -184,6 +193,10 @@ fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) { None, &star, ); + sb.draw_image( + &piet_logo, + Affine::translate((550.0, 250.0)) * Affine::skew(-20f64.to_radians().tan(), 0.0), + ); } fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) { diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index 9ed9598..c7d8d4f 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -19,7 +19,7 @@ use std::time::Instant; use anyhow::Result; use clap::{CommandFactory, Parser}; -use scenes::{SceneParams, SceneSet, SimpleText}; +use scenes::{ImageCache, SceneParams, SceneSet, SimpleText}; use vello::peniko::Color; use vello::util::RenderSurface; use vello::{ @@ -96,6 +96,7 @@ fn run( let mut scene = Scene::new(); let mut fragment = SceneFragment::new(); let mut simple_text = SimpleText::new(); + let mut images = ImageCache::new(); let start = Instant::now(); let mut touch_state = multi_touch::TouchState::new(); @@ -264,6 +265,7 @@ fn run( let mut scene_params = SceneParams { time: start.elapsed().as_secs_f64(), text: &mut simple_text, + images: &mut images, resolution: None, base_color: None, }; diff --git a/shader/coarse.wgsl b/shader/coarse.wgsl index 0963c29..6eced0d 100644 --- a/shader/coarse.wgsl +++ b/shader/coarse.wgsl @@ -126,6 +126,15 @@ fn write_grad(ty: u32, index: u32, info_offset: u32) { cmd_offset += 3u; } +fn write_image(xy: u32, width_height: u32, info_offset: u32) { + alloc_cmd(4u); + ptcl[cmd_offset] = CMD_IMAGE; + ptcl[cmd_offset + 1u] = info_offset; + ptcl[cmd_offset + 2u] = xy; + ptcl[cmd_offset + 3u] = width_height; + cmd_offset += 4u; +} + fn write_begin_clip() { alloc_cmd(1u); ptcl[cmd_offset] = CMD_BEGIN_CLIP; @@ -377,6 +386,15 @@ fn main( write_grad(CMD_RAD_GRAD, index, info_offset); } } + // DRAWTAG_FILL_IMAGE + case 0x1c8u: { + let linewidth = bitcast(info_bin_data[di]); + if write_path(tile, linewidth) { + let xy = scene[dd]; + let width_height = scene[dd + 1u]; + write_image(xy, width_height, di + 1u); + } + } // DRAWTAG_BEGIN_CLIP case 0x9u: { if tile.segments == 0u && tile.backdrop == 0 { diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index 9183c95..d0873de 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -113,7 +113,9 @@ fn main( var matrx: vec4; var translate: vec2; var linewidth = bbox.linewidth; - if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT { + if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT || + tag_word == DRAWTAG_FILL_IMAGE + { let transform = read_transform(config.transform_base, bbox.trans_ix); matrx = transform.matrx; translate = transform.translate; @@ -124,7 +126,7 @@ fn main( } switch tag_word { // DRAWTAG_FILL_COLOR, DRAWTAG_FILL_IMAGE - case 0x44u, 0x48u: { + case 0x44u: { info[di] = bitcast(linewidth); } // DRAWTAG_FILL_LIN_GRADIENT @@ -169,6 +171,19 @@ fn main( info[di + 9u] = bitcast(ra); info[di + 10u] = bitcast(roff); } + // DRAWTAG_FILL_IMAGE + case 0x1c8u: { + info[di] = bitcast(linewidth); + let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z); + let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x); + let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate; + info[di + 1u] = bitcast(inv_mat.x); + info[di + 2u] = bitcast(inv_mat.y); + info[di + 3u] = bitcast(inv_mat.z); + info[di + 4u] = bitcast(inv_mat.w); + info[di + 5u] = bitcast(inv_tr.x); + info[di + 6u] = bitcast(inv_tr.y); + } default: {} } } diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 6851bae..38e6b96 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -40,6 +40,9 @@ var gradients: texture_2d; @group(0) @binding(6) var info: array; +@group(0) @binding(7) +var image_atlas: texture_2d; + fn read_fill(cmd_ix: u32) -> CmdFill { let tile = ptcl[cmd_ix + 1u]; let backdrop = i32(ptcl[cmd_ix + 2u]); @@ -81,6 +84,24 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { return CmdRadGrad(index, matrx, xlat, c1, ra, roff); } +fn read_image(cmd_ix: u32) -> CmdImage { + let info_offset = ptcl[cmd_ix + 1u]; + let xy = ptcl[cmd_ix + 2u]; + let width_height = ptcl[cmd_ix + 3u]; + let m0 = bitcast(info[info_offset]); + let m1 = bitcast(info[info_offset + 1u]); + let m2 = bitcast(info[info_offset + 2u]); + let m3 = bitcast(info[info_offset + 3u]); + let matrx = vec4(m0, m1, m2, m3); + let xlat = vec2(bitcast(info[info_offset + 4u]), bitcast(info[info_offset + 5u])); + // The following are not intended to be bitcasts + let x = f32(xy >> 16u); + let y = f32(xy & 0xffffu); + let width = f32(width_height >> 16u); + let height = f32(width_height & 0xffffu); + return CmdImage(matrx, xlat, vec2(x, y), vec2(width, height)); +} + fn read_end_clip(cmd_ix: u32) -> CmdEndClip { let blend = ptcl[cmd_ix + 1u]; let alpha = bitcast(ptcl[cmd_ix + 2u]); @@ -265,6 +286,29 @@ fn main( } cmd_ix += 3u; } + // CMD_IMAGE + case 8u: { + let image = read_image(cmd_ix); + let atlas_extents = image.atlas_offset + image.extents; + for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { + let my_xy = vec2(xy.x + f32(i), xy.y); + let atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat + image.atlas_offset; + // This currently clips to the image bounds. TODO: extend modes + if all(atlas_uv < atlas_extents) { + let uv_quad = vec4(max(floor(atlas_uv), image.atlas_offset), min(ceil(atlas_uv), atlas_extents)); + let uv_frac = fract(atlas_uv); + let a = textureLoad(image_atlas, vec2(uv_quad.xy), 0); + let b = textureLoad(image_atlas, vec2(uv_quad.xw), 0); + let c = textureLoad(image_atlas, vec2(uv_quad.zy), 0); + let d = textureLoad(image_atlas, vec2(uv_quad.zw), 0); + let color = mix(mix(a, b, uv_frac.y), mix(c, d, uv_frac.y), uv_frac.x); + let fg_rgba = vec4(color.rgb * color.a, color.a); + let fg_i = fg_rgba * area[i]; + rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; + } + } + cmd_ix += 4u; + } // CMD_BEGIN_CLIP case 9u: { if clip_depth < BLEND_STACK_SPLIT { diff --git a/shader/shared/drawtag.wgsl b/shader/shared/drawtag.wgsl index 432cf1b..d2646b8 100644 --- a/shader/shared/drawtag.wgsl +++ b/shader/shared/drawtag.wgsl @@ -19,7 +19,7 @@ let DRAWTAG_NOP = 0u; let DRAWTAG_FILL_COLOR = 0x44u; let DRAWTAG_FILL_LIN_GRADIENT = 0x114u; let DRAWTAG_FILL_RAD_GRADIENT = 0x2dcu; -let DRAWTAG_FILL_IMAGE = 0x48u; +let DRAWTAG_FILL_IMAGE = 0x1c8u; let DRAWTAG_BEGIN_CLIP = 0x9u; let DRAWTAG_END_CLIP = 0x21u; diff --git a/shader/shared/ptcl.wgsl b/shader/shared/ptcl.wgsl index 3527bd0..5d5e528 100644 --- a/shader/shared/ptcl.wgsl +++ b/shader/shared/ptcl.wgsl @@ -16,6 +16,7 @@ let CMD_SOLID = 3u; let CMD_COLOR = 5u; let CMD_LIN_GRAD = 6u; let CMD_RAD_GRAD = 7u; +let CMD_IMAGE = 8u; let CMD_BEGIN_CLIP = 9u; let CMD_END_CLIP = 10u; let CMD_JUMP = 11u; @@ -57,6 +58,13 @@ struct CmdRadGrad { roff: f32, } +struct CmdImage { + matrx: vec4, + xlat: vec2, + atlas_offset: vec2, + extents: vec2, +} + struct CmdEndClip { blend: u32, alpha: f32, diff --git a/src/encoding.rs b/src/encoding.rs index 239bd54..2925256 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -20,6 +20,7 @@ mod draw; mod encoding; mod glyph; mod glyph_cache; +mod image_cache; mod math; mod monoid; mod path; diff --git a/src/encoding/draw.rs b/src/encoding/draw.rs index 1ddaead..fb3aa1f 100644 --- a/src/encoding/draw.rs +++ b/src/encoding/draw.rs @@ -38,7 +38,7 @@ impl DrawTag { pub const RADIAL_GRADIENT: Self = Self(0x2dc); /// Image fill. - pub const IMAGE: Self = Self(0x48); + pub const IMAGE: Self = Self(0x1c8); /// Begin layer/clip. pub const BEGIN_CLIP: Self = Self(0x9); @@ -104,10 +104,10 @@ pub struct DrawRadialGradient { #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] #[repr(C)] pub struct DrawImage { - /// Image index. - pub index: u32, - /// Packed image offset. - pub offset: u32, + /// Packed atlas coordinates. + pub xy: u32, + /// Packed image dimensions. + pub width_height: u32, } /// Draw data for a clip or layer. diff --git a/src/encoding/encoding.rs b/src/encoding/encoding.rs index 0bee364..d212a89 100644 --- a/src/encoding/encoding.rs +++ b/src/encoding/encoding.rs @@ -14,12 +14,14 @@ // // Also licensed under MIT license, at your choice. +use crate::encoding::DrawImage; + use super::{ resolve::Patch, DrawColor, DrawLinearGradient, DrawRadialGradient, DrawTag, Glyph, GlyphRun, PathEncoder, PathTag, Transform, }; -use peniko::{kurbo::Shape, BlendMode, BrushRef, ColorStop, Extend, GradientKind}; +use peniko::{kurbo::Shape, BlendMode, BrushRef, ColorStop, Extend, GradientKind, Image}; /// Encoded data streams for a scene. #[derive(Clone, Default)] @@ -122,16 +124,26 @@ impl Encoding { self.n_open_clips += other.n_open_clips; self.patches .extend(other.patches.iter().map(|patch| match patch { - Patch::Ramp { offset, stops } => { + Patch::Ramp { + draw_data_offset: offset, + stops, + } => { let stops = stops.start + stops_base..stops.end + stops_base; Patch::Ramp { - offset: offset + offsets.draw_data, + draw_data_offset: offset + offsets.draw_data, stops, } } Patch::GlyphRun { index } => Patch::GlyphRun { index: index + glyph_runs_base, }, + Patch::Image { + image, + draw_data_offset, + } => Patch::Image { + image: image.clone(), + draw_data_offset: *draw_data_offset + offsets.draw_data, + }, })); self.color_stops.extend_from_slice(&other.color_stops); if let Some(transform) = *transform { @@ -250,8 +262,8 @@ impl Encoding { todo!("sweep gradients aren't supported yet!") } }, - BrushRef::Image(_) => { - todo!("images aren't supported yet!") + BrushRef::Image(image) => { + self.encode_image(image, alpha); } } } @@ -290,6 +302,22 @@ impl Encoding { .extend_from_slice(bytemuck::bytes_of(&gradient)); } + /// Encodes an image brush. + pub fn encode_image(&mut self, image: &Image, _alpha: f32) { + // TODO: feed the alpha multiplier through the full pipeline for consistency + // with other brushes? + self.patches.push(Patch::Image { + image: image.clone(), + draw_data_offset: self.draw_data.len(), + }); + self.draw_tags.push(DrawTag::IMAGE); + self.draw_data + .extend_from_slice(bytemuck::bytes_of(&DrawImage { + xy: 0, + width_height: (image.width << 16) | (image.height & 0xFFFF), + })); + } + /// Encodes a begin clip command. pub fn encode_begin_clip(&mut self, blend_mode: BlendMode, alpha: f32) { use super::DrawBeginClip; @@ -329,7 +357,7 @@ impl Encoding { self.color_stops.extend(color_stops); } self.patches.push(Patch::Ramp { - offset, + draw_data_offset: offset, stops: stops_start..self.color_stops.len(), }); } diff --git a/src/encoding/image_cache.rs b/src/encoding/image_cache.rs new file mode 100644 index 0000000..cd2734e --- /dev/null +++ b/src/encoding/image_cache.rs @@ -0,0 +1,92 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use guillotiere::{size2, AtlasAllocator}; +use peniko::Image; +use std::collections::{hash_map::Entry, HashMap}; + +const DEFAULT_ATLAS_SIZE: i32 = 1024; +const MAX_ATLAS_SIZE: i32 = 8192; + +pub struct Images<'a> { + pub width: u32, + pub height: u32, + pub images: &'a [(Image, u32, u32)], +} + +pub struct ImageCache { + atlas: AtlasAllocator, + /// Map from image blob id to atlas location. + map: HashMap, + /// List of all allocated images with associated atlas location. + images: Vec<(Image, u32, u32)>, +} + +impl Default for ImageCache { + fn default() -> Self { + Self::new() + } +} + +impl ImageCache { + pub fn new() -> Self { + Self { + atlas: AtlasAllocator::new(size2(DEFAULT_ATLAS_SIZE, DEFAULT_ATLAS_SIZE)), + map: Default::default(), + images: Default::default(), + } + } + + pub fn images(&self) -> Images { + Images { + width: self.atlas.size().width as u32, + height: self.atlas.size().height as u32, + images: &self.images, + } + } + + pub fn bump_size(&mut self) -> bool { + let new_size = self.atlas.size().width * 2; + if new_size > MAX_ATLAS_SIZE { + return false; + } + self.atlas = AtlasAllocator::new(size2(new_size, new_size)); + self.map.clear(); + self.images.clear(); + true + } + + pub fn clear(&mut self) { + self.atlas.clear(); + self.map.clear(); + self.images.clear(); + } + + pub fn get_or_insert(&mut self, image: &Image) -> Option<(u32, u32)> { + match self.map.entry(image.data.id()) { + Entry::Occupied(occupied) => Some(*occupied.get()), + Entry::Vacant(vacant) => { + let alloc = self + .atlas + .allocate(size2(image.width as _, image.height as _))?; + let x = alloc.rectangle.min.x as u32; + let y = alloc.rectangle.min.y as u32; + self.images.push((image.clone(), x, y)); + Some(*vacant.insert((x, y))) + } + } + } +} diff --git a/src/encoding/resolve.rs b/src/encoding/resolve.rs index d36a05b..58b942f 100644 --- a/src/encoding/resolve.rs +++ b/src/encoding/resolve.rs @@ -18,9 +18,11 @@ use std::ops::Range; use bytemuck::{Pod, Zeroable}; use moscato::pinot::FontRef; +use peniko::Image; use super::{ glyph_cache::{CachedRange, GlyphCache, GlyphKey}, + image_cache::{ImageCache, Images}, ramp_cache::{RampCache, Ramps}, DrawTag, Encoding, PathTag, StreamOffsets, Transform, }; @@ -144,6 +146,8 @@ pub struct Resolver { glyph_ranges: Vec, glyph_cx: GlyphContext, ramp_cache: RampCache, + image_cache: ImageCache, + pending_images: Vec, patches: Vec, } @@ -159,8 +163,9 @@ impl Resolver { &'a mut self, encoding: &Encoding, packed: &mut Vec, - ) -> (Layout, Ramps<'a>) { + ) -> (Layout, Ramps<'a>, Images<'a>) { let sizes = self.resolve_patches(encoding); + self.resolve_pending_images(); let data = packed; data.clear(); let mut layout = Layout::default(); @@ -261,6 +266,26 @@ impl Resolver { pos = *draw_data_offset + 4; } ResolvedPatch::GlyphRun { .. } => {} + ResolvedPatch::Image { + index, + draw_data_offset, + } => { + if pos < *draw_data_offset { + data.extend_from_slice(&encoding.draw_data[pos..*draw_data_offset]); + } + if let Some((x, y)) = self.pending_images[*index].xy { + let xy = (x << 16) | y; + data.extend_from_slice(bytemuck::bytes_of(&xy)); + pos = *draw_data_offset + 4; + } else { + // If we get here, we failed to allocate a slot for this image in the atlas. + // In this case, let's zero out the dimensions so we don't attempt to render + // anything. + // TODO: a better strategy: texture array? downsample large images? + data.extend_from_slice(&[0u8; 8]); + pos = *draw_data_offset + 8; + } + } } } if pos < stream.len() { @@ -336,21 +361,26 @@ impl Resolver { } layout.n_draw_objects = layout.n_paths; assert_eq!(capacity, data.len()); - (layout, self.ramp_cache.ramps()) + (layout, self.ramp_cache.ramps(), self.image_cache.images()) } fn resolve_patches(&mut self, encoding: &Encoding) -> StreamOffsets { self.ramp_cache.advance(); self.glyph_cache.clear(); self.glyph_ranges.clear(); + self.image_cache.clear(); + self.pending_images.clear(); self.patches.clear(); let mut sizes = StreamOffsets::default(); for patch in &encoding.patches { match patch { - Patch::Ramp { offset, stops } => { + Patch::Ramp { + draw_data_offset, + stops, + } => { let ramp_id = self.ramp_cache.add(&encoding.color_stops[stops.clone()]); self.patches.push(ResolvedPatch::Ramp { - draw_data_offset: *offset + sizes.draw_data, + draw_data_offset: *draw_data_offset + sizes.draw_data, ramp_id, }); } @@ -414,10 +444,50 @@ impl Resolver { transform, }); } + Patch::Image { + draw_data_offset, + image, + } => { + let index = self.pending_images.len(); + self.pending_images.push(PendingImage { + image: image.clone(), + xy: None, + }); + self.patches.push(ResolvedPatch::Image { + index, + draw_data_offset: *draw_data_offset + sizes.draw_data, + }); + } } } sizes } + + fn resolve_pending_images(&mut self) { + self.image_cache.clear(); + 'outer: loop { + // Loop over the images, attempting to allocate them all into the atlas. + for pending_image in &mut self.pending_images { + if let Some(xy) = self.image_cache.get_or_insert(&pending_image.image) { + pending_image.xy = Some(xy); + } else { + // We failed to allocate. Try to bump the atlas size. + if self.image_cache.bump_size() { + // We were able to increase the atlas size. Restart the outer loop. + continue 'outer; + } else { + // If the atlas is already maximum size, there's nothing we can do. Set + // the xy field to None so this image isn't rendered and then carry on-- + // other images might still fit. + pending_image.xy = None; + } + } + } + // If we made it here, we've either successfully allocated all images or we reached + // the maximum atlas size. + break; + } + } } #[derive(Clone)] @@ -426,7 +496,7 @@ pub enum Patch { /// Gradient ramp resource. Ramp { /// Byte offset to the ramp id in the draw data stream. - offset: usize, + draw_data_offset: usize, /// Range of the gradient stops in the resource set. stops: Range, }, @@ -435,6 +505,20 @@ pub enum Patch { /// Index in the glyph run buffer. index: usize, }, + /// Image resource. + Image { + /// Offset to the atlas coordinates in the draw data stream. + draw_data_offset: usize, + /// Underlying image data. + image: Image, + }, +} + +/// Image to be allocated in the atlas. +#[derive(Clone, Debug)] +struct PendingImage { + image: Image, + xy: Option<(u32, u32)>, } #[derive(Clone, Debug)] @@ -453,6 +537,12 @@ enum ResolvedPatch { /// Global transform. transform: Transform, }, + Image { + /// Index of pending image element. + index: usize, + /// Offset to the atlas location in the draw data stream. + draw_data_offset: usize, + }, } fn slice_size_in_bytes(slice: &[T], extra: usize) -> usize { diff --git a/src/engine.rs b/src/engine.rs index 9250265..805b915 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -89,6 +89,7 @@ pub enum Command { Upload(BufProxy, Vec), UploadUniform(BufProxy, Vec), UploadImage(ImageProxy, Vec), + WriteImage(ImageProxy, [u32; 4], Vec), // Discussion question: third argument is vec of resources? // Maybe use tricks to make more ergonomic? // Alternative: provide bufs & images as separate sequences @@ -275,11 +276,6 @@ impl Engine { self.bind_map.insert_buf(buf_proxy, buf); } Command::UploadImage(image_proxy, bytes) => { - let buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: None, - contents: bytes, - usage: wgpu::BufferUsages::COPY_SRC, - }); let format = image_proxy.format.to_wgpu(); let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, @@ -305,21 +301,19 @@ impl Engine { array_layer_count: None, format: Some(TextureFormat::Rgba8Unorm), }); - encoder.copy_buffer_to_texture( - wgpu::ImageCopyBuffer { - buffer: &buf, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(image_proxy.width * 4), - rows_per_image: None, - }, - }, + queue.write_texture( wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, aspect: TextureAspect::All, }, + bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(image_proxy.width * 4), + rows_per_image: None, + }, wgpu::Extent3d { width: image_proxy.width, height: image_proxy.height, @@ -329,6 +323,29 @@ impl Engine { self.bind_map .insert_image(image_proxy.id, texture, texture_view) } + Command::WriteImage(proxy, [x, y, width, height], data) => { + if let Ok((texture, _)) = self.bind_map.get_or_create_image(*proxy, device) { + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d { x: *x, y: *y, z: 0 }, + aspect: TextureAspect::All, + }, + &data[..], + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(*width * 4), + rows_per_image: None, + }, + wgpu::Extent3d { + width: *width, + height: *height, + depth_or_array_layers: 1, + }, + ); + } + } Command::Dispatch(shader_id, wg_size, bindings) => { // println!("dispatching {:?} with {} bindings", wg_size, bindings.len()); let shader = &self.shaders[shader_id.0]; @@ -444,6 +461,19 @@ impl Recording { image_proxy } + pub fn write_image( + &mut self, + image: ImageProxy, + x: u32, + y: u32, + width: u32, + height: u32, + data: impl Into>, + ) { + let data = data.into(); + self.push(Command::WriteImage(image, [x, y, width, height], data)); + } + pub fn dispatch(&mut self, shader: ShaderId, wg_size: (u32, u32, u32), resources: R) where R: IntoIterator, @@ -716,6 +746,44 @@ impl BindMap { } } } + + fn get_or_create_image( + &mut self, + proxy: ImageProxy, + device: &Device, + ) -> Result<&(Texture, TextureView), Error> { + match self.image_map.entry(proxy.id) { + Entry::Occupied(occupied) => Ok(occupied.into_mut()), + Entry::Vacant(vacant) => { + let format = proxy.format.to_wgpu(); + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: proxy.width, + height: proxy.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + format, + view_formats: &[], + }); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + dimension: Some(TextureViewDimension::D2), + aspect: TextureAspect::All, + mip_level_count: None, + base_mip_level: 0, + base_array_layer: 0, + array_layer_count: None, + format: Some(TextureFormat::Rgba8Unorm), + }); + Ok(vacant.insert((texture, texture_view))) + } + } + } } const SIZE_CLASS_BITS: u32 = 1; diff --git a/src/render.rs b/src/render.rs index 4ad5083..d96815e 100644 --- a/src/render.rs +++ b/src/render.rs @@ -33,6 +33,7 @@ struct FineResources { ptcl_buf: ResourceProxy, gradient_image: ResourceProxy, info_bin_data_buf: ResourceProxy, + image_atlas: ResourceProxy, out_image: ImageProxy, } @@ -216,7 +217,7 @@ impl Render { let mut recording = Recording::default(); let mut resolver = Resolver::new(); let mut packed = vec![]; - let (layout, ramps) = resolver.resolve(encoding, &mut packed); + let (layout, ramps, images) = resolver.resolve(encoding, &mut packed); let gradient_image = if ramps.height == 0 { ResourceProxy::new_image(1, 1, ImageFormat::Rgba8) } else { @@ -228,6 +229,11 @@ impl Render { data, )) }; + let image_atlas = if images.images.is_empty() { + ImageProxy::new(1, 1, ImageFormat::Rgba8) + } else { + ImageProxy::new(images.width, images.height, ImageFormat::Rgba8) + }; // TODO: calculate for real when we do rectangles let n_pathtag = layout.path_tags(&packed).len(); let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG); @@ -251,6 +257,16 @@ impl Render { ptcl_size: self.ptcl_size, layout: layout, }; + for image in images.images { + recording.write_image( + image_atlas, + image.1, + image.2, + image.0.width, + image.0.height, + image.0.data.data(), + ); + } // println!("{:?}", config); let scene_buf = ResourceProxy::Buf(recording.upload("scene", packed)); let config_buf = @@ -504,6 +520,7 @@ impl Render { ptcl_buf, gradient_image, info_bin_data_buf, + image_atlas: ResourceProxy::Image(image_atlas), out_image, }); if robust { @@ -527,6 +544,7 @@ impl Render { fine.ptcl_buf, fine.gradient_image, fine.info_bin_data_buf, + fine.image_atlas, ], ); recording.free_resource(fine.config_buf); @@ -534,6 +552,7 @@ impl Render { recording.free_resource(fine.segments_buf); recording.free_resource(fine.ptcl_buf); recording.free_resource(fine.gradient_image); + recording.free_resource(fine.image_atlas); recording.free_resource(fine.info_bin_data_buf); } diff --git a/src/scene.rs b/src/scene.rs index 29fba23..1d2b345 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -15,7 +15,7 @@ // Also licensed under MIT license, at your choice. use peniko::kurbo::{Affine, Rect, Shape}; -use peniko::{BlendMode, BrushRef, Color, Fill, Font, Stroke, StyleRef}; +use peniko::{BlendMode, BrushRef, Color, Fill, Font, Image, Stroke, StyleRef}; use crate::encoding::{Encoding, Glyph, GlyphRun, Patch, Transform}; @@ -168,6 +168,17 @@ impl<'a> SceneBuilder<'a> { } } + /// Draws an image at its natural size with the given transform. + pub fn draw_image(&mut self, image: &Image, transform: Affine) { + self.fill( + Fill::NonZero, + transform, + image, + None, + &Rect::new(0.0, 0.0, image.width as f64, image.height as f64), + ); + } + /// Returns a builder for encoding a glyph run. pub fn draw_glyphs(&mut self, font: &Font) -> DrawGlyphs { DrawGlyphs::new(self.scene, font) diff --git a/src/shaders.rs b/src/shaders.rs index 4c7b3da..01c3b38 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -351,6 +351,7 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result Date: Fri, 10 Mar 2023 01:42:50 -0500 Subject: [PATCH 2/6] move atlas rect to info Atlas offset and image size were originally stored in the ptcl but are not tile dependent. Moving these to info saves 8 bytes per image tile. --- shader/coarse.wgsl | 14 +++++--------- shader/draw_leaf.wgsl | 6 ++++-- shader/fine.wgsl | 6 +++--- shader/shared/drawtag.wgsl | 2 +- src/encoding/draw.rs | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/shader/coarse.wgsl b/shader/coarse.wgsl index 6eced0d..58fe409 100644 --- a/shader/coarse.wgsl +++ b/shader/coarse.wgsl @@ -126,13 +126,11 @@ fn write_grad(ty: u32, index: u32, info_offset: u32) { cmd_offset += 3u; } -fn write_image(xy: u32, width_height: u32, info_offset: u32) { - alloc_cmd(4u); +fn write_image(info_offset: u32) { + alloc_cmd(2u); ptcl[cmd_offset] = CMD_IMAGE; ptcl[cmd_offset + 1u] = info_offset; - ptcl[cmd_offset + 2u] = xy; - ptcl[cmd_offset + 3u] = width_height; - cmd_offset += 4u; + cmd_offset += 2u; } fn write_begin_clip() { @@ -387,12 +385,10 @@ fn main( } } // DRAWTAG_FILL_IMAGE - case 0x1c8u: { + case 0x248u: { let linewidth = bitcast(info_bin_data[di]); if write_path(tile, linewidth) { - let xy = scene[dd]; - let width_height = scene[dd + 1u]; - write_image(xy, width_height, di + 1u); + write_image(di + 1u); } } // DRAWTAG_BEGIN_CLIP diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index d0873de..b149d27 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -125,7 +125,7 @@ fn main( linewidth *= sqrt(abs(matrx.x * matrx.w - matrx.y * matrx.z)); } switch tag_word { - // DRAWTAG_FILL_COLOR, DRAWTAG_FILL_IMAGE + // DRAWTAG_FILL_COLOR case 0x44u: { info[di] = bitcast(linewidth); } @@ -172,7 +172,7 @@ fn main( info[di + 10u] = bitcast(roff); } // DRAWTAG_FILL_IMAGE - case 0x1c8u: { + case 0x248u: { info[di] = bitcast(linewidth); let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z); let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x); @@ -183,6 +183,8 @@ fn main( info[di + 4u] = bitcast(inv_mat.w); info[di + 5u] = bitcast(inv_tr.x); info[di + 6u] = bitcast(inv_tr.y); + info[di + 7u] = scene[dd]; + info[di + 8u] = scene[dd + 1u]; } default: {} } diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 38e6b96..8292090 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -86,14 +86,14 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { fn read_image(cmd_ix: u32) -> CmdImage { let info_offset = ptcl[cmd_ix + 1u]; - let xy = ptcl[cmd_ix + 2u]; - let width_height = ptcl[cmd_ix + 3u]; let m0 = bitcast(info[info_offset]); let m1 = bitcast(info[info_offset + 1u]); let m2 = bitcast(info[info_offset + 2u]); let m3 = bitcast(info[info_offset + 3u]); let matrx = vec4(m0, m1, m2, m3); let xlat = vec2(bitcast(info[info_offset + 4u]), bitcast(info[info_offset + 5u])); + let xy = info[info_offset + 6u]; + let width_height = info[info_offset + 7u]; // The following are not intended to be bitcasts let x = f32(xy >> 16u); let y = f32(xy & 0xffffu); @@ -307,7 +307,7 @@ fn main( rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; } } - cmd_ix += 4u; + cmd_ix += 2u; } // CMD_BEGIN_CLIP case 9u: { diff --git a/shader/shared/drawtag.wgsl b/shader/shared/drawtag.wgsl index d2646b8..0d532d1 100644 --- a/shader/shared/drawtag.wgsl +++ b/shader/shared/drawtag.wgsl @@ -19,7 +19,7 @@ let DRAWTAG_NOP = 0u; let DRAWTAG_FILL_COLOR = 0x44u; let DRAWTAG_FILL_LIN_GRADIENT = 0x114u; let DRAWTAG_FILL_RAD_GRADIENT = 0x2dcu; -let DRAWTAG_FILL_IMAGE = 0x1c8u; +let DRAWTAG_FILL_IMAGE = 0x248u; let DRAWTAG_BEGIN_CLIP = 0x9u; let DRAWTAG_END_CLIP = 0x21u; diff --git a/src/encoding/draw.rs b/src/encoding/draw.rs index fb3aa1f..64fdd16 100644 --- a/src/encoding/draw.rs +++ b/src/encoding/draw.rs @@ -38,7 +38,7 @@ impl DrawTag { pub const RADIAL_GRADIENT: Self = Self(0x2dc); /// Image fill. - pub const IMAGE: Self = Self(0x1c8); + pub const IMAGE: Self = Self(0x248); /// Begin layer/clip. pub const BEGIN_CLIP: Self = Self(0x9); From d12b711fe1ef1af4dc2c7979ccacda095cf7f607 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Fri, 10 Mar 2023 02:04:21 -0500 Subject: [PATCH 3/6] premultiply alpha before filtering --- shader/fine.wgsl | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 8292090..06436f7 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -297,12 +297,11 @@ fn main( if all(atlas_uv < atlas_extents) { let uv_quad = vec4(max(floor(atlas_uv), image.atlas_offset), min(ceil(atlas_uv), atlas_extents)); let uv_frac = fract(atlas_uv); - let a = textureLoad(image_atlas, vec2(uv_quad.xy), 0); - let b = textureLoad(image_atlas, vec2(uv_quad.xw), 0); - let c = textureLoad(image_atlas, vec2(uv_quad.zy), 0); - let d = textureLoad(image_atlas, vec2(uv_quad.zw), 0); - let color = mix(mix(a, b, uv_frac.y), mix(c, d, uv_frac.y), uv_frac.x); - let fg_rgba = vec4(color.rgb * color.a, color.a); + let a = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.xy), 0)); + let b = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.xw), 0)); + let c = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.zy), 0)); + let d = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.zw), 0)); + let fg_rgba = mix(mix(a, b, uv_frac.y), mix(c, d, uv_frac.y), uv_frac.x); let fg_i = fg_rgba * area[i]; rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; } @@ -370,3 +369,7 @@ fn main( } #endif } + +fn premul_alpha(rgba: vec4) -> vec4 { + return vec4(rgba.rgb * rgba.a, rgba.a); +} From 37f05b853c0fbc64454fdfdf3463ca773020a499 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Fri, 10 Mar 2023 15:14:34 -0500 Subject: [PATCH 4/6] Address initial review feedback * Add comment about naughty ptr2int cast * Change Option to anyhow::Result in test scene ImageCache * Replace magic constant with pixel stride from selected format --- examples/scenes/src/images.rs | 24 +++++++++++------------- examples/scenes/src/test_scenes.rs | 2 ++ src/engine.rs | 13 +++++++++---- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/examples/scenes/src/images.rs b/examples/scenes/src/images.rs index 2086351..27b8978 100644 --- a/examples/scenes/src/images.rs +++ b/examples/scenes/src/images.rs @@ -16,38 +16,36 @@ impl ImageCache { Self::default() } - pub fn from_file(&mut self, path: impl AsRef) -> Option { + pub fn from_file(&mut self, path: impl AsRef) -> anyhow::Result { let path = path.as_ref(); if let Some(image) = self.files.get(path) { - Some(image.clone()) + Ok(image.clone()) } else { - let data = std::fs::read(path).ok()?; + let data = std::fs::read(path)?; let image = decode_image(&data)?; self.files.insert(path.to_owned(), image.clone()); - Some(image) + Ok(image) } } - pub fn from_bytes(&mut self, key: usize, bytes: &[u8]) -> Option { + pub fn from_bytes(&mut self, key: usize, bytes: &[u8]) -> anyhow::Result { if let Some(image) = self.bytes.get(&key) { - Some(image.clone()) + Ok(image.clone()) } else { let image = decode_image(bytes)?; self.bytes.insert(key, image.clone()); - Some(image) + Ok(image) } } } -fn decode_image(data: &[u8]) -> Option { +fn decode_image(data: &[u8]) -> anyhow::Result { let image = image::io::Reader::new(std::io::Cursor::new(data)) - .with_guessed_format() - .ok()? - .decode() - .ok()?; + .with_guessed_format()? + .decode()?; let width = image.width(); let height = image.height(); let data = Arc::new(image.into_rgba8().into_vec()); let blob = Blob::new(data); - Some(Image::new(blob, Format::Rgba8, width, height)) + Ok(Image::new(blob, Format::Rgba8, width, height)) } diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 721b921..41e528b 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -101,6 +101,8 @@ fn cardioid_and_friends(sb: &mut SceneBuilder, _: &mut SceneParams) { } fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) { + // Uses the static array address as a cache key for expedience. Real code + // should use a better strategy. let piet_logo = params .images .from_bytes(PIET_LOGO_IMAGE.as_ptr() as usize, PIET_LOGO_IMAGE) diff --git a/src/engine.rs b/src/engine.rs index 805b915..779dc4c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -299,7 +299,7 @@ impl Engine { base_mip_level: 0, base_array_layer: 0, array_layer_count: None, - format: Some(TextureFormat::Rgba8Unorm), + format: Some(format), }); queue.write_texture( wgpu::ImageCopyTexture { @@ -311,7 +311,9 @@ impl Engine { bytes, wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new(image_proxy.width * 4), + bytes_per_row: NonZeroU32::new( + image_proxy.width * format.describe().block_size as u32, + ), rows_per_image: None, }, wgpu::Extent3d { @@ -325,6 +327,7 @@ impl Engine { } Command::WriteImage(proxy, [x, y, width, height], data) => { if let Ok((texture, _)) = self.bind_map.get_or_create_image(*proxy, device) { + let format = proxy.format.to_wgpu(); queue.write_texture( wgpu::ImageCopyTexture { texture: &texture, @@ -335,7 +338,9 @@ impl Engine { &data[..], wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new(*width * 4), + bytes_per_row: NonZeroU32::new( + *width * format.describe().block_size as u32, + ), rows_per_image: None, }, wgpu::Extent3d { @@ -778,7 +783,7 @@ impl BindMap { base_mip_level: 0, base_array_layer: 0, array_layer_count: None, - format: Some(TextureFormat::Rgba8Unorm), + format: Some(proxy.format.to_wgpu()), }); Ok(vacant.insert((texture, texture_view))) } From 923c949e95d2788d775eef4fcf8043ca647471b2 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Fri, 10 Mar 2023 21:00:42 -0500 Subject: [PATCH 5/6] Remove some unused imports --- examples/scenes/src/test_scenes.rs | 4 +--- src/engine.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 41e528b..ff3ff60 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -1,7 +1,5 @@ -use std::sync::Arc; - use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet}; -use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect, Vec2}; +use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; use vello::peniko::*; use vello::*; diff --git a/src/engine.rs b/src/engine.rs index 779dc4c..1e6253d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -22,8 +22,8 @@ use std::{ }; use wgpu::{ - util::DeviceExt, BindGroup, BindGroupLayout, Buffer, BufferUsages, ComputePipeline, Device, - Queue, Texture, TextureAspect, TextureFormat, TextureUsages, TextureView, TextureViewDimension, + BindGroup, BindGroupLayout, Buffer, BufferUsages, ComputePipeline, Device, Queue, Texture, + TextureAspect, TextureUsages, TextureView, TextureViewDimension, }; pub type Error = Box; From a6307a2520d5cb3590d6683879be17471024997b Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 15 Mar 2023 08:37:00 -0400 Subject: [PATCH 6/6] predicate image loads on non-zero mask --- shader/fine.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 06436f7..ed47bcb 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -294,7 +294,7 @@ fn main( let my_xy = vec2(xy.x + f32(i), xy.y); let atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat + image.atlas_offset; // This currently clips to the image bounds. TODO: extend modes - if all(atlas_uv < atlas_extents) { + if all(atlas_uv < atlas_extents) && area[i] != 0.0 { let uv_quad = vec4(max(floor(atlas_uv), image.atlas_offset), min(ceil(atlas_uv), atlas_extents)); let uv_frac = fract(atlas_uv); let a = premul_alpha(textureLoad(image_atlas, vec2(uv_quad.xy), 0));