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                        draw.content.image_color_mode() != gpui::ImageColorMode::LinearP3Wide,
1444                        "wgpu supports explicit sRGB preview, not wide image presentation"
1445                    );
1446                    let request = gpui::PreparationRequest::new(
1447                        draw.bounds.size.map(|v| gpui::px(v.0)),
1448                        extent,
1449                        1.,
1450                    )?;
1451                    let output =
1452                        draw.content
1453                            .prepare_in_frame(context, &request, &preparation_frame)?;
1454                    let native = output.is::<Background>();
1455                    let texture = if native {
1456                        let background = *output.downcast::<Background>().unwrap();
1457                        let texture = new_texture(&device, extent, self.target_format)?;
1458                        let bounds = Bounds::new(
1459                            gpui::point(ScaledPixels(0.), ScaledPixels(0.)),
1460                            gpui::size(
1461                                ScaledPixels(extent.width.0 as f32),
1462                                ScaledPixels(extent.height.0 as f32),
1463                            ),
1464                        );
1465                        let mut native_scene = Scene::default();
1466                        native_scene.insert_primitive(gpui::Quad {
1467                            bounds,
1468                            content_mask: gpui::ContentMask { bounds },
1469                            background,
1470                            ..Default::default()
1471                        });
1472                        native_scene.finish();
1473                        self.render_frame(
1474                            &native_scene,
1475                            &texture.create_view(&Default::default()),
1476                            extent,
1477                            false,
1478                            wgpu::Color::TRANSPARENT,
1479                        )?;
1480                        texture
1481                    } else {
1482                        output
1483                            .downcast::<WgpuCustomOutput>()
1484                            .map_err(|_| anyhow::anyhow!("producer returned a non-wgpu output"))?
1485                            .validate(context, extent)?
1486                            .texture()
1487                            .clone()
1488                    };
1489                    anyhow::ensure!(
1490                        draw.content.image_color_mode() == gpui::ImageColorMode::EncodedSrgb
1491                            || texture.format() == wgpu::TextureFormat::Rgba16Float,
1492                        "linear P3 image metadata requires RGBA16F texture"
1493                    );
1494                    let mask = if draw.coverage.is_some() {
1495                        let key = CoverageKey::new(draw)?;
1496                        if let Some(mask) = compositor.masks.get(&key) {
1497                            Some(mask.clone())
1498                        } else {
1499                            let mask = new_texture(&device, extent, self.target_format)?;
1500                            let mask_scene = coverage_scene(draw, extent)?;
1501                            self.render_frame(
1502                                &mask_scene,
1503                                &mask.create_view(&Default::default()),
1504                                extent,
1505                                false,
1506                                wgpu::Color::TRANSPARENT,
1507                            )?;
1508                            let bytes = extent.width.0 as usize * extent.height.0 as usize * 4
1509                                + key.byte_len();
1510                            if compositor.mask_bytes + bytes > 64 * 1024 * 1024 {
1511                                compositor.masks.clear();
1512                                compositor.mask_bytes = 0;
1513                            }
1514                            if bytes <= 64 * 1024 * 1024 {
1515                                compositor.mask_bytes += bytes;
1516                                compositor.masks.insert(&key, mask.clone());
1517                            }
1518                            Some(mask)
1519                        }
1520                    } else {
1521                        None
1522                    };
1523                    Ok(
1524                        compositor.prepare(
1525                            &device,
1526                            draw,
1527                            &texture,
1528                            mask.as_ref(),
1529                            native,
1530                            viewport,
1531                        ),
1532                    )
1533                })?;
1534                prepared.push(prepared_draw);
1535            }
1536            batch.submit()?;
1537            // No uploads from failed preparations; all offsets are distinct.
1538            compositor.upload_uniforms(&queue);
1539            Ok(prepared)
1540        })();
1541        self.backdrops = effects;
1542        self.custom_compositor = Some(compositor);
1543        result
1544    }
1545
1546    fn render_frame(
1547        &mut self,
1548        scene: &Scene,
1549        target_view: &wgpu::TextureView,
1550        size: Size<DevicePixels>,
1551        premultiplied_alpha: bool,
1552        clear_color: wgpu::Color,
1553    ) -> Result<wgpu::SubmissionIndex> {
1554        if scene.backdrops.is_empty() && scene.layer_boundaries.is_empty() {
1555            if let Some(effects) = &mut self.backdrops {
1556                effects.clear();
1557            }
1558        }
1559
1560        anyhow::ensure!(
1561            size.width.0 > 0 && size.height.0 > 0,
1562            "invalid render target size: {size:?}"
1563        );
1564        anyhow::ensure!(
1565            size.width.0 as u32 <= self.max_texture_size
1566                && size.height.0 as u32 <= self.max_texture_size,
1567            "render target size {size:?} exceeds maximum texture dimension {}",
1568            self.max_texture_size
1569        );
1570
1571        anyhow::ensure!(
1572            scene
1573                .shader_draws
1574                .iter()
1575                .all(|d| d.program.output_encoding() == gpui::ShaderOutputEncoding::EncodedSrgb),
1576            "linear P3 shaders require a native wide-SDR compositor"
1577        );
1578        let custom_draws = self.prepare_custom_draws(scene, size)?;
1579        if !scene.shader_draws.is_empty() && self.shaders.premultiplied != premultiplied_alpha {
1580            self.shaders.pipelines.clear();
1581            self.shaders.premultiplied = premultiplied_alpha;
1582        }
1583
1584        if let Some(program) = scene
1585            .backdrops
1586            .first()
1587            .map(|d| &d.program)
1588            .or_else(|| scene.layer_boundaries.first().map(|b| &b.capture.program))
1589        {
1590            anyhow::ensure!(
1591                matches!(
1592                    self.target_format,
1593                    wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm
1594                ),
1595                "backdrops require an SDR unorm target"
1596            );
1597            anyhow::ensure!(
1598                target_view
1599                    .texture()
1600                    .usage()
1601                    .contains(wgpu::TextureUsages::COPY_SRC),
1602                "target does not support backdrop copies"
1603            );
1604            if self
1605                .backdrops
1606                .as_ref()
1607                .is_none_or(|b| b.format != self.target_format)
1608            {
1609                self.backdrops = Some(crate::backdrop::Backdrops::new(
1610                    &self.resources.device,
1611                    self.target_format,
1612                    program,
1613                ));
1614            }
1615            self.backdrops.as_mut().unwrap().prepare(
1616                &self.resources.device,
1617                &self.resources.queue,
1618                scene,
1619                size,
1620                &[
1621                    clear_color.r.to_bits(),
1622                    clear_color.g.to_bits(),
1623                    clear_color.b.to_bits(),
1624                    clear_color.a.to_bits(),
1625                    premultiplied_alpha as u64,
1626                    self.is_bgr as u64,
1627                    self.rendering_params.gamma_ratios[0].to_bits() as u64,
1628                    self.rendering_params.gamma_ratios[1].to_bits() as u64,
1629                    self.rendering_params.gamma_ratios[2].to_bits() as u64,
1630                    self.rendering_params.gamma_ratios[3].to_bits() as u64,
1631                    self.rendering_params.grayscale_enhanced_contrast.to_bits() as u64,
1632                    self.rendering_params.subpixel_enhanced_contrast.to_bits() as u64,
1633                ],
1634            )?;
1635        }
1636
1637        self.atlas.before_frame();
1638        self.ensure_intermediate_textures(size);
1639
1640        let gamma_params = GammaParams {
1641            gamma_ratios: self.rendering_params.gamma_ratios,
1642            grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1643            subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1644            is_bgr: self.is_bgr as u32,
1645            _pad: 0,
1646        };
1647        let globals = GlobalParams {
1648            viewport_size: [size.width.0 as f32, size.height.0 as f32],
1649            premultiplied_alpha: premultiplied_alpha as u32,
1650            pad: 0,
1651        };
1652        let path_globals = GlobalParams {
1653            premultiplied_alpha: 0,
1654            ..globals
1655        };
1656        self.resources.queue.write_buffer(
1657            &self.resources.globals_buffer,
1658            0,
1659            bytemuck::bytes_of(&globals),
1660        );
1661        self.resources.queue.write_buffer(
1662            &self.resources.globals_buffer,
1663            self.path_globals_offset,
1664            bytemuck::bytes_of(&path_globals),
1665        );
1666        self.resources.queue.write_buffer(
1667            &self.resources.globals_buffer,
1668            self.gamma_offset,
1669            bytemuck::bytes_of(&gamma_params),
1670        );
1671
1672        self.record_frame(scene, target_view, clear_color, &custom_draws)
1673            .inspect_err(|_| {
1674                // Queue writes are staged before encoding; flush them even if the frame fails.
1675                self.resources.queue.submit(std::iter::empty());
1676            })
1677    }
1678
1679    fn record_frame(
1680        &mut self,
1681        scene: &Scene,
1682        frame_view: &wgpu::TextureView,
1683        clear_color: wgpu::Color,
1684        custom_draws: &[crate::custom_draw::PreparedDraw],
1685    ) -> Result<wgpu::SubmissionIndex> {
1686        let mut executed_backdrops = Vec::new();
1687        let mut executed_groups = Vec::new();
1688        let mut skip_group_end = None;
1689        let mut instance_offset = 0;
1690        let instance_bindings = self
1691            .write_instances(scene, &mut instance_offset)
1692            .with_context(|| {
1693                format!(
1694                    "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1695                    scene.paths.len(),
1696                    scene.shadows.len(),
1697                    scene.quads.len(),
1698                    scene.underlines.len(),
1699                    scene.monochrome_sprites.len(),
1700                    scene.subpixel_sprites.len(),
1701                    scene.polychrome_sprites.len(),
1702                )
1703            })?;
1704        self.prepare_texture_bind_groups(scene);
1705        self.shaders.prepare(
1706            &self.resources.device,
1707            &self.resources.queue,
1708            &self.resources.bind_group_layouts.globals,
1709            &self.resources.bind_group_layouts.texture,
1710            self.target_format,
1711            scene,
1712        )?;
1713
1714        let mut encoder =
1715            self.resources()
1716                .device
1717                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1718                    label: Some("main_encoder"),
1719                });
1720
1721        {
1722            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1723                label: Some("main_pass"),
1724                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1725                    view: frame_view,
1726                    resolve_target: None,
1727                    ops: wgpu::Operations {
1728                        load: wgpu::LoadOp::Clear(clear_color),
1729                        store: wgpu::StoreOp::Store,
1730                    },
1731                    depth_slice: None,
1732                })],
1733                depth_stencil_attachment: None,
1734                ..Default::default()
1735            });
1736
1737            for batch in scene.batches() {
1738                if let Some(end) = skip_group_end {
1739                    if !matches!(&batch,PrimitiveBatch::LayerBoundary(index) if *index==end) {
1740                        continue;
1741                    }
1742                    skip_group_end = None;
1743                }
1744                match batch {
1745                    PrimitiveBatch::LayerBoundary(index) => {
1746                        drop(pass);
1747                        let effects = self.backdrops.as_mut().unwrap();
1748                        let (slot, begin) = effects.groups.boundary(index);
1749                        if begin {
1750                            effects.groups.start(slot);
1751                        }
1752                        let hit = effects.groups.hit(slot);
1753                        if begin {
1754                            executed_groups.push(slot);
1755                        }
1756                        if begin || !hit {
1757                            effects
1758                                .groups
1759                                .copy(&mut encoder, &frame_view.texture(), slot, begin);
1760                        }
1761                        if !begin {
1762                            effects.group_filter(&mut encoder, slot);
1763                        }
1764                        pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1765                            label: Some("captured group boundary"),
1766                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1767                                view: frame_view,
1768                                resolve_target: None,
1769                                ops: wgpu::Operations {
1770                                    load: wgpu::LoadOp::Load,
1771                                    store: wgpu::StoreOp::Store,
1772                                },
1773                                depth_slice: None,
1774                            })],
1775                            ..Default::default()
1776                        });
1777                        if begin && hit {
1778                            skip_group_end = Some(effects.groups.end(slot));
1779                        } else {
1780                            effects.group_draw(&mut pass, slot, begin);
1781                        }
1782                    }
1783
1784                    PrimitiveBatch::Shaders { program_id, range } => {
1785                        if scene.shader_draws[range.start].path_mask.is_some() {
1786                            let paths: Vec<_> = scene.shader_draws[range.clone()]
1787                                .iter()
1788                                .map(|draw| draw.path_mask.as_ref().unwrap().as_ref().clone())
1789                                .collect();
1790                            drop(pass);
1791                            let rasterized = self.draw_paths_to_intermediate(
1792                                &mut encoder,
1793                                &paths,
1794                                &mut instance_offset,
1795                            )?;
1796                            pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1797                                label: Some("main after path brush coverage"),
1798                                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1799                                    view: frame_view,
1800                                    resolve_target: None,
1801                                    ops: wgpu::Operations {
1802                                        load: wgpu::LoadOp::Load,
1803                                        store: wgpu::StoreOp::Store,
1804                                    },
1805                                    depth_slice: None,
1806                                })],
1807                                ..Default::default()
1808                            });
1809                            if !rasterized {
1810                                continue;
1811                            }
1812                            let texture = self.create_texture_bind_group(
1813                                "path brush coverage",
1814                                self.resources.path_intermediate_view.as_ref().unwrap(),
1815                            );
1816                            pass.set_bind_group(1, &texture, &[]);
1817                        }
1818                        if let Some(tile) = scene.shader_draws[range.start].glyph_mask {
1819                            let Some(texture) = self
1820                                .resources
1821                                .atlas_texture_bind_groups
1822                                .get(&tile.texture_id)
1823                            else {
1824                                continue;
1825                            };
1826                            pass.set_bind_group(1, &texture.bind_group, &[]);
1827                        }
1828                        pass.set_pipeline(&self.shaders.pipelines[&program_id]);
1829                        pass.set_bind_group(0, &self.resources.globals_bind_group, &[]);
1830                        // Bind the slice at zero instance index: WebGL2 has no base-instance draw.
1831                        let stride = std::mem::size_of::<[[f32; 4]; 11]>() as u64;
1832                        pass.set_vertex_buffer(
1833                            0,
1834                            self.shaders
1835                                .buffer
1836                                .as_ref()
1837                                .unwrap()
1838                                .slice(range.start as u64 * stride..range.end as u64 * stride),
1839                        );
1840                        pass.draw(0..6, 0..range.len() as u32);
1841                    }
1842                    PrimitiveBatch::Backdrop(index) => {
1843                        drop(pass);
1844                        let backdrops = self.backdrops.as_mut().unwrap();
1845                        executed_backdrops.push(index);
1846                        backdrops.filter(&mut encoder, &frame_view.texture(), index);
1847                        pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1848                            label: Some("main after backdrop"),
1849                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1850                                view: frame_view,
1851                                resolve_target: None,
1852                                ops: wgpu::Operations {
1853                                    load: wgpu::LoadOp::Load,
1854                                    store: wgpu::StoreOp::Store,
1855                                },
1856                                depth_slice: None,
1857                            })],
1858                            ..Default::default()
1859                        });
1860                        backdrops.composite(&mut pass, index);
1861                    }
1862                    PrimitiveBatch::Quads(range) => self.draw_instances(
1863                        &instance_bindings.quads,
1864                        &self.resources().pipelines.quads,
1865                        instance_range(range),
1866                        &mut pass,
1867                    ),
1868                    PrimitiveBatch::Shadows(range) => self.draw_instances(
1869                        &instance_bindings.shadows,
1870                        &self.resources().pipelines.shadows,
1871                        instance_range(range),
1872                        &mut pass,
1873                    ),
1874                    PrimitiveBatch::Paths(range) => {
1875                        let paths = &scene.paths[range];
1876                        if paths.is_empty() {
1877                            continue;
1878                        }
1879
1880                        drop(pass);
1881                        let rasterized = self.draw_paths_to_intermediate(
1882                            &mut encoder,
1883                            paths,
1884                            &mut instance_offset,
1885                        )?;
1886
1887                        pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1888                            label: Some("main_pass_continued"),
1889                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1890                                view: frame_view,
1891                                resolve_target: None,
1892                                ops: wgpu::Operations {
1893                                    load: wgpu::LoadOp::Load,
1894                                    store: wgpu::StoreOp::Store,
1895                                },
1896                                depth_slice: None,
1897                            })],
1898                            depth_stencil_attachment: None,
1899                            ..Default::default()
1900                        });
1901
1902                        if rasterized {
1903                            self.draw_paths_from_intermediate(
1904                                paths,
1905                                &mut instance_offset,
1906                                &mut pass,
1907                            )?;
1908                        }
1909                    }
1910                    PrimitiveBatch::Underlines(range) => self.draw_instances(
1911                        &instance_bindings.underlines,
1912                        &self.resources().pipelines.underlines,
1913                        instance_range(range),
1914                        &mut pass,
1915                    ),
1916                    PrimitiveBatch::MonochromeSprites { texture_id, range } => {
1917                        self.draw_sprites(
1918                            &instance_bindings.monochrome_sprites,
1919                            texture_id,
1920                            &self.resources().pipelines.mono_sprites,
1921                            instance_range(range),
1922                            &mut pass,
1923                        )?;
1924                    }
1925                    PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1926                        let resources = self.resources();
1927                        self.draw_sprites(
1928                            &instance_bindings.subpixel_sprites,
1929                            texture_id,
1930                            resources
1931                                .pipelines
1932                                .subpixel_sprites
1933                                .as_ref()
1934                                .unwrap_or(&resources.pipelines.mono_sprites),
1935                            instance_range(range),
1936                            &mut pass,
1937                        )?;
1938                    }
1939                    PrimitiveBatch::PolychromeSprites { texture_id, range } => {
1940                        self.draw_sprites(
1941                            &instance_bindings.polychrome_sprites,
1942                            texture_id,
1943                            &self.resources().pipelines.poly_sprites,
1944                            instance_range(range),
1945                            &mut pass,
1946                        )?;
1947                    }
1948                    // Surfaces are macOS-only for video playback and are not
1949                    // implemented by the WGPU renderer.
1950                    PrimitiveBatch::Custom(range) => {
1951                        let compositor = self
1952                            .custom_compositor
1953                            .as_ref()
1954                            .expect("prepared custom draws");
1955                        pass.set_pipeline(&compositor.pipeline);
1956                        for draw in &custom_draws[range] {
1957                            pass.set_bind_group(0, &draw.bind_group, &[draw.uniform_offset]);
1958                            pass.draw(0..6, 0..1);
1959                        }
1960                    }
1961                    PrimitiveBatch::Surfaces(_surfaces) => {}
1962                }
1963            }
1964        }
1965
1966        let submission = self
1967            .resources()
1968            .queue
1969            .submit(std::iter::once(encoder.finish()));
1970        // Retain complete immutable source snapshots through GPU sampling, not
1971        // merely their texture handles. This also retains canvas budget leases.
1972        let content = scene
1973            .custom_draws
1974            .iter()
1975            .map(|d| d.content.clone())
1976            .collect::<Vec<_>>();
1977        if !content.is_empty() {
1978            self.resources()
1979                .queue
1980                .on_submitted_work_done(move || drop(content));
1981        }
1982        if let Some(effects) = &mut self.backdrops {
1983            effects.groups.commit(&executed_groups);
1984            effects.cache.commit(&executed_backdrops);
1985        }
1986        Ok(submission)
1987    }
1988
1989    fn write_instances(
1990        &mut self,
1991        scene: &Scene,
1992        instance_offset: &mut u64,
1993    ) -> Result<InstanceBindings> {
1994        Ok(InstanceBindings {
1995            quads: self.write_instance_binding(
1996                "quads_bind_group",
1997                instance_offset,
1998                &scene.quads,
1999            )?,
2000            shadows: self.write_instance_binding(
2001                "shadows_bind_group",
2002                instance_offset,
2003                &scene.shadows,
2004            )?,
2005            underlines: self.write_instance_binding(
2006                "underlines_bind_group",
2007                instance_offset,
2008                &scene.underlines,
2009            )?,
2010            monochrome_sprites: self.write_instance_binding(
2011                "monochrome_sprites_bind_group",
2012                instance_offset,
2013                &scene.monochrome_sprites,
2014            )?,
2015            subpixel_sprites: self.write_instance_binding(
2016                "subpixel_sprites_bind_group",
2017                instance_offset,
2018                &scene.subpixel_sprites,
2019            )?,
2020            polychrome_sprites: self.write_instance_binding(
2021                "polychrome_sprites_bind_group",
2022                instance_offset,
2023                &scene.polychrome_sprites,
2024            )?,
2025        })
2026    }
2027
2028    fn create_texture_bind_group(
2029        &self,
2030        label: &str,
2031        texture_view: &wgpu::TextureView,
2032    ) -> wgpu::BindGroup {
2033        let resources = self.resources();
2034        resources
2035            .device
2036            .create_bind_group(&wgpu::BindGroupDescriptor {
2037                label: Some(label),
2038                layout: &resources.bind_group_layouts.texture,
2039                entries: &[
2040                    wgpu::BindGroupEntry {
2041                        binding: 0,
2042                        resource: wgpu::BindingResource::TextureView(texture_view),
2043                    },
2044                    wgpu::BindGroupEntry {
2045                        binding: 1,
2046                        resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
2047                    },
2048                ],
2049            })
2050    }
2051
2052    fn prepare_texture_bind_groups(&mut self, scene: &Scene) {
2053        let mut texture_ids = SmallVec::<[AtlasTextureId; 8]>::new();
2054        for batch in scene.batches() {
2055            let texture_id = match batch {
2056                PrimitiveBatch::MonochromeSprites { texture_id, .. }
2057                | PrimitiveBatch::SubpixelSprites { texture_id, .. }
2058                | PrimitiveBatch::PolychromeSprites { texture_id, .. } => texture_id,
2059                PrimitiveBatch::Shaders { range, .. } => {
2060                    let Some(tile) = scene.shader_draws[range.start].glyph_mask else {
2061                        continue;
2062                    };
2063                    tile.texture_id
2064                }
2065                _ => continue,
2066            };
2067            if !texture_ids.contains(&texture_id) {
2068                texture_ids.push(texture_id);
2069            }
2070        }
2071
2072        self.resources_mut()
2073            .atlas_texture_bind_groups
2074            .retain(|texture_id, _| texture_ids.contains(texture_id));
2075
2076        for texture_id in texture_ids {
2077            let Some(texture_info) = self.atlas.get_texture_info(texture_id) else {
2078                self.resources_mut()
2079                    .atlas_texture_bind_groups
2080                    .remove(&texture_id);
2081                continue;
2082            };
2083            let is_current = self
2084                .resources()
2085                .atlas_texture_bind_groups
2086                .get(&texture_id)
2087                .is_some_and(|cached| cached.texture_generation == texture_info.generation);
2088            if is_current {
2089                continue;
2090            }
2091
2092            let bind_group =
2093                self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
2094            self.resources_mut().atlas_texture_bind_groups.insert(
2095                texture_id,
2096                CachedTextureBindGroup {
2097                    texture_generation: texture_info.generation,
2098                    bind_group,
2099                },
2100            );
2101        }
2102    }
2103
2104    fn draw_instances(
2105        &self,
2106        instances: &InstanceBinding,
2107        pipeline: &wgpu::RenderPipeline,
2108        range: Range<u32>,
2109        pass: &mut wgpu::RenderPass<'_>,
2110    ) {
2111        if range.is_empty() {
2112            return;
2113        }
2114        pass.set_pipeline(pipeline);
2115        pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
2116        pass.set_bind_group(1, &instances.bind_group, &[]);
2117        pass.draw(
2118            0..4,
2119            instances.first_instance + range.start..instances.first_instance + range.end,
2120        );
2121    }
2122
2123    fn draw_sprites(
2124        &self,
2125        sprite_instances: &InstanceBinding,
2126        texture_id: AtlasTextureId,
2127        pipeline: &wgpu::RenderPipeline,
2128        range: Range<u32>,
2129        pass: &mut wgpu::RenderPass<'_>,
2130    ) -> Result<()> {
2131        if range.is_empty() {
2132            return Ok(());
2133        }
2134        let resources = self.resources();
2135        // The atlas has released this texture; the batch belongs to a stale
2136        // paint that will be replaced once its view re-renders.
2137        let Some(texture) = resources.atlas_texture_bind_groups.get(&texture_id) else {
2138            return Ok(());
2139        };
2140        pass.set_pipeline(pipeline);
2141        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
2142        pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
2143        pass.set_bind_group(2, &texture.bind_group, &[]);
2144        pass.draw(
2145            0..4,
2146            sprite_instances.first_instance + range.start
2147                ..sprite_instances.first_instance + range.end,
2148        );
2149        Ok(())
2150    }
2151
2152    unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
2153        unsafe {
2154            std::slice::from_raw_parts(
2155                instances.as_ptr() as *const u8,
2156                std::mem::size_of_val(instances),
2157            )
2158        }
2159    }
2160
2161    fn draw_paths_from_intermediate(
2162        &mut self,
2163        paths: &[Path<ScaledPixels>],
2164        instance_offset: &mut u64,
2165        pass: &mut wgpu::RenderPass<'_>,
2166    ) -> Result<()> {
2167        let first_path = &paths[0];
2168        let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
2169        {
2170            paths
2171                .iter()
2172                .map(|p| PathSprite {
2173                    bounds: p.clipped_bounds(),
2174                })
2175                .collect()
2176        } else {
2177            let mut bounds = first_path.clipped_bounds();
2178            for path in paths.iter().skip(1) {
2179                bounds = bounds.union(&path.clipped_bounds());
2180            }
2181            vec![PathSprite { bounds }]
2182        };
2183
2184        let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
2185            return Ok(());
2186        };
2187        let instances =
2188            self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
2189        let texture = self.create_texture_bind_group(
2190            "path_intermediate_texture_bind_group",
2191            &path_intermediate_view,
2192        );
2193        let resources = self.resources();
2194        pass.set_pipeline(&resources.pipelines.paths);
2195        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
2196        pass.set_bind_group(1, &instances.bind_group, &[]);
2197        pass.set_bind_group(2, &texture, &[]);
2198        pass.draw(
2199            0..4,
2200            instances.first_instance..instances.first_instance + sprites.len() as u32,
2201        );
2202        Ok(())
2203    }
2204
2205    fn draw_paths_to_intermediate(
2206        &mut self,
2207        encoder: &mut wgpu::CommandEncoder,
2208        paths: &[Path<ScaledPixels>],
2209        instance_offset: &mut u64,
2210    ) -> Result<bool> {
2211        let mut vertices = Vec::new();
2212        for path in paths {
2213            let bounds = path.clipped_bounds();
2214            vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
2215                xy_position: v.xy_position,
2216                st_position: v.st_position,
2217                color: path.color,
2218                bounds,
2219            }));
2220        }
2221
2222        if vertices.is_empty() {
2223            return Ok(false);
2224        }
2225
2226        let vertex_binding = self.write_instance_binding(
2227            "path_rasterization_bind_group",
2228            instance_offset,
2229            &vertices,
2230        )?;
2231
2232        let resources = self.resources();
2233        let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
2234            return Ok(false);
2235        };
2236
2237        let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
2238            (msaa_view, Some(path_intermediate_view))
2239        } else {
2240            (path_intermediate_view, None)
2241        };
2242
2243        {
2244            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2245                label: Some("path_rasterization_pass"),
2246                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2247                    view: target_view,
2248                    resolve_target,
2249                    ops: wgpu::Operations {
2250                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
2251                        store: wgpu::StoreOp::Store,
2252                    },
2253                    depth_slice: None,
2254                })],
2255                depth_stencil_attachment: None,
2256                ..Default::default()
2257            });
2258
2259            pass.set_pipeline(&resources.pipelines.path_rasterization);
2260            pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
2261            pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
2262            // The path rasterization shader loads records by vertex index
2263            // rather than instance index, so the allocation's base shifts the
2264            // vertex range here.
2265            pass.draw(
2266                vertex_binding.first_instance
2267                    ..vertex_binding.first_instance + vertices.len() as u32,
2268                0..1,
2269            );
2270        }
2271
2272        Ok(true)
2273    }
2274
2275    fn write_instance_binding<T>(
2276        &mut self,
2277        label: &str,
2278        instance_offset: &mut u64,
2279        instances: &[T],
2280    ) -> Result<InstanceBinding> {
2281        let data = unsafe { Self::instance_bytes(instances) };
2282        // wgpu rejects zero-sized bindings, so empty primitive arrays still
2283        // reserve the 16-byte minimum.
2284        let size = (data.len() as u64).max(16);
2285        let stride = (std::mem::size_of::<T>() as u64).max(1);
2286        let (alignment, allocation_size) = if self.uses_webgl_instance_data {
2287            // The texture transport has no binding offset: the shader indexes
2288            // the instance texture absolutely, so each allocation must start on
2289            // a whole instance (a stride multiple) and a whole texel, and must
2290            // end on a texel boundary so the zero padding of its final partial
2291            // texel cannot overlap the next allocation.
2292            (
2293                least_common_multiple(self.instance_data_alignment, stride),
2294                size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
2295            )
2296        } else {
2297            (self.instance_data_alignment.max(1), size)
2298        };
2299        let mut offset = (*instance_offset).next_multiple_of(alignment);
2300        if offset + allocation_size > self.instance_data_capacity {
2301            self.grow_instance_data(allocation_size)?;
2302            offset = 0;
2303        }
2304        *instance_offset = offset + allocation_size;
2305
2306        let first_instance = if self.uses_webgl_instance_data {
2307            u32::try_from(offset / stride).context("instance index exceeds u32 range")?
2308        } else {
2309            0
2310        };
2311
2312        let resources = self.resources();
2313        if !data.is_empty() {
2314            match &resources.instance_data {
2315                InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
2316                InstanceData::Texture { .. } => {
2317                    Self::write_instance_texture(resources, offset, data)
2318                }
2319            }
2320        }
2321        let bind_group = resources
2322            .device
2323            .create_bind_group(&wgpu::BindGroupDescriptor {
2324                label: Some(label),
2325                layout: &resources.bind_group_layouts.instances,
2326                entries: &[wgpu::BindGroupEntry {
2327                    binding: 0,
2328                    resource: match &resources.instance_data {
2329                        InstanceData::Storage(buffer) => {
2330                            wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2331                                buffer,
2332                                offset,
2333                                size: NonZeroU64::new(size),
2334                            })
2335                        }
2336                        InstanceData::Texture { view, .. } => {
2337                            wgpu::BindingResource::TextureView(view)
2338                        }
2339                    },
2340                }],
2341            });
2342        Ok(InstanceBinding {
2343            bind_group,
2344            first_instance,
2345        })
2346    }
2347
2348    fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
2349        let InstanceData::Texture {
2350            texture,
2351            width,
2352            height,
2353            ..
2354        } = &resources.instance_data
2355        else {
2356            return;
2357        };
2358        let mut byte_offset = 0usize;
2359        let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
2360        while byte_offset < data.len() {
2361            let x = (texel_offset % u64::from(*width)) as u32;
2362            let y = (texel_offset / u64::from(*width)) as u32;
2363            if y >= *height {
2364                // The capacity check in write_instance_binding should make this
2365                // unreachable. Truncating silently would leave stale bytes in the
2366                // texture and draw garbage for the remaining instances.
2367                debug_assert!(
2368                    false,
2369                    "instance texture write out of bounds: row {y} >= height {}",
2370                    *height
2371                );
2372                log::error!(
2373                    "instance texture write out of bounds; dropping {} bytes of instance data",
2374                    data.len() - byte_offset
2375                );
2376                return;
2377            }
2378            let available_texels = u64::from(*width - x);
2379            let remaining_bytes = data.len() - byte_offset;
2380            let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
2381            let texels = complete_texels.min(available_texels);
2382            if texels > 0 {
2383                let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
2384                resources.queue.write_texture(
2385                    wgpu::TexelCopyTextureInfo {
2386                        texture,
2387                        mip_level: 0,
2388                        origin: wgpu::Origin3d { x, y, z: 0 },
2389                        aspect: wgpu::TextureAspect::All,
2390                    },
2391                    &data[byte_offset..byte_offset + byte_count],
2392                    wgpu::TexelCopyBufferLayout {
2393                        offset: 0,
2394                        bytes_per_row: Some(byte_count as u32),
2395                        rows_per_image: None,
2396                    },
2397                    wgpu::Extent3d {
2398                        width: texels as u32,
2399                        height: 1,
2400                        depth_or_array_layers: 1,
2401                    },
2402                );
2403                byte_offset += byte_count;
2404                texel_offset += texels;
2405                continue;
2406            }
2407
2408            let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
2409            final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
2410            resources.queue.write_texture(
2411                wgpu::TexelCopyTextureInfo {
2412                    texture,
2413                    mip_level: 0,
2414                    origin: wgpu::Origin3d { x, y, z: 0 },
2415                    aspect: wgpu::TextureAspect::All,
2416                },
2417                &final_texel,
2418                wgpu::TexelCopyBufferLayout {
2419                    offset: 0,
2420                    bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
2421                    rows_per_image: None,
2422                },
2423                wgpu::Extent3d {
2424                    width: 1,
2425                    height: 1,
2426                    depth_or_array_layers: 1,
2427                },
2428            );
2429            break;
2430        }
2431    }
2432
2433    fn grow_instance_data(&mut self, required: u64) -> Result<()> {
2434        let capacity = (self.instance_data_capacity * 2)
2435            .max(required.next_power_of_two())
2436            .min(self.max_instance_data_size);
2437        anyhow::ensure!(
2438            capacity >= required,
2439            "instance data needs {required} bytes, above the maximum of {}",
2440            self.max_instance_data_size
2441        );
2442        anyhow::ensure!(
2443            capacity > self.instance_data_capacity,
2444            "frame instance data exceeds the {}-byte maximum",
2445            self.max_instance_data_size
2446        );
2447        log::debug!(
2448            "instance data grown from {} to {capacity}",
2449            self.instance_data_capacity
2450        );
2451        // Bind groups created earlier in the frame keep the previous buffer or
2452        // texture alive, so allocations written before the grow remain valid;
2453        // only subsequent writes land in the new allocation.
2454        let uses_webgl_instance_data = self.uses_webgl_instance_data;
2455        let resources = self.resources_mut();
2456        if uses_webgl_instance_data {
2457            let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
2458            let (instance_data, actual_capacity) =
2459                Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
2460            resources.instance_data = instance_data;
2461            self.instance_data_capacity = actual_capacity;
2462        } else {
2463            resources.instance_data =
2464                InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
2465                    label: Some("instance_buffer"),
2466                    size: capacity,
2467                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2468                    mapped_at_creation: false,
2469                }));
2470            self.instance_data_capacity = capacity;
2471        }
2472        Ok(())
2473    }
2474}
2475
2476impl WgpuRenderer {
2477    /// Mark the surface as unconfigured so rendering is skipped until a new
2478    /// surface is provided via [`replace_surface`](Self::replace_surface).
2479    ///
2480    /// This does **not** drop the renderer — the device, queue, atlas, and
2481    /// pipelines stay alive.  Use this when the native window is destroyed
2482    /// (e.g. Android `TerminateWindow`) but you intend to re-create the
2483    /// surface later without losing cached atlas textures.
2484    pub fn unconfigure_surface(&mut self) {
2485        self.state = match std::mem::replace(&mut self.state, RendererState::Released) {
2486            RendererState::Ready { core, surface } => {
2487                drop(surface);
2488                RendererState::Unconfigured { core }
2489            }
2490            state @ (RendererState::Unconfigured { .. } | RendererState::Released) => state,
2491        };
2492        // Drop intermediate textures since they reference the old surface size.
2493        if let Some(core) = self.core_mut() {
2494            core.resources.invalidate_intermediate_textures();
2495        }
2496    }
2497
2498    /// Replace the wgpu surface with a new one (e.g. after Android destroys
2499    /// and recreates the native window).  Keeps the device, queue, atlas, and
2500    /// all pipelines intact so cached `AtlasTextureId`s remain valid.
2501    ///
2502    /// The `instance` **must** be the same [`wgpu::Instance`] that was used to
2503    /// create the adapter and device (i.e. from the [`WgpuContext`]).  Using a
2504    /// different instance will cause a "Device does not exist" panic because
2505    /// the wgpu device is bound to its originating instance.
2506    #[cfg(not(target_family = "wasm"))]
2507    pub fn replace_surface<W: HasWindowHandle>(
2508        &mut self,
2509        window: &W,
2510        config: WgpuSurfaceConfig,
2511        instance: &wgpu::Instance,
2512    ) -> anyhow::Result<()> {
2513        let window_handle = window
2514            .window_handle()
2515            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2516
2517        let surface = create_surface(instance, window_handle.as_raw())?;
2518
2519        let width = (config.size.width.0 as u32).max(1);
2520        let height = (config.size.height.0 as u32).max(1);
2521
2522        let alpha_mode = if config.transparent {
2523            self.transparent_alpha_mode
2524        } else {
2525            self.opaque_alpha_mode
2526        };
2527
2528        self.surface_config.width = width;
2529        self.surface_config.height = height;
2530        self.surface_config.alpha_mode = alpha_mode;
2531        if let Some(mode) = config.preferred_present_mode {
2532            self.surface_config.present_mode = mode;
2533        }
2534
2535        let mut core = match std::mem::replace(&mut self.state, RendererState::Released) {
2536            RendererState::Ready {
2537                core,
2538                surface: old_surface,
2539            } => {
2540                drop(old_surface);
2541                core
2542            }
2543            RendererState::Unconfigured { core } => core,
2544            RendererState::Released => {
2545                anyhow::bail!("Cannot replace the surface: GPU resources have been released")
2546            }
2547        };
2548        surface.configure(&core.resources.device, &self.surface_config);
2549        core.resources.invalidate_intermediate_textures();
2550        self.state = RendererState::Ready { surface, core };
2551
2552        Ok(())
2553    }
2554
2555    pub fn destroy(&mut self) {
2556        // Release surface-bound GPU resources eagerly so the underlying native
2557        // window can be destroyed before the renderer itself is dropped.
2558        self.state = RendererState::Released;
2559    }
2560
2561    /// Returns true if the GPU device was lost and recovery is needed.
2562    pub fn device_lost(&self) -> bool {
2563        self.device_errors.device_lost()
2564    }
2565
2566    /// Returns true if a redraw is needed because GPU state was cleared.
2567    /// Calling this method clears the flag.
2568    pub fn needs_redraw(&mut self) -> bool {
2569        std::mem::take(&mut self.needs_redraw)
2570    }
2571
2572    /// Recovers from a lost GPU device by recreating the renderer with a new context.
2573    ///
2574    /// Call this after detecting `device_lost()` returns true.
2575    ///
2576    /// This method coordinates recovery across multiple windows:
2577    /// - The first window to call this will recreate the shared context
2578    /// - Subsequent windows will adopt the already-recovered context
2579    #[cfg(not(target_family = "wasm"))]
2580    pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2581    where
2582        W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2583    {
2584        let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2585
2586        // Check if another window already recovered the context
2587        let needs_new_context = gpu_context
2588            .borrow()
2589            .as_ref()
2590            .is_none_or(|ctx| ctx.device_lost());
2591
2592        let window_handle = window
2593            .window_handle()
2594            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2595
2596        let surface = if needs_new_context {
2597            log::warn!("GPU device lost, recreating context...");
2598
2599            // Drop old resources to release Arc<Device>/Arc<Queue> and GPU resources
2600            self.state = RendererState::Released;
2601            *gpu_context.borrow_mut() = None;
2602
2603            // Wait briefly for the GPU driver to stabilize, then try to
2604            // recreate the context without software renderers. If this fails
2605            // the caller should request another frame and retry — the real GPU
2606            // may need more time to come back (e.g. after suspend/resume).
2607            std::thread::sleep(std::time::Duration::from_millis(350));
2608
2609            let instance = WgpuContext::instance(Some(Box::new(window.clone())));
2610            let surface = create_surface(&instance, window_handle.as_raw())?;
2611            let new_context =
2612                WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?;
2613            *gpu_context.borrow_mut() = Some(new_context);
2614            surface
2615        } else {
2616            let ctx_ref = gpu_context.borrow();
2617            let instance = &ctx_ref.as_ref().unwrap().instance;
2618            create_surface(instance, window_handle.as_raw())?
2619        };
2620
2621        let config = WgpuSurfaceConfig {
2622            size: gpui::Size {
2623                width: gpui::DevicePixels(self.surface_config.width as i32),
2624                height: gpui::DevicePixels(self.surface_config.height as i32),
2625            },
2626            transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2627            preferred_present_mode: Some(self.surface_config.present_mode),
2628        };
2629        let gpu_context = Rc::clone(gpu_context);
2630        let ctx_ref = gpu_context.borrow();
2631        let context = ctx_ref.as_ref().expect("context should exist");
2632
2633        self.state = RendererState::Released;
2634        self.atlas.handle_device_lost(context);
2635
2636        let is_bgr = self.is_bgr;
2637        *self = Self::new_internal(
2638            Some(gpu_context.clone()),
2639            context,
2640            surface,
2641            config,
2642            self.compositor_gpu,
2643            self.atlas.clone(),
2644        )?;
2645        self.set_subpixel_layout(is_bgr);
2646
2647        log::info!("GPU recovery complete");
2648        Ok(())
2649    }
2650}
2651
2652#[cfg(all(
2653    not(target_family = "wasm"),
2654    any(test, feature = "bench-support", feature = "test-support")
2655))]
2656struct HeadlessRenderTarget {
2657    texture: wgpu::Texture,
2658    view: wgpu::TextureView,
2659}
2660
2661#[cfg(all(
2662    not(target_family = "wasm"),
2663    any(test, feature = "bench-support", feature = "test-support")
2664))]
2665impl HeadlessRenderTarget {
2666    fn size(&self) -> Size<DevicePixels> {
2667        Size {
2668            width: DevicePixels(self.texture.width() as i32),
2669            height: DevicePixels(self.texture.height() as i32),
2670        }
2671    }
2672}
2673
2674#[cfg(all(
2675    not(target_family = "wasm"),
2676    any(test, feature = "bench-support", feature = "test-support")
2677))]
2678pub struct WgpuHeadlessRenderer {
2679    context: WgpuContext,
2680    core: WgpuRendererCore,
2681    render_target: Option<HeadlessRenderTarget>,
2682    observed_error_generation: u64,
2683}
2684
2685#[cfg(all(
2686    not(target_family = "wasm"),
2687    any(test, feature = "bench-support", feature = "test-support")
2688))]
2689impl WgpuHeadlessRenderer {
2690    pub fn new() -> anyhow::Result<Self> {
2691        let (context, target_format) = WgpuContext::new_headless()?;
2692        // The device may be shared with earlier renderers; observe only errors raised from
2693        // here on, including any while this renderer builds its pipelines.
2694        let observed_error_generation = context.errors().current_generation();
2695        let atlas = Arc::new(WgpuAtlas::from_context(&context));
2696        let core = WgpuRendererCore::new(
2697            &context,
2698            atlas,
2699            target_format,
2700            wgpu::CompositeAlphaMode::Opaque,
2701        );
2702
2703        Ok(Self {
2704            context,
2705            core,
2706            render_target: None,
2707            observed_error_generation,
2708        })
2709    }
2710
2711    fn ensure_render_target(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()> {
2712        anyhow::ensure!(
2713            size.width.0 > 0 && size.height.0 > 0,
2714            "invalid headless render target size: {size:?}"
2715        );
2716        anyhow::ensure!(
2717            size.width.0 as u32 <= self.core.max_texture_size
2718                && size.height.0 as u32 <= self.core.max_texture_size,
2719            "headless render target size {size:?} exceeds maximum texture dimension {}",
2720            self.core.max_texture_size
2721        );
2722        if self
2723            .render_target
2724            .as_ref()
2725            .is_some_and(|target| target.size() == size)
2726        {
2727            return Ok(());
2728        }
2729
2730        let texture = self
2731            .core
2732            .resources
2733            .device
2734            .create_texture(&wgpu::TextureDescriptor {
2735                label: Some("headless_render_target"),
2736                size: wgpu::Extent3d {
2737                    width: size.width.0 as u32,
2738                    height: size.height.0 as u32,
2739                    depth_or_array_layers: 1,
2740                },
2741                mip_level_count: 1,
2742                sample_count: 1,
2743                dimension: wgpu::TextureDimension::D2,
2744                format: self.core.target_format,
2745                usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2746                view_formats: &[],
2747            });
2748        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2749        self.render_target = Some(HeadlessRenderTarget { texture, view });
2750        Ok(())
2751    }
2752
2753    fn check_gpu_errors(&mut self) -> anyhow::Result<()> {
2754        anyhow::ensure!(
2755            !self.context.device_lost(),
2756            "GPU device was lost during headless rendering"
2757        );
2758        if let Some(error) = self
2759            .context
2760            .errors()
2761            .observe_error(&mut self.observed_error_generation)
2762        {
2763            anyhow::bail!("GPU error during headless rendering: {error}");
2764        }
2765        Ok(())
2766    }
2767
2768    /// Bytes in retained subtree textures, excluding driver/in-flight references.
2769    pub fn layer_texture_bytes(&self) -> u64 {
2770        self.core
2771            .backdrops
2772            .as_ref()
2773            .map(|effects| effects.groups.texture_bytes())
2774            .unwrap_or(0)
2775    }
2776
2777    /// Number of retained effect textures; excludes driver and in-flight references.
2778    pub fn effect_texture_count(&self) -> usize {
2779        self.core
2780            .backdrops
2781            .as_ref()
2782            .map(|effects| effects.texture_count())
2783            .unwrap_or(0)
2784    }
2785
2786    /// Cumulative backdrop filter cache statistics.
2787    pub fn backdrop_cache_stats(&self) -> gpui::BackdropCacheStats {
2788        self.core
2789            .backdrops
2790            .as_ref()
2791            .map(|e| e.cache.stats)
2792            .unwrap_or_default()
2793    }
2794
2795    /// Cumulative captured-subtree cache statistics.
2796    pub fn layer_cache_stats(&self) -> gpui::LayerCacheStats {
2797        self.core
2798            .backdrops
2799            .as_ref()
2800            .map(|e| e.groups.stats)
2801            .unwrap_or_default()
2802    }
2803
2804    fn render(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2805        self.check_gpu_errors()?;
2806        self.ensure_render_target(size)?;
2807        let view = self
2808            .render_target
2809            .as_ref()
2810            .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?
2811            .view
2812            .clone();
2813        self.core
2814            .render_frame(scene, &view, size, false, wgpu::Color::BLACK)?;
2815        Ok(())
2816    }
2817
2818    /// Copies the current render target back to the CPU. Dimensions come from the
2819    /// target texture itself, so the copy can never disagree with what was rendered.
2820    fn read_image(&mut self) -> anyhow::Result<image::RgbaImage> {
2821        let target = self
2822            .render_target
2823            .as_ref()
2824            .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?;
2825        let width = target.texture.width();
2826        let height = target.texture.height();
2827        let bytes_per_row = width
2828            .checked_mul(4)
2829            .ok_or_else(|| anyhow::anyhow!("Headless render target row size overflowed"))?;
2830        let padded_bytes_per_row = bytes_per_row
2831            .checked_next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
2832            .ok_or_else(|| anyhow::anyhow!("Headless padded row size overflowed"))?;
2833        let buffer_size = u64::from(padded_bytes_per_row)
2834            .checked_mul(u64::from(height))
2835            .ok_or_else(|| anyhow::anyhow!("Headless readback buffer size overflowed"))?;
2836        anyhow::ensure!(
2837            buffer_size <= self.core.resources.device.limits().max_buffer_size,
2838            "Headless readback buffer size {buffer_size} exceeds maximum buffer size {}",
2839            self.core.resources.device.limits().max_buffer_size
2840        );
2841        let readback_buffer = self
2842            .core
2843            .resources
2844            .device
2845            .create_buffer(&wgpu::BufferDescriptor {
2846                label: Some("headless_readback_buffer"),
2847                size: buffer_size,
2848                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2849                mapped_at_creation: false,
2850            });
2851        let mut encoder =
2852            self.core
2853                .resources
2854                .device
2855                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2856                    label: Some("headless_readback_encoder"),
2857                });
2858        encoder.copy_texture_to_buffer(
2859            wgpu::TexelCopyTextureInfo {
2860                texture: &target.texture,
2861                mip_level: 0,
2862                origin: wgpu::Origin3d::ZERO,
2863                aspect: wgpu::TextureAspect::All,
2864            },
2865            wgpu::TexelCopyBufferInfo {
2866                buffer: &readback_buffer,
2867                layout: wgpu::TexelCopyBufferLayout {
2868                    offset: 0,
2869                    bytes_per_row: Some(padded_bytes_per_row),
2870                    rows_per_image: Some(height),
2871                },
2872            },
2873            wgpu::Extent3d {
2874                width,
2875                height,
2876                depth_or_array_layers: 1,
2877            },
2878        );
2879        let submission = self
2880            .core
2881            .resources
2882            .queue
2883            .submit(std::iter::once(encoder.finish()));
2884        let (sender, receiver) = std::sync::mpsc::channel();
2885        readback_buffer
2886            .slice(..)
2887            .map_async(wgpu::MapMode::Read, move |result| {
2888                if sender.send(result).is_err() {
2889                    log::error!("Headless readback receiver was dropped before mapping completed");
2890                }
2891            });
2892        self.core
2893            .resources
2894            .device
2895            .poll(wgpu::PollType::Wait {
2896                submission_index: Some(submission),
2897                timeout: Some(std::time::Duration::from_secs(30)),
2898            })
2899            .map_err(|error| anyhow::anyhow!("Failed to wait for headless rendering: {error}"))?;
2900        receiver
2901            .recv_timeout(std::time::Duration::from_secs(30))
2902            .map_err(|error| anyhow::anyhow!("Failed to receive headless mapping result: {error}"))?
2903            .map_err(|error| anyhow::anyhow!("Failed to map headless readback buffer: {error}"))?;
2904        self.check_gpu_errors()?;
2905
2906        let mapped_data = readback_buffer.slice(..).get_mapped_range()?;
2907        let pixel_capacity = usize::try_from(u64::from(bytes_per_row) * u64::from(height))
2908            .map_err(|_| anyhow::anyhow!("Headless image size exceeds addressable memory"))?;
2909        let mut pixels = Vec::with_capacity(pixel_capacity);
2910        for row in mapped_data
2911            .chunks_exact(padded_bytes_per_row as usize)
2912            .take(height as usize)
2913        {
2914            pixels.extend_from_slice(&row[..bytes_per_row as usize]);
2915        }
2916        drop(mapped_data);
2917        readback_buffer.unmap();
2918
2919        if self.core.target_format == wgpu::TextureFormat::Bgra8Unorm {
2920            for pixel in pixels.chunks_exact_mut(4) {
2921                pixel.swap(0, 2);
2922            }
2923        }
2924
2925        image::RgbaImage::from_raw(width, height, pixels)
2926            .ok_or_else(|| anyhow::anyhow!("Failed to create image from headless pixel data"))
2927    }
2928}
2929
2930#[cfg(all(
2931    not(target_family = "wasm"),
2932    any(test, feature = "bench-support", feature = "test-support")
2933))]
2934impl gpui::PlatformHeadlessRenderer for WgpuHeadlessRenderer {
2935    fn render_scene_to_image(
2936        &mut self,
2937        scene: &Scene,
2938        size: Size<DevicePixels>,
2939    ) -> anyhow::Result<image::RgbaImage> {
2940        self.render(scene, size)?;
2941        self.read_image()
2942    }
2943
2944    fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2945        self.render(scene, size)
2946    }
2947
2948    fn sprite_atlas(&self) -> Arc<dyn gpui::PlatformAtlas> {
2949        self.core.atlas.clone()
2950    }
2951}
2952
2953fn instance_range(range: Range<usize>) -> Range<u32> {
2954    range.start as u32..range.end as u32
2955}
2956
2957#[cfg(not(target_family = "wasm"))]
2958fn create_surface(
2959    instance: &wgpu::Instance,
2960    raw_window_handle: raw_window_handle::RawWindowHandle,
2961) -> anyhow::Result<wgpu::Surface<'static>> {
2962    unsafe {
2963        instance
2964            .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
2965                // Fall back to the display handle already provided via InstanceDescriptor::display.
2966                raw_display_handle: None,
2967                raw_window_handle,
2968            })
2969            .map_err(|e| anyhow::anyhow!("{e}"))
2970    }
2971}
2972
2973struct RenderingParameters {
2974    path_sample_count: u32,
2975    gamma_ratios: [f32; 4],
2976    grayscale_enhanced_contrast: f32,
2977    subpixel_enhanced_contrast: f32,
2978}
2979
2980impl RenderingParameters {
2981    fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
2982        use std::env;
2983
2984        let format_features = adapter.get_texture_format_features(surface_format);
2985        let path_sample_count = [4, 2, 1]
2986            .into_iter()
2987            .find(|&n| format_features.flags.sample_count_supported(n))
2988            .unwrap_or(1);
2989
2990        let gamma = env::var("ZED_FONTS_GAMMA")
2991            .ok()
2992            .and_then(|v| v.parse().ok())
2993            .unwrap_or(1.8_f32)
2994            .clamp(1.0, 2.2);
2995        let gamma_ratios = get_gamma_correction_ratios(gamma);
2996
2997        let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
2998            .ok()
2999            .and_then(|v| v.parse().ok())
3000            .unwrap_or(1.0_f32)
3001            .max(0.0);
3002
3003        let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
3004            .ok()
3005            .and_then(|v| v.parse().ok())
3006            .unwrap_or(0.5_f32)
3007            .max(0.0);
3008
3009        Self {
3010            path_sample_count,
3011            gamma_ratios,
3012            grayscale_enhanced_contrast,
3013            subpixel_enhanced_contrast,
3014        }
3015    }
3016}
3017
3018#[cfg(test)]
3019mod tests {
3020    use super::*;
3021    use gpui::{
3022        BorderStyle, ColorSpace, ContentMask, Corners, Edges, Hsla, MonochromeSprite,
3023        PolychromeSprite, Quad, Shadow, Size, SubpixelSprite, Underline, linear_color_stop,
3024        linear_gradient,
3025    };
3026    #[cfg(target_os = "linux")]
3027    use gpui::{DevicePixels, PlatformHeadlessRenderer, Scene};
3028
3029    #[cfg(target_os = "linux")]
3030    fn device_size(width: i32, height: i32) -> Size<DevicePixels> {
3031        Size {
3032            width: DevicePixels(width),
3033            height: DevicePixels(height),
3034        }
3035    }
3036
3037    #[cfg(target_os = "linux")]
3038    fn solid_quad(x: f32, y: f32, width: f32, height: f32, color: Hsla) -> Quad {
3039        let bounds = Bounds {
3040            origin: Point {
3041                x: x.into(),
3042                y: y.into(),
3043            },
3044            size: Size {
3045                width: width.into(),
3046                height: height.into(),
3047            },
3048        };
3049        Quad {
3050            order: 0,
3051            border_style: BorderStyle::Solid,
3052            bounds,
3053            content_mask: ContentMask { bounds },
3054            background: color.into(),
3055            border_color: color,
3056            corner_radii: Corners::default(),
3057            border_widths: Edges::default(),
3058        }
3059    }
3060
3061    /// Channels are compared with a small tolerance so the assertions hold across
3062    /// drivers without pinning exact rasterizer output.
3063    #[cfg(target_os = "linux")]
3064    fn assert_pixel(image: &image::RgbaImage, x: u32, y: u32, expected: [u8; 4]) {
3065        let actual = image.get_pixel(x, y).0;
3066        assert!(
3067            actual
3068                .iter()
3069                .zip(expected)
3070                .all(|(actual, expected)| actual.abs_diff(expected) <= 3),
3071            "pixel ({x}, {y}) was {actual:?}, expected {expected:?}"
3072        );
3073    }
3074
3075    #[cfg(target_os = "linux")]
3076    const RED: [u8; 4] = [255, 0, 0, 255];
3077    #[cfg(target_os = "linux")]
3078    const BLUE: [u8; 4] = [0, 0, 255, 255];
3079    #[cfg(target_os = "linux")]
3080    const BLACK: [u8; 4] = [0, 0, 0, 255];
3081
3082    #[cfg(target_os = "linux")]
3083    #[test]
3084    fn headless_renderer_draws_quads_with_distinct_colors() -> anyhow::Result<()> {
3085        let mut renderer = WgpuHeadlessRenderer::new()?;
3086        let mut scene = Scene::default();
3087        scene.insert_primitive(solid_quad(0.0, 0.0, 32.0, 32.0, gpui::red()));
3088        scene.insert_primitive(solid_quad(32.0, 0.0, 32.0, 32.0, gpui::blue()));
3089        scene.finish();
3090
3091        let image = renderer.render_scene_to_image(&scene, device_size(64, 32))?;
3092        assert_eq!(image.dimensions(), (64, 32));
3093        assert_pixel(&image, 8, 16, RED);
3094        assert_pixel(&image, 24, 16, RED);
3095        assert_pixel(&image, 40, 16, BLUE);
3096        assert_pixel(&image, 56, 16, BLUE);
3097        Ok(())
3098    }
3099
3100    #[cfg(target_os = "linux")]
3101    #[test]
3102    fn headless_renderer_captures_each_requested_size() -> anyhow::Result<()> {
3103        let mut renderer = WgpuHeadlessRenderer::new()?;
3104        let mut scene = Scene::default();
3105        scene.insert_primitive(solid_quad(2.0, 2.0, 4.0, 3.0, gpui::red()));
3106        scene.finish();
3107
3108        // 13 px rows are 52 bytes, forcing readback to strip copy-row padding.
3109        let image = renderer.render_scene_to_image(&scene, device_size(13, 7))?;
3110        assert_eq!(image.dimensions(), (13, 7));
3111        assert_pixel(&image, 0, 0, BLACK);
3112        assert_pixel(&image, 3, 3, RED);
3113        assert_pixel(&image, 12, 6, BLACK);
3114
3115        let image = renderer.render_scene_to_image(&scene, device_size(17, 9))?;
3116        assert_eq!(image.dimensions(), (17, 9));
3117        assert_pixel(&image, 3, 3, RED);
3118        assert_pixel(&image, 16, 8, BLACK);
3119
3120        let image = renderer.render_scene_to_image(&Scene::default(), device_size(13, 7))?;
3121        assert_eq!(image.dimensions(), (13, 7));
3122        assert!(image.pixels().all(|pixel| pixel.0 == BLACK));
3123        Ok(())
3124    }
3125
3126    #[cfg(target_os = "linux")]
3127    #[test]
3128    fn headless_renderer_reuses_target_for_same_size() -> anyhow::Result<()> {
3129        let mut renderer = WgpuHeadlessRenderer::new()?;
3130        let target_texture = |renderer: &WgpuHeadlessRenderer| {
3131            renderer
3132                .render_target
3133                .as_ref()
3134                .map(|target| target.texture.clone())
3135        };
3136
3137        renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3138        let first = target_texture(&renderer);
3139        assert!(first.is_some());
3140
3141        renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3142        assert_eq!(target_texture(&renderer), first);
3143
3144        renderer.render_scene(&Scene::default(), device_size(16, 17))?;
3145        assert_ne!(target_texture(&renderer), first);
3146        Ok(())
3147    }
3148
3149    #[cfg(target_os = "linux")]
3150    #[test]
3151    fn headless_renderer_rejects_invalid_sizes() -> anyhow::Result<()> {
3152        let mut renderer = WgpuHeadlessRenderer::new()?;
3153        let too_large = renderer.core.max_texture_size as i32 + 1;
3154
3155        for size in [
3156            device_size(0, 8),
3157            device_size(8, 0),
3158            device_size(-1, 8),
3159            device_size(too_large, 8),
3160        ] {
3161            assert!(
3162                renderer.render_scene(&Scene::default(), size).is_err(),
3163                "{size:?} should be rejected"
3164            );
3165            assert!(
3166                renderer
3167                    .render_scene_to_image(&Scene::default(), size)
3168                    .is_err(),
3169                "{size:?} should be rejected"
3170            );
3171        }
3172
3173        // Rejection must leave the renderer usable.
3174        let image = renderer.render_scene_to_image(&Scene::default(), device_size(4, 4))?;
3175        assert_eq!(image.dimensions(), (4, 4));
3176        Ok(())
3177    }
3178
3179    #[test]
3180    fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
3181        assert!(!WEBGL_SHADERS.contains("var<storage"));
3182        validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
3183    }
3184
3185    #[test]
3186    fn storage_buffer_shader_is_valid_wgsl() {
3187        validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
3188    }
3189
3190    #[test]
3191    fn subpixel_shader_is_valid_wgsl() {
3192        validate_wgsl(
3193            SUBPIXEL_SHADERS,
3194            naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
3195        );
3196    }
3197
3198    fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
3199        let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
3200        naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
3201            .validate(&module)
3202            .expect("shader should validate");
3203    }
3204
3205    #[test]
3206    fn webgl_record_sizes_match_shader_word_strides() {
3207        assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
3208        assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
3209        assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
3210        assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
3211        assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
3212        assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
3213        assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
3214        assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
3215    }
3216
3217    #[test]
3218    fn webgl_quad_layout_matches_fixed_decoder() {
3219        let quad = Quad {
3220            order: 41,
3221            border_style: BorderStyle::Dashed,
3222            bounds: Bounds {
3223                origin: Point {
3224                    x: 1.0.into(),
3225                    y: 2.0.into(),
3226                },
3227                size: Size {
3228                    width: 3.0.into(),
3229                    height: 4.0.into(),
3230                },
3231            },
3232            content_mask: ContentMask {
3233                bounds: Bounds {
3234                    origin: Point {
3235                        x: 5.0.into(),
3236                        y: 6.0.into(),
3237                    },
3238                    size: Size {
3239                        width: 7.0.into(),
3240                        height: 8.0.into(),
3241                    },
3242                },
3243            },
3244            background: linear_gradient(
3245                11.0,
3246                linear_color_stop(
3247                    Hsla {
3248                        h: 12.0,
3249                        s: 13.0,
3250                        l: 14.0,
3251                        a: 15.0,
3252                    },
3253                    16.0,
3254                ),
3255                linear_color_stop(
3256                    Hsla {
3257                        h: 17.0,
3258                        s: 18.0,
3259                        l: 19.0,
3260                        a: 20.0,
3261                    },
3262                    21.0,
3263                ),
3264            )
3265            .color_space(ColorSpace::Oklab),
3266            border_color: Hsla {
3267                h: 22.0,
3268                s: 23.0,
3269                l: 24.0,
3270                a: 25.0,
3271            },
3272            corner_radii: Corners {
3273                top_left: 26.0.into(),
3274                top_right: 27.0.into(),
3275                bottom_right: 28.0.into(),
3276                bottom_left: 29.0.into(),
3277            },
3278            border_widths: Edges {
3279                top: 30.0.into(),
3280                right: 31.0.into(),
3281                bottom: 32.0.into(),
3282                left: 33.0.into(),
3283            },
3284        };
3285
3286        let bytes = unsafe { WgpuRendererCore::instance_bytes(std::slice::from_ref(&quad)) };
3287        let words: &[u32] = bytemuck::cast_slice(bytes);
3288        assert_eq!(
3289            words,
3290            &[
3291                41,
3292                1,
3293                1.0_f32.to_bits(),
3294                2.0_f32.to_bits(),
3295                3.0_f32.to_bits(),
3296                4.0_f32.to_bits(),
3297                5.0_f32.to_bits(),
3298                6.0_f32.to_bits(),
3299                7.0_f32.to_bits(),
3300                8.0_f32.to_bits(),
3301                1,
3302                1,
3303                0,
3304                0,
3305                0,
3306                0,
3307                11.0_f32.to_bits(),
3308                12.0_f32.to_bits(),
3309                13.0_f32.to_bits(),
3310                14.0_f32.to_bits(),
3311                15.0_f32.to_bits(),
3312                16.0_f32.to_bits(),
3313                17.0_f32.to_bits(),
3314                18.0_f32.to_bits(),
3315                19.0_f32.to_bits(),
3316                20.0_f32.to_bits(),
3317                21.0_f32.to_bits(),
3318                0,
3319                22.0_f32.to_bits(),
3320                23.0_f32.to_bits(),
3321                24.0_f32.to_bits(),
3322                25.0_f32.to_bits(),
3323                26.0_f32.to_bits(),
3324                27.0_f32.to_bits(),
3325                28.0_f32.to_bits(),
3326                29.0_f32.to_bits(),
3327                30.0_f32.to_bits(),
3328                31.0_f32.to_bits(),
3329                32.0_f32.to_bits(),
3330                33.0_f32.to_bits(),
3331            ]
3332        );
3333    }
3334}
3335
3336#[derive(Default)]
3337struct ShaderRenderer {
3338    pipelines: FxHashMap<u64, wgpu::RenderPipeline>,
3339    buffer: Option<wgpu::Buffer>,
3340    capacity: u64,
3341    premultiplied: bool,
3342}
3343impl ShaderRenderer {
3344    fn prepare(
3345        &mut self,
3346        device: &wgpu::Device,
3347        queue: &wgpu::Queue,
3348        globals: &wgpu::BindGroupLayout,
3349        atlas: &wgpu::BindGroupLayout,
3350        format: wgpu::TextureFormat,
3351        scene: &Scene,
3352    ) -> Result<()> {
3353        // Native-only coverage subpasses must not evict the main scene's pipelines.
3354        if scene.shader_draws.is_empty() {
3355            return Ok(());
3356        }
3357        self.pipelines
3358            .retain(|id, _| scene.shader_draws.iter().any(|d| d.program.id() == *id));
3359        let bytes = scene
3360            .shader_draws
3361            .len()
3362            .checked_mul(176)
3363            .context("shader instance size overflow")? as u64;
3364        anyhow::ensure!(
3365            bytes <= device.limits().max_buffer_size.min(64 * 1024 * 1024),
3366            "shader instances exceed 64 MiB/device buffer limit"
3367        );
3368        if bytes > self.capacity {
3369            self.capacity = bytes
3370                .next_power_of_two()
3371                .min(device.limits().max_buffer_size)
3372                .min(64 * 1024 * 1024);
3373            self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
3374                label: Some("fragment shader instances"),
3375                size: self.capacity,
3376                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
3377                mapped_at_creation: false,
3378            }));
3379        }
3380        let records: Vec<_> = scene
3381            .shader_draws
3382            .iter()
3383            .map(|d| d.instance_data())
3384            .collect();
3385        queue.write_buffer(
3386            self.buffer.as_ref().unwrap(),
3387            0,
3388            bytemuck::cast_slice(&records),
3389        );
3390        let attributes: Vec<_> = (0..11)
3391            .map(|index| wgpu::VertexAttribute {
3392                format: wgpu::VertexFormat::Float32x4,
3393                offset: index * 16,
3394                shader_location: index as u32,
3395            })
3396            .collect();
3397        for draw in &scene.shader_draws {
3398            draw.validate_masks()?;
3399            self.pipelines.entry(draw.program.id()).or_insert_with(|| {
3400                let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
3401                    label: Some("instanced fragment shader"),
3402                    source: wgpu::ShaderSource::Wgsl(draw.program.wgsl().into()),
3403                });
3404                let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3405                    label: Some("instanced fragment shader"),
3406                    bind_group_layouts: &if draw.program.uses_glyph_mask()
3407                        || draw.program.uses_path_mask()
3408                    {
3409                        vec![Some(globals), Some(atlas)]
3410                    } else {
3411                        vec![Some(globals)]
3412                    },
3413                    immediate_size: 0,
3414                });
3415                device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3416                    label: Some("instanced fragment shader"),
3417                    layout: Some(&layout),
3418                    vertex: wgpu::VertexState {
3419                        module: &module,
3420                        entry_point: Some("shader_vertex"),
3421                        buffers: &[Some(wgpu::VertexBufferLayout {
3422                            array_stride: 176,
3423                            step_mode: wgpu::VertexStepMode::Instance,
3424                            attributes: &attributes,
3425                        })],
3426                        compilation_options: Default::default(),
3427                    },
3428                    fragment: Some(wgpu::FragmentState {
3429                        module: &module,
3430                        entry_point: Some("shader_fragment"),
3431                        targets: &[Some(wgpu::ColorTargetState {
3432                            format,
3433                            blend: Some(if self.premultiplied {
3434                                wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
3435                            } else {
3436                                wgpu::BlendState::ALPHA_BLENDING
3437                            }),
3438                            write_mask: wgpu::ColorWrites::ALL,
3439                        })],
3440                        compilation_options: Default::default(),
3441                    }),
3442                    primitive: wgpu::PrimitiveState::default(),
3443                    depth_stencil: None,
3444                    multisample: Default::default(),
3445                    multiview_mask: None,
3446                    cache: None,
3447                })
3448            });
3449        }
3450        Ok(())
3451    }
3452}