From 8eabc12a729c4fa2be1fd61ab713ce0a0064a7d9 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Thu, 2 Mar 2023 09:26:31 -0800 Subject: [PATCH 1/6] Add a clear_color uniform Introduced an RGBA8 config parameter to apply as a base blend color in the fine stage of the full pipeline. --- shader/fine.wgsl | 3 +++ shader/shared/config.wgsl | 5 +++++ src/encoding/packed.rs | 2 ++ src/render.rs | 6 ++++++ 4 files changed, 16 insertions(+) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index e0d885b..bfcfb66 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -187,6 +187,9 @@ fn main( let xy = vec2(f32(global_id.x * PIXELS_PER_THREAD), f32(global_id.y)); #ifdef full var rgba: array, PIXELS_PER_THREAD>; + for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { + rgba[i] = unpack4x8unorm(config.clear_color).wzyx; + } var blend_stack: array, BLEND_STACK_SPLIT>; var clip_depth = 0u; var area: array; diff --git a/shader/shared/config.wgsl b/shader/shared/config.wgsl index f586f47..af5ff3d 100644 --- a/shader/shared/config.wgsl +++ b/shader/shared/config.wgsl @@ -7,6 +7,11 @@ struct Config { target_width: u32, target_height: u32, + // The initial color applied to the pixels in a tile during the fine stage. + // This is only used in the full pipeline. The format is packed RGBA8 in MSB + // order. + clear_color: u32, + n_drawobj: u32, n_path: u32, n_clip: u32, diff --git a/src/encoding/packed.rs b/src/encoding/packed.rs index 55ffc17..2ee7c67 100644 --- a/src/encoding/packed.rs +++ b/src/encoding/packed.rs @@ -60,6 +60,8 @@ pub struct Config { pub target_width: u32, /// Height of the target in pixels. pub target_height: u32, + /// The initial background color applied to the target + pub clear_color: u32, /// Layout of packed scene data. pub layout: Layout, /// Size of binning buffer allocation (in u32s). diff --git a/src/render.rs b/src/render.rs index 5874dbc..7c06908 100644 --- a/src/render.rs +++ b/src/render.rs @@ -5,6 +5,7 @@ use bytemuck::{Pod, Zeroable}; use crate::{ encoding::Encoding, engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy}, + peniko::Color, shaders::{self, FullShaders, Shaders}, Scene, }; @@ -21,6 +22,8 @@ pub struct Render { ptcl_size: u32, width_in_tiles: u32, height_in_tiles: u32, + /// The background color applied to the target + clear_color: Color, fine: Option, } @@ -61,6 +64,7 @@ struct Config { height_in_tiles: u32, target_width: u32, target_height: u32, + clear_color: u32, n_drawobj: u32, n_path: u32, n_clip: u32, @@ -216,6 +220,7 @@ impl Render { ptcl_size: (1 << 25) / 4 as u32, width_in_tiles: 0, height_in_tiles: 0, + clear_color: Color::BLACK, fine: None, } } @@ -265,6 +270,7 @@ impl Render { height_in_tiles: new_height / 16, target_width: width, target_height: height, + clear_color: self.clear_color.to_premul_u32(), binning_size: self.binning_info_size - info_size, tiles_size: self.tiles_size, segments_size: self.segments_size, From 05fa8c7c39ef59d22251457f1e87f588e0ee895b Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Thu, 2 Mar 2023 11:22:19 -0800 Subject: [PATCH 2/6] RenderParams struct for render-time options The texture and surface render API now takes render-time parameters (such as clear color, target width/height) as a RenderParams struct. The examples have been updated to demonstrate this. The with_winit example now accepts a clear color as a command line option. --- examples/headless/src/main.rs | 7 +++- examples/with_bevy/src/main.rs | 9 +++-- examples/with_winit/src/main.rs | 71 +++++++++++++++++++++++++++++---- src/lib.rs | 35 +++++++++++----- src/render.rs | 30 ++++++-------- 5 files changed, 111 insertions(+), 41 deletions(-) diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index c82318e..0a6ea17 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -126,6 +126,11 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { (Some(x), Some(y)) => (x.try_into()?, y.try_into()?), } }; + let params = vello::RenderParams { + clear_color: vello::peniko::Color::BLACK, + width, + height, + }; let mut scene = Scene::new(); let mut builder = SceneBuilder::for_scene(&mut scene); builder.append(&fragment, Some(transform)); @@ -147,7 +152,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { }); let view = target.create_view(&wgpu::TextureViewDescriptor::default()); renderer - .render_to_texture(&device, &queue, &scene, &view, width, height) + .render_to_texture(&device, &queue, &scene, &view, ¶ms) .or_else(|_| bail!("Got non-Send/Sync error from rendering"))?; // (width * 4).next_multiple_of(256) let padded_byte_width = { diff --git a/examples/with_bevy/src/main.rs b/examples/with_bevy/src/main.rs index e46c7c8..690d980 100644 --- a/examples/with_bevy/src/main.rs +++ b/examples/with_bevy/src/main.rs @@ -46,7 +46,11 @@ fn render_scenes( ) { for scene in &mut scenes { let gpu_image = gpu_images.get(&scene.1).unwrap(); - + let params = vello::RenderParams { + clear_color: vello::peniko::Color::AQUAMARINE, + width: gpu_image.size.x as u32, + height: gpu_image.size.y as u32, + }; renderer .0 .render_to_texture( @@ -54,8 +58,7 @@ fn render_scenes( &*queue, &scene.0, &gpu_image.texture_view, - gpu_image.size.x as u32, - gpu_image.size.y as u32, + ¶ms, ) .unwrap(); } diff --git a/examples/with_winit/src/main.rs b/examples/with_winit/src/main.rs index 5d42e78..6d2aa5c 100644 --- a/examples/with_winit/src/main.rs +++ b/examples/with_winit/src/main.rs @@ -17,12 +17,16 @@ use std::time::Instant; use anyhow::Result; -use clap::{CommandFactory, Parser}; +use clap::{ + builder::{PossibleValuesParser, TypedValueParser as _}, + CommandFactory, Parser, +}; use scenes::{SceneParams, SceneSet, SimpleText}; use vello::SceneFragment; use vello::{ block_on_wgpu, kurbo::{Affine, Vec2}, + peniko::Color, util::RenderContext, Renderer, Scene, SceneBuilder, }; @@ -49,10 +53,51 @@ struct Args { /// Switch between scenes with left and right arrow keys #[arg(long)] scene: Option, + #[arg( + long, + default_value_t = ClearColor::Black, + value_parser = PossibleValuesParser::new(["black", "white", "aquamarine", "crimson"]) + .map(|s| s.parse::().unwrap()) + )] + clear_color: ClearColor, #[command(flatten)] args: scenes::Arguments, } +#[derive(Debug, Clone)] +enum ClearColor { + Black, + White, + Crimson, + Aquamarine, +} + +impl std::fmt::Display for ClearColor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Self::Black => "black", + Self::White => "white", + Self::Crimson => "crimson", + Self::Aquamarine => "aquamarine", + }; + s.fmt(f) + } +} + +impl std::str::FromStr for ClearColor { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "black" => Ok(Self::Black), + "white" => Ok(Self::White), + "crimson" => Ok(Self::Crimson), + "aquamarine" => Ok(Self::Aquamarine), + _ => Err(format!("invalid color: {s}")), + } + } +} + async fn run(event_loop: EventLoop, window: Window, args: Args, mut scenes: SceneSet) { use winit::{event::*, event_loop::ControlFlow}; let mut render_cx = RenderContext::new().unwrap(); @@ -75,6 +120,12 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s if let Some(set_scene) = args.scene { scene_ix = set_scene; } + let clear_color = match args.clear_color { + ClearColor::Black => Color::BLACK, + ClearColor::White => Color::WHITE, + ClearColor::Crimson => Color::CRIMSON, + ClearColor::Aquamarine => Color::AQUAMARINE, + }; let mut prev_scene_ix = scene_ix - 1; event_loop.run(move |event, _, control_flow| match event { Event::WindowEvent { @@ -155,6 +206,12 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s let height = surface.config.height; let device_handle = &render_cx.devices[surface.dev_id]; + let render_params = vello::RenderParams { + clear_color, + width, + height, + }; + // Allow looping forever scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); let example_scene = &mut scenes.scenes[scene_ix as usize]; @@ -164,16 +221,16 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s window.set_title(&format!("Vello demo - {}", example_scene.config.name)); } let mut builder = SceneBuilder::for_fragment(&mut fragment); - let mut params = SceneParams { + let mut scene_params = SceneParams { time: start.elapsed().as_secs_f64(), text: &mut simple_text, resolution: None, }; - (example_scene.function)(&mut builder, &mut params); + (example_scene.function)(&mut builder, &mut scene_params); builder.finish(); let mut builder = SceneBuilder::for_scene(&mut scene); let mut transform = transform; - if let Some(resolution) = params.resolution { + if let Some(resolution) = scene_params.resolution { let factor = Vec2::new(surface.config.width as f64, surface.config.height as f64); let scale_factor = (factor.x / resolution.x).min(factor.y / resolution.y); transform = transform * Affine::scale(scale_factor); @@ -193,8 +250,7 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s &device_handle.queue, &scene, &surface_texture, - width, - height, + &render_params, ), ) .expect("failed to render to surface"); @@ -208,8 +264,7 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s &device_handle.queue, &scene, &surface_texture, - width, - height, + &render_params, ) .expect("failed to render to surface"); surface_texture.present(); diff --git a/src/lib.rs b/src/lib.rs index 4116f49..5cb4b1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,8 +52,19 @@ pub struct Renderer { target: Option, } +/// Parameters used in a single render that are configurable by the client. +pub struct RenderParams { + /// The background color applied to the target. This value is only applicable to the full + /// pipeline. + pub clear_color: peniko::Color, + + /// Dimensions of the rasterization target + pub width: u32, + pub height: u32, +} + impl Renderer { - /// Creates a new renderer for the specified device. + /// Creates a new renderer for the specified device with default options. pub fn new(device: &Device) -> Result { let mut engine = Engine::new(); let shaders = shaders::full_shaders(device, &mut engine)?; @@ -77,10 +88,9 @@ impl Renderer { queue: &Queue, scene: &Scene, texture: &TextureView, - width: u32, - height: u32, + params: &RenderParams, ) -> Result<()> { - let (recording, target) = render::render_full(scene, &self.shaders, width, height); + let (recording, target) = render::render_full(scene, &self.shaders, params); let external_resources = [ExternalResource::Image( *target.as_image().unwrap(), texture, @@ -103,9 +113,12 @@ impl Renderer { queue: &Queue, scene: &Scene, surface: &SurfaceTexture, + params: &RenderParams, width: u32, height: u32, ) -> Result<()> { + let width = params.width; + let height = params.height; let mut target = self .target .take() @@ -115,7 +128,7 @@ impl Renderer { if target.width != width || target.height != height { target = TargetTexture::new(device, width, height); } - self.render_to_texture(device, queue, scene, &target.view, width, height)?; + self.render_to_texture(device, queue, scene, &target.view, ¶ms); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { @@ -177,12 +190,11 @@ impl Renderer { queue: &Queue, scene: &Scene, texture: &TextureView, - width: u32, - height: u32, + params: &RenderParams, ) -> Result<()> { let mut render = Render::new(); let encoding = scene.data(); - let recording = render.render_encoding_coarse(encoding, &self.shaders, width, height, true); + let recording = render.render_encoding_coarse(encoding, &self.shaders, params, true); let target = render.out_image(); let bump_buf = render.bump_buf(); self.engine.run_recording(device, queue, &recording, &[])?; @@ -216,9 +228,10 @@ impl Renderer { queue: &Queue, scene: &Scene, surface: &SurfaceTexture, - width: u32, - height: u32, + params: &RenderParams, ) -> Result<()> { + let width = params.width; + let height = params.height; let mut target = self .target .take() @@ -228,7 +241,7 @@ impl Renderer { if target.width != width || target.height != height { target = TargetTexture::new(device, width, height); } - self.render_to_texture_async(device, queue, scene, &target.view, width, height) + self.render_to_texture_async(device, queue, scene, &target.view, params) .await?; let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); diff --git a/src/render.rs b/src/render.rs index 7c06908..e25be78 100644 --- a/src/render.rs +++ b/src/render.rs @@ -7,7 +7,7 @@ use crate::{ engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy}, peniko::Color, shaders::{self, FullShaders, Shaders}, - Scene, + RenderParams, Scene, }; /// State for a render in progress. @@ -22,8 +22,6 @@ pub struct Render { ptcl_size: u32, width_in_tiles: u32, height_in_tiles: u32, - /// The background color applied to the target - clear_color: Color, fine: Option, } @@ -183,10 +181,9 @@ fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) { pub fn render_full( scene: &Scene, shaders: &FullShaders, - width: u32, - height: u32, + params: &RenderParams, ) -> (Recording, ResourceProxy) { - render_encoding_full(scene.data(), shaders, width, height) + render_encoding_full(scene.data(), shaders, params) } /// Create a single recording with both coarse and fine render stages. @@ -196,11 +193,10 @@ pub fn render_full( pub fn render_encoding_full( encoding: &Encoding, shaders: &FullShaders, - width: u32, - height: u32, + params: &RenderParams, ) -> (Recording, ResourceProxy) { let mut render = Render::new(); - let mut recording = render.render_encoding_coarse(encoding, shaders, width, height, false); + let mut recording = render.render_encoding_coarse(encoding, shaders, params, false); let out_image = render.out_image(); render.record_fine(shaders, &mut recording); (recording, out_image.into()) @@ -220,7 +216,6 @@ impl Render { ptcl_size: (1 << 25) / 4 as u32, width_in_tiles: 0, height_in_tiles: 0, - clear_color: Color::BLACK, fine: None, } } @@ -233,8 +228,7 @@ impl Render { &mut self, encoding: &Encoding, shaders: &FullShaders, - width: u32, - height: u32, + params: &RenderParams, robust: bool, ) -> Recording { use crate::encoding::{resource::ResourceCache, PackedEncoding}; @@ -261,16 +255,16 @@ impl Render { let n_drawobj = n_paths; let n_clip = encoding.n_clips; - let new_width = next_multiple_of(width, 16); - let new_height = next_multiple_of(height, 16); + let new_width = next_multiple_of(params.width, 16); + let new_height = next_multiple_of(params.height, 16); let info_size = packed.layout.bin_data_start; let config = crate::encoding::Config { width_in_tiles: new_width / 16, height_in_tiles: new_height / 16, - target_width: width, - target_height: height, - clear_color: self.clear_color.to_premul_u32(), + target_width: params.width, + target_height: params.height, + clear_color: params.clear_color.to_premul_u32(), binning_size: self.binning_info_size - info_size, tiles_size: self.tiles_size, segments_size: self.segments_size, @@ -521,7 +515,7 @@ impl Render { recording.free_resource(draw_monoid_buf); recording.free_resource(bin_header_buf); recording.free_resource(path_buf); - let out_image = ImageProxy::new(width, height, ImageFormat::Rgba8); + let out_image = ImageProxy::new(params.width, params.height, ImageFormat::Rgba8); self.width_in_tiles = config.width_in_tiles; self.height_in_tiles = config.height_in_tiles; self.fine = Some(FineResources { From 3bbf108df5042a845ad7ebabadbf33bdcdf95e70 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Thu, 2 Mar 2023 14:29:06 -0800 Subject: [PATCH 3/6] Renamed clear_color to base_color; addressed review comments --- examples/headless/src/main.rs | 15 +++++--- examples/scenes/src/lib.rs | 21 +++++++++- examples/with_bevy/src/main.rs | 2 +- examples/with_winit/src/main.rs | 68 +++++++-------------------------- shader/fine.wgsl | 2 +- shader/shared/config.wgsl | 2 +- src/encoding/packed.rs | 7 ++-- src/lib.rs | 4 +- src/render.rs | 6 ++- 9 files changed, 54 insertions(+), 73 deletions(-) diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index 0a6ea17..5664b32 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -92,15 +92,15 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { let mut builder = SceneBuilder::for_fragment(&mut fragment); let example_scene = &mut scenes.scenes[index]; let mut text = SimpleText::new(); - let mut params = SceneParams { + let mut scene_params = SceneParams { time: args.time.unwrap_or(0.), text: &mut text, resolution: None, }; - (example_scene.function)(&mut builder, &mut params); + (example_scene.function)(&mut builder, &mut scene_params); builder.finish(); let mut transform = Affine::IDENTITY; - let (width, height) = if let Some(resolution) = params.resolution { + let (width, height) = if let Some(resolution) = scene_params.resolution { let ratio = resolution.x / resolution.y; let (new_width, new_height) = match (args.x_resolution, args.y_resolution) { (None, None) => (resolution.x.ceil() as u32, resolution.y.ceil() as u32), @@ -126,8 +126,11 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { (Some(x), Some(y)) => (x.try_into()?, y.try_into()?), } }; - let params = vello::RenderParams { - clear_color: vello::peniko::Color::BLACK, + let render_params = vello::RenderParams { + base_color: args + .args + .get_base_color()? + .unwrap_or(vello::peniko::Color::BLACK), width, height, }; @@ -152,7 +155,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { }); let view = target.create_view(&wgpu::TextureViewDescriptor::default()); renderer - .render_to_texture(&device, &queue, &scene, &view, ¶ms) + .render_to_texture(&device, &queue, &scene, &view, &render_params) .or_else(|_| bail!("Got non-Send/Sync error from rendering"))?; // (width * 4).next_multiple_of(256) let padded_byte_width = { diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs index b23e54a..2aed412 100644 --- a/examples/scenes/src/lib.rs +++ b/examples/scenes/src/lib.rs @@ -5,7 +5,7 @@ mod svg; mod test_scenes; use std::path::PathBuf; -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::{Args, Subcommand}; use download::Download; pub use simple_text::SimpleText; @@ -13,7 +13,7 @@ pub use simple_text::SimpleText; pub use svg::{default_scene, scene_from_files}; pub use test_scenes::test_scenes; -use vello::{kurbo::Vec2, SceneBuilder}; +use vello::{kurbo::Vec2, peniko::Color, SceneBuilder}; pub struct SceneParams<'a> { pub time: f64, @@ -46,6 +46,12 @@ pub struct Arguments { #[arg(help_heading = "Scene Selection", global(false))] /// The svg files paths to render svgs: Option>, + #[arg(help_heading = "Render Parameters")] + #[arg(long, global(false))] + /// The base color applied as the blend background to the rasterizer. + /// Format is CSS style hexidecimal (#RGB, #RGBA, #RRGGBB, #RRGGBBAA) or + /// an SVG color name such as "aliceblue" + base_color: Option, #[clap(subcommand)] command: Option, } @@ -79,6 +85,17 @@ impl Arguments { .map(Some) } } + + pub fn get_base_color(&self) -> Result> { + self.base_color.as_ref().map_or_else( + || Ok(None), + |s| { + Color::parse(&s) + .ok_or_else(|| anyhow!("malformed color: {}", s)) + .map(Some) + }, + ) + } } impl Command { diff --git a/examples/with_bevy/src/main.rs b/examples/with_bevy/src/main.rs index 690d980..f98ce73 100644 --- a/examples/with_bevy/src/main.rs +++ b/examples/with_bevy/src/main.rs @@ -47,7 +47,7 @@ fn render_scenes( for scene in &mut scenes { let gpu_image = gpu_images.get(&scene.1).unwrap(); let params = vello::RenderParams { - clear_color: vello::peniko::Color::AQUAMARINE, + base_color: vello::peniko::Color::AQUAMARINE, width: gpu_image.size.x as u32, height: gpu_image.size.y as u32, }; diff --git a/examples/with_winit/src/main.rs b/examples/with_winit/src/main.rs index 6d2aa5c..8aee854 100644 --- a/examples/with_winit/src/main.rs +++ b/examples/with_winit/src/main.rs @@ -17,10 +17,7 @@ use std::time::Instant; use anyhow::Result; -use clap::{ - builder::{PossibleValuesParser, TypedValueParser as _}, - CommandFactory, Parser, -}; +use clap::{CommandFactory, Parser}; use scenes::{SceneParams, SceneSet, SimpleText}; use vello::SceneFragment; use vello::{ @@ -53,52 +50,18 @@ struct Args { /// Switch between scenes with left and right arrow keys #[arg(long)] scene: Option, - #[arg( - long, - default_value_t = ClearColor::Black, - value_parser = PossibleValuesParser::new(["black", "white", "aquamarine", "crimson"]) - .map(|s| s.parse::().unwrap()) - )] - clear_color: ClearColor, + /// The base color used as the #[command(flatten)] args: scenes::Arguments, } -#[derive(Debug, Clone)] -enum ClearColor { - Black, - White, - Crimson, - Aquamarine, -} - -impl std::fmt::Display for ClearColor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - Self::Black => "black", - Self::White => "white", - Self::Crimson => "crimson", - Self::Aquamarine => "aquamarine", - }; - s.fmt(f) - } -} - -impl std::str::FromStr for ClearColor { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "black" => Ok(Self::Black), - "white" => Ok(Self::White), - "crimson" => Ok(Self::Crimson), - "aquamarine" => Ok(Self::Aquamarine), - _ => Err(format!("invalid color: {s}")), - } - } -} - -async fn run(event_loop: EventLoop, window: Window, args: Args, mut scenes: SceneSet) { +async fn run( + event_loop: EventLoop, + window: Window, + args: Args, + base_color: Color, + mut scenes: SceneSet, +) { use winit::{event::*, event_loop::ControlFlow}; let mut render_cx = RenderContext::new().unwrap(); let size = window.inner_size(); @@ -120,12 +83,6 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s if let Some(set_scene) = args.scene { scene_ix = set_scene; } - let clear_color = match args.clear_color { - ClearColor::Black => Color::BLACK, - ClearColor::White => Color::WHITE, - ClearColor::Crimson => Color::CRIMSON, - ClearColor::Aquamarine => Color::AQUAMARINE, - }; let mut prev_scene_ix = scene_ix - 1; event_loop.run(move |event, _, control_flow| match event { Event::WindowEvent { @@ -207,7 +164,7 @@ async fn run(event_loop: EventLoop, window: Window, args: Args, mut s let device_handle = &render_cx.devices[surface.dev_id]; let render_params = vello::RenderParams { - clear_color, + base_color, width, height, }; @@ -300,6 +257,7 @@ fn main() -> Result<()> { env_logger::init(); let args = Args::parse(); let scenes = args.args.select_scene_set(|| Args::command())?; + let base_color = args.args.get_base_color()?.unwrap_or(Color::BLACK); if let Some(scenes) = scenes { #[cfg(not(target_arch = "wasm32"))] { @@ -317,7 +275,7 @@ fn main() -> Result<()> { .with_title("Vello demo") .build(&event_loop) .unwrap(); - pollster::block_on(run(event_loop, window, args, scenes)); + pollster::block_on(run(event_loop, window, args, base_color, scenes)); } #[cfg(target_arch = "wasm32")] { @@ -337,7 +295,7 @@ fn main() -> Result<()> { .and_then(|doc| doc.body()) .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) .expect("couldn't append canvas to document body"); - wasm_bindgen_futures::spawn_local(run(event_loop, window, args, scenes)); + wasm_bindgen_futures::spawn_local(run(event_loop, window, args, base_color, scenes)); } } Ok(()) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index bfcfb66..6851bae 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -188,7 +188,7 @@ fn main( #ifdef full var rgba: array, PIXELS_PER_THREAD>; for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { - rgba[i] = unpack4x8unorm(config.clear_color).wzyx; + rgba[i] = unpack4x8unorm(config.base_color).wzyx; } var blend_stack: array, BLEND_STACK_SPLIT>; var clip_depth = 0u; diff --git a/shader/shared/config.wgsl b/shader/shared/config.wgsl index af5ff3d..7ab9ec6 100644 --- a/shader/shared/config.wgsl +++ b/shader/shared/config.wgsl @@ -10,7 +10,7 @@ struct Config { // The initial color applied to the pixels in a tile during the fine stage. // This is only used in the full pipeline. The format is packed RGBA8 in MSB // order. - clear_color: u32, + base_color: u32, n_drawobj: u32, n_path: u32, diff --git a/src/encoding/packed.rs b/src/encoding/packed.rs index 2ee7c67..4455f79 100644 --- a/src/encoding/packed.rs +++ b/src/encoding/packed.rs @@ -48,7 +48,8 @@ pub struct Layout { pub linewidth_base: u32, } -/// Scene configuration. +/// Scene configuration. This data structure must be kept in sync with the definition in +/// shaders/shared/config.wgsl. #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] #[repr(C)] pub struct Config { @@ -60,8 +61,8 @@ pub struct Config { pub target_width: u32, /// Height of the target in pixels. pub target_height: u32, - /// The initial background color applied to the target - pub clear_color: u32, + /// The base background color applied to the target before any blends. + pub base_color: u32, /// Layout of packed scene data. pub layout: Layout, /// Size of binning buffer allocation (in u32s). diff --git a/src/lib.rs b/src/lib.rs index 5cb4b1a..f4aabca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ pub struct Renderer { pub struct RenderParams { /// The background color applied to the target. This value is only applicable to the full /// pipeline. - pub clear_color: peniko::Color, + pub base_color: peniko::Color, /// Dimensions of the rasterization target pub width: u32, @@ -64,7 +64,7 @@ pub struct RenderParams { } impl Renderer { - /// Creates a new renderer for the specified device with default options. + /// Creates a new renderer for the specified device. pub fn new(device: &Device) -> Result { let mut engine = Engine::new(); let shaders = shaders::full_shaders(device, &mut engine)?; diff --git a/src/render.rs b/src/render.rs index e25be78..d5d29a6 100644 --- a/src/render.rs +++ b/src/render.rs @@ -55,6 +55,8 @@ const BIN_HEADER_SIZE: u64 = 8; const TILE_SIZE: u64 = 8; const SEGMENT_SIZE: u64 = 24; +// This data structure must be kept in sync with encoding::Config and the definition in +// shaders/shared/config.wgsl. #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] struct Config { @@ -62,7 +64,7 @@ struct Config { height_in_tiles: u32, target_width: u32, target_height: u32, - clear_color: u32, + base_color: u32, n_drawobj: u32, n_path: u32, n_clip: u32, @@ -264,7 +266,7 @@ impl Render { height_in_tiles: new_height / 16, target_width: params.width, target_height: params.height, - clear_color: params.clear_color.to_premul_u32(), + base_color: params.base_color.to_premul_u32(), binning_size: self.binning_info_size - info_size, tiles_size: self.tiles_size, segments_size: self.segments_size, From d72ad1405924bd46c08ae6b8f7fc6f86b6f86ff7 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Mar 2023 10:47:04 -0800 Subject: [PATCH 4/6] Add a test scene that animates the base color The test scenes can now supply their own optional base (background) color. In the with_winit example, we also allow the user to provide a base color as a CLI option. The precedence is as follows: 1. Use the color from the CLI options, if any. 2. Otherwise use the scene provided base color, if any. 3. Otherwise default to black. --- examples/headless/src/main.rs | 2 ++ examples/scenes/src/lib.rs | 1 + examples/scenes/src/test_scenes.rs | 16 ++++++++++++++++ examples/with_winit/src/main.rs | 21 +++++++++++++-------- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index 5664b32..f14bbb3 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -96,6 +96,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { time: args.time.unwrap_or(0.), text: &mut text, resolution: None, + base_color: None, }; (example_scene.function)(&mut builder, &mut scene_params); builder.finish(); @@ -130,6 +131,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { base_color: args .args .get_base_color()? + .or(scene_params.base_color) .unwrap_or(vello::peniko::Color::BLACK), width, height, diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs index 2aed412..c806894 100644 --- a/examples/scenes/src/lib.rs +++ b/examples/scenes/src/lib.rs @@ -19,6 +19,7 @@ pub struct SceneParams<'a> { pub time: f64, pub text: &'a mut SimpleText, pub resolution: Option, + pub base_color: Option, } pub struct SceneConfig { diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index f268076..46ede47 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -32,6 +32,7 @@ pub fn test_scenes() -> SceneSet { scene!(blend_grid), scene!(conflation_artifacts), scene!(labyrinth), + scene!(base_color_test: animated), ]; #[cfg(target_arch = "wasm32")] scenes.push(ExampleScene { @@ -582,6 +583,21 @@ fn labyrinth(sb: &mut SceneBuilder, _: &mut SceneParams) { ) } +fn base_color_test(sb: &mut SceneBuilder, params: &mut SceneParams) { + // Cycle through the hue value every 5 seconds (t % 5) * 360/5 + let color = Color::hlc((params.time % 5.0) * 72.0, 80.0, 80.0); + params.base_color = Some(color); + + // Blend a white square over it. + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgba8(255, 255, 255, 128), + None, + &Rect::new(50.0, 50.0, 500.0, 500.0), + ); +} + fn around_center(xform: Affine, center: Point) -> Affine { Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2()) } diff --git a/examples/with_winit/src/main.rs b/examples/with_winit/src/main.rs index 8aee854..91a6e55 100644 --- a/examples/with_winit/src/main.rs +++ b/examples/with_winit/src/main.rs @@ -59,7 +59,7 @@ async fn run( event_loop: EventLoop, window: Window, args: Args, - base_color: Color, + base_color_arg: Option, mut scenes: SceneSet, ) { use winit::{event::*, event_loop::ControlFlow}; @@ -163,12 +163,6 @@ async fn run( let height = surface.config.height; let device_handle = &render_cx.devices[surface.dev_id]; - let render_params = vello::RenderParams { - base_color, - width, - height, - }; - // Allow looping forever scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); let example_scene = &mut scenes.scenes[scene_ix as usize]; @@ -182,9 +176,20 @@ async fn run( time: start.elapsed().as_secs_f64(), text: &mut simple_text, resolution: None, + base_color: None, }; (example_scene.function)(&mut builder, &mut scene_params); builder.finish(); + + // If the user specifies a base color in the CLI we use that. Otherwise we use any + // color specified by the scene. The default is black. + let render_params = vello::RenderParams { + base_color: base_color_arg + .or(scene_params.base_color) + .unwrap_or(Color::BLACK), + width, + height, + }; let mut builder = SceneBuilder::for_scene(&mut scene); let mut transform = transform; if let Some(resolution) = scene_params.resolution { @@ -257,7 +262,7 @@ fn main() -> Result<()> { env_logger::init(); let args = Args::parse(); let scenes = args.args.select_scene_set(|| Args::command())?; - let base_color = args.args.get_base_color()?.unwrap_or(Color::BLACK); + let base_color = args.args.get_base_color()?; if let Some(scenes) = scenes { #[cfg(not(target_arch = "wasm32"))] { From acfd5704404f01989cefcd0b61af72a13928f3f0 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Mar 2023 11:32:39 -0800 Subject: [PATCH 5/6] Handle error that can be returned by render_to_texture --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f4aabca..53482e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,7 +128,7 @@ impl Renderer { if target.width != width || target.height != height { target = TargetTexture::new(device, width, height); } - self.render_to_texture(device, queue, scene, &target.view, ¶ms); + self.render_to_texture(device, queue, scene, &target.view, ¶ms)?; let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { From 12d5dcd34f8c565c97aebaa14ec404a0888a4dff Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Fri, 3 Mar 2023 14:04:54 -0800 Subject: [PATCH 6/6] Use clap to parse base-color option; remove unused width/height parameters --- examples/headless/src/main.rs | 2 +- examples/scenes/src/lib.rs | 19 ++++++------------- examples/with_winit/src/main.rs | 18 ++++++------------ src/lib.rs | 2 -- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index f14bbb3..c145d06 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -130,7 +130,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { let render_params = vello::RenderParams { base_color: args .args - .get_base_color()? + .base_color .or(scene_params.base_color) .unwrap_or(vello::peniko::Color::BLACK), width, diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs index c806894..ea84dc6 100644 --- a/examples/scenes/src/lib.rs +++ b/examples/scenes/src/lib.rs @@ -48,11 +48,11 @@ pub struct Arguments { /// The svg files paths to render svgs: Option>, #[arg(help_heading = "Render Parameters")] - #[arg(long, global(false))] + #[arg(long, global(false), value_parser = parse_color)] /// The base color applied as the blend background to the rasterizer. /// Format is CSS style hexidecimal (#RGB, #RGBA, #RRGGBB, #RRGGBBAA) or /// an SVG color name such as "aliceblue" - base_color: Option, + pub base_color: Option, #[clap(subcommand)] command: Option, } @@ -86,17 +86,6 @@ impl Arguments { .map(Some) } } - - pub fn get_base_color(&self) -> Result> { - self.base_color.as_ref().map_or_else( - || Ok(None), - |s| { - Color::parse(&s) - .ok_or_else(|| anyhow!("malformed color: {}", s)) - .map(Some) - }, - ) - } } impl Command { @@ -106,3 +95,7 @@ impl Command { } } } + +fn parse_color(s: &str) -> Result { + Color::parse(s).ok_or(anyhow!("'{s}' is not a valid color")) +} diff --git a/examples/with_winit/src/main.rs b/examples/with_winit/src/main.rs index 91a6e55..dc3b202 100644 --- a/examples/with_winit/src/main.rs +++ b/examples/with_winit/src/main.rs @@ -50,18 +50,11 @@ struct Args { /// Switch between scenes with left and right arrow keys #[arg(long)] scene: Option, - /// The base color used as the #[command(flatten)] args: scenes::Arguments, } -async fn run( - event_loop: EventLoop, - window: Window, - args: Args, - base_color_arg: Option, - mut scenes: SceneSet, -) { +async fn run(event_loop: EventLoop, window: Window, args: Args, mut scenes: SceneSet) { use winit::{event::*, event_loop::ControlFlow}; let mut render_cx = RenderContext::new().unwrap(); let size = window.inner_size(); @@ -184,7 +177,9 @@ async fn run( // If the user specifies a base color in the CLI we use that. Otherwise we use any // color specified by the scene. The default is black. let render_params = vello::RenderParams { - base_color: base_color_arg + base_color: args + .args + .base_color .or(scene_params.base_color) .unwrap_or(Color::BLACK), width, @@ -262,7 +257,6 @@ fn main() -> Result<()> { env_logger::init(); let args = Args::parse(); let scenes = args.args.select_scene_set(|| Args::command())?; - let base_color = args.args.get_base_color()?; if let Some(scenes) = scenes { #[cfg(not(target_arch = "wasm32"))] { @@ -280,7 +274,7 @@ fn main() -> Result<()> { .with_title("Vello demo") .build(&event_loop) .unwrap(); - pollster::block_on(run(event_loop, window, args, base_color, scenes)); + pollster::block_on(run(event_loop, window, args, scenes)); } #[cfg(target_arch = "wasm32")] { @@ -300,7 +294,7 @@ fn main() -> Result<()> { .and_then(|doc| doc.body()) .and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok()) .expect("couldn't append canvas to document body"); - wasm_bindgen_futures::spawn_local(run(event_loop, window, args, base_color, scenes)); + wasm_bindgen_futures::spawn_local(run(event_loop, window, args, scenes)); } } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 53482e8..fcba817 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,8 +114,6 @@ impl Renderer { scene: &Scene, surface: &SurfaceTexture, params: &RenderParams, - width: u32, - height: u32, ) -> Result<()> { let width = params.width; let height = params.height;