vello/
lib.rs

1// Copyright 2022 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Vello is a 2d graphics rendering engine written in Rust, using [`wgpu`].
5//! It efficiently draws large 2d scenes with interactive or near-interactive performance.
6//!
7//! ![image](https://github.com/linebender/vello/assets/8573618/cc2b742e-2135-4b70-8051-c49aeddb5d19)
8//!
9//!
10//! ## Motivation
11//!
12//! Vello is meant to fill the same place in the graphics stack as other vector graphics renderers like [Skia](https://skia.org/), [Cairo](https://www.cairographics.org/), and its predecessor project [Piet](https://www.cairographics.org/).
13//! On a basic level, that means it provides tools to render shapes, images, gradients, texts, etc, using a PostScript-inspired API, the same that powers SVG files and [the browser `<canvas>` element](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D).
14//!
15//! Vello's selling point is that it gets better performance than other renderers by better leveraging the GPU.
16//! In traditional PostScript-style renderers, some steps of the render process like sorting and clipping either need to be handled in the CPU or done through the use of intermediary textures.
17//! Vello avoids this by using prefix-scan algorithms to parallelize work that usually needs to happen in sequence, so that work can be offloaded to the GPU with minimal use of temporary buffers.
18//!
19//! This means that Vello needs a GPU with support for compute shaders to run.
20//!
21//!
22//! ## Getting started
23//!
24//! Vello is meant to be integrated deep in UI render stacks.
25//! 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.
26//!
27//! To use Vello as the renderer for your PDF reader / GUI toolkit / etc, your code will have to look roughly like this:
28//!
29//! ```ignore
30//! // Initialize wgpu and get handles
31//! let (width, height) = ...;
32//! let device: wgpu::Device = ...;
33//! let queue: wgpu::Queue = ...;
34//! let surface: wgpu::Surface<'_> = ...;
35//! let texture_format: wgpu::TextureFormat = ...;
36//! let mut renderer = Renderer::new(
37//!    &device,
38//!    RendererOptions {
39//!       surface_format: Some(texture_format),
40//!       use_cpu: false,
41//!       antialiasing_support: vello::AaSupport::all(),
42//!       num_init_threads: NonZeroUsize::new(1),
43//!    },
44//! ).expect("Failed to create renderer");
45//!
46//! // Create scene and draw stuff in it
47//! let mut scene = vello::Scene::new();
48//! scene.fill(
49//!    vello::peniko::Fill::NonZero,
50//!    vello::Affine::IDENTITY,
51//!    vello::Color::from_rgb8(242, 140, 168),
52//!    None,
53//!    &vello::Circle::new((420.0, 200.0), 120.0),
54//! );
55//!
56//! // Draw more stuff
57//! scene.push_layer(...);
58//! scene.fill(...);
59//! scene.stroke(...);
60//! scene.pop_layer(...);
61//!
62//! // Render to your window/buffer/etc.
63//! let surface_texture = surface.get_current_texture()
64//!    .expect("failed to get surface texture");
65//! renderer
66//!    .render_to_surface(
67//!       &device,
68//!       &queue,
69//!       &scene,
70//!       &surface_texture,
71//!       &vello::RenderParams {
72//!          base_color: palette::css::BLACK, // Background color
73//!          width,
74//!          height,
75//!          antialiasing_method: AaConfig::Msaa16,
76//!       },
77//!    )
78//!    .expect("Failed to render to surface");
79//! surface_texture.present();
80//! ```
81//!
82//! See the [`examples/`](https://github.com/linebender/vello/tree/main/examples) folder to see how that code integrates with frameworks like winit.
83
84// LINEBENDER LINT SET - lib.rs - v2
85// See https://linebender.org/wiki/canonical-lints/
86// These lints aren't included in Cargo.toml because they
87// shouldn't apply to examples and tests
88#![warn(unused_crate_dependencies)]
89#![warn(clippy::print_stdout, clippy::print_stderr)]
90// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
91#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
92// END LINEBENDER LINT SET
93#![cfg_attr(docsrs, feature(doc_auto_cfg))]
94// The following lints are part of the Linebender standard set,
95// but resolving them has been deferred for now.
96// Feel free to send a PR that solves one or more of these.
97// Need to allow instead of expect until Rust 1.83 https://github.com/rust-lang/rust/pull/130025
98#![allow(missing_docs, reason = "We have many as-yet undocumented items.")]
99#![expect(
100    missing_debug_implementations,
101    single_use_lifetimes,
102    unnameable_types,
103    unreachable_pub,
104    clippy::cast_possible_truncation,
105    clippy::missing_assert_message,
106    clippy::shadow_unrelated,
107    clippy::missing_panics_doc,
108    clippy::exhaustive_enums,
109    clippy::print_stderr,
110    clippy::match_same_arms,
111    reason = "Deferred"
112)]
113#![allow(
114    clippy::missing_errors_doc,
115    clippy::todo,
116    clippy::partial_pub_fields,
117    reason = "Deferred, only apply in some feature sets so not expect"
118)]
119
120mod debug;
121mod recording;
122mod render;
123mod scene;
124mod shaders;
125
126#[cfg(feature = "wgpu")]
127pub mod util;
128#[cfg(feature = "wgpu")]
129mod wgpu_engine;
130
131pub mod low_level {
132    //! Utilities which can be used to create an alternative Vello renderer to [`Renderer`][crate::Renderer].
133    //!
134    //! These APIs have not been carefully designed, and might not be powerful enough for this use case.
135
136    pub use crate::debug::DebugLayers;
137    pub use crate::recording::{
138        BindType, BufferProxy, Command, ImageFormat, ImageProxy, Recording, ResourceId,
139        ResourceProxy, ShaderId,
140    };
141    pub use crate::render::Render;
142    pub use crate::shaders::FullShaders;
143    /// Temporary export, used in `with_winit` for stats
144    pub use vello_encoding::BumpAllocators;
145}
146/// Styling and composition primitives.
147pub use peniko;
148/// 2D geometry, with a focus on curves.
149pub use peniko::kurbo;
150
151#[cfg(feature = "wgpu")]
152pub use wgpu;
153
154pub use scene::{DrawGlyphs, Scene};
155pub use vello_encoding::{Glyph, NormalizedCoord};
156
157use low_level::ShaderId;
158#[cfg(feature = "wgpu")]
159use low_level::{
160    BindType, BumpAllocators, FullShaders, ImageFormat, ImageProxy, Recording, Render,
161    ResourceProxy,
162};
163use thiserror::Error;
164
165#[cfg(feature = "wgpu")]
166use debug::DebugLayers;
167#[cfg(feature = "wgpu")]
168use vello_encoding::Resolver;
169#[cfg(feature = "wgpu")]
170use wgpu_engine::{ExternalResource, WgpuEngine};
171
172#[cfg(feature = "wgpu")]
173use std::{
174    num::NonZeroUsize,
175    sync::{atomic::AtomicBool, Arc},
176};
177#[cfg(feature = "wgpu")]
178use wgpu::{Device, Queue, SurfaceTexture, TextureFormat, TextureView};
179#[cfg(all(feature = "wgpu", feature = "wgpu-profiler"))]
180use wgpu_profiler::{GpuProfiler, GpuProfilerSettings};
181
182/// Represents the anti-aliasing method to use during a render pass.
183///
184/// Can be configured for a render operation by setting [`RenderParams::antialiasing_method`].
185/// Each value of this can only be used if the corresponding field on [`AaSupport`] was used.
186///
187/// This can be converted into an `AaSupport` using [`Iterator::collect`],
188/// as `AaSupport` implements `FromIterator`.
189#[derive(Debug, Copy, Clone, PartialEq, Eq)]
190pub enum AaConfig {
191    /// Area anti-aliasing, where the alpha value for a pixel is computed from integrating
192    /// the winding number over its square area.
193    ///
194    /// This technique produces very accurate values when the shape has winding number of 0 or 1
195    /// everywhere, but can result in conflation artifacts otherwise.
196    /// It generally has better performance than the multi-sampling methods.
197    ///
198    /// Can only be used if [enabled][AaSupport::area] for the `Renderer`.
199    Area,
200    /// 8x Multisampling
201    ///
202    /// Can only be used if [enabled][AaSupport::msaa8] for the `Renderer`.
203    Msaa8,
204    /// 16x Multisampling
205    ///
206    /// Can only be used if [enabled][AaSupport::msaa16] for the `Renderer`.
207    Msaa16,
208}
209
210/// Represents the set of anti-aliasing configurations to enable during pipeline creation.
211///
212/// This is configured at `Renderer` creation time ([`Renderer::new`]) by setting
213/// [`RendererOptions::antialiasing_support`].
214///
215/// This can be created from a set of `AaConfig` using [`Iterator::collect`],
216/// as `AaSupport` implements `FromIterator`.
217#[derive(Debug, Copy, Clone, PartialEq, Eq)]
218pub struct AaSupport {
219    /// Support [`AaConfig::Area`].
220    pub area: bool,
221    /// Support [`AaConfig::Msaa8`].
222    pub msaa8: bool,
223    /// Support [`AaConfig::Msaa16`].
224    pub msaa16: bool,
225}
226
227impl AaSupport {
228    /// Support every anti-aliasing method.
229    ///
230    /// This might increase startup time, as more shader variations must be compiled.
231    pub fn all() -> Self {
232        Self {
233            area: true,
234            msaa8: true,
235            msaa16: true,
236        }
237    }
238
239    /// Support only [`AaConfig::Area`].
240    ///
241    /// This should be the default choice for most users.
242    pub fn area_only() -> Self {
243        Self {
244            area: true,
245            msaa8: false,
246            msaa16: false,
247        }
248    }
249}
250
251impl FromIterator<AaConfig> for AaSupport {
252    fn from_iter<T: IntoIterator<Item = AaConfig>>(iter: T) -> Self {
253        let mut result = Self {
254            area: false,
255            msaa8: false,
256            msaa16: false,
257        };
258        for config in iter {
259            match config {
260                AaConfig::Area => result.area = true,
261                AaConfig::Msaa8 => result.msaa8 = true,
262                AaConfig::Msaa16 => result.msaa16 = true,
263            }
264        }
265        result
266    }
267}
268
269/// Errors that can occur in Vello.
270#[derive(Error, Debug)]
271#[non_exhaustive]
272pub enum Error {
273    /// There is no available device with the features required by Vello.
274    #[cfg(feature = "wgpu")]
275    #[error("Couldn't find suitable device")]
276    NoCompatibleDevice,
277    /// Failed to create surface.
278    /// See [`wgpu::CreateSurfaceError`] for more information.
279    #[cfg(feature = "wgpu")]
280    #[error("Couldn't create wgpu surface")]
281    WgpuCreateSurfaceError(#[from] wgpu::CreateSurfaceError),
282    /// Surface doesn't support the required texture formats.
283    /// Make sure that you have a surface which provides one of
284    /// [`TextureFormat::Rgba8Unorm`] or [`TextureFormat::Bgra8Unorm`] as texture formats.
285    #[cfg(feature = "wgpu")]
286    #[error("Couldn't find `Rgba8Unorm` or `Bgra8Unorm` texture formats for surface")]
287    UnsupportedSurfaceFormat,
288
289    /// Used a buffer inside a recording while it was not available.
290    /// Check if you have created it and not freed before its last usage.
291    #[cfg(feature = "wgpu")]
292    #[error("Buffer '{0}' is not available but used for {1}")]
293    UnavailableBufferUsed(&'static str, &'static str),
294    /// Failed to async map a buffer.
295    /// See [`wgpu::BufferAsyncError`] for more information.
296    #[cfg(feature = "wgpu")]
297    #[error("Failed to async map a buffer")]
298    BufferAsyncError(#[from] wgpu::BufferAsyncError),
299    /// Failed to download an internal buffer for debug visualization.
300    #[cfg(feature = "wgpu")]
301    #[cfg(feature = "debug_layers")]
302    #[error("Failed to download internal buffer '{0}' for visualization")]
303    DownloadError(&'static str),
304
305    #[cfg(feature = "wgpu")]
306    #[error("wgpu Error from scope")]
307    WgpuErrorFromScope(#[from] wgpu::Error),
308
309    /// Failed to create [`GpuProfiler`].
310    /// See [`wgpu_profiler::CreationError`] for more information.
311    #[cfg(feature = "wgpu-profiler")]
312    #[error("Couldn't create wgpu profiler")]
313    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
314    ProfilerCreationError(#[from] wgpu_profiler::CreationError),
315
316    /// Failed to compile the shaders.
317    #[cfg(feature = "hot_reload")]
318    #[error("Failed to compile shaders:\n{0}")]
319    #[doc(hidden)] // End-users of Vello should not have `hot_reload` enabled.
320    ShaderCompilation(#[from] vello_shaders::compile::ErrorVec),
321}
322
323#[cfg_attr(
324    not(feature = "wgpu"),
325    expect(dead_code, reason = "this can be unused when wgpu feature is not used")
326)]
327pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
328
329/// Renders a scene into a texture or surface.
330///
331/// Currently, each renderer only supports a single surface format, if it
332/// supports drawing to surfaces at all.
333/// This is an assumption which is known to be limiting, and is planned to change.
334#[cfg(feature = "wgpu")]
335pub struct Renderer {
336    #[cfg_attr(
337        not(feature = "hot_reload"),
338        expect(
339            dead_code,
340            reason = "Options are only used to reinitialise on a hot reload"
341        )
342    )]
343    options: RendererOptions,
344    engine: WgpuEngine,
345    resolver: Resolver,
346    shaders: FullShaders,
347    blit: Option<BlitPipeline>,
348    #[cfg(feature = "debug_layers")]
349    debug: Option<debug::DebugRenderer>,
350    target: Option<TargetTexture>,
351    #[cfg(feature = "wgpu-profiler")]
352    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
353    /// The profiler used with events for this renderer. This is *not* treated as public API.
354    pub profiler: GpuProfiler,
355    #[cfg(feature = "wgpu-profiler")]
356    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
357    /// The results from profiling. This is *not* treated as public API.
358    pub profile_result: Option<Vec<wgpu_profiler::GpuTimerQueryResult>>,
359}
360// This is not `Send` (or `Sync`) on WebAssembly as the
361// underlying wgpu types are not. This can be enabled with the
362// `fragile-send-sync-non-atomic-wasm` feature in wgpu.
363// See https://github.com/gfx-rs/wgpu/discussions/4127 for
364// further discussion of this topic.
365#[cfg(all(feature = "wgpu", not(target_arch = "wasm32")))]
366static_assertions::assert_impl_all!(Renderer: Send);
367
368/// Parameters used in a single render that are configurable by the client.
369///
370/// These are used in [`Renderer::render_to_surface`] and [`Renderer::render_to_texture`].
371pub struct RenderParams {
372    /// The background color applied to the target. This value is only applicable to the full
373    /// pipeline.
374    pub base_color: peniko::Color,
375
376    /// Dimensions of the rasterization target
377    pub width: u32,
378    pub height: u32,
379
380    /// The anti-aliasing algorithm. The selected algorithm must have been initialized while
381    /// constructing the `Renderer`.
382    pub antialiasing_method: AaConfig,
383}
384
385#[cfg(feature = "wgpu")]
386/// Options which are set at renderer creation time, used in [`Renderer::new`].
387pub struct RendererOptions {
388    /// The format of the texture used for surfaces with this renderer/device
389    /// If None, the renderer cannot be used with surfaces
390    pub surface_format: Option<TextureFormat>,
391
392    /// If true, run all stages up to fine rasterization on the CPU.
393    // TODO: Consider evolving this so that the CPU stages can be configured dynamically via
394    // `RenderParams`.
395    pub use_cpu: bool,
396
397    /// Represents the enabled set of AA configurations. This will be used to determine which
398    /// pipeline permutations should be compiled at startup.
399    pub antialiasing_support: AaSupport,
400
401    /// How many threads to use for initialisation of shaders.
402    ///
403    /// Use `Some(1)` to use a single thread. This is recommended when on macOS
404    /// (see <https://github.com/bevyengine/bevy/pull/10812#discussion_r1496138004>)
405    ///
406    /// Set to `None` to use a heuristic which will use many but not all threads
407    ///
408    /// Has no effect on WebAssembly
409    pub num_init_threads: Option<NonZeroUsize>,
410}
411
412#[cfg(feature = "wgpu")]
413struct RenderResult {
414    bump: Option<BumpAllocators>,
415    #[cfg(feature = "debug_layers")]
416    captured: Option<render::CapturedBuffers>,
417}
418
419#[cfg(feature = "wgpu")]
420impl Renderer {
421    /// Creates a new renderer for the specified device.
422    pub fn new(device: &Device, options: RendererOptions) -> Result<Self> {
423        let mut engine = WgpuEngine::new(options.use_cpu);
424        // If we are running in parallel (i.e. the number of threads is not 1)
425        if options.num_init_threads != NonZeroUsize::new(1) {
426            #[cfg(not(target_arch = "wasm32"))]
427            engine.use_parallel_initialisation();
428        }
429        let shaders = shaders::full_shaders(device, &mut engine, &options)?;
430        #[cfg(not(target_arch = "wasm32"))]
431        engine.build_shaders_if_needed(device, options.num_init_threads);
432        let blit = options
433            .surface_format
434            .map(|surface_format| BlitPipeline::new(device, surface_format, &mut engine))
435            .transpose()?;
436        #[cfg(feature = "debug_layers")]
437        let debug = options
438            .surface_format
439            .map(|surface_format| debug::DebugRenderer::new(device, surface_format, &mut engine));
440
441        Ok(Self {
442            options,
443            engine,
444            resolver: Resolver::new(),
445            shaders,
446            blit,
447            #[cfg(feature = "debug_layers")]
448            debug,
449            target: None,
450            #[cfg(feature = "wgpu-profiler")]
451            profiler: GpuProfiler::new(GpuProfilerSettings {
452                ..Default::default()
453            })?,
454            #[cfg(feature = "wgpu-profiler")]
455            profile_result: None,
456        })
457    }
458
459    /// Renders a scene to the target texture.
460    ///
461    /// The texture is assumed to be of the specified dimensions and have been created with
462    /// the [`wgpu::TextureFormat::Rgba8Unorm`] format and the [`wgpu::TextureUsages::STORAGE_BINDING`]
463    /// flag set.
464    pub fn render_to_texture(
465        &mut self,
466        device: &Device,
467        queue: &Queue,
468        scene: &Scene,
469        texture: &TextureView,
470        params: &RenderParams,
471    ) -> Result<()> {
472        let (recording, target) =
473            render::render_full(scene, &mut self.resolver, &self.shaders, params);
474        let external_resources = [ExternalResource::Image(
475            *target.as_image().unwrap(),
476            texture,
477        )];
478        self.engine.run_recording(
479            device,
480            queue,
481            &recording,
482            &external_resources,
483            "render_to_texture",
484            #[cfg(feature = "wgpu-profiler")]
485            &mut self.profiler,
486        )?;
487        Ok(())
488    }
489
490    /// Renders a scene to the target surface.
491    ///
492    /// This renders to an intermediate texture and then runs a render pass to blit to the
493    /// specified surface texture.
494    ///
495    /// The surface is assumed to be of the specified dimensions and have been configured with
496    /// the same format passed in the constructing [`RendererOptions`]' `surface_format`.
497    /// Panics if `surface_format` was `None`
498    pub fn render_to_surface(
499        &mut self,
500        device: &Device,
501        queue: &Queue,
502        scene: &Scene,
503        surface: &SurfaceTexture,
504        params: &RenderParams,
505    ) -> Result<()> {
506        let width = params.width;
507        let height = params.height;
508        let mut target = self
509            .target
510            .take()
511            .unwrap_or_else(|| TargetTexture::new(device, width, height));
512        // TODO: implement clever resizing semantics here to avoid thrashing the memory allocator
513        // during resize, specifically on metal.
514        if target.width != width || target.height != height {
515            target = TargetTexture::new(device, width, height);
516        }
517        self.render_to_texture(device, queue, scene, &target.view, params)?;
518        let blit = self
519            .blit
520            .as_ref()
521            .expect("renderer should have configured surface_format to use on a surface");
522        let mut recording = Recording::default();
523        let target_proxy = ImageProxy::new(
524            width,
525            height,
526            ImageFormat::from_wgpu(target.format)
527                .expect("`TargetTexture` always has a supported texture format"),
528        );
529        let surface_proxy = ImageProxy::new(
530            width,
531            height,
532            ImageFormat::from_wgpu(surface.texture.format())
533                .ok_or(Error::UnsupportedSurfaceFormat)?,
534        );
535        recording.draw(recording::DrawParams {
536            shader_id: blit.0,
537            instance_count: 1,
538            vertex_count: 6,
539            vertex_buffer: None,
540            resources: vec![ResourceProxy::Image(target_proxy)],
541            target: surface_proxy,
542            clear_color: Some([0., 0., 0., 0.]),
543        });
544
545        let surface_view = surface
546            .texture
547            .create_view(&wgpu::TextureViewDescriptor::default());
548        let external_resources = [
549            ExternalResource::Image(target_proxy, &target.view),
550            ExternalResource::Image(surface_proxy, &surface_view),
551        ];
552        self.engine.run_recording(
553            device,
554            queue,
555            &recording,
556            &external_resources,
557            "blit (render_to_surface)",
558            #[cfg(feature = "wgpu-profiler")]
559            &mut self.profiler,
560        )?;
561        self.target = Some(target);
562        #[cfg(feature = "wgpu-profiler")]
563        {
564            self.profiler.end_frame().unwrap();
565            if let Some(result) = self
566                .profiler
567                .process_finished_frame(queue.get_timestamp_period())
568            {
569                self.profile_result = Some(result);
570            }
571        }
572        Ok(())
573    }
574
575    /// Overwrite `image` with `texture`.
576    ///
577    /// Whenever `image` would be rendered, instead the given `Texture` will be used.
578    ///
579    /// Correct behaviour is not guaranteed if the texture does not have the same
580    /// dimensions as the image, nor if an image which uses the same [data] but different
581    /// dimensions would be rendered.
582    ///
583    /// [data]: peniko::Image::data
584    pub fn override_image(
585        &mut self,
586        image: &peniko::Image,
587        texture: Option<wgpu::ImageCopyTextureBase<Arc<wgpu::Texture>>>,
588    ) -> Option<wgpu::ImageCopyTextureBase<Arc<wgpu::Texture>>> {
589        match texture {
590            Some(texture) => self.engine.image_overrides.insert(image.data.id(), texture),
591            None => self.engine.image_overrides.remove(&image.data.id()),
592        }
593    }
594
595    /// Reload the shaders. This should only be used during `vello` development
596    #[cfg(feature = "hot_reload")]
597    #[doc(hidden)] // End-users of Vello should not have `hot_reload` enabled.
598    pub async fn reload_shaders(&mut self, device: &Device) -> Result<(), Error> {
599        device.push_error_scope(wgpu::ErrorFilter::Validation);
600        let mut engine = WgpuEngine::new(self.options.use_cpu);
601        // We choose not to initialise these shaders in parallel, to ensure the error scope works correctly
602        let shaders = shaders::full_shaders(device, &mut engine, &self.options)?;
603        let blit = self
604            .options
605            .surface_format
606            .map(|surface_format| BlitPipeline::new(device, surface_format, &mut engine))
607            .transpose()?;
608        #[cfg(feature = "debug_layers")]
609        let debug = self
610            .options
611            .surface_format
612            .map(|format| debug::DebugRenderer::new(device, format, &mut engine));
613        let error = device.pop_error_scope().await;
614        if let Some(error) = error {
615            return Err(error.into());
616        }
617        self.engine = engine;
618        self.shaders = shaders;
619        self.blit = blit;
620        #[cfg(feature = "debug_layers")]
621        {
622            self.debug = debug;
623        }
624        Ok(())
625    }
626
627    /// Renders a scene to the target texture using an async pipeline.
628    ///
629    /// Almost all consumers should prefer [`Self::render_to_texture`].
630    ///
631    /// The texture is assumed to be of the specified dimensions and have been created with
632    /// the [`wgpu::TextureFormat::Rgba8Unorm`] format and the [`wgpu::TextureUsages::STORAGE_BINDING`]
633    /// flag set.
634    ///
635    /// The return value is the value of the `BumpAllocators` in this rendering, which is currently used
636    /// for debug output.
637    ///
638    /// This return type is not stable, and will likely be changed when a more principled way to access
639    /// relevant statistics is implemented
640    #[cfg_attr(docsrs, doc(hidden))]
641    #[deprecated(
642        note = "render_to_texture should be preferred, as the _async version has no stability guarantees"
643    )]
644    pub async fn render_to_texture_async(
645        &mut self,
646        device: &Device,
647        queue: &Queue,
648        scene: &Scene,
649        texture: &TextureView,
650        params: &RenderParams,
651    ) -> Result<Option<BumpAllocators>> {
652        let result = self
653            .render_to_texture_async_internal(device, queue, scene, texture, params)
654            .await?;
655        #[cfg(feature = "debug_layers")]
656        {
657            // TODO: it would be better to improve buffer ownership tracking so that it's not
658            // necessary to submit a whole new Recording to free the captured buffers.
659            if let Some(captured) = result.captured {
660                let mut recording = Recording::default();
661                // TODO: this sucks. better to release everything in a helper
662                self.engine.free_download(captured.lines);
663                captured.release_buffers(&mut recording);
664                self.engine.run_recording(
665                    device,
666                    queue,
667                    &recording,
668                    &[],
669                    "free memory",
670                    #[cfg(feature = "wgpu-profiler")]
671                    &mut self.profiler,
672                )?;
673            }
674        }
675        Ok(result.bump)
676    }
677
678    async fn render_to_texture_async_internal(
679        &mut self,
680        device: &Device,
681        queue: &Queue,
682        scene: &Scene,
683        texture: &TextureView,
684        params: &RenderParams,
685    ) -> Result<RenderResult> {
686        let mut render = Render::new();
687        let encoding = scene.encoding();
688        // TODO: turn this on; the download feature interacts with CPU dispatch.
689        // Currently this is always enabled when the `debug_layers` setting is enabled as the bump
690        // counts are used for debug visualiation.
691        let robust = cfg!(feature = "debug_layers");
692        let recording = render.render_encoding_coarse(
693            encoding,
694            &mut self.resolver,
695            &self.shaders,
696            params,
697            robust,
698        );
699        let target = render.out_image();
700        let bump_buf = render.bump_buf();
701        #[cfg(feature = "debug_layers")]
702        let captured = render.take_captured_buffers();
703        self.engine.run_recording(
704            device,
705            queue,
706            &recording,
707            &[],
708            "t_async_coarse",
709            #[cfg(feature = "wgpu-profiler")]
710            &mut self.profiler,
711        )?;
712
713        let mut bump: Option<BumpAllocators> = None;
714        if let Some(bump_buf) = self.engine.get_download(bump_buf) {
715            let buf_slice = bump_buf.slice(..);
716            let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
717            buf_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
718            receiver.receive().await.expect("channel was closed")?;
719            let mapped = buf_slice.get_mapped_range();
720            bump = Some(bytemuck::pod_read_unaligned(&mapped));
721        }
722        // TODO: apply logic to determine whether we need to rerun coarse, and also
723        // allocate the blend stack as needed.
724        self.engine.free_download(bump_buf);
725        // Maybe clear to reuse allocation?
726        let mut recording = Recording::default();
727        render.record_fine(&self.shaders, &mut recording);
728        let external_resources = [ExternalResource::Image(target, texture)];
729        self.engine.run_recording(
730            device,
731            queue,
732            &recording,
733            &external_resources,
734            "t_async_fine",
735            #[cfg(feature = "wgpu-profiler")]
736            &mut self.profiler,
737        )?;
738        Ok(RenderResult {
739            bump,
740            #[cfg(feature = "debug_layers")]
741            captured,
742        })
743    }
744
745    /// This is a version of [`render_to_surface`](Self::render_to_surface) which uses an async pipeline
746    /// to allow improved debugging of Vello itself.
747    /// Most users should prefer `render_to_surface`.
748    ///
749    /// See [`render_to_texture_async`](Self::render_to_texture_async) for more details.
750    #[cfg_attr(docsrs, doc(hidden))]
751    #[deprecated(
752        note = "render_to_surface should be preferred, as the _async version has no stability guarantees"
753    )]
754    pub async fn render_to_surface_async(
755        &mut self,
756        device: &Device,
757        queue: &Queue,
758        scene: &Scene,
759        surface: &SurfaceTexture,
760        params: &RenderParams,
761        debug_layers: DebugLayers,
762    ) -> Result<Option<BumpAllocators>> {
763        if cfg!(not(feature = "debug_layers")) && !debug_layers.is_empty() {
764            static HAS_WARNED: AtomicBool = AtomicBool::new(false);
765            if !HAS_WARNED.swap(true, std::sync::atomic::Ordering::Release) {
766                log::warn!(
767                    "Requested debug layers {debug:?} but `debug_layers` feature is not enabled.",
768                    debug = debug_layers
769                );
770            }
771        }
772
773        let width = params.width;
774        let height = params.height;
775        let mut target = self
776            .target
777            .take()
778            .unwrap_or_else(|| TargetTexture::new(device, width, height));
779        // TODO: implement clever resizing semantics here to avoid thrashing the memory allocator
780        // during resize, specifically on metal.
781        if target.width != width || target.height != height {
782            target = TargetTexture::new(device, width, height);
783        }
784        let result = self
785            .render_to_texture_async_internal(device, queue, scene, &target.view, params)
786            .await?;
787        let blit = self
788            .blit
789            .as_ref()
790            .expect("renderer should have configured surface_format to use on a surface");
791        let mut recording = Recording::default();
792        let target_proxy = ImageProxy::new(
793            width,
794            height,
795            ImageFormat::from_wgpu(target.format)
796                .expect("`TargetTexture` always has a supported texture format"),
797        );
798        let surface_proxy = ImageProxy::new(
799            width,
800            height,
801            ImageFormat::from_wgpu(surface.texture.format())
802                .ok_or(Error::UnsupportedSurfaceFormat)?,
803        );
804        recording.draw(recording::DrawParams {
805            shader_id: blit.0,
806            instance_count: 1,
807            vertex_count: 6,
808            vertex_buffer: None,
809            resources: vec![ResourceProxy::Image(target_proxy)],
810            target: surface_proxy,
811            clear_color: Some([0., 0., 0., 0.]),
812        });
813
814        #[cfg(feature = "debug_layers")]
815        {
816            if let Some(captured) = result.captured {
817                let debug = self
818                    .debug
819                    .as_ref()
820                    .expect("renderer should have configured surface_format to use on a surface");
821                let bump = result.bump.as_ref().unwrap();
822                // TODO: We could avoid this download if `DebugLayers::VALIDATION` is unset.
823                let downloads = DebugDownloads::map(&self.engine, &captured, bump).await?;
824                debug.render(
825                    &mut recording,
826                    surface_proxy,
827                    &captured,
828                    bump,
829                    params,
830                    &downloads,
831                    debug_layers,
832                );
833
834                // TODO: this sucks. better to release everything in a helper
835                // TODO: it would be much better to have a way to safely destroy a buffer.
836                self.engine.free_download(captured.lines);
837                captured.release_buffers(&mut recording);
838            }
839        }
840
841        let surface_view = surface
842            .texture
843            .create_view(&wgpu::TextureViewDescriptor::default());
844        let external_resources = [
845            ExternalResource::Image(target_proxy, &target.view),
846            ExternalResource::Image(surface_proxy, &surface_view),
847        ];
848        self.engine.run_recording(
849            device,
850            queue,
851            &recording,
852            &external_resources,
853            "blit (render_to_surface_async)",
854            #[cfg(feature = "wgpu-profiler")]
855            &mut self.profiler,
856        )?;
857
858        #[cfg(feature = "wgpu-profiler")]
859        {
860            self.profiler.end_frame().unwrap();
861            if let Some(result) = self
862                .profiler
863                .process_finished_frame(queue.get_timestamp_period())
864            {
865                self.profile_result = Some(result);
866            }
867        }
868
869        self.target = Some(target);
870        Ok(result.bump)
871    }
872}
873
874#[cfg(feature = "wgpu")]
875struct TargetTexture {
876    view: TextureView,
877    width: u32,
878    height: u32,
879    format: TextureFormat,
880}
881
882#[cfg(feature = "wgpu")]
883impl TargetTexture {
884    fn new(device: &Device, width: u32, height: u32) -> Self {
885        let format = TextureFormat::Rgba8Unorm;
886        let texture = device.create_texture(&wgpu::TextureDescriptor {
887            label: None,
888            size: wgpu::Extent3d {
889                width,
890                height,
891                depth_or_array_layers: 1,
892            },
893            mip_level_count: 1,
894            sample_count: 1,
895            dimension: wgpu::TextureDimension::D2,
896            usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
897            format,
898            view_formats: &[],
899        });
900        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
901        Self {
902            view,
903            width,
904            height,
905            format,
906        }
907    }
908}
909
910#[cfg(feature = "wgpu")]
911struct BlitPipeline(ShaderId);
912
913#[cfg(feature = "wgpu")]
914impl BlitPipeline {
915    fn new(device: &Device, format: TextureFormat, engine: &mut WgpuEngine) -> Result<Self> {
916        const SHADERS: &str = r#"
917            @vertex
918            fn vs_main(@builtin(vertex_index) ix: u32) -> @builtin(position) vec4<f32> {
919                // Generate a full screen quad in normalized device coordinates
920                var vertex = vec2(-1.0, 1.0);
921                switch ix {
922                    case 1u: {
923                        vertex = vec2(-1.0, -1.0);
924                    }
925                    case 2u, 4u: {
926                        vertex = vec2(1.0, -1.0);
927                    }
928                    case 5u: {
929                        vertex = vec2(1.0, 1.0);
930                    }
931                    default: {}
932                }
933                return vec4(vertex, 0.0, 1.0);
934            }
935
936            @group(0) @binding(0)
937            var fine_output: texture_2d<f32>;
938
939            @fragment
940            fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
941                let rgba_sep = textureLoad(fine_output, vec2<i32>(pos.xy), 0);
942                return vec4(rgba_sep.rgb * rgba_sep.a, rgba_sep.a);
943            }
944        "#;
945        let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
946            label: Some("blit shaders"),
947            source: wgpu::ShaderSource::Wgsl(SHADERS.into()),
948        });
949        let shader_id = engine.add_render_shader(
950            device,
951            "vello.blit",
952            &module,
953            "vs_main",
954            "fs_main",
955            wgpu::PrimitiveTopology::TriangleList,
956            wgpu::ColorTargetState {
957                format,
958                blend: None,
959                write_mask: wgpu::ColorWrites::ALL,
960            },
961            None,
962            &[(
963                BindType::ImageRead(
964                    ImageFormat::from_wgpu(format).ok_or(Error::UnsupportedSurfaceFormat)?,
965                ),
966                wgpu::ShaderStages::FRAGMENT,
967            )],
968        );
969        Ok(Self(shader_id))
970    }
971}
972
973#[cfg(all(feature = "debug_layers", feature = "wgpu"))]
974pub(crate) struct DebugDownloads<'a> {
975    pub lines: wgpu::BufferSlice<'a>,
976}
977
978#[cfg(all(feature = "debug_layers", feature = "wgpu"))]
979impl<'a> DebugDownloads<'a> {
980    pub async fn map(
981        engine: &'a WgpuEngine,
982        captured: &render::CapturedBuffers,
983        bump: &BumpAllocators,
984    ) -> Result<DebugDownloads<'a>> {
985        use vello_encoding::LineSoup;
986
987        let Some(lines_buf) = engine.get_download(captured.lines) else {
988            return Err(Error::DownloadError("linesoup"));
989        };
990
991        let lines = lines_buf.slice(..bump.lines as u64 * size_of::<LineSoup>() as u64);
992        let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
993        lines.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
994        receiver.receive().await.expect("channel was closed")?;
995        Ok(Self { lines })
996    }
997}