diff --git a/doc/roadmap_2023.md b/doc/roadmap_2023.md index 2eb5b257..ee832b7b 100644 --- a/doc/roadmap_2023.md +++ b/doc/roadmap_2023.md @@ -135,7 +135,7 @@ Vello now has a "recording" abstraction that includes lightweight proxies for re The [standard compositing model] calls for first doing antialiased rendering of vector shapes, then compositing using Porter-Duff rules. That leads to "conflation artifacts," or seams that are visible when multiple objects have adjoining boundaries. (Another form of conflation artifacts involves rendering within a single path, where the path is self-intersecting; this may be quite important for compatibility when trying to match the exact appearance of existing renderers) -It remains an interesting question whether we can render without conflation artifacts and retain good performance. Tatsuyuki Ishii prototyped a patch which appears promising. It should also be noted that a supersampling approach might also be a good way to get gamma-correct antialiasing without changing the semantics of alpha blending, in addition to eliminating conflation artifacts. +It remains an interesting question whether we can render without conflation artifacts and retain good performance. Tatsuyuki Ishii prototyped a patch which appears promising. It should also be noted that a supersampling approach might also be a good way to get gamma-correct anti-aliasing without changing the semantics of alpha blending, in addition to eliminating conflation artifacts. This is a hard problem, but interesting, and might be motivated by needs of a specific client. diff --git a/doc/vision.md b/doc/vision.md index ee49d7f9..27a48d6d 100644 --- a/doc/vision.md +++ b/doc/vision.md @@ -105,7 +105,7 @@ The major advantages of GPU-side variable font rendering are to allow efficient The question of quality in GPU 2D rendering has long been complex. Many rasterization based approaches are dependent on [MSAA] in the GPU’s fixed-function pipeline, which may not always be available or perhaps only practical at lower settings (especially on mobile). Thus, GPU accelerated 2D rendering quality has gotten something of a bad name. -A compute-centric approach changes the story. All actual pixels are generated by code; the quality of the rendering is entirely up to the author of that code. The current piet-gpu codebase uses an exact-area approach to antialiasing (in the [tradition of libart]), and thus does not exhibit stepping or graininess characteristic of MSAA at low or medium settings. The quality should be the same as a good software renderer, because it *is* a software renderer, just one that happens to be running on hardware with orders of magnitude more parallelism than any reasonable CPU. +A compute-centric approach changes the story. All actual pixels are generated by code; the quality of the rendering is entirely up to the author of that code. The current piet-gpu codebase uses an exact-area approach to anti-aliasing (in the [tradition of libart]), and thus does not exhibit stepping or graininess characteristic of MSAA at low or medium settings. The quality should be the same as a good software renderer, because it *is* a software renderer, just one that happens to be running on hardware with orders of magnitude more parallelism than any reasonable CPU. Even so, I believe it’s possible to do even better. A CPU-bound renderer has barely enough performance to get pixels to the screen, so takes whatever shortcuts are needed to get the job done in that performance budget. A GPU typically has an order of magnitude more raw compute bandwidth, so there is headroom that can be used to improve quality. diff --git a/vello/src/lib.rs b/vello/src/lib.rs index fc6dbd72..7a256091 100644 --- a/vello/src/lib.rs +++ b/vello/src/lib.rs @@ -22,7 +22,7 @@ //! ## Getting started //! //! Vello is meant to be integrated deep in UI render stacks. -//! While drawing in a Vello scene is easy, actually rendering that scene to a surface setting up a wgpu context, which is a non-trivial task. +//! While drawing in a Vello [`Scene`] is easy, actually rendering that scene to a surface setting up a wgpu context, which is a non-trivial task. //! //! To use Vello as the renderer for your PDF reader / GUI toolkit / etc, your code will have to look roughly like this: //! @@ -131,23 +131,55 @@ use wgpu::{Device, Queue, SurfaceTexture, TextureFormat, TextureView}; #[cfg(all(feature = "wgpu", feature = "wgpu-profiler"))] use wgpu_profiler::{GpuProfiler, GpuProfilerSettings}; -/// Represents the antialiasing method to use during a render pass. +/// Represents the anti-aliasing method to use during a render pass. +/// +/// Can be configured for a render operation by setting [`RenderParams::antialiasing_method`]. +/// Each value of this can only be used if the corresponding field on [`AaSupport`] was used. +/// +/// This can be converted into an `AaSupport` using [`Iterator::collect`], +/// as `AaSupport` implements `FromIterator`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AaConfig { + /// Area anti-aliasing, where the alpha value for a pixel is computed from integrating + /// the winding number over its square area. + /// + /// This technique produces very accurate values when the shape has winding number of 0 or 1 + /// everywhere, but can result in conflation artifacts otherwise. + /// It generally has better performance than the multi-sampling methods. + /// + /// Can only be used if [enabled][AaSupport::area] for the `Renderer`. Area, + /// 8x Multisampling + /// + /// Can only be used if [enabled][AaSupport::msaa8] for the `Renderer`. Msaa8, + /// 16x Multisampling + /// + /// Can only be used if [enabled][AaSupport::msaa16] for the `Renderer`. Msaa16, } -/// Represents the set of antialiasing configurations to enable during pipeline creation. +/// Represents the set of anti-aliasing configurations to enable during pipeline creation. +/// +/// This is configured at `Renderer` creation time ([`Renderer::new`]) by setting +/// [`RendererOptions::antialiasing_support`]. +/// +/// This can be created from a set of `AaConfig` using [`Iterator::collect`], +/// as `AaSupport` implements `FromIterator`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct AaSupport { + /// Support [`AaConfig::Area`]. pub area: bool, + /// Support [`AaConfig::Msaa8`]. pub msaa8: bool, + /// Support [`AaConfig::Msaa16`]. pub msaa16: bool, } impl AaSupport { + /// Support every anti-aliasing method. + /// + /// This might increase startup time, as more shader variations must be compiled. pub fn all() -> Self { Self { area: true, @@ -156,6 +188,9 @@ impl AaSupport { } } + /// Support only [`AaConfig::Area`]. + /// + /// This should be the default choice for most users. pub fn area_only() -> Self { Self { area: true, @@ -241,6 +276,10 @@ pub enum Error { pub(crate) type Result = std::result::Result; /// Renders a scene into a texture or surface. +/// +/// Currently, each renderer only supports a single surface format, if it +/// supports drawing to surfaces at all. +/// This is an assumption which is known to be limiting, and is planned to change. #[cfg(feature = "wgpu")] pub struct Renderer { #[cfg_attr(not(feature = "hot_reload"), allow(dead_code))] @@ -270,6 +309,8 @@ pub struct Renderer { static_assertions::assert_impl_all!(Renderer: Send); /// Parameters used in a single render that are configurable by the client. +/// +/// These are used in [`Renderer::render_to_surface`] and [`Renderer::render_to_texture`]. pub struct RenderParams { /// The background color applied to the target. This value is only applicable to the full /// pipeline. @@ -292,6 +333,7 @@ pub struct RenderParams { } #[cfg(feature = "wgpu")] +/// Options which are set at renderer creation time, used in [`Renderer::new`]. pub struct RendererOptions { /// The format of the texture used for surfaces with this renderer/device /// If None, the renderer cannot be used with surfaces @@ -363,20 +405,6 @@ impl Renderer { }) } - /// Overwrite the `Image` with the `Texture` texture. - /// - /// If texture is `None`, removes the override. - pub fn override_image( - &mut self, - image: &peniko::Image, - texture: Option>>, - ) -> Option>> { - match texture { - Some(texture) => self.engine.image_overrides.insert(image.data.id(), texture), - None => self.engine.image_overrides.remove(&image.data.id()), - } - } - /// Renders a scene to the target texture. /// /// The texture is assumed to be of the specified dimensions and have been created with @@ -487,6 +515,26 @@ impl Renderer { Ok(()) } + /// Overwrite `image` with `texture`. + /// + /// Whenever `image` would be rendered, instead the given `Texture` will be used. + /// + /// Correct behaviour is not guaranteed if the texture does not have the same + /// dimensions as the image, nor if an image which uses the same [data] but different + /// dimensions would be rendered. + /// + /// [data]: peniko::Image::data + pub fn override_image( + &mut self, + image: &peniko::Image, + texture: Option>>, + ) -> Option>> { + match texture { + Some(texture) => self.engine.image_overrides.insert(image.data.id(), texture), + None => self.engine.image_overrides.remove(&image.data.id()), + } + } + /// Reload the shaders. This should only be used during `vello` development #[cfg(feature = "hot_reload")] #[doc(hidden)] // End-users of Vello should not have `hot_reload` enabled. diff --git a/vello/src/scene.rs b/vello/src/scene.rs index 83edd04b..d3b3e193 100644 --- a/vello/src/scene.rs +++ b/vello/src/scene.rs @@ -29,8 +29,16 @@ use vello_encoding::{Encoding, Glyph, GlyphRun, Patch, Transform}; /// The main datatype for rendering graphics. /// -/// A Scene stores a sequence of drawing commands, their context, and the +/// A `Scene` stores a sequence of drawing commands, their context, and the /// associated resources, which can later be rendered. +/// +/// Most users will render this using [`Renderer::render_to_surface`][crate::Renderer::render_to_surface] +/// or [`Renderer::render_to_texture`][crate::Renderer::render_to_texture]. +/// +/// Rendering from a `Scene` will *not* clear it, which should be done in a separate step, by calling [`Scene::reset`]. +/// +/// If this is not done for a scene which is retained (to avoid allocations) between frames, this will likely +/// quickly increase the complexity of the render result, leading to crashes or potential host system instability. #[derive(Clone, Default)] pub struct Scene { encoding: Encoding, @@ -322,6 +330,8 @@ impl From for Scene { } /// Builder for encoding a glyph run. +/// +/// Created using [`Scene::draw_glyphs`]. pub struct DrawGlyphs<'a> { scene: &'a mut Scene, run: GlyphRun, diff --git a/vello_shaders/shader/fine.wgsl b/vello_shaders/shader/fine.wgsl index 91b0ddda..922c7ec2 100644 --- a/vello_shaders/shader/fine.wgsl +++ b/vello_shaders/shader/fine.wgsl @@ -857,7 +857,7 @@ let PIXELS_PER_THREAD = 4u; #ifndef msaa -// Analytic area antialiasing. +// Analytic area anti-aliasing. // // This is currently dead code if msaa is enabled, but it would be fairly straightforward // to wire this so it's a dynamic choice (even per-path).