Skip to main content

gpui_wgpu/
wgpu_renderer.rs

1use crate::{CompositorGpuHint, DeviceErrorState, WgpuAtlas, WgpuContext, WgpuImportedTexture};
2use anyhow::{Context as _, Result};
3use bytemuck::{Pod, Zeroable};
4use collections::FxHashMap;
5use gpui_engine::{
6    AtlasTextureId, CustomRenderPrimitive, PaintSurface, Path, PlatformAtlas, PrimitiveBatch, Scene,
7    SceneRenderer, get_gamma_correction_ratios,
8};
9#[cfg(all(
10    not(target_family = "wasm"),
11    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
12))]
13use gpui_engine::GpuRenderer;
14use gpui_platform::{Background, Bounds, DevicePixels, GpuSpecs, Point, ScaledPixels, Size};
15use log::warn;
16#[cfg(not(target_family = "wasm"))]
17use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
18use smallvec::SmallVec;
19use std::cell::RefCell;
20use std::num::NonZeroU64;
21use std::ops::Range;
22use std::os::fd::OwnedFd;
23use std::rc::Rc;
24use std::sync::Arc;
25
26const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
27
28const INSTANCE_TEXTURE_TEXEL_SIZE: u64 = 16;
29
30/// Shader variant for backends with storage buffer support: the shared shader
31/// logic plus the storage-buffer instance transport.
32const STORAGE_BUFFER_SHADERS: &str = concat!(
33    include_str!("shaders.wgsl"),
34    include_str!("shaders_storage.wgsl"),
35);
36
37/// Shader variant for WebGL2, which has no storage buffers: the shared shader
38/// logic plus the texture-based instance transport.
39const WEBGL_SHADERS: &str = concat!(
40    include_str!("shaders.wgsl"),
41    include_str!("shaders_webgl.wgsl"),
42);
43
44/// Subpixel text rendering requires dual-source blending, which WebGL2 lacks, so
45/// this variant only ever runs with the storage-buffer transport. The `enable`
46/// directive must precede all declarations.
47const SUBPIXEL_SHADERS: &str = concat!(
48    "enable dual_source_blending;\n",
49    include_str!("shaders.wgsl"),
50    include_str!("shaders_storage.wgsl"),
51    include_str!("shaders_subpixel.wgsl"),
52);
53
54fn least_common_multiple(left: u64, right: u64) -> u64 {
55    let mut first = left;
56    let mut second = right;
57    while second != 0 {
58        let remainder = first % second;
59        first = second;
60        second = remainder;
61    }
62    left / first * right
63}
64
65#[repr(C)]
66#[derive(Clone, Copy, Pod, Zeroable)]
67struct GlobalParams {
68    viewport_size: [f32; 2],
69    premultiplied_alpha: u32,
70    pad: u32,
71}
72
73#[repr(C)]
74#[derive(Clone, Copy, Pod, Zeroable)]
75struct PodBounds {
76    origin: [f32; 2],
77    size: [f32; 2],
78}
79
80impl From<Bounds<ScaledPixels>> for PodBounds {
81    fn from(bounds: Bounds<ScaledPixels>) -> Self {
82        Self {
83            origin: [bounds.origin.x.0, bounds.origin.y.0],
84            size: [bounds.size.width.0, bounds.size.height.0],
85        }
86    }
87}
88
89#[repr(C)]
90#[derive(Clone, Copy, Pod, Zeroable)]
91struct SurfaceParams {
92    bounds: PodBounds,
93    content_mask: PodBounds,
94    /// `0` = `NV12` (sample and convert), `1` = a single RGBA/BGRA plane (sample straight through).
95    /// `chroma_reconstruction` is `0` for bilinear, `1` for luma-guided. The padding keeps the two
96    /// words before `ycbcr_to_rgb` 16-byte aligned, so the `mat4x4` lands where the shader expects.
97    surface_format: u32,
98    chroma_reconstruction: u32,
99    _pad: [u32; 2],
100    /// The `YUV -> RGB` matrix for the colour space the producer declared, column-major as WGSL's
101    /// `mat4x4<f32>` reads it; unused for a single-plane buffer.
102    ycbcr_to_rgb: [[f32; 4]; 4],
103}
104
105/// The matrix the surface fragment multiplies `vec4(y, cb, cr, 1)` by, column-major as WGSL's
106/// `mat4x4<f32>` reads it, inverting the colour space the producer declared.
107///
108/// Standard `YUV -> RGB`: the luma columns are the range's luma scale, the chroma columns carry the
109/// matrix's `Kr`/`Kb` terms times the range's chroma scale, and the constant column folds in both zero
110/// points. For BT.601 full range this reproduces the fixed matrix the shader used to carry.
111fn yuv_to_rgb(color_space: gpui_engine::YuvColorSpace) -> [[f32; 4]; 4] {
112    use gpui_engine::{YuvMatrix, YuvRange};
113
114    let (kr, kb) = match color_space.matrix {
115        YuvMatrix::Bt601 => (0.299f32, 0.114f32),
116        YuvMatrix::Bt709 => (0.2126f32, 0.0722f32),
117        YuvMatrix::Bt2020 => (0.2627f32, 0.0593f32),
118    };
119    let kg = 1.0 - kr - kb;
120    // The offsets the chroma columns carry, before the range's scale: `2(1-Kr)` for red from Cr,
121    // `2(1-Kb)` for blue from Cb, and the two green terms that keep the luma weights summing to one.
122    let red_cr = 2.0 * (1.0 - kr);
123    let blue_cb = 2.0 * (1.0 - kb);
124    let green_cb = 2.0 * kb * (1.0 - kb) / kg;
125    let green_cr = 2.0 * kr * (1.0 - kr) / kg;
126
127    // How the range maps a sample to its full-range value: a scale and a zero point each for luma and
128    // chroma. Full range has no offset and half-scale chroma, so for BT.601 it reproduces the fixed
129    // matrix the shader used to carry and leaves the hand-filled buffers' result unchanged; limited
130    // range uses the `16..235`/`128` studio points.
131    let (y_scale, c_scale, y_zero, c_zero) = match color_space.range {
132        YuvRange::Full => (1.0, 1.0, 0.0, 0.5),
133        YuvRange::Limited => (255.0 / 219.0, 255.0 / 224.0, 16.0 / 255.0, 128.0 / 255.0),
134    };
135
136    let (red_cr, blue_cb) = (red_cr * c_scale, blue_cb * c_scale);
137    let (green_cb, green_cr) = (green_cb * c_scale, green_cr * c_scale);
138    [
139        [y_scale, y_scale, y_scale, 0.0],
140        [0.0, -green_cb, blue_cb, 0.0],
141        [red_cr, -green_cr, 0.0, 0.0],
142        [
143            -red_cr * c_zero - y_zero * y_scale,
144            (green_cb + green_cr) * c_zero - y_zero * y_scale,
145            -blue_cb * c_zero - y_zero * y_scale,
146            1.0,
147        ],
148    ]
149}
150
151#[repr(C)]
152#[derive(Clone, Copy, Pod, Zeroable)]
153struct GammaParams {
154    gamma_ratios: [f32; 4],
155    grayscale_enhanced_contrast: f32,
156    subpixel_enhanced_contrast: f32,
157    is_bgr: u32,
158    _pad: u32,
159}
160
161#[derive(Clone, Debug)]
162#[repr(C)]
163struct PathSprite {
164    bounds: Bounds<ScaledPixels>,
165}
166
167#[derive(Clone, Debug)]
168#[repr(C)]
169struct PathRasterizationVertex {
170    xy_position: Point<ScaledPixels>,
171    st_position: Point<f32>,
172    color: Background,
173    bounds: Bounds<ScaledPixels>,
174}
175
176pub struct WgpuSurfaceConfig {
177    pub size: Size<DevicePixels>,
178    pub transparent: bool,
179    /// Preferred presentation mode. When `Some`, the renderer will use this
180    /// mode if supported by the surface, falling back to `Fifo`.
181    /// When `None`, defaults to `Fifo` (VSync).
182    ///
183    /// Mobile platforms may prefer `Mailbox` (triple-buffering) to avoid
184    /// blocking in `get_current_texture()` during lifecycle transitions.
185    pub preferred_present_mode: Option<wgpu::PresentMode>,
186}
187
188struct WgpuPipelines {
189    quads: wgpu::RenderPipeline,
190    shadows: wgpu::RenderPipeline,
191    path_rasterization: wgpu::RenderPipeline,
192    paths: wgpu::RenderPipeline,
193    underlines: wgpu::RenderPipeline,
194    mono_sprites: wgpu::RenderPipeline,
195    subpixel_sprites: Option<wgpu::RenderPipeline>,
196    poly_sprites: wgpu::RenderPipeline,
197    surfaces: wgpu::RenderPipeline,
198    imported_textures: wgpu::RenderPipeline,
199}
200
201/// One frame allocation of instance data, ready to bind.
202struct InstanceBinding {
203    bind_group: wgpu::BindGroup,
204    /// Index of the allocation's first instance within the bound data. Always
205    /// zero on the storage-buffer path, where the binding offset already
206    /// positions the array; on the WebGL texture path the shader indexes the
207    /// shared instance texture absolutely, so draws must offset their
208    /// instance (or vertex) ranges by this value.
209    first_instance: u32,
210}
211
212struct InstanceBindings {
213    quads: InstanceBinding,
214    shadows: InstanceBinding,
215    underlines: InstanceBinding,
216    monochrome_sprites: InstanceBinding,
217    subpixel_sprites: InstanceBinding,
218    polychrome_sprites: InstanceBinding,
219    imported_textures: InstanceBinding,
220}
221
222struct WgpuBindGroupLayouts {
223    globals: wgpu::BindGroupLayout,
224    instances: wgpu::BindGroupLayout,
225    texture: wgpu::BindGroupLayout,
226    surfaces: wgpu::BindGroupLayout,
227    imported_texture: wgpu::BindGroupLayout,
228}
229
230/// Shared GPU context reference, used to coordinate device recovery across multiple windows.
231pub type WgpuContextSlot = Rc<RefCell<Option<WgpuContext>>>;
232
233enum InstanceData {
234    Storage(wgpu::Buffer),
235    // WebGL2 has no storage buffers. A uint texture keeps the records available to both shader
236    // stages while preserving integer and floating-point bit patterns exactly.
237    Texture {
238        texture: wgpu::Texture,
239        view: wgpu::TextureView,
240        width: u32,
241        height: u32,
242    },
243}
244
245/// GPU resources that must be dropped together during device recovery.
246struct WgpuResources {
247    device: Arc<wgpu::Device>,
248    queue: Arc<wgpu::Queue>,
249    pipelines: WgpuPipelines,
250    bind_group_layouts: WgpuBindGroupLayouts,
251    atlas_sampler: wgpu::Sampler,
252    imported_texture_sampler: wgpu::Sampler,
253    atlas_texture_bind_groups: FxHashMap<AtlasTextureId, CachedTextureBindGroup>,
254    globals_buffer: wgpu::Buffer,
255    /// One uniform buffer for every surface's params in a frame, addressed by dynamic offset, held
256    /// across frames so the surface bind groups that reference it stay valid.
257    surface_params: wgpu::Buffer,
258    globals_bind_group: wgpu::BindGroup,
259    path_globals_bind_group: wgpu::BindGroup,
260    instance_data: InstanceData,
261    path_intermediate_texture: Option<wgpu::Texture>,
262    path_intermediate_view: Option<wgpu::TextureView>,
263    path_msaa_texture: Option<wgpu::Texture>,
264    path_msaa_view: Option<wgpu::TextureView>,
265    /// The imported dma-buf textures, kept across frames like the Metal renderer's `CVMetalTextureCache`.
266    #[cfg(target_os = "linux")]
267    dmabuf_textures: crate::DmaBufTextureCache,
268}
269
270struct CachedTextureBindGroup {
271    texture_generation: u64,
272    bind_group: wgpu::BindGroup,
273}
274
275impl WgpuResources {
276    fn invalidate_intermediate_textures(&mut self) {
277        self.path_intermediate_texture = None;
278        self.path_intermediate_view = None;
279        self.path_msaa_texture = None;
280        self.path_msaa_view = None;
281    }
282}
283
284struct WgpuRendererCore {
285    resources: WgpuResources,
286    atlas: Arc<WgpuAtlas>,
287    path_globals_offset: u64,
288    gamma_offset: u64,
289    instance_data_capacity: u64,
290    max_instance_data_size: u64,
291    instance_data_alignment: u64,
292    uses_webgl_instance_data: bool,
293    rendering_params: RenderingParameters,
294    is_bgr: bool,
295    dual_source_blending: bool,
296    adapter_info: wgpu::AdapterInfo,
297    target_format: wgpu::TextureFormat,
298    max_texture_size: u32,
299}
300
301/// GPU resources of a windowed renderer. A surface is only ever configured against the
302/// device that owns `core`, so it cannot outlive it: there is no surface-only state.
303enum RendererState {
304    /// Frames can be drawn.
305    Ready {
306        surface: wgpu::Surface<'static>,
307        core: WgpuRendererCore,
308    },
309    /// The native surface is gone (Android `TerminateWindow`, browser context loss) but
310    /// the device, pipelines, and atlas remain so `replace_surface` can resume without
311    /// re-uploading cached textures.
312    Unconfigured { core: WgpuRendererCore },
313    /// Released by `destroy`, or dropped for a device recovery that has not succeeded yet.
314    Released,
315}
316
317pub struct WgpuRenderer {
318    /// Shared GPU context for device recovery coordination (unused on WASM).
319    #[allow(dead_code)]
320    context: Option<WgpuContextSlot>,
321    /// Compositor GPU hint for adapter selection (unused on WASM).
322    #[allow(dead_code)]
323    compositor_gpu: Option<CompositorGpuHint>,
324    state: RendererState,
325    surface_config: wgpu::SurfaceConfiguration,
326    atlas: Arc<WgpuAtlas>,
327    transparent_alpha_mode: wgpu::CompositeAlphaMode,
328    opaque_alpha_mode: wgpu::CompositeAlphaMode,
329    max_texture_size: u32,
330    is_bgr: bool,
331    failed_frame_count: u32,
332    device_errors: Arc<DeviceErrorState>,
333    observed_error_generation: u64,
334    last_surface_error: Option<String>,
335    needs_redraw: bool,
336}
337
338impl WgpuRenderer {
339    fn core(&self) -> Option<&WgpuRendererCore> {
340        match &self.state {
341            RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
342            RendererState::Released => None,
343        }
344    }
345
346    fn core_mut(&mut self) -> Option<&mut WgpuRendererCore> {
347        match &mut self.state {
348            RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
349            RendererState::Released => None,
350        }
351    }
352
353    /// Creates a new WgpuRenderer from raw window handles.
354    ///
355    /// The `gpu_context` is a shared reference that coordinates GPU context across
356    /// multiple windows. The first window to create a renderer will initialize the
357    /// context; subsequent windows will share it.
358    ///
359    /// # Safety
360    /// The caller must ensure that the window handle remains valid for the lifetime
361    /// of the returned renderer.
362    #[cfg(not(target_family = "wasm"))]
363    pub fn new<W>(
364        gpu_context: WgpuContextSlot,
365        window: &W,
366        config: WgpuSurfaceConfig,
367        compositor_gpu: Option<CompositorGpuHint>,
368    ) -> anyhow::Result<Self>
369    where
370        W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
371    {
372        let window_handle = window
373            .window_handle()
374            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
375
376        let target = wgpu::SurfaceTargetUnsafe::RawHandle {
377            // Fall back to the display handle already provided via InstanceDescriptor::display.
378            raw_display_handle: None,
379            raw_window_handle: window_handle.as_raw(),
380        };
381
382        // Use the existing context's instance if available, otherwise create a new one.
383        // The surface must be created with the same instance that will be used for
384        // adapter selection, otherwise wgpu will panic.
385        let instance = gpu_context
386            .borrow()
387            .as_ref()
388            .map(|ctx| ctx.instance.clone())
389            .unwrap_or_else(|| WgpuContext::instance(Some(Box::new(window.clone()))));
390
391        // Safety: The caller guarantees that the window handle is valid for the
392        // lifetime of this renderer. In practice, the RawWindow struct is created
393        // from the native window handles and the surface is dropped before the window.
394        let surface = unsafe {
395            instance
396                .create_surface_unsafe(target)
397                .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
398        };
399
400        let mut ctx_ref = gpu_context.borrow_mut();
401        let context = match ctx_ref.as_mut() {
402            Some(context) => {
403                context.check_compatible_with_surface(&surface)?;
404                context
405            }
406            None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?),
407        };
408
409        let atlas = Arc::new(WgpuAtlas::from_context(context));
410
411        Self::new_internal(
412            Some(Rc::clone(&gpu_context)),
413            context,
414            surface,
415            config,
416            compositor_gpu,
417            atlas,
418        )
419    }
420
421    #[cfg(target_family = "wasm")]
422    pub fn new_from_canvas(
423        context: &WgpuContext,
424        canvas: &web_sys::HtmlCanvasElement,
425        config: WgpuSurfaceConfig,
426    ) -> anyhow::Result<Self> {
427        let surface = context
428            .instance
429            .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
430            .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
431        Self::new_from_surface(context, surface, config)
432    }
433
434    #[cfg(target_family = "wasm")]
435    #[allow(clippy::arc_with_non_send_sync)]
436    pub fn new_from_surface(
437        context: &WgpuContext,
438        surface: wgpu::Surface<'static>,
439        config: WgpuSurfaceConfig,
440    ) -> anyhow::Result<Self> {
441        let atlas = Arc::new(WgpuAtlas::from_context(context));
442        Self::new_internal(None, context, surface, config, None, atlas)
443    }
444
445    fn new_internal(
446        gpu_context: Option<WgpuContextSlot>,
447        context: &WgpuContext,
448        surface: wgpu::Surface<'static>,
449        config: WgpuSurfaceConfig,
450        compositor_gpu: Option<CompositorGpuHint>,
451        atlas: Arc<WgpuAtlas>,
452    ) -> anyhow::Result<Self> {
453        let surface_caps = surface.get_capabilities(&context.adapter);
454        let preferred_formats = [
455            wgpu::TextureFormat::Bgra8Unorm,
456            wgpu::TextureFormat::Rgba8Unorm,
457        ];
458        let surface_format = preferred_formats
459            .iter()
460            .find(|f| surface_caps.formats.contains(f))
461            .copied()
462            .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
463            .or_else(|| surface_caps.formats.first().copied())
464            .ok_or_else(|| {
465                anyhow::anyhow!(
466                    "Surface reports no supported texture formats for adapter {:?}",
467                    context.adapter.get_info().name
468                )
469            })?;
470
471        let pick_alpha_mode =
472            |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
473                preferences
474                    .iter()
475                    .find(|p| surface_caps.alpha_modes.contains(p))
476                    .copied()
477                    .or_else(|| surface_caps.alpha_modes.first().copied())
478                    .ok_or_else(|| {
479                        anyhow::anyhow!(
480                            "Surface reports no supported alpha modes for adapter {:?}",
481                            context.adapter.get_info().name
482                        )
483                    })
484            };
485
486        let transparent_alpha_mode = pick_alpha_mode(&[
487            wgpu::CompositeAlphaMode::PreMultiplied,
488            wgpu::CompositeAlphaMode::Inherit,
489        ])?;
490
491        let opaque_alpha_mode = pick_alpha_mode(&[
492            wgpu::CompositeAlphaMode::Opaque,
493            wgpu::CompositeAlphaMode::Inherit,
494        ])?;
495
496        let alpha_mode = if config.transparent {
497            transparent_alpha_mode
498        } else {
499            opaque_alpha_mode
500        };
501
502        let device = Arc::clone(&context.device);
503        let max_texture_size = device.limits().max_texture_dimension_2d;
504
505        let requested_width = config.size.width.0 as u32;
506        let requested_height = config.size.height.0 as u32;
507        let clamped_width = requested_width.min(max_texture_size);
508        let clamped_height = requested_height.min(max_texture_size);
509
510        if clamped_width != requested_width || clamped_height != requested_height {
511            warn!(
512                "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
513                 Clamping to ({}, {}). Window content may not fill the entire window.",
514                requested_width, requested_height, max_texture_size, clamped_width, clamped_height
515            );
516        }
517
518        let surface_config = wgpu::SurfaceConfiguration {
519            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
520            format: surface_format,
521            width: clamped_width.max(1),
522            height: clamped_height.max(1),
523            present_mode: config
524                .preferred_present_mode
525                .filter(|mode| surface_caps.present_modes.contains(mode))
526                .unwrap_or(wgpu::PresentMode::Fifo),
527            desired_maximum_frame_latency: 2,
528            alpha_mode,
529            view_formats: vec![],
530        };
531        // Configure the surface immediately. The adapter selection process already validated
532        // that this adapter can successfully configure this surface.
533        surface.configure(&context.device, &surface_config);
534
535        let core = WgpuRendererCore::new(context, atlas.clone(), surface_format, alpha_mode);
536
537        Ok(Self {
538            context: gpu_context,
539            compositor_gpu,
540            state: RendererState::Ready { surface, core },
541            surface_config,
542            atlas,
543            transparent_alpha_mode,
544            opaque_alpha_mode,
545            max_texture_size,
546            is_bgr: false,
547            failed_frame_count: 0,
548            device_errors: Arc::clone(context.errors()),
549            observed_error_generation: 0,
550            last_surface_error: None,
551            needs_redraw: false,
552        })
553    }
554}
555
556impl WgpuRendererCore {
557    fn create_bind_group_layouts(
558        device: &wgpu::Device,
559        uses_webgl_instance_data: bool,
560    ) -> WgpuBindGroupLayouts {
561        let globals =
562            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
563                label: Some("globals_layout"),
564                entries: &[
565                    wgpu::BindGroupLayoutEntry {
566                        binding: 0,
567                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
568                        ty: wgpu::BindingType::Buffer {
569                            ty: wgpu::BufferBindingType::Uniform,
570                            has_dynamic_offset: false,
571                            min_binding_size: NonZeroU64::new(
572                                std::mem::size_of::<GlobalParams>() as u64
573                            ),
574                        },
575                        count: None,
576                    },
577                    wgpu::BindGroupLayoutEntry {
578                        binding: 1,
579                        visibility: wgpu::ShaderStages::FRAGMENT,
580                        ty: wgpu::BindingType::Buffer {
581                            ty: wgpu::BufferBindingType::Uniform,
582                            has_dynamic_offset: false,
583                            min_binding_size: NonZeroU64::new(
584                                std::mem::size_of::<GammaParams>() as u64
585                            ),
586                        },
587                        count: None,
588                    },
589                ],
590            });
591
592        let instance_data_entry = wgpu::BindGroupLayoutEntry {
593            binding: 0,
594            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
595            ty: if uses_webgl_instance_data {
596                wgpu::BindingType::Texture {
597                    sample_type: wgpu::TextureSampleType::Uint,
598                    view_dimension: wgpu::TextureViewDimension::D2,
599                    multisampled: false,
600                }
601            } else {
602                wgpu::BindingType::Buffer {
603                    ty: wgpu::BufferBindingType::Storage { read_only: true },
604                    has_dynamic_offset: false,
605                    min_binding_size: None,
606                }
607            },
608            count: None,
609        };
610
611        let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
612            label: Some("instances_layout"),
613            entries: &[instance_data_entry],
614        });
615
616        let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
617            label: Some("texture_layout"),
618            entries: &[
619                wgpu::BindGroupLayoutEntry {
620                    binding: 0,
621                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
622                    ty: wgpu::BindingType::Texture {
623                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
624                        view_dimension: wgpu::TextureViewDimension::D2,
625                        multisampled: false,
626                    },
627                    count: None,
628                },
629                wgpu::BindGroupLayoutEntry {
630                    binding: 1,
631                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
632                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
633                    count: None,
634                },
635            ],
636        });
637
638        let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
639            label: Some("surfaces_layout"),
640            entries: &[
641                wgpu::BindGroupLayoutEntry {
642                    binding: 0,
643                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
644                    ty: wgpu::BindingType::Buffer {
645                        ty: wgpu::BufferBindingType::Uniform,
646                        has_dynamic_offset: true,
647                        min_binding_size: NonZeroU64::new(
648                            std::mem::size_of::<SurfaceParams>() as u64
649                        ),
650                    },
651                    count: None,
652                },
653                wgpu::BindGroupLayoutEntry {
654                    binding: 1,
655                    visibility: wgpu::ShaderStages::FRAGMENT,
656                    ty: wgpu::BindingType::Texture {
657                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
658                        view_dimension: wgpu::TextureViewDimension::D2,
659                        multisampled: false,
660                    },
661                    count: None,
662                },
663                wgpu::BindGroupLayoutEntry {
664                    binding: 2,
665                    visibility: wgpu::ShaderStages::FRAGMENT,
666                    ty: wgpu::BindingType::Texture {
667                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
668                        view_dimension: wgpu::TextureViewDimension::D2,
669                        multisampled: false,
670                    },
671                    count: None,
672                },
673                wgpu::BindGroupLayoutEntry {
674                    binding: 3,
675                    visibility: wgpu::ShaderStages::FRAGMENT,
676                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
677                    count: None,
678                },
679            ],
680        });
681
682        let imported_texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
683            label: Some("imported_texture_layout"),
684            entries: &[
685                wgpu::BindGroupLayoutEntry {
686                    binding: 0,
687                    visibility: wgpu::ShaderStages::FRAGMENT,
688                    ty: wgpu::BindingType::Texture {
689                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
690                        view_dimension: wgpu::TextureViewDimension::D2,
691                        multisampled: false,
692                    },
693                    count: None,
694                },
695                wgpu::BindGroupLayoutEntry {
696                    binding: 1,
697                    visibility: wgpu::ShaderStages::FRAGMENT,
698                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
699                    count: None,
700                },
701            ],
702        });
703
704        WgpuBindGroupLayouts {
705            globals,
706            instances,
707            texture,
708            surfaces,
709            imported_texture,
710        }
711    }
712
713    fn create_instance_texture(
714        device: &wgpu::Device,
715        requested_capacity: u64,
716        max_texture_dimension: u32,
717    ) -> (InstanceData, u64) {
718        let texel_count = requested_capacity.div_ceil(INSTANCE_TEXTURE_TEXEL_SIZE);
719        let width = texel_count.min(u64::from(max_texture_dimension)).max(1) as u32;
720        let height = texel_count
721            .div_ceil(u64::from(width))
722            .min(u64::from(max_texture_dimension))
723            .max(1) as u32;
724        let texture = device.create_texture(&wgpu::TextureDescriptor {
725            label: Some("instance_texture"),
726            size: wgpu::Extent3d {
727                width,
728                height,
729                depth_or_array_layers: 1,
730            },
731            mip_level_count: 1,
732            sample_count: 1,
733            dimension: wgpu::TextureDimension::D2,
734            format: wgpu::TextureFormat::Rgba32Uint,
735            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
736            view_formats: &[],
737        });
738        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
739        let capacity = u64::from(width) * u64::from(height) * INSTANCE_TEXTURE_TEXEL_SIZE;
740        (
741            InstanceData::Texture {
742                texture,
743                view,
744                width,
745                height,
746            },
747            capacity,
748        )
749    }
750
751    fn create_pipelines(
752        device: &wgpu::Device,
753        layouts: &WgpuBindGroupLayouts,
754        surface_format: wgpu::TextureFormat,
755        alpha_mode: wgpu::CompositeAlphaMode,
756        path_sample_count: u32,
757        dual_source_blending: bool,
758        uses_webgl_instance_data: bool,
759    ) -> WgpuPipelines {
760        // Diagnostic guard: verify the device actually has
761        // DUAL_SOURCE_BLENDING. We have a crash report (ZED-5G1) where a
762        // feature mismatch caused a wgpu-hal abort, but we haven't
763        // identified the code path that produces the mismatch. This
764        // guard prevents the crash and logs more evidence.
765        // Remove this check once:
766        // a) We find and fix the root cause, or
767        // b) There are no reports of this warning appearing for some time.
768        let device_has_feature = device
769            .features()
770            .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
771        if dual_source_blending && !device_has_feature {
772            log::error!(
773                "BUG: dual_source_blending flag is true but device does not \
774                 have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \
775                 Falling back to mono text rendering. Please report this at \
776                 https://github.com/zed-industries/zed/issues",
777                device.features(),
778            );
779        }
780        let dual_source_blending =
781            dual_source_blending && device_has_feature && !uses_webgl_instance_data;
782
783        let shader_source = if uses_webgl_instance_data {
784            WEBGL_SHADERS
785        } else {
786            STORAGE_BUFFER_SHADERS
787        };
788        let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
789            label: Some("gpui_shaders"),
790            source: wgpu::ShaderSource::Wgsl(shader_source.into()),
791        });
792
793        let subpixel_shader_module = if dual_source_blending {
794            Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
795                label: Some("gpui_subpixel_shaders"),
796                source: wgpu::ShaderSource::Wgsl(SUBPIXEL_SHADERS.into()),
797            }))
798        } else {
799            None
800        };
801
802        let blend_mode = match alpha_mode {
803            wgpu::CompositeAlphaMode::PreMultiplied => {
804                wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
805            }
806            _ => wgpu::BlendState::ALPHA_BLENDING,
807        };
808
809        let color_target = wgpu::ColorTargetState {
810            format: surface_format,
811            blend: Some(blend_mode),
812            write_mask: wgpu::ColorWrites::ALL,
813        };
814
815        let create_pipeline = |name: &str,
816                               vs_entry: &str,
817                               fs_entry: &str,
818                               globals_layout: &wgpu::BindGroupLayout,
819                               data_layout: &wgpu::BindGroupLayout,
820                               texture_layout: Option<&wgpu::BindGroupLayout>,
821                               topology: wgpu::PrimitiveTopology,
822                               color_targets: &[Option<wgpu::ColorTargetState>],
823                               sample_count: u32,
824                               module: &wgpu::ShaderModule| {
825            let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
826            bind_group_layouts.extend(texture_layout.map(Some));
827            let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
828                label: Some(&format!("{name}_layout")),
829                bind_group_layouts: &bind_group_layouts,
830                immediate_size: 0,
831            });
832
833            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
834                label: Some(name),
835                layout: Some(&pipeline_layout),
836                vertex: wgpu::VertexState {
837                    module,
838                    entry_point: Some(vs_entry),
839                    buffers: &[],
840                    compilation_options: wgpu::PipelineCompilationOptions::default(),
841                },
842                fragment: Some(wgpu::FragmentState {
843                    module,
844                    entry_point: Some(fs_entry),
845                    targets: color_targets,
846                    compilation_options: wgpu::PipelineCompilationOptions::default(),
847                }),
848                primitive: wgpu::PrimitiveState {
849                    topology,
850                    strip_index_format: None,
851                    front_face: wgpu::FrontFace::Ccw,
852                    cull_mode: None,
853                    polygon_mode: wgpu::PolygonMode::Fill,
854                    unclipped_depth: false,
855                    conservative: false,
856                },
857                depth_stencil: None,
858                multisample: wgpu::MultisampleState {
859                    count: sample_count,
860                    mask: !0,
861                    alpha_to_coverage_enabled: false,
862                },
863                multiview_mask: None,
864                cache: None,
865            })
866        };
867
868        let quads = create_pipeline(
869            "quads",
870            "vs_quad",
871            "fs_quad",
872            &layouts.globals,
873            &layouts.instances,
874            None,
875            wgpu::PrimitiveTopology::TriangleStrip,
876            &[Some(color_target.clone())],
877            1,
878            &shader_module,
879        );
880
881        let shadows = create_pipeline(
882            "shadows",
883            "vs_shadow",
884            "fs_shadow",
885            &layouts.globals,
886            &layouts.instances,
887            None,
888            wgpu::PrimitiveTopology::TriangleStrip,
889            &[Some(color_target.clone())],
890            1,
891            &shader_module,
892        );
893
894        let path_rasterization = create_pipeline(
895            "path_rasterization",
896            "vs_path_rasterization",
897            "fs_path_rasterization",
898            &layouts.globals,
899            &layouts.instances,
900            None,
901            wgpu::PrimitiveTopology::TriangleList,
902            &[Some(wgpu::ColorTargetState {
903                format: surface_format,
904                blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
905                write_mask: wgpu::ColorWrites::ALL,
906            })],
907            path_sample_count,
908            &shader_module,
909        );
910
911        let paths_blend = wgpu::BlendState {
912            color: wgpu::BlendComponent {
913                src_factor: wgpu::BlendFactor::One,
914                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
915                operation: wgpu::BlendOperation::Add,
916            },
917            alpha: wgpu::BlendComponent {
918                src_factor: wgpu::BlendFactor::One,
919                dst_factor: wgpu::BlendFactor::One,
920                operation: wgpu::BlendOperation::Add,
921            },
922        };
923
924        let paths = create_pipeline(
925            "paths",
926            "vs_path",
927            "fs_path",
928            &layouts.globals,
929            &layouts.instances,
930            Some(&layouts.texture),
931            wgpu::PrimitiveTopology::TriangleStrip,
932            &[Some(wgpu::ColorTargetState {
933                format: surface_format,
934                blend: Some(paths_blend),
935                write_mask: wgpu::ColorWrites::ALL,
936            })],
937            1,
938            &shader_module,
939        );
940
941        let underlines = create_pipeline(
942            "underlines",
943            "vs_underline",
944            "fs_underline",
945            &layouts.globals,
946            &layouts.instances,
947            None,
948            wgpu::PrimitiveTopology::TriangleStrip,
949            &[Some(color_target.clone())],
950            1,
951            &shader_module,
952        );
953
954        let mono_sprites = create_pipeline(
955            "mono_sprites",
956            "vs_mono_sprite",
957            "fs_mono_sprite",
958            &layouts.globals,
959            &layouts.instances,
960            Some(&layouts.texture),
961            wgpu::PrimitiveTopology::TriangleStrip,
962            &[Some(color_target.clone())],
963            1,
964            &shader_module,
965        );
966
967        let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
968            let subpixel_blend = wgpu::BlendState {
969                color: wgpu::BlendComponent {
970                    src_factor: wgpu::BlendFactor::Src1,
971                    dst_factor: wgpu::BlendFactor::OneMinusSrc1,
972                    operation: wgpu::BlendOperation::Add,
973                },
974                alpha: wgpu::BlendComponent {
975                    src_factor: wgpu::BlendFactor::One,
976                    dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
977                    operation: wgpu::BlendOperation::Add,
978                },
979            };
980
981            Some(create_pipeline(
982                "subpixel_sprites",
983                "vs_subpixel_sprite",
984                "fs_subpixel_sprite",
985                &layouts.globals,
986                &layouts.instances,
987                Some(&layouts.texture),
988                wgpu::PrimitiveTopology::TriangleStrip,
989                &[Some(wgpu::ColorTargetState {
990                    format: surface_format,
991                    blend: Some(subpixel_blend),
992                    write_mask: wgpu::ColorWrites::COLOR,
993                })],
994                1,
995                subpixel_module,
996            ))
997        } else {
998            None
999        };
1000
1001        let poly_sprites = create_pipeline(
1002            "poly_sprites",
1003            "vs_poly_sprite",
1004            "fs_poly_sprite",
1005            &layouts.globals,
1006            &layouts.instances,
1007            Some(&layouts.texture),
1008            wgpu::PrimitiveTopology::TriangleStrip,
1009            &[Some(color_target.clone())],
1010            1,
1011            &shader_module,
1012        );
1013
1014        let imported_textures = create_pipeline(
1015            "imported_textures",
1016            "vs_quad",
1017            "fs_imported_texture",
1018            &layouts.globals,
1019            &layouts.instances,
1020            Some(&layouts.imported_texture),
1021            wgpu::PrimitiveTopology::TriangleStrip,
1022            &[Some(color_target.clone())],
1023            1,
1024            &shader_module,
1025        );
1026
1027        let surfaces = create_pipeline(
1028            "surfaces",
1029            "vs_surface",
1030            "fs_surface",
1031            &layouts.globals,
1032            &layouts.surfaces,
1033            None,
1034            wgpu::PrimitiveTopology::TriangleStrip,
1035            &[Some(color_target)],
1036            1,
1037            &shader_module,
1038        );
1039
1040        WgpuPipelines {
1041            quads,
1042            shadows,
1043            path_rasterization,
1044            paths,
1045            underlines,
1046            mono_sprites,
1047            subpixel_sprites,
1048            poly_sprites,
1049            surfaces,
1050            imported_textures,
1051        }
1052    }
1053
1054    fn create_path_intermediate(
1055        device: &wgpu::Device,
1056        format: wgpu::TextureFormat,
1057        width: u32,
1058        height: u32,
1059    ) -> (wgpu::Texture, wgpu::TextureView) {
1060        let texture = device.create_texture(&wgpu::TextureDescriptor {
1061            label: Some("path_intermediate"),
1062            size: wgpu::Extent3d {
1063                width: width.max(1),
1064                height: height.max(1),
1065                depth_or_array_layers: 1,
1066            },
1067            mip_level_count: 1,
1068            sample_count: 1,
1069            dimension: wgpu::TextureDimension::D2,
1070            format,
1071            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1072            view_formats: &[],
1073        });
1074        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1075        (texture, view)
1076    }
1077
1078    fn create_msaa_if_needed(
1079        device: &wgpu::Device,
1080        format: wgpu::TextureFormat,
1081        width: u32,
1082        height: u32,
1083        sample_count: u32,
1084    ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
1085        if sample_count <= 1 {
1086            return None;
1087        }
1088        let texture = device.create_texture(&wgpu::TextureDescriptor {
1089            label: Some("path_msaa"),
1090            size: wgpu::Extent3d {
1091                width: width.max(1),
1092                height: height.max(1),
1093                depth_or_array_layers: 1,
1094            },
1095            mip_level_count: 1,
1096            sample_count,
1097            dimension: wgpu::TextureDimension::D2,
1098            format,
1099            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1100            view_formats: &[],
1101        });
1102        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1103        Some((texture, view))
1104    }
1105}
1106
1107impl WgpuRenderer {
1108    /// Records the new drawable size and reconfigures the surface when one is present.
1109    /// The size is kept even without GPU resources so that recovery restores it.
1110    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
1111        let width = size.width.0 as u32;
1112        let height = size.height.0 as u32;
1113
1114        if width == self.surface_config.width && height == self.surface_config.height {
1115            return;
1116        }
1117
1118        let clamped_width = width.min(self.max_texture_size);
1119        let clamped_height = height.min(self.max_texture_size);
1120        if clamped_width != width || clamped_height != height {
1121            warn!(
1122                "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
1123                 Clamping to ({}, {}). Window content may not fill the entire window.",
1124                width, height, self.max_texture_size, clamped_width, clamped_height
1125            );
1126        }
1127        self.surface_config.width = clamped_width.max(1);
1128        self.surface_config.height = clamped_height.max(1);
1129
1130        let Some(core) = self.core_mut() else {
1131            return;
1132        };
1133        let resources = &mut core.resources;
1134
1135        // Wait for any in-flight GPU work to complete before destroying textures
1136        if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
1137            submission_index: None,
1138            timeout: None,
1139        }) {
1140            warn!("Failed to poll device during resize: {e:?}");
1141        }
1142
1143        // Destroy old textures before allocating new ones to avoid GPU memory spikes
1144        if let Some(ref texture) = resources.path_intermediate_texture {
1145            texture.destroy();
1146        }
1147        if let Some(ref texture) = resources.path_msaa_texture {
1148            texture.destroy();
1149        }
1150
1151        // Invalidate intermediate textures - they will be lazily recreated
1152        // in draw() after we confirm the surface is healthy. This avoids
1153        // panics when the device/surface is in an invalid state during resize.
1154        resources.invalidate_intermediate_textures();
1155
1156        if let RendererState::Ready { surface, core } = &self.state {
1157            surface.configure(&core.resources.device, &self.surface_config);
1158        }
1159    }
1160
1161    pub fn set_subpixel_layout(&mut self, is_bgr: bool) {
1162        self.is_bgr = is_bgr;
1163        if let Some(core) = self.core_mut() {
1164            core.is_bgr = is_bgr;
1165        }
1166    }
1167
1168    pub fn update_transparency(&mut self, transparent: bool) {
1169        let new_alpha_mode = if transparent {
1170            self.transparent_alpha_mode
1171        } else {
1172            self.opaque_alpha_mode
1173        };
1174        if new_alpha_mode == self.surface_config.alpha_mode {
1175            return;
1176        }
1177        self.surface_config.alpha_mode = new_alpha_mode;
1178        let format = self.surface_config.format;
1179
1180        let Some(core) = self.core_mut() else {
1181            return;
1182        };
1183        let resources = &mut core.resources;
1184        resources.pipelines = WgpuRendererCore::create_pipelines(
1185            &resources.device,
1186            &resources.bind_group_layouts,
1187            format,
1188            new_alpha_mode,
1189            core.rendering_params.path_sample_count,
1190            core.dual_source_blending,
1191            core.uses_webgl_instance_data,
1192        );
1193
1194        if let RendererState::Ready { surface, core } = &self.state {
1195            surface.configure(&core.resources.device, &self.surface_config);
1196        }
1197    }
1198
1199    #[allow(dead_code)]
1200    pub fn viewport_size(&self) -> Size<DevicePixels> {
1201        Size {
1202            width: DevicePixels(self.surface_config.width as i32),
1203            height: DevicePixels(self.surface_config.height as i32),
1204        }
1205    }
1206
1207    pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
1208        &self.atlas
1209    }
1210
1211    pub fn supports_dual_source_blending(&self) -> bool {
1212        self.core().is_some_and(|core| core.dual_source_blending)
1213    }
1214
1215    /// Returns `None` once GPU resources have been released by `destroy` or a pending
1216    /// device recovery.
1217    /// A producer on this path renders its texture on *this* device -- that is the whole of the
1218    /// same-device rule -- so a renderer that can be built without a window is where an
1219    /// application that owns one reaches a device from. A window's is reached through
1220    /// `GpuRenderer::device`, which is the same device by another route.
1221    pub fn device(&self) -> &wgpu::Device {
1222        self.core().expect("renderer has no core").resources.device.as_ref()
1223    }
1224
1225    /// The queue this renderer submits on.
1226    pub fn queue(&self) -> &wgpu::Queue {
1227        self.core().expect("renderer has no core").resources.queue.as_ref()
1228    }
1229
1230    /// The shared context slot this renderer draws through, if it has one.
1231    #[cfg(not(target_family = "wasm"))]
1232    pub fn gpu_context(&self) -> Option<WgpuContextSlot> {
1233        self.context.clone()
1234    }
1235
1236    pub fn gpu_specs(&self) -> Option<GpuSpecs> {
1237        let adapter_info = &self.core()?.adapter_info;
1238        Some(GpuSpecs {
1239            is_software_emulated: adapter_info.device_type == wgpu::DeviceType::Cpu,
1240            device_name: adapter_info.name.clone(),
1241            driver_name: adapter_info.driver.clone(),
1242            driver_info: adapter_info.driver_info.clone(),
1243        })
1244    }
1245
1246    pub fn max_texture_size(&self) -> u32 {
1247        self.max_texture_size
1248    }
1249
1250    pub fn draw(&mut self, scene: &Scene) -> bool {
1251        #[cfg(target_family = "wasm")]
1252        if self.device_lost() {
1253            if matches!(self.state, RendererState::Ready { .. }) {
1254                log::error!(
1255                    "Browser graphics context was lost; rendering has stopped. Reload the page to recover."
1256                );
1257                self.unconfigure_surface();
1258            }
1259            return false;
1260        }
1261
1262        // Bail out early if the surface has been unconfigured (e.g. during
1263        // Android background/rotation transitions).  Attempting to acquire
1264        // a texture from an unconfigured surface can block indefinitely on
1265        // some drivers (Adreno).
1266        let RendererState::Ready { surface, core } = &mut self.state else {
1267            return false;
1268        };
1269
1270        if let Some(error) = self.last_surface_error.take().or_else(|| {
1271            self.device_errors
1272                .observe_error(&mut self.observed_error_generation)
1273        }) {
1274            self.failed_frame_count += 1;
1275            log::error!(
1276                "GPU error during frame (failure {} of 10): {error}",
1277                self.failed_frame_count
1278            );
1279
1280            // TBD. Does retrying more actually help?
1281            if self.failed_frame_count > 10 {
1282                panic!("Too many consecutive GPU errors. Last error: {error}");
1283            } else if self.failed_frame_count > 5 {
1284                core.resources.invalidate_intermediate_textures();
1285                self.atlas.clear();
1286                self.needs_redraw = true;
1287                self.failed_frame_count = 0;
1288                return false;
1289            }
1290        } else {
1291            self.failed_frame_count = 0;
1292        }
1293
1294        let frame = match surface.get_current_texture() {
1295            wgpu::CurrentSurfaceTexture::Success(frame) => frame,
1296            wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
1297                // Textures must be destroyed before the surface can be reconfigured.
1298                drop(frame);
1299                surface.configure(&core.resources.device, &self.surface_config);
1300                return core.release_unpresented(scene);
1301            }
1302            wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
1303                surface.configure(&core.resources.device, &self.surface_config);
1304                return core.release_unpresented(scene);
1305            }
1306            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
1307                return core.release_unpresented(scene);
1308            }
1309            wgpu::CurrentSurfaceTexture::Validation => {
1310                self.last_surface_error = Some("Surface texture validation error".to_string());
1311                return core.release_unpresented(scene);
1312            }
1313        };
1314
1315        // The acquired texture is the authority on frame dimensions; the surface
1316        // configuration is only a request.
1317        let size = Size {
1318            width: DevicePixels(frame.texture.width() as i32),
1319            height: DevicePixels(frame.texture.height() as i32),
1320        };
1321        let frame_view = frame
1322            .texture
1323            .create_view(&wgpu::TextureViewDescriptor::default());
1324        let premultiplied_alpha =
1325            self.surface_config.alpha_mode == wgpu::CompositeAlphaMode::PreMultiplied;
1326        if let Err(error) = core.render_frame(
1327            scene,
1328            &frame_view,
1329            size,
1330            premultiplied_alpha,
1331            wgpu::Color::TRANSPARENT,
1332        ) {
1333            log::error!("{error:#}");
1334            return core.release_unpresented(scene);
1335        }
1336
1337        frame.present();
1338        true
1339    }
1340}
1341
1342impl WgpuRendererCore {
1343    fn new(
1344        context: &WgpuContext,
1345        atlas: Arc<WgpuAtlas>,
1346        target_format: wgpu::TextureFormat,
1347        alpha_mode: wgpu::CompositeAlphaMode,
1348    ) -> Self {
1349        let device = Arc::clone(&context.device);
1350        let queue = Arc::clone(&context.queue);
1351        let rendering_params = RenderingParameters::new(&context.adapter, target_format);
1352        let uses_webgl_instance_data = context.uses_webgl_instance_data();
1353        let dual_source_blending =
1354            context.supports_dual_source_blending() && !uses_webgl_instance_data;
1355        let bind_group_layouts = Self::create_bind_group_layouts(&device, uses_webgl_instance_data);
1356        let pipelines = Self::create_pipelines(
1357            &device,
1358            &bind_group_layouts,
1359            target_format,
1360            alpha_mode,
1361            rendering_params.path_sample_count,
1362            dual_source_blending,
1363            uses_webgl_instance_data,
1364        );
1365        let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1366            label: Some("atlas_sampler"),
1367            mag_filter: wgpu::FilterMode::Linear,
1368            min_filter: wgpu::FilterMode::Linear,
1369            ..Default::default()
1370        });
1371
1372        // §4: a clip against a rounded corner must not bleed the opposite edge in,
1373        // so the address mode is pinned rather than inherited from the defaults.
1374        let imported_texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1375            label: Some("imported_texture_sampler"),
1376            address_mode_u: wgpu::AddressMode::ClampToEdge,
1377            address_mode_v: wgpu::AddressMode::ClampToEdge,
1378            address_mode_w: wgpu::AddressMode::ClampToEdge,
1379            mag_filter: wgpu::FilterMode::Linear,
1380            min_filter: wgpu::FilterMode::Linear,
1381            ..Default::default()
1382        });
1383        let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
1384        let globals_size = std::mem::size_of::<GlobalParams>() as u64;
1385        let gamma_size = std::mem::size_of::<GammaParams>() as u64;
1386        let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
1387        let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
1388        let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1389            label: Some("globals_buffer"),
1390            size: gamma_offset + gamma_size,
1391            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1392            mapped_at_creation: false,
1393        });
1394        let (
1395            instance_data,
1396            instance_data_capacity,
1397            max_instance_data_size,
1398            instance_data_alignment,
1399        ) = if uses_webgl_instance_data {
1400            let max_texture_dimension = device.limits().max_texture_dimension_2d;
1401            let max_instance_data_size = (u64::from(max_texture_dimension).pow(2)
1402                * INSTANCE_TEXTURE_TEXEL_SIZE)
1403                .min(MAX_INSTANCE_BUFFER_SIZE);
1404            let initial_capacity = (2 * 1024 * 1024).min(max_instance_data_size);
1405            let (instance_data, capacity) =
1406                Self::create_instance_texture(&device, initial_capacity, max_texture_dimension);
1407            (
1408                instance_data,
1409                capacity,
1410                max_instance_data_size,
1411                INSTANCE_TEXTURE_TEXEL_SIZE,
1412            )
1413        } else {
1414            let max_buffer_size = device
1415                .limits()
1416                .max_buffer_size
1417                .min(device.limits().max_storage_buffer_binding_size)
1418                .min(MAX_INSTANCE_BUFFER_SIZE);
1419            let initial_capacity = (2 * 1024 * 1024).min(max_buffer_size);
1420            let buffer = device.create_buffer(&wgpu::BufferDescriptor {
1421                label: Some("instance_buffer"),
1422                size: initial_capacity,
1423                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1424                mapped_at_creation: false,
1425            });
1426            (
1427                InstanceData::Storage(buffer),
1428                initial_capacity,
1429                max_buffer_size,
1430                device.limits().min_storage_buffer_offset_alignment as u64,
1431            )
1432        };
1433        let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1434            label: Some("globals_bind_group"),
1435            layout: &bind_group_layouts.globals,
1436            entries: &[
1437                wgpu::BindGroupEntry {
1438                    binding: 0,
1439                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1440                        buffer: &globals_buffer,
1441                        offset: 0,
1442                        size: NonZeroU64::new(globals_size),
1443                    }),
1444                },
1445                wgpu::BindGroupEntry {
1446                    binding: 1,
1447                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1448                        buffer: &globals_buffer,
1449                        offset: gamma_offset,
1450                        size: NonZeroU64::new(gamma_size),
1451                    }),
1452                },
1453            ],
1454        });
1455        let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1456            label: Some("path_globals_bind_group"),
1457            layout: &bind_group_layouts.globals,
1458            entries: &[
1459                wgpu::BindGroupEntry {
1460                    binding: 0,
1461                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1462                        buffer: &globals_buffer,
1463                        offset: path_globals_offset,
1464                        size: NonZeroU64::new(globals_size),
1465                    }),
1466                },
1467                wgpu::BindGroupEntry {
1468                    binding: 1,
1469                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1470                        buffer: &globals_buffer,
1471                        offset: gamma_offset,
1472                        size: NonZeroU64::new(gamma_size),
1473                    }),
1474                },
1475            ],
1476        });
1477        let max_texture_size = device.limits().max_texture_dimension_2d;
1478        let surface_params = device.create_buffer(&wgpu::BufferDescriptor {
1479            label: Some("surface_params"),
1480            size: 1 << 20,
1481            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1482            mapped_at_creation: false,
1483        });
1484
1485        Self {
1486            resources: WgpuResources {
1487                device,
1488                queue,
1489                pipelines,
1490                bind_group_layouts,
1491                atlas_sampler,
1492                imported_texture_sampler,
1493                atlas_texture_bind_groups: FxHashMap::default(),
1494                globals_buffer,
1495                surface_params,
1496                globals_bind_group,
1497                path_globals_bind_group,
1498                instance_data,
1499                path_intermediate_texture: None,
1500                path_intermediate_view: None,
1501                path_msaa_texture: None,
1502                path_msaa_view: None,
1503                #[cfg(target_os = "linux")]
1504                dmabuf_textures: crate::DmaBufTextureCache::new(),
1505            },
1506            atlas,
1507            path_globals_offset,
1508            gamma_offset,
1509            instance_data_capacity,
1510            max_instance_data_size,
1511            instance_data_alignment,
1512            uses_webgl_instance_data,
1513            rendering_params,
1514            is_bgr: false,
1515            dual_source_blending,
1516            adapter_info: context.adapter.get_info(),
1517            target_format,
1518            max_texture_size,
1519        }
1520    }
1521
1522    fn resources(&self) -> &WgpuResources {
1523        &self.resources
1524    }
1525
1526    fn resources_mut(&mut self) -> &mut WgpuResources {
1527        &mut self.resources
1528    }
1529
1530    /// Path rendering goes through an intermediate texture that must match the frame
1531    /// target's dimensions, so the textures are keyed by size and rebuilt on mismatch.
1532    fn ensure_intermediate_textures(&mut self, size: Size<DevicePixels>) {
1533        let width = size.width.0 as u32;
1534        let height = size.height.0 as u32;
1535        if self
1536            .resources
1537            .path_intermediate_texture
1538            .as_ref()
1539            .is_some_and(|texture| texture.width() == width && texture.height() == height)
1540        {
1541            return;
1542        }
1543
1544        let format = self.target_format;
1545        let path_sample_count = self.rendering_params.path_sample_count;
1546        let resources = &mut self.resources;
1547
1548        let (texture, view) =
1549            Self::create_path_intermediate(&resources.device, format, width, height);
1550        resources.path_intermediate_texture = Some(texture);
1551        resources.path_intermediate_view = Some(view);
1552
1553        let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
1554            &resources.device,
1555            format,
1556            width,
1557            height,
1558            path_sample_count,
1559        )
1560        .map(|(texture, view)| (Some(texture), Some(view)))
1561        .unwrap_or((None, None));
1562        resources.path_msaa_texture = path_msaa_texture;
1563        resources.path_msaa_view = path_msaa_view;
1564    }
1565
1566    fn render_frame(
1567        &mut self,
1568        scene: &Scene,
1569        target_view: &wgpu::TextureView,
1570        size: Size<DevicePixels>,
1571        premultiplied_alpha: bool,
1572        clear_color: wgpu::Color,
1573    ) -> Result<wgpu::SubmissionIndex> {
1574        anyhow::ensure!(
1575            size.width.0 > 0 && size.height.0 > 0,
1576            "invalid render target size: {size:?}"
1577        );
1578        anyhow::ensure!(
1579            size.width.0 as u32 <= self.max_texture_size
1580                && size.height.0 as u32 <= self.max_texture_size,
1581            "render target size {size:?} exceeds maximum texture dimension {}",
1582            self.max_texture_size
1583        );
1584
1585        self.atlas.before_frame();
1586        self.ensure_intermediate_textures(size);
1587
1588        let gamma_params = GammaParams {
1589            gamma_ratios: self.rendering_params.gamma_ratios,
1590            grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1591            subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1592            is_bgr: self.is_bgr as u32,
1593            _pad: 0,
1594        };
1595        let globals = GlobalParams {
1596            viewport_size: [size.width.0 as f32, size.height.0 as f32],
1597            premultiplied_alpha: premultiplied_alpha as u32,
1598            pad: 0,
1599        };
1600        let path_globals = GlobalParams {
1601            premultiplied_alpha: 0,
1602            ..globals
1603        };
1604        self.resources.queue.write_buffer(
1605            &self.resources.globals_buffer,
1606            0,
1607            bytemuck::bytes_of(&globals),
1608        );
1609        self.resources.queue.write_buffer(
1610            &self.resources.globals_buffer,
1611            self.path_globals_offset,
1612            bytemuck::bytes_of(&path_globals),
1613        );
1614        self.resources.queue.write_buffer(
1615            &self.resources.globals_buffer,
1616            self.gamma_offset,
1617            bytemuck::bytes_of(&gamma_params),
1618        );
1619
1620        self.record_frame(scene, target_view, clear_color)
1621            .inspect_err(|_| {
1622                // Queue writes are staged before encoding; flush them even if the frame fails.
1623                self.resources.queue.submit(std::iter::empty());
1624            })
1625    }
1626
1627    fn record_frame(
1628        &mut self,
1629        scene: &Scene,
1630        frame_view: &wgpu::TextureView,
1631        clear_color: wgpu::Color,
1632    ) -> Result<wgpu::SubmissionIndex> {
1633        let mut instance_offset = 0;
1634        let releases = Self::surface_releases(scene);
1635        let instance_bindings = self
1636            .write_instances(scene, &mut instance_offset)
1637            .with_context(|| {
1638                format!(
1639                    "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1640                    scene.paths.len(),
1641                    scene.shadows.len(),
1642                    scene.quads.len(),
1643                    scene.underlines.len(),
1644                    scene.monochrome_sprites.len(),
1645                    scene.subpixel_sprites.len(),
1646                    scene.polychrome_sprites.len(),
1647                )
1648            })?;
1649        self.prepare_texture_bind_groups(scene);
1650
1651        let mut encoder =
1652            self.resources()
1653                .device
1654                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1655                    label: Some("main_encoder"),
1656                });
1657
1658        {
1659            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1660                label: Some("main_pass"),
1661                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1662                    view: frame_view,
1663                    resolve_target: None,
1664                    ops: wgpu::Operations {
1665                        load: wgpu::LoadOp::Clear(clear_color),
1666                        store: wgpu::StoreOp::Store,
1667                    },
1668                    depth_slice: None,
1669                })],
1670                depth_stencil_attachment: None,
1671                ..Default::default()
1672            });
1673
1674            for batch in scene.batches() {
1675                match batch {
1676                    PrimitiveBatch::Quads(range) => self.draw_instances(
1677                        &instance_bindings.quads,
1678                        &self.resources().pipelines.quads,
1679                        instance_range(range),
1680                        &mut pass,
1681                    ),
1682                    PrimitiveBatch::Shadows(range) => self.draw_instances(
1683                        &instance_bindings.shadows,
1684                        &self.resources().pipelines.shadows,
1685                        instance_range(range),
1686                        &mut pass,
1687                    ),
1688                    PrimitiveBatch::Paths(range) => {
1689                        let paths = &scene.paths[range];
1690                        if paths.is_empty() {
1691                            continue;
1692                        }
1693
1694                        drop(pass);
1695                        let rasterized = self.draw_paths_to_intermediate(
1696                            &mut encoder,
1697                            paths,
1698                            &mut instance_offset,
1699                        )?;
1700
1701                        pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1702                            label: Some("main_pass_continued"),
1703                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1704                                view: frame_view,
1705                                resolve_target: None,
1706                                ops: wgpu::Operations {
1707                                    load: wgpu::LoadOp::Load,
1708                                    store: wgpu::StoreOp::Store,
1709                                },
1710                                depth_slice: None,
1711                            })],
1712                            depth_stencil_attachment: None,
1713                            ..Default::default()
1714                        });
1715
1716                        if rasterized {
1717                            self.draw_paths_from_intermediate(
1718                                paths,
1719                                &mut instance_offset,
1720                                &mut pass,
1721                            )?;
1722                        }
1723                    }
1724                    PrimitiveBatch::Underlines(range) => self.draw_instances(
1725                        &instance_bindings.underlines,
1726                        &self.resources().pipelines.underlines,
1727                        instance_range(range),
1728                        &mut pass,
1729                    ),
1730                    PrimitiveBatch::MonochromeSprites { texture_id, range } => {
1731                        self.draw_sprites(
1732                            &instance_bindings.monochrome_sprites,
1733                            texture_id,
1734                            &self.resources().pipelines.mono_sprites,
1735                            instance_range(range),
1736                            &mut pass,
1737                        )?;
1738                    }
1739                    PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1740                        let resources = self.resources();
1741                        self.draw_sprites(
1742                            &instance_bindings.subpixel_sprites,
1743                            texture_id,
1744                            resources
1745                                .pipelines
1746                                .subpixel_sprites
1747                                .as_ref()
1748                                .unwrap_or(&resources.pipelines.mono_sprites),
1749                            instance_range(range),
1750                            &mut pass,
1751                        )?;
1752                    }
1753                    PrimitiveBatch::PolychromeSprites { texture_id, range } => {
1754                        self.draw_sprites(
1755                            &instance_bindings.polychrome_sprites,
1756                            texture_id,
1757                            &self.resources().pipelines.poly_sprites,
1758                            instance_range(range),
1759                            &mut pass,
1760                        )?;
1761                    }
1762                    PrimitiveBatch::Custom(range) => {
1763                        self.draw_imported_textures(
1764                            &instance_bindings.imported_textures,
1765                            &scene.custom[range.clone()],
1766                            instance_bindings.imported_textures.first_instance + range.start as u32,
1767                            &mut pass,
1768                        )?;
1769                    }
1770                    // Surfaces arrive as a texture (or a plane pair) the renderer samples: a
1771                    // dma-buf on Linux, and nothing under this renderer on the other platforms
1772                    // yet.
1773                    PrimitiveBatch::Surfaces(range) => {
1774                        self.draw_surfaces(&scene.surfaces[range.clone()], &mut pass)?;
1775                    }
1776                }
1777            }
1778        }
1779
1780        let submission = self
1781            .resources()
1782            .queue
1783            .submit(std::iter::once(encoder.finish()));
1784        // Tell each producer its surface is free once the GPU has finished the submission that
1785        // sampled it — the release signal's half of the dma-buf handshake.
1786        if !releases.is_empty() {
1787            self.resources()
1788                .queue
1789                .on_submitted_work_done(move || {
1790                    for release in &releases {
1791                        crate::dmabuf::signal_release(release);
1792                    }
1793                });
1794        }
1795        Ok(submission)
1796    }
1797
1798    fn write_instances(
1799        &mut self,
1800        scene: &Scene,
1801        instance_offset: &mut u64,
1802    ) -> Result<InstanceBindings> {
1803        Ok(InstanceBindings {
1804            quads: self.write_instance_binding(
1805                "quads_bind_group",
1806                instance_offset,
1807                &scene.quads,
1808            )?,
1809            shadows: self.write_instance_binding(
1810                "shadows_bind_group",
1811                instance_offset,
1812                &scene.shadows,
1813            )?,
1814            underlines: self.write_instance_binding(
1815                "underlines_bind_group",
1816                instance_offset,
1817                &scene.underlines,
1818            )?,
1819            monochrome_sprites: self.write_instance_binding(
1820                "monochrome_sprites_bind_group",
1821                instance_offset,
1822                &scene.monochrome_sprites,
1823            )?,
1824            subpixel_sprites: self.write_instance_binding(
1825                "subpixel_sprites_bind_group",
1826                instance_offset,
1827                &scene.subpixel_sprites,
1828            )?,
1829            polychrome_sprites: self.write_instance_binding(
1830                "polychrome_sprites_bind_group",
1831                instance_offset,
1832                &scene.polychrome_sprites,
1833            )?,
1834            imported_textures: self.write_instance_binding(
1835                "imported_textures_bind_group",
1836                instance_offset,
1837                &scene
1838                    .custom
1839                    .iter()
1840                    .map(CustomRenderPrimitive::to_quad_record)
1841                    .collect::<Vec<_>>(),
1842            )?,
1843        })
1844    }
1845
1846    fn create_texture_bind_group(
1847        &self,
1848        label: &str,
1849        texture_view: &wgpu::TextureView,
1850    ) -> wgpu::BindGroup {
1851        let resources = self.resources();
1852        resources
1853            .device
1854            .create_bind_group(&wgpu::BindGroupDescriptor {
1855                label: Some(label),
1856                layout: &resources.bind_group_layouts.texture,
1857                entries: &[
1858                    wgpu::BindGroupEntry {
1859                        binding: 0,
1860                        resource: wgpu::BindingResource::TextureView(texture_view),
1861                    },
1862                    wgpu::BindGroupEntry {
1863                        binding: 1,
1864                        resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1865                    },
1866                ],
1867            })
1868    }
1869
1870    fn prepare_texture_bind_groups(&mut self, scene: &Scene) {
1871        let mut texture_ids = SmallVec::<[AtlasTextureId; 8]>::new();
1872        for batch in scene.batches() {
1873            let texture_id = match batch {
1874                PrimitiveBatch::MonochromeSprites { texture_id, .. }
1875                | PrimitiveBatch::SubpixelSprites { texture_id, .. }
1876                | PrimitiveBatch::PolychromeSprites { texture_id, .. } => texture_id,
1877                _ => continue,
1878            };
1879            if !texture_ids.contains(&texture_id) {
1880                texture_ids.push(texture_id);
1881            }
1882        }
1883
1884        self.resources_mut()
1885            .atlas_texture_bind_groups
1886            .retain(|texture_id, _| texture_ids.contains(texture_id));
1887
1888        for texture_id in texture_ids {
1889            let Some(texture_info) = self.atlas.get_texture_info(texture_id) else {
1890                self.resources_mut()
1891                    .atlas_texture_bind_groups
1892                    .remove(&texture_id);
1893                continue;
1894            };
1895            let is_current = self
1896                .resources()
1897                .atlas_texture_bind_groups
1898                .get(&texture_id)
1899                .is_some_and(|cached| cached.texture_generation == texture_info.generation);
1900            if is_current {
1901                continue;
1902            }
1903
1904            let bind_group =
1905                self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
1906            self.resources_mut().atlas_texture_bind_groups.insert(
1907                texture_id,
1908                CachedTextureBindGroup {
1909                    texture_generation: texture_info.generation,
1910                    bind_group,
1911                },
1912            );
1913        }
1914    }
1915
1916    fn draw_instances(
1917        &self,
1918        instances: &InstanceBinding,
1919        pipeline: &wgpu::RenderPipeline,
1920        range: Range<u32>,
1921        pass: &mut wgpu::RenderPass<'_>,
1922    ) {
1923        if range.is_empty() {
1924            return;
1925        }
1926        pass.set_pipeline(pipeline);
1927        pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1928        pass.set_bind_group(1, &instances.bind_group, &[]);
1929        pass.draw(
1930            0..4,
1931            instances.first_instance + range.start..instances.first_instance + range.end,
1932        );
1933    }
1934
1935    fn draw_sprites(
1936        &self,
1937        sprite_instances: &InstanceBinding,
1938        texture_id: AtlasTextureId,
1939        pipeline: &wgpu::RenderPipeline,
1940        range: Range<u32>,
1941        pass: &mut wgpu::RenderPass<'_>,
1942    ) -> Result<()> {
1943        if range.is_empty() {
1944            return Ok(());
1945        }
1946        let resources = self.resources();
1947        // The atlas has released this texture; the batch belongs to a stale
1948        // paint that will be replaced once its view re-renders.
1949        let Some(texture) = resources.atlas_texture_bind_groups.get(&texture_id) else {
1950            return Ok(());
1951        };
1952        pass.set_pipeline(pipeline);
1953        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1954        pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
1955        pass.set_bind_group(2, &texture.bind_group, &[]);
1956        pass.draw(
1957            0..4,
1958            sprite_instances.first_instance + range.start
1959                ..sprite_instances.first_instance + range.end,
1960        );
1961        Ok(())
1962    }
1963
1964    fn draw_imported_textures(
1965        &self,
1966        instances: &InstanceBinding,
1967        primitives: &[CustomRenderPrimitive],
1968        first_instance: u32,
1969        pass: &mut wgpu::RenderPass<'_>,
1970    ) -> Result<()> {
1971        if primitives.is_empty() {
1972            return Ok(());
1973        }
1974
1975        let resources = self.resources();
1976        pass.set_pipeline(&resources.pipelines.imported_textures);
1977        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1978        pass.set_bind_group(1, &instances.bind_group, &[]);
1979
1980        for (offset, primitive) in primitives.iter().enumerate() {
1981            let CustomRenderPrimitive::Texture { handle, .. } = primitive;
1982            let Some(texture) = handle.payload.downcast_ref::<WgpuImportedTexture>() else {
1983                anyhow::bail!("imported texture handle does not hold a wgpu texture view");
1984            };
1985
1986            let bind_group = self.create_imported_texture_bind_group(&texture.view);
1987            pass.set_bind_group(2, &bind_group, &[]);
1988
1989            let instance = first_instance + offset as u32;
1990            pass.draw(0..4, instance..instance + 1);
1991        }
1992        Ok(())
1993    }
1994
1995    fn create_imported_texture_bind_group(&self, view: &wgpu::TextureView) -> wgpu::BindGroup {
1996        let resources = self.resources();
1997        resources
1998            .device
1999            .create_bind_group(&wgpu::BindGroupDescriptor {
2000                label: Some("imported_texture_bind_group"),
2001                layout: &resources.bind_group_layouts.imported_texture,
2002                entries: &[
2003                    wgpu::BindGroupEntry {
2004                        binding: 0,
2005                        resource: wgpu::BindingResource::TextureView(view),
2006                    },
2007                    wgpu::BindGroupEntry {
2008                        binding: 1,
2009                        resource: wgpu::BindingResource::Sampler(
2010                            &resources.imported_texture_sampler,
2011                        ),
2012                    },
2013                ],
2014            })
2015    }
2016
2017    /// The release descriptors of a scene's dma-buf surfaces: the signals a producer waits on to
2018    /// know the renderer is done with each buffer. Collected from the whole scene rather than only
2019    /// the surfaces that are drawn, so a frame that culls a surface still releases it. Empty where
2020    /// the platform has no dma-buf source.
2021    fn surface_releases(scene: &Scene) -> Vec<Arc<OwnedFd>> {
2022        #[cfg(target_os = "linux")]
2023        {
2024            scene
2025                .surfaces
2026                .iter()
2027                .filter_map(|surface| match &surface.source {
2028                    gpui_engine::SurfaceSource::DmaBuf(handle) => handle.release.clone(),
2029                })
2030                .collect()
2031        }
2032        #[cfg(not(target_os = "linux"))]
2033        {
2034            let _ = scene;
2035            Vec::new()
2036        }
2037    }
2038
2039    /// Register the releases of a scene the frame will not present, so a producer's surface is not
2040    /// left waiting on a frame that never sampled it. A window that drops a frame — suboptimal,
2041    /// timed out, occluded — must still release the buffers the application handed it, or the
2042    /// producer's bounded ring waits on one for good. The closures fire once the GPU has caught up
2043    /// with the queue's existing work, so nothing is sampling the buffer in the meantime.
2044    fn release_unpresented(&self, scene: &Scene) -> bool {
2045        let releases = Self::surface_releases(scene);
2046        if !releases.is_empty() {
2047            self.resources()
2048                .queue
2049                .on_submitted_work_done(move || {
2050                    for release in &releases {
2051                        crate::dmabuf::signal_release(release);
2052                    }
2053                });
2054        }
2055        false
2056    }
2057
2058    /// Composite the scene's surfaces.
2059    ///
2060    /// A surface's source becomes one or two textures: a single RGBA/BGRA plane samples straight
2061    /// through, and an `NV12` pair samples luma and chroma and converts in the fragment shader. The
2062    /// geometry is the shader's (`vs_surface`), driven by a per-surface uniform, so a surface needs
2063    /// no instance record and no vertex buffer. A buffer the renderer cannot import drops its
2064    /// surface rather than the frame.
2065    fn draw_surfaces(
2066        &mut self,
2067        surfaces: &[PaintSurface],
2068        pass: &mut wgpu::RenderPass<'_>,
2069    ) -> Result<()> {
2070        if surfaces.is_empty() {
2071            return Ok(());
2072        }
2073        pass.set_pipeline(&self.resources().pipelines.surfaces);
2074        pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
2075
2076        // One params buffer, addressed at a dynamic offset per surface, rather than a `vkCreateBuffer`
2077        // and its allocation per surface, per frame.
2078        #[cfg(target_os = "linux")]
2079        let params_stride = {
2080            let align = u64::from(
2081                self.resources()
2082                    .device
2083                    .limits()
2084                    .min_uniform_buffer_offset_alignment,
2085            );
2086            let size = std::mem::size_of::<SurfaceParams>() as u64;
2087            size.div_ceil(align) * align
2088        };
2089        #[cfg(target_os = "linux")]
2090        let mut params_bytes = vec![0u8; params_stride as usize * surfaces.len()];
2091        #[cfg(target_os = "linux")]
2092        for (index, surface) in surfaces.iter().enumerate() {
2093            let gpui_engine::SurfaceSource::DmaBuf(handle) = &surface.source;
2094            // Wait for the producer's fence before sampling; a producer that never signals loses
2095            // this frame rather than stalling the UI.
2096            if let Some(fence) = &handle.acquire_fence
2097                && !crate::wait_for_acquire_fence(fence, crate::ACQUIRE_FENCE_TIMEOUT)
2098            {
2099                warn!("dropping a dma-buf surface: its acquire fence did not signal in time");
2100                continue;
2101            }
2102            // The cache owns the imported textures and hands back cheap clones of their handles — as
2103            // the Metal renderer's `CVMetalTextureCache` does. The mutable borrow ends before the rest
2104            // of the resources are read.
2105            let textures = {
2106                let resources = self.resources_mut();
2107                match resources
2108                    .dmabuf_textures
2109                    .textures(&resources.device, handle)
2110                {
2111                    Ok(textures) => textures,
2112                    Err(error) => {
2113                        warn!("dropping a dma-buf surface: {error:#}");
2114                        continue;
2115                    }
2116                }
2117            };
2118            let resources = self.resources();
2119
2120            let luma = textures[0].view.clone();
2121            // A single plane samples as RGBA (format 1) and binds its own view for both texture
2122            // slots; a second plane is chroma (format 0). Both views come from the importer, which for
2123            // `Nv12` makes them the two planes of one multi-planar image.
2124            let (chroma, surface_format) = if textures.len() == 1 {
2125                (luma.clone(), 1u32)
2126            } else {
2127                (textures[1].view.clone(), 0u32)
2128            };
2129
2130            let chroma_reconstruction = match handle.chroma {
2131                gpui_engine::ChromaReconstruction::Bilinear => 0,
2132                gpui_engine::ChromaReconstruction::LumaGuided => 1,
2133            };
2134
2135            let params = SurfaceParams {
2136                bounds: surface.bounds.into(),
2137                content_mask: surface.content_mask.bounds.into(),
2138                surface_format,
2139                chroma_reconstruction,
2140                _pad: [0; 2],
2141                ycbcr_to_rgb: yuv_to_rgb(handle.color_space),
2142            };
2143            // Stage the bytes; one upload carries every surface's params, below the loop.
2144            let params_offset = params_stride as usize * index;
2145            params_bytes[params_offset..params_offset + std::mem::size_of::<SurfaceParams>()]
2146                .copy_from_slice(bytemuck::bytes_of(&params));
2147
2148            let bind_group = resources
2149                .device
2150                .create_bind_group(&wgpu::BindGroupDescriptor {
2151                    label: Some("surface_bind_group"),
2152                    layout: &resources.bind_group_layouts.surfaces,
2153                    entries: &[
2154                        wgpu::BindGroupEntry {
2155                            binding: 0,
2156                            resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2157                                buffer: &resources.surface_params,
2158                                offset: 0,
2159                                size: NonZeroU64::new(
2160                                    std::mem::size_of::<SurfaceParams>() as u64,
2161                                ),
2162                            }),
2163                        },
2164                        wgpu::BindGroupEntry {
2165                            binding: 1,
2166                            resource: wgpu::BindingResource::TextureView(&luma),
2167                        },
2168                        wgpu::BindGroupEntry {
2169                            binding: 2,
2170                            resource: wgpu::BindingResource::TextureView(&chroma),
2171                        },
2172                        wgpu::BindGroupEntry {
2173                            binding: 3,
2174                            resource: wgpu::BindingResource::Sampler(
2175                                &resources.imported_texture_sampler,
2176                            ),
2177                        },
2178                    ],
2179                });
2180            pass.set_bind_group(1, &bind_group, &[params_offset as u32]);
2181            pass.draw(0..4, 0..1);
2182        }
2183
2184        // One upload for every surface's params, not one per surface.
2185        #[cfg(target_os = "linux")]
2186        self.resources()
2187            .queue
2188            .write_buffer(&self.resources().surface_params, 0, &params_bytes);
2189
2190        Ok(())
2191    }
2192
2193    unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
2194        unsafe {
2195            std::slice::from_raw_parts(
2196                instances.as_ptr() as *const u8,
2197                std::mem::size_of_val(instances),
2198            )
2199        }
2200    }
2201
2202    fn draw_paths_from_intermediate(
2203        &mut self,
2204        paths: &[Path<ScaledPixels>],
2205        instance_offset: &mut u64,
2206        pass: &mut wgpu::RenderPass<'_>,
2207    ) -> Result<()> {
2208        let first_path = &paths[0];
2209        let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
2210        {
2211            paths
2212                .iter()
2213                .map(|p| PathSprite {
2214                    bounds: p.clipped_bounds(),
2215                })
2216                .collect()
2217        } else {
2218            let mut bounds = first_path.clipped_bounds();
2219            for path in paths.iter().skip(1) {
2220                bounds = bounds.union(&path.clipped_bounds());
2221            }
2222            vec![PathSprite { bounds }]
2223        };
2224
2225        let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
2226            return Ok(());
2227        };
2228        let instances =
2229            self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
2230        let texture = self.create_texture_bind_group(
2231            "path_intermediate_texture_bind_group",
2232            &path_intermediate_view,
2233        );
2234        let resources = self.resources();
2235        pass.set_pipeline(&resources.pipelines.paths);
2236        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
2237        pass.set_bind_group(1, &instances.bind_group, &[]);
2238        pass.set_bind_group(2, &texture, &[]);
2239        pass.draw(
2240            0..4,
2241            instances.first_instance..instances.first_instance + sprites.len() as u32,
2242        );
2243        Ok(())
2244    }
2245
2246    fn draw_paths_to_intermediate(
2247        &mut self,
2248        encoder: &mut wgpu::CommandEncoder,
2249        paths: &[Path<ScaledPixels>],
2250        instance_offset: &mut u64,
2251    ) -> Result<bool> {
2252        let mut vertices = Vec::new();
2253        for path in paths {
2254            let bounds = path.clipped_bounds();
2255            vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
2256                xy_position: v.xy_position,
2257                st_position: v.st_position,
2258                color: path.color,
2259                bounds,
2260            }));
2261        }
2262
2263        if vertices.is_empty() {
2264            return Ok(false);
2265        }
2266
2267        let vertex_binding = self.write_instance_binding(
2268            "path_rasterization_bind_group",
2269            instance_offset,
2270            &vertices,
2271        )?;
2272
2273        let resources = self.resources();
2274        let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
2275            return Ok(false);
2276        };
2277
2278        let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
2279            (msaa_view, Some(path_intermediate_view))
2280        } else {
2281            (path_intermediate_view, None)
2282        };
2283
2284        {
2285            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2286                label: Some("path_rasterization_pass"),
2287                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2288                    view: target_view,
2289                    resolve_target,
2290                    ops: wgpu::Operations {
2291                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
2292                        store: wgpu::StoreOp::Store,
2293                    },
2294                    depth_slice: None,
2295                })],
2296                depth_stencil_attachment: None,
2297                ..Default::default()
2298            });
2299
2300            pass.set_pipeline(&resources.pipelines.path_rasterization);
2301            pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
2302            pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
2303            // The path rasterization shader loads records by vertex index
2304            // rather than instance index, so the allocation's base shifts the
2305            // vertex range here.
2306            pass.draw(
2307                vertex_binding.first_instance
2308                    ..vertex_binding.first_instance + vertices.len() as u32,
2309                0..1,
2310            );
2311        }
2312
2313        Ok(true)
2314    }
2315
2316    fn write_instance_binding<T>(
2317        &mut self,
2318        label: &str,
2319        instance_offset: &mut u64,
2320        instances: &[T],
2321    ) -> Result<InstanceBinding> {
2322        let data = unsafe { Self::instance_bytes(instances) };
2323        // wgpu rejects zero-sized bindings, so empty primitive arrays still
2324        // reserve the 16-byte minimum.
2325        let size = (data.len() as u64).max(16);
2326        let stride = (std::mem::size_of::<T>() as u64).max(1);
2327        let (alignment, allocation_size) = if self.uses_webgl_instance_data {
2328            // The texture transport has no binding offset: the shader indexes
2329            // the instance texture absolutely, so each allocation must start on
2330            // a whole instance (a stride multiple) and a whole texel, and must
2331            // end on a texel boundary so the zero padding of its final partial
2332            // texel cannot overlap the next allocation.
2333            (
2334                least_common_multiple(self.instance_data_alignment, stride),
2335                size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
2336            )
2337        } else {
2338            (self.instance_data_alignment.max(1), size)
2339        };
2340        let mut offset = (*instance_offset).next_multiple_of(alignment);
2341        if offset + allocation_size > self.instance_data_capacity {
2342            self.grow_instance_data(allocation_size)?;
2343            offset = 0;
2344        }
2345        *instance_offset = offset + allocation_size;
2346
2347        let first_instance = if self.uses_webgl_instance_data {
2348            u32::try_from(offset / stride).context("instance index exceeds u32 range")?
2349        } else {
2350            0
2351        };
2352
2353        let resources = self.resources();
2354        if !data.is_empty() {
2355            match &resources.instance_data {
2356                InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
2357                InstanceData::Texture { .. } => {
2358                    Self::write_instance_texture(resources, offset, data)
2359                }
2360            }
2361        }
2362        let bind_group = resources
2363            .device
2364            .create_bind_group(&wgpu::BindGroupDescriptor {
2365                label: Some(label),
2366                layout: &resources.bind_group_layouts.instances,
2367                entries: &[wgpu::BindGroupEntry {
2368                    binding: 0,
2369                    resource: match &resources.instance_data {
2370                        InstanceData::Storage(buffer) => {
2371                            wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2372                                buffer,
2373                                offset,
2374                                size: NonZeroU64::new(size),
2375                            })
2376                        }
2377                        InstanceData::Texture { view, .. } => {
2378                            wgpu::BindingResource::TextureView(view)
2379                        }
2380                    },
2381                }],
2382            });
2383        Ok(InstanceBinding {
2384            bind_group,
2385            first_instance,
2386        })
2387    }
2388
2389    fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
2390        let InstanceData::Texture {
2391            texture,
2392            width,
2393            height,
2394            ..
2395        } = &resources.instance_data
2396        else {
2397            return;
2398        };
2399        let mut byte_offset = 0usize;
2400        let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
2401        while byte_offset < data.len() {
2402            let x = (texel_offset % u64::from(*width)) as u32;
2403            let y = (texel_offset / u64::from(*width)) as u32;
2404            if y >= *height {
2405                // The capacity check in write_instance_binding should make this
2406                // unreachable. Truncating silently would leave stale bytes in the
2407                // texture and draw garbage for the remaining instances.
2408                debug_assert!(
2409                    false,
2410                    "instance texture write out of bounds: row {y} >= height {}",
2411                    *height
2412                );
2413                log::error!(
2414                    "instance texture write out of bounds; dropping {} bytes of instance data",
2415                    data.len() - byte_offset
2416                );
2417                return;
2418            }
2419            let available_texels = u64::from(*width - x);
2420            let remaining_bytes = data.len() - byte_offset;
2421            let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
2422            let texels = complete_texels.min(available_texels);
2423            if texels > 0 {
2424                let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
2425                resources.queue.write_texture(
2426                    wgpu::TexelCopyTextureInfo {
2427                        texture,
2428                        mip_level: 0,
2429                        origin: wgpu::Origin3d { x, y, z: 0 },
2430                        aspect: wgpu::TextureAspect::All,
2431                    },
2432                    &data[byte_offset..byte_offset + byte_count],
2433                    wgpu::TexelCopyBufferLayout {
2434                        offset: 0,
2435                        bytes_per_row: Some(byte_count as u32),
2436                        rows_per_image: None,
2437                    },
2438                    wgpu::Extent3d {
2439                        width: texels as u32,
2440                        height: 1,
2441                        depth_or_array_layers: 1,
2442                    },
2443                );
2444                byte_offset += byte_count;
2445                texel_offset += texels;
2446                continue;
2447            }
2448
2449            let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
2450            final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
2451            resources.queue.write_texture(
2452                wgpu::TexelCopyTextureInfo {
2453                    texture,
2454                    mip_level: 0,
2455                    origin: wgpu::Origin3d { x, y, z: 0 },
2456                    aspect: wgpu::TextureAspect::All,
2457                },
2458                &final_texel,
2459                wgpu::TexelCopyBufferLayout {
2460                    offset: 0,
2461                    bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
2462                    rows_per_image: None,
2463                },
2464                wgpu::Extent3d {
2465                    width: 1,
2466                    height: 1,
2467                    depth_or_array_layers: 1,
2468                },
2469            );
2470            break;
2471        }
2472    }
2473
2474    fn grow_instance_data(&mut self, required: u64) -> Result<()> {
2475        let capacity = (self.instance_data_capacity * 2)
2476            .max(required.next_power_of_two())
2477            .min(self.max_instance_data_size);
2478        anyhow::ensure!(
2479            capacity >= required,
2480            "instance data needs {required} bytes, above the maximum of {}",
2481            self.max_instance_data_size
2482        );
2483        anyhow::ensure!(
2484            capacity > self.instance_data_capacity,
2485            "frame instance data exceeds the {}-byte maximum",
2486            self.max_instance_data_size
2487        );
2488        log::debug!(
2489            "instance data grown from {} to {capacity}",
2490            self.instance_data_capacity
2491        );
2492        // Bind groups created earlier in the frame keep the previous buffer or
2493        // texture alive, so allocations written before the grow remain valid;
2494        // only subsequent writes land in the new allocation.
2495        let uses_webgl_instance_data = self.uses_webgl_instance_data;
2496        let resources = self.resources_mut();
2497        if uses_webgl_instance_data {
2498            let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
2499            let (instance_data, actual_capacity) =
2500                Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
2501            resources.instance_data = instance_data;
2502            self.instance_data_capacity = actual_capacity;
2503        } else {
2504            resources.instance_data =
2505                InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
2506                    label: Some("instance_buffer"),
2507                    size: capacity,
2508                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2509                    mapped_at_creation: false,
2510                }));
2511            self.instance_data_capacity = capacity;
2512        }
2513        Ok(())
2514    }
2515}
2516
2517impl WgpuRenderer {
2518    /// Mark the surface as unconfigured so rendering is skipped until a new
2519    /// surface is provided via [`replace_surface`](Self::replace_surface).
2520    ///
2521    /// This does **not** drop the renderer — the device, queue, atlas, and
2522    /// pipelines stay alive.  Use this when the native window is destroyed
2523    /// (e.g. Android `TerminateWindow`) but you intend to re-create the
2524    /// surface later without losing cached atlas textures.
2525    pub fn unconfigure_surface(&mut self) {
2526        self.state = match std::mem::replace(&mut self.state, RendererState::Released) {
2527            RendererState::Ready { core, surface } => {
2528                drop(surface);
2529                RendererState::Unconfigured { core }
2530            }
2531            state @ (RendererState::Unconfigured { .. } | RendererState::Released) => state,
2532        };
2533        // Drop intermediate textures since they reference the old surface size.
2534        if let Some(core) = self.core_mut() {
2535            core.resources.invalidate_intermediate_textures();
2536        }
2537    }
2538
2539    /// Replace the wgpu surface with a new one (e.g. after Android destroys
2540    /// and recreates the native window).  Keeps the device, queue, atlas, and
2541    /// all pipelines intact so cached `AtlasTextureId`s remain valid.
2542    ///
2543    /// The `instance` **must** be the same [`wgpu::Instance`] that was used to
2544    /// create the adapter and device (i.e. from the [`WgpuContext`]).  Using a
2545    /// different instance will cause a "Device does not exist" panic because
2546    /// the wgpu device is bound to its originating instance.
2547    #[cfg(not(target_family = "wasm"))]
2548    pub fn replace_surface<W: HasWindowHandle>(
2549        &mut self,
2550        window: &W,
2551        config: WgpuSurfaceConfig,
2552        instance: &wgpu::Instance,
2553    ) -> anyhow::Result<()> {
2554        let window_handle = window
2555            .window_handle()
2556            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2557
2558        let surface = create_surface(instance, window_handle.as_raw())?;
2559
2560        let width = (config.size.width.0 as u32).max(1);
2561        let height = (config.size.height.0 as u32).max(1);
2562
2563        let alpha_mode = if config.transparent {
2564            self.transparent_alpha_mode
2565        } else {
2566            self.opaque_alpha_mode
2567        };
2568
2569        self.surface_config.width = width;
2570        self.surface_config.height = height;
2571        self.surface_config.alpha_mode = alpha_mode;
2572        if let Some(mode) = config.preferred_present_mode {
2573            self.surface_config.present_mode = mode;
2574        }
2575
2576        let mut core = match std::mem::replace(&mut self.state, RendererState::Released) {
2577            RendererState::Ready {
2578                core,
2579                surface: old_surface,
2580            } => {
2581                drop(old_surface);
2582                core
2583            }
2584            RendererState::Unconfigured { core } => core,
2585            RendererState::Released => {
2586                anyhow::bail!("Cannot replace the surface: GPU resources have been released")
2587            }
2588        };
2589        surface.configure(&core.resources.device, &self.surface_config);
2590        core.resources.invalidate_intermediate_textures();
2591        self.state = RendererState::Ready { surface, core };
2592
2593        Ok(())
2594    }
2595
2596    pub fn destroy(&mut self) {
2597        // Release surface-bound GPU resources eagerly so the underlying native
2598        // window can be destroyed before the renderer itself is dropped.
2599        self.state = RendererState::Released;
2600    }
2601
2602    /// Returns true if the GPU device was lost and recovery is needed.
2603    pub fn device_lost(&self) -> bool {
2604        self.device_errors.device_lost()
2605    }
2606
2607    /// Returns true if a redraw is needed because GPU state was cleared.
2608    /// Calling this method clears the flag.
2609    pub fn needs_redraw(&mut self) -> bool {
2610        std::mem::take(&mut self.needs_redraw)
2611    }
2612
2613    /// Recovers from a lost GPU device by recreating the renderer with a new context.
2614    ///
2615    /// Call this after detecting `device_lost()` returns true.
2616    ///
2617    /// This method coordinates recovery across multiple windows:
2618    /// - The first window to call this will recreate the shared context
2619    /// - Subsequent windows will adopt the already-recovered context
2620    #[cfg(not(target_family = "wasm"))]
2621    pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2622    where
2623        W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2624    {
2625        let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2626
2627        // Check if another window already recovered the context
2628        let needs_new_context = gpu_context
2629            .borrow()
2630            .as_ref()
2631            .is_none_or(|ctx| ctx.device_lost());
2632
2633        let window_handle = window
2634            .window_handle()
2635            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2636
2637        let surface = if needs_new_context {
2638            log::warn!("GPU device lost, recreating context...");
2639
2640            // Drop old resources to release Arc<Device>/Arc<Queue> and GPU resources
2641            self.state = RendererState::Released;
2642            *gpu_context.borrow_mut() = None;
2643
2644            // Wait briefly for the GPU driver to stabilize, then try to
2645            // recreate the context without software renderers. If this fails
2646            // the caller should request another frame and retry — the real GPU
2647            // may need more time to come back (e.g. after suspend/resume).
2648            std::thread::sleep(std::time::Duration::from_millis(350));
2649
2650            let instance = WgpuContext::instance(Some(Box::new(window.clone())));
2651            let surface = create_surface(&instance, window_handle.as_raw())?;
2652            let new_context =
2653                WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?;
2654            *gpu_context.borrow_mut() = Some(new_context);
2655            surface
2656        } else {
2657            let ctx_ref = gpu_context.borrow();
2658            let instance = &ctx_ref.as_ref().unwrap().instance;
2659            create_surface(instance, window_handle.as_raw())?
2660        };
2661
2662        let config = WgpuSurfaceConfig {
2663            size: gpui_platform::Size {
2664                width: gpui_platform::DevicePixels(self.surface_config.width as i32),
2665                height: gpui_platform::DevicePixels(self.surface_config.height as i32),
2666            },
2667            transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2668            preferred_present_mode: Some(self.surface_config.present_mode),
2669        };
2670        let gpu_context = Rc::clone(gpu_context);
2671        let ctx_ref = gpu_context.borrow();
2672        let context = ctx_ref.as_ref().expect("context should exist");
2673
2674        self.state = RendererState::Released;
2675        self.atlas.handle_device_lost(context);
2676
2677        let is_bgr = self.is_bgr;
2678        *self = Self::new_internal(
2679            Some(gpu_context.clone()),
2680            context,
2681            surface,
2682            config,
2683            self.compositor_gpu,
2684            self.atlas.clone(),
2685        )?;
2686        self.set_subpixel_layout(is_bgr);
2687
2688        log::info!("GPU recovery complete");
2689        Ok(())
2690    }
2691}
2692
2693#[cfg(all(
2694    not(target_family = "wasm"),
2695    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2696))]
2697struct HeadlessRenderTarget {
2698    texture: wgpu::Texture,
2699    view: wgpu::TextureView,
2700}
2701
2702#[cfg(all(
2703    not(target_family = "wasm"),
2704    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2705))]
2706impl HeadlessRenderTarget {
2707    fn size(&self) -> Size<DevicePixels> {
2708        Size {
2709            width: DevicePixels(self.texture.width() as i32),
2710            height: DevicePixels(self.texture.height() as i32),
2711        }
2712    }
2713}
2714
2715#[cfg(all(
2716    not(target_family = "wasm"),
2717    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2718))]
2719pub struct WgpuHeadlessRenderer {
2720    context: WgpuContext,
2721    core: WgpuRendererCore,
2722    render_target: Option<HeadlessRenderTarget>,
2723    observed_error_generation: u64,
2724    viewport_size: Option<Size<DevicePixels>>,
2725}
2726
2727#[cfg(all(
2728    not(target_family = "wasm"),
2729    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2730))]
2731impl WgpuHeadlessRenderer {
2732    pub fn new() -> anyhow::Result<Self> {
2733        let (context, target_format) = WgpuContext::new_headless()?;
2734        // The device may be shared with earlier renderers; observe only errors raised from
2735        // here on, including any while this renderer builds its pipelines.
2736        let observed_error_generation = context.errors().current_generation();
2737        let atlas = Arc::new(WgpuAtlas::from_context(&context));
2738        let core = WgpuRendererCore::new(
2739            &context,
2740            atlas,
2741            target_format,
2742            wgpu::CompositeAlphaMode::Opaque,
2743        );
2744
2745        Ok(Self {
2746            context,
2747            core,
2748            render_target: None,
2749            observed_error_generation,
2750            viewport_size: None,
2751        })
2752    }
2753
2754    fn ensure_render_target(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()> {
2755        anyhow::ensure!(
2756            size.width.0 > 0 && size.height.0 > 0,
2757            "invalid headless render target size: {size:?}"
2758        );
2759        anyhow::ensure!(
2760            size.width.0 as u32 <= self.core.max_texture_size
2761                && size.height.0 as u32 <= self.core.max_texture_size,
2762            "headless render target size {size:?} exceeds maximum texture dimension {}",
2763            self.core.max_texture_size
2764        );
2765        if self
2766            .render_target
2767            .as_ref()
2768            .is_some_and(|target| target.size() == size)
2769        {
2770            return Ok(());
2771        }
2772
2773        let texture = self
2774            .core
2775            .resources
2776            .device
2777            .create_texture(&wgpu::TextureDescriptor {
2778                label: Some("headless_render_target"),
2779                size: wgpu::Extent3d {
2780                    width: size.width.0 as u32,
2781                    height: size.height.0 as u32,
2782                    depth_or_array_layers: 1,
2783                },
2784                mip_level_count: 1,
2785                sample_count: 1,
2786                dimension: wgpu::TextureDimension::D2,
2787                format: self.core.target_format,
2788                usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2789                view_formats: &[],
2790            });
2791        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2792        self.render_target = Some(HeadlessRenderTarget { texture, view });
2793        Ok(())
2794    }
2795
2796    fn check_gpu_errors(&mut self) -> anyhow::Result<()> {
2797        anyhow::ensure!(
2798            !self.context.device_lost(),
2799            "GPU device was lost during headless rendering"
2800        );
2801        if let Some(error) = self
2802            .context
2803            .errors()
2804            .observe_error(&mut self.observed_error_generation)
2805        {
2806            anyhow::bail!("GPU error during headless rendering: {error}");
2807        }
2808        Ok(())
2809    }
2810
2811    fn render(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2812        self.check_gpu_errors()?;
2813        self.ensure_render_target(size)?;
2814        let view = self
2815            .render_target
2816            .as_ref()
2817            .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?
2818            .view
2819            .clone();
2820        self.core
2821            .render_frame(scene, &view, size, false, wgpu::Color::BLACK)?;
2822        Ok(())
2823    }
2824
2825    /// Copies the current render target back to the CPU. Dimensions come from the
2826    /// target texture itself, so the copy can never disagree with what was rendered.
2827    fn read_image(&mut self) -> anyhow::Result<image::RgbaImage> {
2828        let target = self
2829            .render_target
2830            .as_ref()
2831            .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?;
2832        let width = target.texture.width();
2833        let height = target.texture.height();
2834        let bytes_per_row = width
2835            .checked_mul(4)
2836            .ok_or_else(|| anyhow::anyhow!("Headless render target row size overflowed"))?;
2837        let padded_bytes_per_row = bytes_per_row
2838            .checked_next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
2839            .ok_or_else(|| anyhow::anyhow!("Headless padded row size overflowed"))?;
2840        let buffer_size = u64::from(padded_bytes_per_row)
2841            .checked_mul(u64::from(height))
2842            .ok_or_else(|| anyhow::anyhow!("Headless readback buffer size overflowed"))?;
2843        anyhow::ensure!(
2844            buffer_size <= self.core.resources.device.limits().max_buffer_size,
2845            "Headless readback buffer size {buffer_size} exceeds maximum buffer size {}",
2846            self.core.resources.device.limits().max_buffer_size
2847        );
2848        let readback_buffer = self
2849            .core
2850            .resources
2851            .device
2852            .create_buffer(&wgpu::BufferDescriptor {
2853                label: Some("headless_readback_buffer"),
2854                size: buffer_size,
2855                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2856                mapped_at_creation: false,
2857            });
2858        let mut encoder =
2859            self.core
2860                .resources
2861                .device
2862                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2863                    label: Some("headless_readback_encoder"),
2864                });
2865        encoder.copy_texture_to_buffer(
2866            wgpu::TexelCopyTextureInfo {
2867                texture: &target.texture,
2868                mip_level: 0,
2869                origin: wgpu::Origin3d::ZERO,
2870                aspect: wgpu::TextureAspect::All,
2871            },
2872            wgpu::TexelCopyBufferInfo {
2873                buffer: &readback_buffer,
2874                layout: wgpu::TexelCopyBufferLayout {
2875                    offset: 0,
2876                    bytes_per_row: Some(padded_bytes_per_row),
2877                    rows_per_image: Some(height),
2878                },
2879            },
2880            wgpu::Extent3d {
2881                width,
2882                height,
2883                depth_or_array_layers: 1,
2884            },
2885        );
2886        let submission = self
2887            .core
2888            .resources
2889            .queue
2890            .submit(std::iter::once(encoder.finish()));
2891        let (sender, receiver) = std::sync::mpsc::channel();
2892        readback_buffer
2893            .slice(..)
2894            .map_async(wgpu::MapMode::Read, move |result| {
2895                if sender.send(result).is_err() {
2896                    log::error!("Headless readback receiver was dropped before mapping completed");
2897                }
2898            });
2899        self.core
2900            .resources
2901            .device
2902            .poll(wgpu::PollType::Wait {
2903                submission_index: Some(submission),
2904                timeout: Some(std::time::Duration::from_secs(30)),
2905            })
2906            .map_err(|error| anyhow::anyhow!("Failed to wait for headless rendering: {error}"))?;
2907        receiver
2908            .recv_timeout(std::time::Duration::from_secs(30))
2909            .map_err(|error| anyhow::anyhow!("Failed to receive headless mapping result: {error}"))?
2910            .map_err(|error| anyhow::anyhow!("Failed to map headless readback buffer: {error}"))?;
2911        self.check_gpu_errors()?;
2912
2913        let mapped_data = readback_buffer.slice(..).get_mapped_range();
2914        let pixel_capacity = usize::try_from(u64::from(bytes_per_row) * u64::from(height))
2915            .map_err(|_| anyhow::anyhow!("Headless image size exceeds addressable memory"))?;
2916        let mut pixels = Vec::with_capacity(pixel_capacity);
2917        for row in mapped_data
2918            .chunks_exact(padded_bytes_per_row as usize)
2919            .take(height as usize)
2920        {
2921            pixels.extend_from_slice(&row[..bytes_per_row as usize]);
2922        }
2923        drop(mapped_data);
2924        readback_buffer.unmap();
2925
2926        if self.core.target_format == wgpu::TextureFormat::Bgra8Unorm {
2927            for pixel in pixels.chunks_exact_mut(4) {
2928                pixel.swap(0, 2);
2929            }
2930        }
2931
2932        image::RgbaImage::from_raw(width, height, pixels)
2933            .ok_or_else(|| anyhow::anyhow!("Failed to create image from headless pixel data"))
2934    }
2935}
2936
2937#[cfg(all(
2938    not(target_family = "wasm"),
2939    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2940))]
2941impl gpui_engine::SceneRenderer for WgpuHeadlessRenderer {
2942    fn draw(&mut self, scene: &Scene) -> bool {
2943        // A headless renderer has no compositor to observe, so it reports presented and
2944        // renders offscreen at the size the caller last set.
2945        if let Some(size) = self.viewport_size {
2946            let _ = self.render(scene, size);
2947        }
2948        true
2949    }
2950
2951    fn sprite_atlas(&self) -> Arc<dyn gpui_engine::PlatformAtlas> {
2952        self.core.atlas.clone()
2953    }
2954
2955    fn set_viewport_size(&mut self, size: Size<DevicePixels>) {
2956        self.viewport_size = Some(size);
2957    }
2958
2959    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2960        self.render(scene, size)
2961    }
2962
2963    fn read_pixels(&mut self) -> anyhow::Result<gpui_engine::PixelBuffer> {
2964        let image = self.read_image()?;
2965        let (width, height) = image.dimensions();
2966        gpui_engine::PixelBuffer::new(width, height, image.into_raw())
2967    }
2968}
2969
2970#[cfg(all(
2971    not(target_family = "wasm"),
2972    any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2973))]
2974impl GpuRenderer for WgpuHeadlessRenderer {
2975    type Device = (Arc<wgpu::Device>, Arc<wgpu::Queue>);
2976
2977    /// The context this headless renderer built: offscreen, but a real device and queue.
2978    fn device(&self) -> Option<Self::Device> {
2979        Some((self.context.device.clone(), self.context.queue.clone()))
2980    }
2981}
2982
2983fn instance_range(range: Range<usize>) -> Range<u32> {
2984    range.start as u32..range.end as u32
2985}
2986
2987#[cfg(not(target_family = "wasm"))]
2988fn create_surface(
2989    instance: &wgpu::Instance,
2990    raw_window_handle: raw_window_handle::RawWindowHandle,
2991) -> anyhow::Result<wgpu::Surface<'static>> {
2992    unsafe {
2993        instance
2994            .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
2995                // Fall back to the display handle already provided via InstanceDescriptor::display.
2996                raw_display_handle: None,
2997                raw_window_handle,
2998            })
2999            .map_err(|e| anyhow::anyhow!("{e}"))
3000    }
3001}
3002
3003struct RenderingParameters {
3004    path_sample_count: u32,
3005    gamma_ratios: [f32; 4],
3006    grayscale_enhanced_contrast: f32,
3007    subpixel_enhanced_contrast: f32,
3008}
3009
3010impl RenderingParameters {
3011    fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
3012        use std::env;
3013
3014        let format_features = adapter.get_texture_format_features(surface_format);
3015        let path_sample_count = [4, 2, 1]
3016            .into_iter()
3017            .find(|&n| format_features.flags.sample_count_supported(n))
3018            .unwrap_or(1);
3019
3020        let gamma = env::var("ZED_FONTS_GAMMA")
3021            .ok()
3022            .and_then(|v| v.parse().ok())
3023            .unwrap_or(1.8_f32)
3024            .clamp(1.0, 2.2);
3025        let gamma_ratios = get_gamma_correction_ratios(gamma);
3026
3027        let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
3028            .ok()
3029            .and_then(|v| v.parse().ok())
3030            .unwrap_or(1.0_f32)
3031            .max(0.0);
3032
3033        let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
3034            .ok()
3035            .and_then(|v| v.parse().ok())
3036            .unwrap_or(0.5_f32)
3037            .max(0.0);
3038
3039        Self {
3040            path_sample_count,
3041            gamma_ratios,
3042            grayscale_enhanced_contrast,
3043            subpixel_enhanced_contrast,
3044        }
3045    }
3046}
3047
3048#[cfg(test)]
3049mod tests {
3050    use super::*;
3051    use gpui_engine::{
3052        BorderStyle, MonochromeSprite, PixelBuffer, PolychromeSprite, Quad, SubpixelSprite,
3053        Underline,
3054    };
3055    use gpui_platform::{
3056        ColorSpace, ContentMask, Corners, Edges, Hsla, Shadow, linear_color_stop, linear_gradient,
3057    };
3058    #[cfg(target_os = "linux")]
3059    use gpui::{DevicePixels, Scene};
3060
3061    #[cfg(target_os = "linux")]
3062    fn device_size(width: i32, height: i32) -> Size<DevicePixels> {
3063        Size {
3064            width: DevicePixels(width),
3065            height: DevicePixels(height),
3066        }
3067    }
3068
3069    #[cfg(target_os = "linux")]
3070    fn solid_quad(x: f32, y: f32, width: f32, height: f32, color: Hsla) -> Quad {
3071        let bounds = Bounds {
3072            origin: Point {
3073                x: x.into(),
3074                y: y.into(),
3075            },
3076            size: Size {
3077                width: width.into(),
3078                height: height.into(),
3079            },
3080        };
3081        Quad {
3082            order: 0,
3083            border_style: BorderStyle::Solid,
3084            bounds,
3085            content_mask: ContentMask { bounds },
3086            background: color.into(),
3087            border_color: color,
3088            corner_radii: Corners::default(),
3089            border_widths: Edges::default(),
3090        }
3091    }
3092
3093    /// Channels are compared with a small tolerance so the assertions hold across
3094    /// drivers without pinning exact rasterizer output.
3095    #[cfg(target_os = "linux")]
3096    fn assert_pixel(image: &PixelBuffer, x: u32, y: u32, expected: [u8; 4]) {
3097        let offset = ((y * image.width() + x) * 4) as usize;
3098        let actual = &image.data()[offset..offset + 4];
3099        assert!(
3100            actual
3101                .iter()
3102                .zip(expected)
3103                .all(|(actual, expected)| actual.abs_diff(expected) <= 3),
3104            "pixel ({x}, {y}) was {actual:?}, expected {expected:?}"
3105        );
3106    }
3107
3108    #[cfg(target_os = "linux")]
3109    const RED: [u8; 4] = [255, 0, 0, 255];
3110    #[cfg(target_os = "linux")]
3111    const BLUE: [u8; 4] = [0, 0, 255, 255];
3112    #[cfg(target_os = "linux")]
3113    const BLACK: [u8; 4] = [0, 0, 0, 255];
3114
3115    #[cfg(target_os = "linux")]
3116    #[test]
3117    fn headless_renderer_draws_quads_with_distinct_colors() -> anyhow::Result<()> {
3118        let mut renderer = WgpuHeadlessRenderer::new()?;
3119        let mut scene = Scene::default();
3120        scene.insert_primitive(solid_quad(0.0, 0.0, 32.0, 32.0, gpui::red()));
3121        scene.insert_primitive(solid_quad(32.0, 0.0, 32.0, 32.0, gpui::blue()));
3122        scene.finish();
3123
3124        let image = renderer.render_scene_to_image(&scene, device_size(64, 32))?;
3125        assert_eq!((image.width(), image.height()), (64, 32));
3126        assert_pixel(&image, 8, 16, RED);
3127        assert_pixel(&image, 24, 16, RED);
3128        assert_pixel(&image, 40, 16, BLUE);
3129        assert_pixel(&image, 56, 16, BLUE);
3130        Ok(())
3131    }
3132
3133    #[cfg(target_os = "linux")]
3134    #[test]
3135    fn headless_renderer_captures_each_requested_size() -> anyhow::Result<()> {
3136        let mut renderer = WgpuHeadlessRenderer::new()?;
3137        let mut scene = Scene::default();
3138        scene.insert_primitive(solid_quad(2.0, 2.0, 4.0, 3.0, gpui::red()));
3139        scene.finish();
3140
3141        // 13 px rows are 52 bytes, forcing readback to strip copy-row padding.
3142        let image = renderer.render_scene_to_image(&scene, device_size(13, 7))?;
3143        assert_eq!((image.width(), image.height()), (13, 7));
3144        assert_pixel(&image, 0, 0, BLACK);
3145        assert_pixel(&image, 3, 3, RED);
3146        assert_pixel(&image, 12, 6, BLACK);
3147
3148        let image = renderer.render_scene_to_image(&scene, device_size(17, 9))?;
3149        assert_eq!((image.width(), image.height()), (17, 9));
3150        assert_pixel(&image, 3, 3, RED);
3151        assert_pixel(&image, 16, 8, BLACK);
3152
3153        let image = renderer.render_scene_to_image(&Scene::default(), device_size(13, 7))?;
3154        assert_eq!((image.width(), image.height()), (13, 7));
3155        assert!(image.data().chunks_exact(4).all(|pixel| pixel == BLACK));
3156        Ok(())
3157    }
3158
3159    #[cfg(target_os = "linux")]
3160    #[test]
3161    fn headless_renderer_reuses_target_for_same_size() -> anyhow::Result<()> {
3162        let mut renderer = WgpuHeadlessRenderer::new()?;
3163        let target_texture = |renderer: &WgpuHeadlessRenderer| {
3164            renderer
3165                .render_target
3166                .as_ref()
3167                .map(|target| target.texture.clone())
3168        };
3169
3170        renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3171        let first = target_texture(&renderer);
3172        assert!(first.is_some());
3173
3174        renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3175        assert_eq!(target_texture(&renderer), first);
3176
3177        renderer.render_scene(&Scene::default(), device_size(16, 17))?;
3178        assert_ne!(target_texture(&renderer), first);
3179        Ok(())
3180    }
3181
3182    #[cfg(target_os = "linux")]
3183    #[test]
3184    fn headless_renderer_rejects_invalid_sizes() -> anyhow::Result<()> {
3185        let mut renderer = WgpuHeadlessRenderer::new()?;
3186        let too_large = renderer.core.max_texture_size as i32 + 1;
3187
3188        for size in [
3189            device_size(0, 8),
3190            device_size(8, 0),
3191            device_size(-1, 8),
3192            device_size(too_large, 8),
3193        ] {
3194            assert!(
3195                renderer.render_scene(&Scene::default(), size).is_err(),
3196                "{size:?} should be rejected"
3197            );
3198            assert!(
3199                renderer
3200                    .render_scene_to_image(&Scene::default(), size)
3201                    .is_err(),
3202                "{size:?} should be rejected"
3203            );
3204        }
3205
3206        // Rejection must leave the renderer usable.
3207        let image = renderer.render_scene_to_image(&Scene::default(), device_size(4, 4))?;
3208        assert_eq!((image.width(), image.height()), (4, 4));
3209        Ok(())
3210    }
3211
3212    #[test]
3213    fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
3214        assert!(!WEBGL_SHADERS.contains("var<storage"));
3215        validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
3216    }
3217
3218    #[test]
3219    fn storage_buffer_shader_is_valid_wgsl() {
3220        validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
3221    }
3222
3223    #[test]
3224    fn subpixel_shader_is_valid_wgsl() {
3225        validate_wgsl(
3226            SUBPIXEL_SHADERS,
3227            naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
3228        );
3229    }
3230
3231    fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
3232        let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
3233        naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
3234            .validate(&module)
3235            .expect("shader should validate");
3236    }
3237
3238    #[test]
3239    fn webgl_record_sizes_match_shader_word_strides() {
3240        assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
3241        assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
3242        assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
3243        assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
3244        assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
3245        assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
3246        assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
3247        assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
3248    }
3249
3250    #[test]
3251    fn webgl_quad_layout_matches_fixed_decoder() {
3252        let quad = Quad {
3253            order: 41,
3254            border_style: BorderStyle::Dashed,
3255            bounds: Bounds {
3256                origin: Point {
3257                    x: 1.0.into(),
3258                    y: 2.0.into(),
3259                },
3260                size: Size {
3261                    width: 3.0.into(),
3262                    height: 4.0.into(),
3263                },
3264            },
3265            content_mask: ContentMask {
3266                bounds: Bounds {
3267                    origin: Point {
3268                        x: 5.0.into(),
3269                        y: 6.0.into(),
3270                    },
3271                    size: Size {
3272                        width: 7.0.into(),
3273                        height: 8.0.into(),
3274                    },
3275                },
3276            },
3277            background: linear_gradient(
3278                11.0,
3279                linear_color_stop(
3280                    Hsla {
3281                        h: 12.0,
3282                        s: 13.0,
3283                        l: 14.0,
3284                        a: 15.0,
3285                    },
3286                    16.0,
3287                ),
3288                linear_color_stop(
3289                    Hsla {
3290                        h: 17.0,
3291                        s: 18.0,
3292                        l: 19.0,
3293                        a: 20.0,
3294                    },
3295                    21.0,
3296                ),
3297            )
3298            .color_space(ColorSpace::Oklab),
3299            border_color: Hsla {
3300                h: 22.0,
3301                s: 23.0,
3302                l: 24.0,
3303                a: 25.0,
3304            },
3305            corner_radii: Corners {
3306                top_left: 26.0.into(),
3307                top_right: 27.0.into(),
3308                bottom_right: 28.0.into(),
3309                bottom_left: 29.0.into(),
3310            },
3311            border_widths: Edges {
3312                top: 30.0.into(),
3313                right: 31.0.into(),
3314                bottom: 32.0.into(),
3315                left: 33.0.into(),
3316            },
3317        };
3318
3319        let bytes = unsafe { WgpuRendererCore::instance_bytes(std::slice::from_ref(&quad)) };
3320        let words: &[u32] = bytemuck::cast_slice(bytes);
3321        assert_eq!(
3322            words,
3323            &[
3324                41,
3325                1,
3326                1.0_f32.to_bits(),
3327                2.0_f32.to_bits(),
3328                3.0_f32.to_bits(),
3329                4.0_f32.to_bits(),
3330                5.0_f32.to_bits(),
3331                6.0_f32.to_bits(),
3332                7.0_f32.to_bits(),
3333                8.0_f32.to_bits(),
3334                1,
3335                1,
3336                0,
3337                0,
3338                0,
3339                0,
3340                11.0_f32.to_bits(),
3341                12.0_f32.to_bits(),
3342                13.0_f32.to_bits(),
3343                14.0_f32.to_bits(),
3344                15.0_f32.to_bits(),
3345                16.0_f32.to_bits(),
3346                17.0_f32.to_bits(),
3347                18.0_f32.to_bits(),
3348                19.0_f32.to_bits(),
3349                20.0_f32.to_bits(),
3350                21.0_f32.to_bits(),
3351                0,
3352                22.0_f32.to_bits(),
3353                23.0_f32.to_bits(),
3354                24.0_f32.to_bits(),
3355                25.0_f32.to_bits(),
3356                26.0_f32.to_bits(),
3357                27.0_f32.to_bits(),
3358                28.0_f32.to_bits(),
3359                29.0_f32.to_bits(),
3360                30.0_f32.to_bits(),
3361                31.0_f32.to_bits(),
3362                32.0_f32.to_bits(),
3363                33.0_f32.to_bits(),
3364            ]
3365        );
3366    }
3367}
3368
3369impl SceneRenderer for WgpuRenderer {
3370    fn draw(&mut self, scene: &Scene) -> bool {
3371        WgpuRenderer::draw(self, scene)
3372    }
3373
3374    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
3375        self.sprite_atlas().clone()
3376    }
3377}