diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index 802f9070d..a48d41cb0 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, + antialiasing_support: vello::AaSupport::area_only(), }, ) .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 75678d80f..4e41e26cc 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -1246,10 +1246,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..9ddf875c7 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(), + antialiasing_support: vello::AaSupport::area_only(), }, ) .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..225f3ea87 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, + antialiasing_support: vello::AaSupport::all(), }, ) .expect("Could create renderer"), @@ -111,6 +112,11 @@ 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]; + // We allow cycling through AA configs in either direction, so use a signed index + let mut aa_config_ix: i32 = 0; + let mut frame_start_time = Instant::now(); let start = Instant::now(); @@ -130,6 +136,7 @@ fn run( let mut profile_stored = None; let mut prev_scene_ix = scene_ix - 1; let mut profile_taken = Instant::now(); + let mut modifiers = ModifiersState::default(); // _event_loop is used on non-wasm platforms to create new windows event_loop.run(move |event, _event_loop, control_flow| match event { Event::WindowEvent { @@ -144,6 +151,7 @@ fn run( } match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::ModifiersChanged(m) => modifiers = *m, WindowEvent::KeyboardInput { input, .. } => { if input.state == ElementState::Pressed { match input.virtual_keycode { @@ -173,6 +181,13 @@ fn run( Some(VirtualKeyCode::C) => { stats.clear_min_and_max(); } + Some(VirtualKeyCode::M) => { + aa_config_ix = if modifiers.shift() { + aa_config_ix.saturating_sub(1) + } else { + aa_config_ix.saturating_add(1) + }; + } Some(VirtualKeyCode::P) => { if let Some(renderer) = &renderers[render_state.surface.dev_id] { if let Some(profile_result) = &renderer @@ -324,6 +339,8 @@ fn run( // Allow looping forever scene_ix = scene_ix.rem_euclid(scenes.scenes.len() as i32); + aa_config_ix = aa_config_ix.rem_euclid(AA_CONFIGS.len() as i32); + let example_scene = &mut scenes.scenes[scene_ix as usize]; if prev_scene_ix != scene_ix { transform = Affine::IDENTITY; @@ -348,14 +365,17 @@ 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 base_color = args + .args + .base_color + .or(scene_params.base_color) + .unwrap_or(Color::BLACK); + let antialiasing_method = AA_CONFIGS[aa_config_ix as usize]; let render_params = vello::RenderParams { - base_color: args - .args - .base_color - .or(scene_params.base_color) - .unwrap_or(Color::BLACK), + base_color, width, height, + antialiasing_method, }; let mut builder = SceneBuilder::for_scene(&mut scene); let mut transform = transform; @@ -376,6 +396,7 @@ fn run( stats.samples(), complexity_shown.then_some(scene_complexity).flatten(), vsync_on, + antialiasing_method, ); if let Some(profiling_result) = renderers[render_state.surface.dev_id] .as_mut() @@ -492,12 +513,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, + antialiasing_support: vello::AaSupport::all(), }, ) .expect("Could create renderer") diff --git a/examples/with_winit/src/stats.rs b/examples/with_winit/src/stats.rs index 08d8bd457..a93a21a49 100644 --- a/examples/with_winit/src/stats.rs +++ b/examples/with_winit/src/stats.rs @@ -19,7 +19,7 @@ use std::{collections::VecDeque, time::Duration}; use vello::{ kurbo::{Affine, Line, PathEl, Rect, Stroke}, peniko::{Brush, Color, Fill}, - BumpAllocators, SceneBuilder, + AaConfig, BumpAllocators, SceneBuilder, }; use wgpu_profiler::GpuTimerScopeResult; @@ -44,6 +44,7 @@ impl Snapshot { samples: T, bump: Option, vsync: bool, + aa_config: AaConfig, ) where T: Iterator, { @@ -67,6 +68,14 @@ impl Snapshot { format!("Frame Time (min): {:.2} ms", self.frame_time_min_ms), format!("Frame Time (max): {:.2} ms", self.frame_time_max_ms), format!("VSync: {}", if vsync { "on" } else { "off" }), + format!( + "AA method: {}", + match aa_config { + AaConfig::Area => "Analytic Area", + AaConfig::Msaa16 => "16xMSAA", + AaConfig::Msaa8 => "8xMSAA", + } + ), format!("Resolution: {viewport_width}x{viewport_height}"), ]; if let Some(bump) = &bump { diff --git a/src/lib.rs b/src/lib.rs index 053f30afb..efbe74a95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,22 +62,43 @@ pub type Error = Box; /// Specialization of `Result` for our catch-all error type. pub type Result = std::result::Result; -/// Possible configurations for antialiasing. -#[derive(PartialEq, Eq)] -#[allow(unused)] -enum AaConfig { +/// Represents the antialiasing method to use during a render pass. +#[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; +/// Represents the set of antialiasing configurations to enable during pipeline creation. +pub struct AaSupport { + pub area: bool, + pub msaa8: bool, + pub msaa16: bool, +} + +impl AaSupport { + pub fn all() -> Self { + Self { + area: true, + msaa8: true, + msaa16: true, + } + } + + pub fn area_only() -> Self { + Self { + area: true, + msaa8: false, + msaa16: false, + } + } +} /// Renders a scene into a texture or surface. #[cfg(feature = "wgpu")] pub struct Renderer { + options: RendererOptions, engine: WgpuEngine, shaders: FullShaders, blit: Option, @@ -86,8 +107,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 +118,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,33 +129,43 @@ 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, + + /// Represents the enabled set of AA configurations. This will be used to determine which + /// pipeline permutations should be compiled at startup. + pub antialiasing_support: AaSupport, } #[cfg(feature = "wgpu")] impl Renderer { /// Creates a new renderer for the specified device. - pub fn new(device: &Device, render_options: &RendererOptions) -> Result { - let mut engine = WgpuEngine::new(render_options.use_cpu); - let shaders = shaders::full_shaders(device, &mut engine)?; - let blit = render_options + pub fn new(device: &Device, options: RendererOptions) -> Result { + let mut engine = WgpuEngine::new(options.use_cpu); + let shaders = shaders::full_shaders(device, &mut engine, &options)?; + let blit = options .surface_format .map(|surface_format| BlitPipeline::new(device, surface_format)); + #[cfg(feature = "wgpu-profiler")] + 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, }) } @@ -237,8 +270,8 @@ impl Renderer { #[cfg(feature = "hot_reload")] pub async fn reload_shaders(&mut self, device: &Device) -> Result<()> { device.push_error_scope(wgpu::ErrorFilter::Validation); - let mut engine = WgpuEngine::new(self.use_cpu); - let shaders = shaders::full_shaders(device, &mut engine)?; + let mut engine = WgpuEngine::new(self.options.use_cpu); + let shaders = shaders::full_shaders(device, &mut engine, &self.options)?; let error = device.pop_error_scope().await; if let Some(error) = error { return Err(error.into()); diff --git a/src/render.rs b/src/render.rs index 0bb657954..22e0a1341 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,12 @@ 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("shaders not configured to support AA mode: area"), fine_wg_count, [ fine.config_buf, @@ -432,7 +437,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 +445,17 @@ impl Render { let buf = recording.upload("mask lut", mask_lut); self.mask_buf = Some(buf.into()); } + let fine_shader = match fine.aa_config { + AaConfig::Msaa16 => shaders + .fine_msaa16 + .expect("shaders not configured to support AA mode: msaa16"), + AaConfig::Msaa8 => shaders + .fine_msaa8 + .expect("shaders not configured to support AA mode: msaa8"), + _ => unreachable!(), + }; recording.dispatch( - shaders.fine, + fine_shader, fine_wg_count, [ fine.config_buf, diff --git a/src/shaders.rs b/src/shaders.rs index de23e077d..3facd602d 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -29,7 +29,7 @@ use crate::{ }; #[cfg(feature = "wgpu")] -use crate::wgpu_engine::WgpuEngine; +use crate::{wgpu_engine::WgpuEngine, RendererOptions}; macro_rules! shader { ($name:expr) => {&{ @@ -78,18 +78,22 @@ 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 { +pub fn full_shaders( + device: &Device, + engine: &mut WgpuEngine, + options: &RendererOptions, +) -> Result { use crate::wgpu_engine::CpuShaderType; - use crate::ANTIALIASING; use BindType::*; - let imports = SHARED_SHADERS .iter() .copied() @@ -97,17 +101,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()); @@ -121,13 +114,13 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result {{ + ($name:ident, $label:expr, $bindings:expr, $defines:expr, $cpu:expr) => {{ if force_gpu_from == Some(stringify!($name)) { force_gpu = true; } engine.add_shader( device, - stringify!($name), + $label, preprocess::preprocess(shader!(stringify!($name)), &$defines, &imports).into(), &$bindings, if force_gpu { @@ -137,6 +130,9 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result {{ + add_shader!($name, stringify!($name), $bindings, &$defines, $cpu) + }}; ($name:ident, $bindings:expr, $defines:expr) => { add_shader!( $name, @@ -269,36 +265,43 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result add_shader!( - fine, - [ - Uniform, - BufReadOnly, - BufReadOnly, - BufReadOnly, - Image(ImageFormat::Rgba8), - ImageRead(ImageFormat::Rgba8), - ImageRead(ImageFormat::Rgba8), - ], - &full_config, - CpuShaderType::Missing - ), - _ => add_shader!( - fine, - [ - Uniform, - BufReadOnly, - BufReadOnly, - BufReadOnly, - Image(ImageFormat::Rgba8), - ImageRead(ImageFormat::Rgba8), - ImageRead(ImageFormat::Rgba8), - BufReadOnly, // mask buffer - ], - &full_config, - CpuShaderType::Missing - ), + 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_area, fine_msaa8, fine_msaa16] = { + let aa_support = &options.antialiasing_support; + let aa_modes = [ + (aa_support.area, 1, "fine_area", None), + (aa_support.msaa8, 0, "fine_msaa8", Some("msaa8")), + (aa_support.msaa16, 0, "fine_msaa16", Some("msaa16")), + ]; + let mut pipelines = [None, None, None]; + for (i, (enabled, offset, label, aa_config)) in aa_modes.iter().enumerate() { + if !enabled { + continue; + } + 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(add_shader!( + fine, + label, + fine_resources[..fine_resources.len() - offset], + config, + CpuShaderType::Missing + )); + } + pipelines }; Ok(FullShaders { pathtag_reduce, @@ -320,8 +323,10 @@ pub fn full_shaders(device: &Device, engine: &mut WgpuEngine) -> Result