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