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//! 
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}