Skip to main content

gpui_wgpu/
wgpu_renderer.rs

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