这是indexloc提供的服务,不要输入任何密码

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 mut renderer = Renderer::new(
35//!    &device,
36//!    RendererOptions {
37//!       use_cpu: false,
38//!       antialiasing_support: vello::AaSupport::all(),
39//!       num_init_threads: NonZeroUsize::new(1),
40//!    },
41//! ).expect("Failed to create renderer");
42//!
43//! // Create scene and draw stuff in it
44//! let mut scene = vello::Scene::new();
45//! scene.fill(
46//!    vello::peniko::Fill::NonZero,
47//!    vello::Affine::IDENTITY,
48//!    vello::Color::from_rgb8(242, 140, 168),
49//!    None,
50//!    &vello::Circle::new((420.0, 200.0), 120.0),
51//! );
52//!
53//! // Draw more stuff
54//! scene.push_layer(...);
55//! scene.fill(...);
56//! scene.stroke(...);
57//! scene.pop_layer(...);
58//!
59//! let texture = device.create_texture(&...);
60//! // Render to a wgpu Texture
61//! renderer
62//!    .render_to_texture(
63//!       &device,
64//!       &queue,
65//!       &scene,
66//!       &texture,
67//!       &vello::RenderParams {
68//!          base_color: palette::css::BLACK, // Background color
69//!          width,
70//!          height,
71//!          antialiasing_method: AaConfig::Msaa16,
72//!       },
73//!    )
74//!    .expect("Failed to render to a texture");
75//! // Do things with surface texture, such as blitting it to the Surface using
76//! // wgpu::util::TextureBlitter.
77//! ```
78//!
79//! See the [`examples/`](https://github.com/linebender/vello/tree/main/examples) folder to see how that code integrates with frameworks like winit.
80
81// LINEBENDER LINT SET - lib.rs - v2
82// See https://linebender.org/wiki/canonical-lints/
83// These lints aren't included in Cargo.toml because they
84// shouldn't apply to examples and tests
85#![warn(unused_crate_dependencies)]
86#![warn(clippy::print_stdout, clippy::print_stderr)]
87// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
88#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
89// END LINEBENDER LINT SET
90#![cfg_attr(docsrs, feature(doc_auto_cfg))]
91// The following lints are part of the Linebender standard set,
92// but resolving them has been deferred for now.
93// Feel free to send a PR that solves one or more of these.
94// Need to allow instead of expect until Rust 1.83 https://github.com/rust-lang/rust/pull/130025
95#![allow(missing_docs, reason = "We have many as-yet undocumented items.")]
96#![expect(
97    missing_debug_implementations,
98    clippy::cast_possible_truncation,
99    clippy::missing_assert_message,
100    clippy::shadow_unrelated,
101    clippy::print_stderr,
102    reason = "Deferred"
103)]
104#![allow(
105    clippy::todo,
106    unreachable_pub,
107    unnameable_types,
108    reason = "Deferred, only apply in some feature sets so not expect"
109)]
110
111mod debug;
112mod recording;
113mod render;
114mod scene;
115mod shaders;
116
117#[cfg(feature = "wgpu")]
118pub mod util;
119#[cfg(feature = "wgpu")]
120mod wgpu_engine;
121
122pub mod low_level {
123    //! Utilities which can be used to create an alternative Vello renderer to [`Renderer`][crate::Renderer].
124    //!
125    //! These APIs have not been carefully designed, and might not be powerful enough for this use case.
126
127    pub use crate::debug::DebugLayers;
128    pub use crate::recording::{
129        BindType, BufferProxy, Command, ImageFormat, ImageProxy, Recording, ResourceId,
130        ResourceProxy, ShaderId,
131    };
132    pub use crate::render::Render;
133    pub use crate::shaders::FullShaders;
134    /// Temporary export, used in `with_winit` for stats
135    pub use vello_encoding::BumpAllocators;
136}
137/// Styling and composition primitives.
138pub use peniko;
139/// 2D geometry, with a focus on curves.
140pub use peniko::kurbo;
141
142#[cfg(feature = "wgpu")]
143pub use wgpu;
144
145pub use scene::{DrawGlyphs, Scene};
146pub use vello_encoding::{Glyph, NormalizedCoord};
147
148use low_level::ShaderId;
149#[cfg(feature = "wgpu")]
150use low_level::{BumpAllocators, FullShaders, Recording, Render};
151use thiserror::Error;
152
153#[cfg(feature = "wgpu")]
154use debug::DebugLayers;
155#[cfg(feature = "wgpu")]
156use vello_encoding::Resolver;
157#[cfg(feature = "wgpu")]
158use wgpu_engine::{ExternalResource, WgpuEngine};
159
160#[cfg(feature = "wgpu")]
161use std::{num::NonZeroUsize, sync::atomic::AtomicBool};
162#[cfg(feature = "wgpu")]
163use wgpu::{Device, Queue, TextureView};
164#[cfg(all(feature = "wgpu", feature = "wgpu-profiler"))]
165use wgpu_profiler::{GpuProfiler, GpuProfilerSettings};
166
167/// Represents the anti-aliasing method to use during a render pass.
168///
169/// Can be configured for a render operation by setting [`RenderParams::antialiasing_method`].
170/// Each value of this can only be used if the corresponding field on [`AaSupport`] was used.
171///
172/// This can be converted into an `AaSupport` using [`Iterator::collect`],
173/// as `AaSupport` implements `FromIterator`.
174#[derive(Debug, Copy, Clone, PartialEq, Eq)]
175pub enum AaConfig {
176    /// Area anti-aliasing, where the alpha value for a pixel is computed from integrating
177    /// the winding number over its square area.
178    ///
179    /// This technique produces very accurate values when the shape has winding number of 0 or 1
180    /// everywhere, but can result in conflation artifacts otherwise.
181    /// It generally has better performance than the multi-sampling methods.
182    ///
183    /// Can only be used if [enabled][AaSupport::area] for the `Renderer`.
184    Area,
185    /// 8x Multisampling
186    ///
187    /// Can only be used if [enabled][AaSupport::msaa8] for the `Renderer`.
188    Msaa8,
189    /// 16x Multisampling
190    ///
191    /// Can only be used if [enabled][AaSupport::msaa16] for the `Renderer`.
192    Msaa16,
193}
194
195/// Represents the set of anti-aliasing configurations to enable during pipeline creation.
196///
197/// This is configured at `Renderer` creation time ([`Renderer::new`]) by setting
198/// [`RendererOptions::antialiasing_support`].
199///
200/// This can be created from a set of `AaConfig` using [`Iterator::collect`],
201/// as `AaSupport` implements `FromIterator`.
202#[derive(Debug, Copy, Clone, PartialEq, Eq)]
203pub struct AaSupport {
204    /// Support [`AaConfig::Area`].
205    pub area: bool,
206    /// Support [`AaConfig::Msaa8`].
207    pub msaa8: bool,
208    /// Support [`AaConfig::Msaa16`].
209    pub msaa16: bool,
210}
211
212impl AaSupport {
213    /// Support every anti-aliasing method.
214    ///
215    /// This might increase startup time, as more shader variations must be compiled.
216    pub fn all() -> Self {
217        Self {
218            area: true,
219            msaa8: true,
220            msaa16: true,
221        }
222    }
223
224    /// Support only [`AaConfig::Area`].
225    ///
226    /// This should be the default choice for most users.
227    pub fn area_only() -> Self {
228        Self {
229            area: true,
230            msaa8: false,
231            msaa16: false,
232        }
233    }
234}
235
236impl FromIterator<AaConfig> for AaSupport {
237    fn from_iter<T: IntoIterator<Item = AaConfig>>(iter: T) -> Self {
238        let mut result = Self {
239            area: false,
240            msaa8: false,
241            msaa16: false,
242        };
243        for config in iter {
244            match config {
245                AaConfig::Area => result.area = true,
246                AaConfig::Msaa8 => result.msaa8 = true,
247                AaConfig::Msaa16 => result.msaa16 = true,
248            }
249        }
250        result
251    }
252}
253
254/// Errors that can occur in Vello.
255#[derive(Error, Debug)]
256#[non_exhaustive]
257pub enum Error {
258    /// There is no available device with the features required by Vello.
259    #[cfg(feature = "wgpu")]
260    #[error("Couldn't find suitable device")]
261    NoCompatibleDevice,
262    /// Failed to create surface.
263    /// See [`wgpu::CreateSurfaceError`] for more information.
264    #[cfg(feature = "wgpu")]
265    #[error("Couldn't create wgpu surface")]
266    WgpuCreateSurfaceError(#[from] wgpu::CreateSurfaceError),
267    /// Surface doesn't support the required texture formats.
268    /// Make sure that you have a surface which provides one of
269    /// [`TextureFormat::Rgba8Unorm`][wgpu::TextureFormat::Rgba8Unorm]
270    /// or [`TextureFormat::Bgra8Unorm`][wgpu::TextureFormat::Bgra8Unorm] as texture formats.
271    // TODO: Why does this restriction exist?
272    #[cfg(feature = "wgpu")]
273    #[error("Couldn't find `Rgba8Unorm` or `Bgra8Unorm` texture formats for surface")]
274    UnsupportedSurfaceFormat,
275
276    /// Used a buffer inside a recording while it was not available.
277    /// Check if you have created it and not freed before its last usage.
278    #[cfg(feature = "wgpu")]
279    #[error("Buffer '{0}' is not available but used for {1}")]
280    UnavailableBufferUsed(&'static str, &'static str),
281    /// Failed to async map a buffer.
282    /// See [`wgpu::BufferAsyncError`] for more information.
283    #[cfg(feature = "wgpu")]
284    #[error("Failed to async map a buffer")]
285    BufferAsyncError(#[from] wgpu::BufferAsyncError),
286    /// Failed to download an internal buffer for debug visualization.
287    #[cfg(feature = "wgpu")]
288    #[cfg(feature = "debug_layers")]
289    #[error("Failed to download internal buffer '{0}' for visualization")]
290    DownloadError(&'static str),
291
292    #[cfg(feature = "wgpu")]
293    #[error("wgpu Error from scope")]
294    WgpuErrorFromScope(#[from] wgpu::Error),
295
296    /// Failed to create [`GpuProfiler`].
297    /// See [`wgpu_profiler::CreationError`] for more information.
298    #[cfg(feature = "wgpu-profiler")]
299    #[error("Couldn't create wgpu profiler")]
300    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
301    ProfilerCreationError(#[from] wgpu_profiler::CreationError),
302
303    /// Failed to compile the shaders.
304    #[cfg(feature = "hot_reload")]
305    #[error("Failed to compile shaders:\n{0}")]
306    #[doc(hidden)] // End-users of Vello should not have `hot_reload` enabled.
307    ShaderCompilation(#[from] vello_shaders::compile::ErrorVec),
308}
309
310#[cfg_attr(
311    not(feature = "wgpu"),
312    expect(dead_code, reason = "this can be unused when wgpu feature is not used")
313)]
314pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
315
316/// Renders a scene into a texture or surface.
317///
318/// Currently, each renderer only supports a single surface format, if it
319/// supports drawing to surfaces at all.
320/// This is an assumption which is known to be limiting, and is planned to change.
321#[cfg(feature = "wgpu")]
322pub struct Renderer {
323    #[cfg_attr(
324        not(feature = "hot_reload"),
325        expect(
326            dead_code,
327            reason = "Options are only used to reinitialise on a hot reload"
328        )
329    )]
330    options: RendererOptions,
331    engine: WgpuEngine,
332    resolver: Resolver,
333    shaders: FullShaders,
334    #[cfg(feature = "debug_layers")]
335    debug: debug::DebugRenderer,
336    #[cfg(feature = "wgpu-profiler")]
337    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
338    /// The profiler used with events for this renderer. This is *not* treated as public API.
339    pub profiler: GpuProfiler,
340    #[cfg(feature = "wgpu-profiler")]
341    #[doc(hidden)] // End-users of Vello should not have `wgpu-profiler` enabled.
342    /// The results from profiling. This is *not* treated as public API.
343    pub profile_result: Option<Vec<wgpu_profiler::GpuTimerQueryResult>>,
344}
345// This is not `Send` (or `Sync`) on WebAssembly as the
346// underlying wgpu types are not. This can be enabled with the
347// `fragile-send-sync-non-atomic-wasm` feature in wgpu.
348// See https://github.com/gfx-rs/wgpu/discussions/4127 for
349// further discussion of this topic.
350#[cfg(all(feature = "wgpu", not(target_arch = "wasm32")))]
351static_assertions::assert_impl_all!(Renderer: Send);
352
353/// Parameters used in a single render that are configurable by the client.
354///
355/// These are used in [`Renderer::render_to_texture`].
356pub struct RenderParams {
357    /// The background color applied to the target. This value is only applicable to the full
358    /// pipeline.
359    pub base_color: peniko::Color,
360
361    /// Dimensions of the rasterization target
362    pub width: u32,
363    pub height: u32,
364
365    /// The anti-aliasing algorithm. The selected algorithm must have been initialized while
366    /// constructing the `Renderer`.
367    pub antialiasing_method: AaConfig,
368}
369
370#[cfg(feature = "wgpu")]
371/// Options which are set at renderer creation time, used in [`Renderer::new`].
372pub struct RendererOptions {
373    /// If true, run all stages up to fine rasterization on the CPU.
374    ///
375    /// This is not a recommended configuration as it is expected to have poor performance,
376    /// but it can be useful for debugging.
377    // TODO: Consider evolving this so that the CPU stages can be configured dynamically via
378    // `RenderParams`.
379    pub use_cpu: bool,
380
381    /// Represents the enabled set of AA configurations. This will be used to determine which
382    /// pipeline permutations should be compiled at startup.
383    ///
384    /// By default this will be all modes, to support the widest range of.
385    /// It is recommended that most users configure this.
386    pub antialiasing_support: AaSupport,
387
388    /// How many threads to use for initialisation of shaders.
389    ///
390    /// Use `Some(1)` to use a single thread. This is recommended when on macOS
391    /// (see <https://github.com/bevyengine/bevy/pull/10812#discussion_r1496138004>)
392    ///
393    /// Set to `None` to use a heuristic which will use many but not all threads
394    ///
395    /// Has no effect on WebAssembly
396    ///
397    /// Will default to `None` on most platforms, `Some(1)` on macOS.
398    pub num_init_threads: Option<NonZeroUsize>,
399
400    /// The pipeline cache to use when creating the shaders.
401    ///
402    /// For much more discussion of expected usage patterns, see the documentation on that type.
403    pub pipeline_cache: Option<wgpu::PipelineCache>,
404}
405
406#[cfg(feature = "wgpu")]
407impl Default for RendererOptions {
408    fn default() -> Self {
409        Self {
410            use_cpu: false,
411            antialiasing_support: AaSupport::all(),
412            #[cfg(target_os = "macos")]
413            num_init_threads: NonZeroUsize::new(1),
414            #[cfg(not(target_os = "macos"))]
415            num_init_threads: None,
416            pipeline_cache: None,
417        }
418    }
419}
420
421#[cfg(feature = "wgpu")]
422struct RenderResult {
423    bump: Option<BumpAllocators>,
424    #[cfg(feature = "debug_layers")]
425    captured: Option<render::CapturedBuffers>,
426}
427
428#[cfg(feature = "wgpu")]
429impl Renderer {
430    /// Creates a new renderer for the specified device.
431    pub fn new(device: &Device, options: RendererOptions) -> Result<Self> {
432        let mut engine = WgpuEngine::new(options.use_cpu, options.pipeline_cache.clone());
433        // If we are running in parallel (i.e. the number of threads is not 1)
434        if options.num_init_threads != NonZeroUsize::new(1) {
435            #[cfg(not(target_arch = "wasm32"))]
436            engine.use_parallel_initialisation();
437        }
438        let shaders = shaders::full_shaders(device, &mut engine, &options)?;
439        #[cfg(not(target_arch = "wasm32"))]
440        engine.build_shaders_if_needed(device, options.num_init_threads);
441        #[cfg(feature = "debug_layers")]
442        let debug = debug::DebugRenderer::new(device, wgpu::TextureFormat::Rgba8Unorm, &mut engine);
443
444        Ok(Self {
445            options,
446            engine,
447            resolver: Resolver::new(),
448            shaders,
449            #[cfg(feature = "debug_layers")]
450            debug,
451            #[cfg(feature = "wgpu-profiler")]
452            profiler: GpuProfiler::new(
453                device,
454                GpuProfilerSettings {
455                    ..Default::default()
456                },
457            )?,
458            #[cfg(feature = "wgpu-profiler")]
459            profile_result: None,
460        })
461    }
462
463    /// Renders a scene to the target texture.
464    ///
465    /// The texture is assumed to be of the specified dimensions and have been created with
466    /// the [`wgpu::TextureFormat::Rgba8Unorm`] format and the [`wgpu::TextureUsages::STORAGE_BINDING`]
467    /// flag set.
468    ///
469    /// If you want to render Vello content to a surface (such as in a UI toolkit), you have two options:
470    /// 1) Render to an intermediate texture, which is the same size as the surface.
471    ///    You would then use [`TextureBlitter`][wgpu::util::TextureBlitter] to blit the rendered result from
472    ///    that texture to the surface.
473    ///    This pattern is supported by the [`util`] module.
474    /// 2) Call `render_to_texture` directly on the [`SurfaceTexture`][wgpu::SurfaceTexture]'s texture, if
475    ///    it has the right usages. This should generally be avoided, as some GPUs assume that you will not
476    ///    be rendering to the surface using a compute pipeline, and optimise accordingly.
477    pub fn render_to_texture(
478        &mut self,
479        device: &Device,
480        queue: &Queue,
481        scene: &Scene,
482        texture: &TextureView,
483        params: &RenderParams,
484    ) -> Result<()> {
485        let (recording, target) =
486            render::render_full(scene, &mut self.resolver, &self.shaders, params);
487        let external_resources = [ExternalResource::Image(
488            *target.as_image().unwrap(),
489            texture,
490        )];
491        self.engine.run_recording(
492            device,
493            queue,
494            &recording,
495            &external_resources,
496            "render_to_texture",
497            #[cfg(feature = "wgpu-profiler")]
498            &mut self.profiler,
499        )?;
500        // N.B. This is horrible; this integration of wgpu-profiler really needs some work...
501        #[cfg(feature = "wgpu-profiler")]
502        {
503            self.profiler.end_frame().unwrap();
504            if let Some(result) = self
505                .profiler
506                .process_finished_frame(queue.get_timestamp_period())
507            {
508                self.profile_result = Some(result);
509            }
510        }
511
512        Ok(())
513    }
514
515    /// Overwrite `image` with `texture`.
516    ///
517    /// Whenever `image` would be rendered, instead the given `Texture` will be used.
518    ///
519    /// Correct behaviour is not guaranteed if the texture does not have the same
520    /// dimensions as the image, nor if an image which uses the same [data] but different
521    /// dimensions would be rendered.
522    ///
523    /// [data]: peniko::Image::data
524    pub fn override_image(
525        &mut self,
526        image: &peniko::Image,
527        texture: Option<wgpu::TexelCopyTextureInfoBase<wgpu::Texture>>,
528    ) -> Option<wgpu::TexelCopyTextureInfoBase<wgpu::Texture>> {
529        match texture {
530            Some(texture) => self.engine.image_overrides.insert(image.data.id(), texture),
531            None => self.engine.image_overrides.remove(&image.data.id()),
532        }
533    }
534
535    /// Reload the shaders. This should only be used during `vello` development
536    #[cfg(feature = "hot_reload")]
537    #[doc(hidden)] // End-users of Vello should not have `hot_reload` enabled.
538    pub async fn reload_shaders(&mut self, device: &Device) -> Result<(), Error> {
539        device.push_error_scope(wgpu::ErrorFilter::Validation);
540        let mut engine = WgpuEngine::new(self.options.use_cpu, self.options.pipeline_cache.clone());
541        // We choose not to initialise these shaders in parallel, to ensure the error scope works correctly
542        let shaders = shaders::full_shaders(device, &mut engine, &self.options)?;
543        #[cfg(feature = "debug_layers")]
544        let debug = debug::DebugRenderer::new(device, wgpu::TextureFormat::Rgba8Unorm, &mut engine);
545        let error = device.pop_error_scope().await;
546        if let Some(error) = error {
547            return Err(error.into());
548        }
549        self.engine = engine;
550        self.shaders = shaders;
551        #[cfg(feature = "debug_layers")]
552        {
553            self.debug = debug;
554        }
555        Ok(())
556    }
557
558    /// Renders a scene to the target texture using an async pipeline.
559    ///
560    /// Almost all consumers should prefer [`Self::render_to_texture`].
561    ///
562    /// The return value is the value of the `BumpAllocators` in this rendering, which is currently used
563    /// for debug output.
564    ///
565    /// This return type is not stable, and will likely be changed when a more principled way to access
566    /// relevant statistics is implemented
567    #[cfg_attr(docsrs, doc(hidden))]
568    #[deprecated(
569        note = "render_to_texture should be preferred, as the _async version has no stability guarantees"
570    )]
571    pub async fn render_to_texture_async(
572        &mut self,
573        device: &Device,
574        queue: &Queue,
575        scene: &Scene,
576        texture: &TextureView,
577        params: &RenderParams,
578        debug_layers: DebugLayers,
579    ) -> Result<Option<BumpAllocators>> {
580        if cfg!(not(feature = "debug_layers")) && !debug_layers.is_empty() {
581            static HAS_WARNED: AtomicBool = AtomicBool::new(false);
582            if !HAS_WARNED.swap(true, std::sync::atomic::Ordering::Release) {
583                log::warn!(
584                    "Requested debug layers {debug:?} but `debug_layers` feature is not enabled.",
585                    debug = debug_layers
586                );
587            }
588        }
589
590        let result = self
591            .render_to_texture_async_internal(device, queue, scene, texture, params)
592            .await?;
593
594        #[cfg(feature = "debug_layers")]
595        {
596            let mut recording = Recording::default();
597            let target_proxy = recording::ImageProxy::new(
598                params.width,
599                params.height,
600                recording::ImageFormat::Rgba8,
601            );
602            if let Some(captured) = result.captured {
603                let bump = result.bump.as_ref().unwrap();
604                // TODO: We could avoid this download if `DebugLayers::VALIDATION` is unset.
605                let downloads = DebugDownloads::map(&self.engine, &captured, bump).await?;
606                self.debug.render(
607                    &mut recording,
608                    target_proxy,
609                    &captured,
610                    bump,
611                    params,
612                    &downloads,
613                    debug_layers,
614                );
615
616                // TODO: this sucks. better to release everything in a helper
617                // TODO: it would be much better to have a way to safely destroy a buffer.
618                self.engine.free_download(captured.lines);
619                captured.release_buffers(&mut recording);
620            }
621            let external_resources = [ExternalResource::Image(target_proxy, texture)];
622            self.engine.run_recording(
623                device,
624                queue,
625                &recording,
626                &external_resources,
627                "render_to_texture_async debug layers",
628                #[cfg(feature = "wgpu-profiler")]
629                &mut self.profiler,
630            )?;
631        }
632
633        #[cfg(feature = "wgpu-profiler")]
634        {
635            self.profiler.end_frame().unwrap();
636            if let Some(result) = self
637                .profiler
638                .process_finished_frame(queue.get_timestamp_period())
639            {
640                self.profile_result = Some(result);
641            }
642        }
643
644        Ok(result.bump)
645    }
646
647    async fn render_to_texture_async_internal(
648        &mut self,
649        device: &Device,
650        queue: &Queue,
651        scene: &Scene,
652        texture: &TextureView,
653        params: &RenderParams,
654    ) -> Result<RenderResult> {
655        let mut render = Render::new();
656        let encoding = scene.encoding();
657        // TODO: turn this on; the download feature interacts with CPU dispatch.
658        // Currently this is always enabled when the `debug_layers` setting is enabled as the bump
659        // counts are used for debug visualiation.
660        let robust = cfg!(feature = "debug_layers");
661        let recording = render.render_encoding_coarse(
662            encoding,
663            &mut self.resolver,
664            &self.shaders,
665            params,
666            robust,
667        );
668        let target = render.out_image();
669        let bump_buf = render.bump_buf();
670        #[cfg(feature = "debug_layers")]
671        let captured = render.take_captured_buffers();
672        self.engine.run_recording(
673            device,
674            queue,
675            &recording,
676            &[],
677            "t_async_coarse",
678            #[cfg(feature = "wgpu-profiler")]
679            &mut self.profiler,
680        )?;
681
682        let mut bump: Option<BumpAllocators> = None;
683        if let Some(bump_buf) = self.engine.get_download(bump_buf) {
684            let buf_slice = bump_buf.slice(..);
685            let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
686            buf_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
687            receiver.receive().await.expect("channel was closed")?;
688            let mapped = buf_slice.get_mapped_range();
689            bump = Some(bytemuck::pod_read_unaligned(&mapped));
690        }
691        // TODO: apply logic to determine whether we need to rerun coarse, and also
692        // allocate the blend stack as needed.
693        self.engine.free_download(bump_buf);
694        // Maybe clear to reuse allocation?
695        let mut recording = Recording::default();
696        render.record_fine(&self.shaders, &mut recording);
697        let external_resources = [ExternalResource::Image(target, texture)];
698        self.engine.run_recording(
699            device,
700            queue,
701            &recording,
702            &external_resources,
703            "t_async_fine",
704            #[cfg(feature = "wgpu-profiler")]
705            &mut self.profiler,
706        )?;
707        Ok(RenderResult {
708            bump,
709            #[cfg(feature = "debug_layers")]
710            captured,
711        })
712    }
713}
714#[cfg(all(feature = "debug_layers", feature = "wgpu"))]
715pub(crate) struct DebugDownloads<'a> {
716    pub lines: wgpu::BufferSlice<'a>,
717}
718
719#[cfg(all(feature = "debug_layers", feature = "wgpu"))]
720impl<'a> DebugDownloads<'a> {
721    pub async fn map(
722        engine: &'a WgpuEngine,
723        captured: &render::CapturedBuffers,
724        bump: &BumpAllocators,
725    ) -> Result<DebugDownloads<'a>> {
726        use vello_encoding::LineSoup;
727
728        let Some(lines_buf) = engine.get_download(captured.lines) else {
729            return Err(Error::DownloadError("linesoup"));
730        };
731
732        let lines = lines_buf.slice(..bump.lines as u64 * size_of::<LineSoup>() as u64);
733        let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
734        lines.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
735        receiver.receive().await.expect("channel was closed")?;
736        Ok(Self { lines })
737    }
738}