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.
This commit is contained in:
Arman Uguray 2023-03-02 11:22:19 -08:00
parent 8eabc12a72
commit 05fa8c7c39
5 changed files with 111 additions and 41 deletions

View file

@ -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()?), (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 scene = Scene::new();
let mut builder = SceneBuilder::for_scene(&mut scene); let mut builder = SceneBuilder::for_scene(&mut scene);
builder.append(&fragment, Some(transform)); 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()); let view = target.create_view(&wgpu::TextureViewDescriptor::default());
renderer renderer
.render_to_texture(&device, &queue, &scene, &view, width, height) .render_to_texture(&device, &queue, &scene, &view, &params)
.or_else(|_| bail!("Got non-Send/Sync error from rendering"))?; .or_else(|_| bail!("Got non-Send/Sync error from rendering"))?;
// (width * 4).next_multiple_of(256) // (width * 4).next_multiple_of(256)
let padded_byte_width = { let padded_byte_width = {

View file

@ -46,7 +46,11 @@ fn render_scenes(
) { ) {
for scene in &mut scenes { for scene in &mut scenes {
let gpu_image = gpu_images.get(&scene.1).unwrap(); 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 renderer
.0 .0
.render_to_texture( .render_to_texture(
@ -54,8 +58,7 @@ fn render_scenes(
&*queue, &*queue,
&scene.0, &scene.0,
&gpu_image.texture_view, &gpu_image.texture_view,
gpu_image.size.x as u32, &params,
gpu_image.size.y as u32,
) )
.unwrap(); .unwrap();
} }

View file

@ -17,12 +17,16 @@
use std::time::Instant; use std::time::Instant;
use anyhow::Result; use anyhow::Result;
use clap::{CommandFactory, Parser}; use clap::{
builder::{PossibleValuesParser, TypedValueParser as _},
CommandFactory, Parser,
};
use scenes::{SceneParams, SceneSet, SimpleText}; use scenes::{SceneParams, SceneSet, SimpleText};
use vello::SceneFragment; use vello::SceneFragment;
use vello::{ use vello::{
block_on_wgpu, block_on_wgpu,
kurbo::{Affine, Vec2}, kurbo::{Affine, Vec2},
peniko::Color,
util::RenderContext, util::RenderContext,
Renderer, Scene, SceneBuilder, Renderer, Scene, SceneBuilder,
}; };
@ -49,10 +53,51 @@ struct Args {
/// Switch between scenes with left and right arrow keys /// Switch between scenes with left and right arrow keys
#[arg(long)] #[arg(long)]
scene: Option<i32>, scene: Option<i32>,
#[arg(
long,
default_value_t = ClearColor::Black,
value_parser = PossibleValuesParser::new(["black", "white", "aquamarine", "crimson"])
.map(|s| s.parse::<ClearColor>().unwrap())
)]
clear_color: ClearColor,
#[command(flatten)] #[command(flatten)]
args: scenes::Arguments, 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<Self, Self::Err> {
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<UserEvent>, window: Window, args: Args, mut scenes: SceneSet) { async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut scenes: SceneSet) {
use winit::{event::*, event_loop::ControlFlow}; use winit::{event::*, event_loop::ControlFlow};
let mut render_cx = RenderContext::new().unwrap(); let mut render_cx = RenderContext::new().unwrap();
@ -75,6 +120,12 @@ async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut s
if let Some(set_scene) = args.scene { if let Some(set_scene) = args.scene {
scene_ix = set_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; let mut prev_scene_ix = scene_ix - 1;
event_loop.run(move |event, _, control_flow| match event { event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent { Event::WindowEvent {
@ -155,6 +206,12 @@ async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut s
let height = surface.config.height; let height = surface.config.height;
let device_handle = &render_cx.devices[surface.dev_id]; let device_handle = &render_cx.devices[surface.dev_id];
let render_params = vello::RenderParams {
clear_color,
width,
height,
};
// Allow looping forever // Allow looping forever
scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32);
let example_scene = &mut scenes.scenes[scene_ix as usize]; let example_scene = &mut scenes.scenes[scene_ix as usize];
@ -164,16 +221,16 @@ async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut s
window.set_title(&format!("Vello demo - {}", example_scene.config.name)); window.set_title(&format!("Vello demo - {}", example_scene.config.name));
} }
let mut builder = SceneBuilder::for_fragment(&mut fragment); let mut builder = SceneBuilder::for_fragment(&mut fragment);
let mut params = SceneParams { let mut scene_params = SceneParams {
time: start.elapsed().as_secs_f64(), time: start.elapsed().as_secs_f64(),
text: &mut simple_text, text: &mut simple_text,
resolution: None, resolution: None,
}; };
(example_scene.function)(&mut builder, &mut params); (example_scene.function)(&mut builder, &mut scene_params);
builder.finish(); builder.finish();
let mut builder = SceneBuilder::for_scene(&mut scene); let mut builder = SceneBuilder::for_scene(&mut scene);
let mut transform = transform; 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 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); let scale_factor = (factor.x / resolution.x).min(factor.y / resolution.y);
transform = transform * Affine::scale(scale_factor); transform = transform * Affine::scale(scale_factor);
@ -193,8 +250,7 @@ async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut s
&device_handle.queue, &device_handle.queue,
&scene, &scene,
&surface_texture, &surface_texture,
width, &render_params,
height,
), ),
) )
.expect("failed to render to surface"); .expect("failed to render to surface");
@ -208,8 +264,7 @@ async fn run(event_loop: EventLoop<UserEvent>, window: Window, args: Args, mut s
&device_handle.queue, &device_handle.queue,
&scene, &scene,
&surface_texture, &surface_texture,
width, &render_params,
height,
) )
.expect("failed to render to surface"); .expect("failed to render to surface");
surface_texture.present(); surface_texture.present();

View file

@ -52,8 +52,19 @@ pub struct Renderer {
target: Option<TargetTexture>, target: Option<TargetTexture>,
} }
/// 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 { 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<Self> { pub fn new(device: &Device) -> Result<Self> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let shaders = shaders::full_shaders(device, &mut engine)?; let shaders = shaders::full_shaders(device, &mut engine)?;
@ -77,10 +88,9 @@ impl Renderer {
queue: &Queue, queue: &Queue,
scene: &Scene, scene: &Scene,
texture: &TextureView, texture: &TextureView,
width: u32, params: &RenderParams,
height: u32,
) -> Result<()> { ) -> 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( let external_resources = [ExternalResource::Image(
*target.as_image().unwrap(), *target.as_image().unwrap(),
texture, texture,
@ -103,9 +113,12 @@ impl Renderer {
queue: &Queue, queue: &Queue,
scene: &Scene, scene: &Scene,
surface: &SurfaceTexture, surface: &SurfaceTexture,
params: &RenderParams,
width: u32, width: u32,
height: u32, height: u32,
) -> Result<()> { ) -> Result<()> {
let width = params.width;
let height = params.height;
let mut target = self let mut target = self
.target .target
.take() .take()
@ -115,7 +128,7 @@ impl Renderer {
if target.width != width || target.height != height { if target.width != width || target.height != height {
target = TargetTexture::new(device, width, 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, &params);
let mut encoder = let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{ {
@ -177,12 +190,11 @@ impl Renderer {
queue: &Queue, queue: &Queue,
scene: &Scene, scene: &Scene,
texture: &TextureView, texture: &TextureView,
width: u32, params: &RenderParams,
height: u32,
) -> Result<()> { ) -> Result<()> {
let mut render = Render::new(); let mut render = Render::new();
let encoding = scene.data(); 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 target = render.out_image();
let bump_buf = render.bump_buf(); let bump_buf = render.bump_buf();
self.engine.run_recording(device, queue, &recording, &[])?; self.engine.run_recording(device, queue, &recording, &[])?;
@ -216,9 +228,10 @@ impl Renderer {
queue: &Queue, queue: &Queue,
scene: &Scene, scene: &Scene,
surface: &SurfaceTexture, surface: &SurfaceTexture,
width: u32, params: &RenderParams,
height: u32,
) -> Result<()> { ) -> Result<()> {
let width = params.width;
let height = params.height;
let mut target = self let mut target = self
.target .target
.take() .take()
@ -228,7 +241,7 @@ impl Renderer {
if target.width != width || target.height != height { if target.width != width || target.height != height {
target = TargetTexture::new(device, width, 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?; .await?;
let mut encoder = let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });

View file

@ -7,7 +7,7 @@ use crate::{
engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy}, engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy},
peniko::Color, peniko::Color,
shaders::{self, FullShaders, Shaders}, shaders::{self, FullShaders, Shaders},
Scene, RenderParams, Scene,
}; };
/// State for a render in progress. /// State for a render in progress.
@ -22,8 +22,6 @@ pub struct Render {
ptcl_size: u32, ptcl_size: u32,
width_in_tiles: u32, width_in_tiles: u32,
height_in_tiles: u32, height_in_tiles: u32,
/// The background color applied to the target
clear_color: Color,
fine: Option<FineResources>, fine: Option<FineResources>,
} }
@ -183,10 +181,9 @@ fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
pub fn render_full( pub fn render_full(
scene: &Scene, scene: &Scene,
shaders: &FullShaders, shaders: &FullShaders,
width: u32, params: &RenderParams,
height: u32,
) -> (Recording, ResourceProxy) { ) -> (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. /// Create a single recording with both coarse and fine render stages.
@ -196,11 +193,10 @@ pub fn render_full(
pub fn render_encoding_full( pub fn render_encoding_full(
encoding: &Encoding, encoding: &Encoding,
shaders: &FullShaders, shaders: &FullShaders,
width: u32, params: &RenderParams,
height: u32,
) -> (Recording, ResourceProxy) { ) -> (Recording, ResourceProxy) {
let mut render = Render::new(); 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(); let out_image = render.out_image();
render.record_fine(shaders, &mut recording); render.record_fine(shaders, &mut recording);
(recording, out_image.into()) (recording, out_image.into())
@ -220,7 +216,6 @@ impl Render {
ptcl_size: (1 << 25) / 4 as u32, ptcl_size: (1 << 25) / 4 as u32,
width_in_tiles: 0, width_in_tiles: 0,
height_in_tiles: 0, height_in_tiles: 0,
clear_color: Color::BLACK,
fine: None, fine: None,
} }
} }
@ -233,8 +228,7 @@ impl Render {
&mut self, &mut self,
encoding: &Encoding, encoding: &Encoding,
shaders: &FullShaders, shaders: &FullShaders,
width: u32, params: &RenderParams,
height: u32,
robust: bool, robust: bool,
) -> Recording { ) -> Recording {
use crate::encoding::{resource::ResourceCache, PackedEncoding}; use crate::encoding::{resource::ResourceCache, PackedEncoding};
@ -261,16 +255,16 @@ impl Render {
let n_drawobj = n_paths; let n_drawobj = n_paths;
let n_clip = encoding.n_clips; let n_clip = encoding.n_clips;
let new_width = next_multiple_of(width, 16); let new_width = next_multiple_of(params.width, 16);
let new_height = next_multiple_of(height, 16); let new_height = next_multiple_of(params.height, 16);
let info_size = packed.layout.bin_data_start; let info_size = packed.layout.bin_data_start;
let config = crate::encoding::Config { let config = crate::encoding::Config {
width_in_tiles: new_width / 16, width_in_tiles: new_width / 16,
height_in_tiles: new_height / 16, height_in_tiles: new_height / 16,
target_width: width, target_width: params.width,
target_height: height, target_height: params.height,
clear_color: self.clear_color.to_premul_u32(), clear_color: params.clear_color.to_premul_u32(),
binning_size: self.binning_info_size - info_size, binning_size: self.binning_info_size - info_size,
tiles_size: self.tiles_size, tiles_size: self.tiles_size,
segments_size: self.segments_size, segments_size: self.segments_size,
@ -521,7 +515,7 @@ impl Render {
recording.free_resource(draw_monoid_buf); recording.free_resource(draw_monoid_buf);
recording.free_resource(bin_header_buf); recording.free_resource(bin_header_buf);
recording.free_resource(path_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.width_in_tiles = config.width_in_tiles;
self.height_in_tiles = config.height_in_tiles; self.height_in_tiles = config.height_in_tiles;
self.fine = Some(FineResources { self.fine = Some(FineResources {