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