//! Window-facing GPU plumbing for the vello backend: adapter, device, //! swapchain, and the intermediate texture vello draws into. //! //! vello ships `vello::util::RenderContext` for exactly this, and it is the //! obvious thing to reach for, but it hard-codes the device descriptor — //! including wgpu's default `MemoryHints::Performance`, which tells the //! driver to sub-allocate in very large blocks. On a discrete card that //! costs about a quarter of a gigabyte of video memory before a single //! fragment is drawn, and it is the wrong trade for a GUI: guiduck renders //! a few megabytes of geometry per frame and would rather have the memory //! back than shave a microsecond off an allocation it makes once. //! //! Owning the device here also puts it where the rest of the swapchain and //! present plumbing already lives, which is what the vello backend crate //! documents as the shell's job. use std::error::Error; use std::sync::Arc; use wgpu::util::TextureBlitter; use winit::window::Window; /// The device and swapchain behind one window. pub struct GpuSurface { pub device: wgpu::Device, pub queue: wgpu::Queue, surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration, /// vello rasterizes with a compute shader, which cannot bind a swapchain /// texture. It draws here instead, and this is blitted onto the frame. target_texture: wgpu::Texture, target_view: wgpu::TextureView, blitter: TextureBlitter, } impl GpuSurface { pub async fn new( window: Arc, width: u32, height: u32, present_mode: wgpu::PresentMode, ) -> Result> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { display: None, backends: wgpu::Backends::from_env().unwrap_or_default(), flags: wgpu::InstanceFlags::from_build_config().with_env(), memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), backend_options: wgpu::BackendOptions::from_env_or_default(), }); let surface = instance.create_surface(window)?; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::from_env().unwrap_or_default(), force_fallback_adapter: false, compatible_surface: Some(&surface), }) .await?; // vello uses these when the adapter has them and does without // otherwise, so ask for the intersection rather than requiring them. let optional = wgpu::Features::CLEAR_TEXTURE | wgpu::Features::PIPELINE_CACHE; let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: Some("guiduck"), required_features: adapter.features() & optional, required_limits: wgpu::Limits::default(), memory_hints: wgpu::MemoryHints::MemoryUsage, ..Default::default() }) .await?; let capabilities = surface.get_capabilities(&adapter); let format = capabilities .formats .iter() .copied() .find(wgpu::TextureFormat::is_srgb) .unwrap_or_else(|| { *capabilities .formats .first() .expect("surface supports no formats") }); let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format, width, height, present_mode, desired_maximum_frame_latency: 2, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], }; surface.configure(&device, &config); let (target_texture, target_view) = create_target(&device, width, height); let blitter = TextureBlitter::new(&device, format); Ok(Self { device, queue, surface, config, target_texture, target_view, blitter, }) } /// The view vello renders into. pub fn target_view(&self) -> wgpu::TextureView { self.target_view.clone() } pub fn resize(&mut self, width: u32, height: u32) { if width == 0 || height == 0 || (self.config.width == width && self.config.height == height) { return; } self.config.width = width; self.config.height = height; self.surface.configure(&self.device, &self.config); let (texture, view) = create_target(&self.device, width, height); self.target_texture = texture; self.target_view = view; } /// Reconfigure without resizing, for a surface the compositor has /// invalidated. pub fn reconfigure(&self) { self.surface.configure(&self.device, &self.config); } pub fn get_current_texture(&self) -> wgpu::CurrentSurfaceTexture { self.surface.get_current_texture() } /// Copy what vello drew onto the frame's texture and submit it. pub fn blit_to(&self, frame: &wgpu::TextureView) { let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("guiduck surface blit"), }); self.blitter .copy(&self.device, &mut encoder, &self.target_view, frame); self.queue.submit([encoder.finish()]); } } fn create_target( device: &wgpu::Device, width: u32, height: u32, ) -> (wgpu::Texture, wgpu::TextureView) { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("guiduck vello target"), size: wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, format: wgpu::TextureFormat::Rgba8Unorm, view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); (texture, view) }