From 951caccd2c312bd88d01e98028231238ea2f9a58 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Mon, 30 Oct 2023 22:59:34 -0700 Subject: [PATCH] Support setting AA method dynamically This replaces the static anti-aliasing setting with a dynamic option in the form of two new settings (one used during pipeline creation and one used during a render): - The `FullShaders` collection now maintains up to 3 `fine` stage pipeline variants. This can be driven using a new optional `RendererOptions` field called `preferred_antialiasing_method`, which determines which fine stage pipeline variants should get instantiated at start up. - `RenderParams` now requires an `AaConfig` which selects which `fine` stage variant to use. - Added a new key binding (`M`) to the `with_winit` example to dynamically cycle through all 3 AA methods. --- examples/headless/src/main.rs | 4 +- examples/scenes/src/test_scenes.rs | 3 +- examples/with_bevy/src/main.rs | 2 + examples/with_winit/src/lib.rs | 37 +++++++++-- src/lib.rs | 43 +++++++----- src/render.rs | 18 +++-- src/shaders.rs | 103 ++++++++++++++++------------- 7 files changed, 132 insertions(+), 78 deletions(-) diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index 802f9070d..01fd6cee4 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -87,10 +87,11 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { let queue = &device_handle.queue; let mut renderer = vello::Renderer::new( device, - &RendererOptions { + RendererOptions { surface_format: None, timestamp_period: queue.get_timestamp_period(), use_cpu: false, + preferred_antialiasing_method: Some(vello::AaConfig::Area), }, ) .or_else(|_| bail!("Got non-Send/Sync error from creating renderer"))?; @@ -140,6 +141,7 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> { .unwrap_or(vello::peniko::Color::BLACK), width, height, + antialiasing_method: vello::AaConfig::Area, }; let mut scene = Scene::new(); let mut builder = SceneBuilder::for_scene(&mut scene); diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 89606caf4..a06ab6c08 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -1210,10 +1210,11 @@ fn splash_screen(sb: &mut SceneBuilder, params: &mut SceneParams) { " Space: reset transform", " S: toggle stats", " V: toggle vsync", + " M: cycle AA method", " Q, E: rotate", ]; // Tweak to make it fit with tiger - let a = Affine::scale(0.12) * Affine::translate((-90.0, -50.0)); + let a = Affine::scale(0.11) * Affine::translate((-90.0, -50.0)); for (i, s) in strings.iter().enumerate() { let text_size = if i == 0 { 60.0 } else { 40.0 }; params.text.add( diff --git a/examples/with_bevy/src/main.rs b/examples/with_bevy/src/main.rs index 39d50d59f..0c3d694cc 100644 --- a/examples/with_bevy/src/main.rs +++ b/examples/with_bevy/src/main.rs @@ -30,6 +30,7 @@ impl FromWorld for VelloRenderer { &RendererOptions { surface_format: None, timestamp_period: queue.0.get_timestamp_period(), + preferred_antialiasing_method: Some(vello::AaConfig::Area), }, ) .unwrap(), @@ -65,6 +66,7 @@ fn render_scenes( base_color: vello::peniko::Color::AQUAMARINE, width: gpu_image.size.x as u32, height: gpu_image.size.y as u32, + antialiasing_method: vello::AaConfig::Area, }; renderer .0 diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index b3b63177a..218013b58 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -25,7 +25,7 @@ use vello::util::RenderSurface; use vello::{ kurbo::{Affine, Vec2}, util::RenderContext, - Renderer, Scene, SceneBuilder, + AaConfig, Renderer, Scene, SceneBuilder, }; use vello::{BumpAllocators, RendererOptions, SceneFragment}; @@ -85,10 +85,11 @@ fn run( renderers[id] = Some( Renderer::new( &render_cx.devices[id].device, - &RendererOptions { + RendererOptions { surface_format: Some(render_state.surface.format), timestamp_period: render_cx.devices[id].queue.get_timestamp_period(), use_cpu: use_cpu, + preferred_antialiasing_method: None, }, ) .expect("Could create renderer"), @@ -111,6 +112,10 @@ fn run( let mut scene_complexity: Option = None; let mut complexity_shown = false; let mut vsync_on = true; + + const AA_CONFIGS: [AaConfig; 3] = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16]; + let mut aa_config_ix = 0; + let mut frame_start_time = Instant::now(); let start = Instant::now(); @@ -129,6 +134,7 @@ fn run( } let mut profile_stored = None; let mut prev_scene_ix = scene_ix - 1; + let mut prev_aa_config_ix = aa_config_ix; let mut profile_taken = Instant::now(); // _event_loop is used on non-wasm platforms to create new windows event_loop.run(move |event, _event_loop, control_flow| match event { @@ -173,6 +179,9 @@ fn run( Some(VirtualKeyCode::C) => { stats.clear_min_and_max(); } + Some(VirtualKeyCode::M) => { + aa_config_ix = (aa_config_ix + 1) % AA_CONFIGS.len(); + } Some(VirtualKeyCode::P) => { if let Some(renderer) = &renderers[render_state.surface.dev_id] { if let Some(profile_result) = &renderer @@ -325,12 +334,26 @@ fn run( // Allow looping forever scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); let example_scene = &mut scenes.scenes[scene_ix as usize]; + let mut reset_title = false; if prev_scene_ix != scene_ix { transform = Affine::IDENTITY; prev_scene_ix = scene_ix; - render_state - .window - .set_title(&format!("Vello demo - {}", example_scene.config.name)); + reset_title = true; + } + if prev_aa_config_ix != aa_config_ix { + prev_aa_config_ix = aa_config_ix; + reset_title = true; + } + if reset_title { + let aa_str = match AA_CONFIGS[aa_config_ix] { + AaConfig::Area => "Analytic Area", + AaConfig::Msaa16 => "16xMSAA", + AaConfig::Msaa8 => "8xMSAA", + }; + render_state.window.set_title(&format!( + "Vello demo - {} - AA method: {}", + example_scene.config.name, aa_str + )); } let mut builder = SceneBuilder::for_fragment(&mut fragment); let mut scene_params = SceneParams { @@ -356,6 +379,7 @@ fn run( .unwrap_or(Color::BLACK), width, height, + antialiasing_method: AA_CONFIGS[aa_config_ix], }; let mut builder = SceneBuilder::for_scene(&mut scene); let mut transform = transform; @@ -492,12 +516,13 @@ fn run( eprintln!("Creating renderer {id}"); Renderer::new( &render_cx.devices[id].device, - &RendererOptions { + RendererOptions { surface_format: Some(render_state.surface.format), timestamp_period: render_cx.devices[id] .queue .get_timestamp_period(), use_cpu, + preferred_antialiasing_method: None, }, ) .expect("Could create renderer") diff --git a/src/lib.rs b/src/lib.rs index f383fd0fd..8e08918df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,21 +63,17 @@ pub type Error = Box; pub type Result = std::result::Result; /// Possible configurations for antialiasing. -#[derive(PartialEq, Eq)] -#[allow(unused)] -enum AaConfig { +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum AaConfig { Area, Msaa8, Msaa16, } -/// Configuration of antialiasing. Currently this is static, but could be switched to -/// a launch option or even finer-grained. -const ANTIALIASING: AaConfig = AaConfig::Msaa16; - /// Renders a scene into a texture or surface. #[cfg(feature = "wgpu")] pub struct Renderer { + options: RendererOptions, engine: WgpuEngine, shaders: FullShaders, blit: Option, @@ -86,8 +82,6 @@ pub struct Renderer { profiler: GpuProfiler, #[cfg(feature = "wgpu-profiler")] pub profile_result: Option>, - #[cfg(feature = "hot_reload")] - use_cpu: bool, } /// Parameters used in a single render that are configurable by the client. @@ -99,6 +93,10 @@ pub struct RenderParams { /// Dimensions of the rasterization target pub width: u32, pub height: u32, + + /// The anti-aliasing algorithm. The selected algorithm must have been initialized while + /// constructing the `Renderer`. + pub antialiasing_method: AaConfig, } #[cfg(feature = "wgpu")] @@ -106,36 +104,45 @@ pub struct RendererOptions { /// The format of the texture used for surfaces with this renderer/device /// If None, the renderer cannot be used with surfaces pub surface_format: Option, + /// The timestamp period from [`wgpu::Queue::get_timestamp_period`] /// Used when the wgpu-profiler feature is enabled pub timestamp_period: f32, + + /// If true, run all stages up to fine rasterization on the CPU. + /// TODO: Consider evolving this so that the CPU stages can be configured dynamically via + /// `RenderParams`. pub use_cpu: bool, + + /// The anti-aliasing specialization that should be used when creating the pipelines. `None` + /// initializes all variants. + pub preferred_antialiasing_method: Option, } #[cfg(feature = "wgpu")] impl Renderer { /// Creates a new renderer for the specified device. - pub fn new(device: &Device, render_options: &RendererOptions) -> Result { + pub fn new(device: &Device, options: RendererOptions) -> Result { let mut engine = WgpuEngine::new(); - let mut shaders = shaders::full_shaders(device, &mut engine)?; - if render_options.use_cpu { + let mut shaders = shaders::full_shaders(device, &mut engine, &options)?; + if options.use_cpu { shaders.install_cpu_shaders(&mut engine); } - let blit = render_options + let blit = options .surface_format .map(|surface_format| BlitPipeline::new(device, surface_format)); + let timestamp_period = options.timestamp_period; Ok(Self { + options, engine, shaders, blit, target: None, // Use 3 pending frames #[cfg(feature = "wgpu-profiler")] - profiler: GpuProfiler::new(3, render_options.timestamp_period, device.features()), + profiler: GpuProfiler::new(3, timestamp_period, device.features()), #[cfg(feature = "wgpu-profiler")] profile_result: None, - #[cfg(feature = "hot_reload")] - use_cpu: render_options.use_cpu, }) } @@ -241,8 +248,8 @@ impl Renderer { pub async fn reload_shaders(&mut self, device: &Device) -> Result<()> { device.push_error_scope(wgpu::ErrorFilter::Validation); let mut engine = WgpuEngine::new(); - let mut shaders = shaders::full_shaders(device, &mut engine)?; - if self.use_cpu { + let mut shaders = shaders::full_shaders(device, &mut engine, &self.options)?; + if self.options.use_cpu { shaders.install_cpu_shaders(&mut engine); } let error = device.pop_error_scope().await; diff --git a/src/render.rs b/src/render.rs index 0bb657954..5f5f94141 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,7 +3,7 @@ use crate::{ engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy}, shaders::FullShaders, - AaConfig, RenderParams, Scene, ANTIALIASING, + AaConfig, RenderParams, Scene, }; use vello_encoding::{Encoding, WorkgroupSize}; @@ -16,6 +16,8 @@ pub struct Render { /// Resources produced by pipeline, needed for fine rasterization. struct FineResources { + aa_config: AaConfig, + config_buf: ResourceProxy, bump_buf: ResourceProxy, tile_buf: ResourceProxy, @@ -393,6 +395,7 @@ impl Render { let out_image = ImageProxy::new(params.width, params.height, ImageFormat::Rgba8); self.fine_wg_count = Some(wg_counts.fine); self.fine_resources = Some(FineResources { + aa_config: params.antialiasing_method, config_buf, bump_buf, tile_buf, @@ -414,10 +417,10 @@ impl Render { pub fn record_fine(&mut self, shaders: &FullShaders, recording: &mut Recording) { let fine_wg_count = self.fine_wg_count.take().unwrap(); let fine = self.fine_resources.take().unwrap(); - match ANTIALIASING { + match fine.aa_config { AaConfig::Area => { recording.dispatch( - shaders.fine, + shaders.fine_area.expect("unsupported AA mode: area"), fine_wg_count, [ fine.config_buf, @@ -432,7 +435,7 @@ impl Render { } _ => { if self.mask_buf.is_none() { - let mask_lut = match ANTIALIASING { + let mask_lut = match fine.aa_config { AaConfig::Msaa16 => crate::mask::make_mask_lut_16(), AaConfig::Msaa8 => crate::mask::make_mask_lut(), _ => unreachable!(), @@ -440,8 +443,13 @@ impl Render { let buf = recording.upload("mask lut", mask_lut); self.mask_buf = Some(buf.into()); } + let shader = match fine.aa_config { + AaConfig::Msaa16 => shaders.fine_msaa16.expect("unsupported AA mode: msaa16"), + AaConfig::Msaa8 => shaders.fine_msaa8.expect("unsupported AA mode: msaa8"), + _ => unreachable!(), + }; recording.dispatch( - shaders.fine, + shader, fine_wg_count, [ fine.config_buf, diff --git a/src/shaders.rs b/src/shaders.rs index 91d56db97..cd7231394 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -26,10 +26,11 @@ use wgpu::Device; use crate::{ cpu_shader, engine::{BindType, Error, ImageFormat, ShaderId}, + AaConfig, }; #[cfg(feature = "wgpu")] -use crate::wgpu_engine::WgpuEngine; +use crate::{wgpu_engine::WgpuEngine, RendererOptions}; macro_rules! shader { ($name:expr) => {&{ @@ -78,16 +79,20 @@ pub struct FullShaders { pub coarse: ShaderId, pub path_tiling_setup: ShaderId, pub path_tiling: ShaderId, - pub fine: ShaderId, + pub fine_area: Option, + pub fine_msaa8: Option, + pub fine_msaa16: Option, // 2-level dispatch works for CPU pathtag scan even for large // inputs, 3-level is not yet implemented. pub pathtag_is_cpu: bool, } #[cfg(feature = "wgpu")] -pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result { - use crate::ANTIALIASING; - +pub fn full_shaders( + device: &Device, + engine: &mut WgpuEngine, + options: &RendererOptions, +) -> Result { let imports = SHARED_SHADERS .iter() .copied() @@ -95,17 +100,6 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result { - full_config.insert("msaa".into()); - full_config.insert("msaa16".into()); - } - crate::AaConfig::Msaa8 => { - full_config.insert("msaa".into()); - full_config.insert("msaa8".into()); - } - crate::AaConfig::Area => (), - } let mut small_config = HashSet::new(); small_config.insert("full".into()); small_config.insert("small".into()); @@ -305,38 +299,51 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result engine.add_shader( - device, - "fine", - preprocess::preprocess(shader!("fine"), &full_config, &imports).into(), - &[ - BindType::Uniform, - BindType::BufReadOnly, - BindType::BufReadOnly, - BindType::BufReadOnly, - BindType::Image(ImageFormat::Rgba8), - BindType::ImageRead(ImageFormat::Rgba8), - BindType::ImageRead(ImageFormat::Rgba8), - ], - )?, - _ => { - engine.add_shader( + + let fine_resources = [ + BindType::Uniform, + BindType::BufReadOnly, + BindType::BufReadOnly, + BindType::BufReadOnly, + BindType::Image(ImageFormat::Rgba8), + BindType::ImageRead(ImageFormat::Rgba8), + BindType::ImageRead(ImageFormat::Rgba8), + // Mask LUT buffer, used only when MSAA is enabled. + BindType::BufReadOnly, + ]; + let fine_variants = { + const AA_MODES: [AaConfig; 3] = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16]; + const MSAA_INFO: [Option<(&str, &str)>; 3] = [ + None, + Some(("fine_msaa8", "msaa8")), + Some(("fine_msaa16", "msaa16")), + ]; + let mut pipelines = [None, None, None]; + for (i, aa_mode) in AA_MODES.iter().enumerate() { + if options + .preferred_antialiasing_method + .as_ref() + .map_or(false, |m| m != aa_mode) + { + continue; + } + let (range_end_offset, label, aa_config) = match MSAA_INFO[i] { + Some((label, config)) => (0, label, Some(config)), + None => (1, "fine_area", None), + }; + let mut config = full_config.clone(); + if let Some(aa_config) = aa_config { + config.insert("msaa".into()); + config.insert(aa_config.into()); + } + pipelines[i] = Some(engine.add_shader( device, - "fine", - preprocess::preprocess(shader!("fine"), &full_config, &imports).into(), - &[ - BindType::Uniform, - BindType::BufReadOnly, - BindType::BufReadOnly, - BindType::BufReadOnly, - BindType::Image(ImageFormat::Rgba8), - BindType::ImageRead(ImageFormat::Rgba8), - BindType::ImageRead(ImageFormat::Rgba8), - BindType::BufReadOnly, // mask buffer - ], - )? + label, + preprocess::preprocess(shader!("fine"), &config, &imports).into(), + &fine_resources[..fine_resources.len() - range_end_offset], + )?); } + pipelines }; Ok(FullShaders { pathtag_reduce, @@ -358,7 +365,9 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result