Skip to main content

gpui_wgpu/
wgpu_renderer.rs

1use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
2use anyhow::{Context as _, Result};
3use bytemuck::{Pod, Zeroable};
4use gpui::{
5    AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch,
6    ScaledPixels, Scene, Size, get_gamma_correction_ratios,
7};
8use log::warn;
9#[cfg(not(target_family = "wasm"))]
10use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
11use std::cell::RefCell;
12use std::num::NonZeroU64;
13use std::ops::Range;
14use std::rc::Rc;
15use std::sync::{Arc, Mutex};
16
17const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
18
19const INSTANCE_TEXTURE_TEXEL_SIZE: u64 = 16;
20
21/// Shader variant for backends with storage buffer support: the shared shader
22/// logic plus the storage-buffer instance transport.
23const STORAGE_BUFFER_SHADERS: &str = concat!(
24    include_str!("shaders.wgsl"),
25    include_str!("shaders_storage.wgsl"),
26);
27
28/// Shader variant for WebGL2, which has no storage buffers: the shared shader
29/// logic plus the texture-based instance transport.
30const WEBGL_SHADERS: &str = concat!(
31    include_str!("shaders.wgsl"),
32    include_str!("shaders_webgl.wgsl"),
33);
34
35/// Subpixel text rendering requires dual-source blending, which WebGL2 lacks, so
36/// this variant only ever runs with the storage-buffer transport. The `enable`
37/// directive must precede all declarations.
38const SUBPIXEL_SHADERS: &str = concat!(
39    "enable dual_source_blending;\n",
40    include_str!("shaders.wgsl"),
41    include_str!("shaders_storage.wgsl"),
42    include_str!("shaders_subpixel.wgsl"),
43);
44
45fn least_common_multiple(left: u64, right: u64) -> u64 {
46    let mut first = left;
47    let mut second = right;
48    while second != 0 {
49        let remainder = first % second;
50        first = second;
51        second = remainder;
52    }
53    left / first * right
54}
55
56#[repr(C)]
57#[derive(Clone, Copy, Pod, Zeroable)]
58struct GlobalParams {
59    viewport_size: [f32; 2],
60    premultiplied_alpha: u32,
61    pad: u32,
62}
63
64#[repr(C)]
65#[derive(Clone, Copy, Pod, Zeroable)]
66struct PodBounds {
67    origin: [f32; 2],
68    size: [f32; 2],
69}
70
71impl From<Bounds<ScaledPixels>> for PodBounds {
72    fn from(bounds: Bounds<ScaledPixels>) -> Self {
73        Self {
74            origin: [bounds.origin.x.0, bounds.origin.y.0],
75            size: [bounds.size.width.0, bounds.size.height.0],
76        }
77    }
78}
79
80#[repr(C)]
81#[derive(Clone, Copy, Pod, Zeroable)]
82struct SurfaceParams {
83    bounds: PodBounds,
84    content_mask: PodBounds,
85}
86
87#[repr(C)]
88#[derive(Clone, Copy, Pod, Zeroable)]
89struct GammaParams {
90    gamma_ratios: [f32; 4],
91    grayscale_enhanced_contrast: f32,
92    subpixel_enhanced_contrast: f32,
93    is_bgr: u32,
94    _pad: u32,
95}
96
97#[derive(Clone, Debug)]
98#[repr(C)]
99struct PathSprite {
100    bounds: Bounds<ScaledPixels>,
101}
102
103#[derive(Clone, Debug)]
104#[repr(C)]
105struct PathRasterizationVertex {
106    xy_position: Point<ScaledPixels>,
107    st_position: Point<f32>,
108    color: Background,
109    bounds: Bounds<ScaledPixels>,
110}
111
112pub struct WgpuSurfaceConfig {
113    pub size: Size<DevicePixels>,
114    pub transparent: bool,
115    /// Preferred presentation mode. When `Some`, the renderer will use this
116    /// mode if supported by the surface, falling back to `Fifo`.
117    /// When `None`, defaults to `Fifo` (VSync).
118    ///
119    /// Mobile platforms may prefer `Mailbox` (triple-buffering) to avoid
120    /// blocking in `get_current_texture()` during lifecycle transitions.
121    pub preferred_present_mode: Option<wgpu::PresentMode>,
122}
123
124struct WgpuPipelines {
125    quads: wgpu::RenderPipeline,
126    shadows: wgpu::RenderPipeline,
127    path_rasterization: wgpu::RenderPipeline,
128    paths: wgpu::RenderPipeline,
129    underlines: wgpu::RenderPipeline,
130    mono_sprites: wgpu::RenderPipeline,
131    subpixel_sprites: Option<wgpu::RenderPipeline>,
132    poly_sprites: wgpu::RenderPipeline,
133    #[allow(dead_code)]
134    surfaces: wgpu::RenderPipeline,
135}
136
137/// One frame allocation of instance data, ready to bind.
138struct InstanceBinding {
139    bind_group: wgpu::BindGroup,
140    /// Index of the allocation's first instance within the bound data. Always
141    /// zero on the storage-buffer path, where the binding offset already
142    /// positions the array; on the WebGL texture path the shader indexes the
143    /// shared instance texture absolutely, so draws must offset their
144    /// instance (or vertex) ranges by this value.
145    first_instance: u32,
146}
147
148struct InstanceBindings {
149    quads: InstanceBinding,
150    shadows: InstanceBinding,
151    underlines: InstanceBinding,
152    monochrome_sprites: InstanceBinding,
153    subpixel_sprites: InstanceBinding,
154    polychrome_sprites: InstanceBinding,
155}
156
157struct WgpuBindGroupLayouts {
158    globals: wgpu::BindGroupLayout,
159    instances: wgpu::BindGroupLayout,
160    texture: wgpu::BindGroupLayout,
161    surfaces: wgpu::BindGroupLayout,
162}
163
164/// Shared GPU context reference, used to coordinate device recovery across multiple windows.
165pub type GpuContext = Rc<RefCell<Option<WgpuContext>>>;
166
167enum InstanceData {
168    Storage(wgpu::Buffer),
169    // WebGL2 has no storage buffers. A uint texture keeps the records available to both shader
170    // stages while preserving integer and floating-point bit patterns exactly.
171    Texture {
172        texture: wgpu::Texture,
173        view: wgpu::TextureView,
174        width: u32,
175        height: u32,
176    },
177}
178
179/// GPU resources that must be dropped together during device recovery.
180struct WgpuResources {
181    device: Arc<wgpu::Device>,
182    queue: Arc<wgpu::Queue>,
183    surface: wgpu::Surface<'static>,
184    pipelines: WgpuPipelines,
185    bind_group_layouts: WgpuBindGroupLayouts,
186    atlas_sampler: wgpu::Sampler,
187    globals_buffer: wgpu::Buffer,
188    globals_bind_group: wgpu::BindGroup,
189    path_globals_bind_group: wgpu::BindGroup,
190    instance_data: InstanceData,
191    path_intermediate_texture: Option<wgpu::Texture>,
192    path_intermediate_view: Option<wgpu::TextureView>,
193    path_msaa_texture: Option<wgpu::Texture>,
194    path_msaa_view: Option<wgpu::TextureView>,
195}
196
197impl WgpuResources {
198    fn invalidate_intermediate_textures(&mut self) {
199        self.path_intermediate_texture = None;
200        self.path_intermediate_view = None;
201        self.path_msaa_texture = None;
202        self.path_msaa_view = None;
203    }
204}
205
206pub struct WgpuRenderer {
207    /// Shared GPU context for device recovery coordination (unused on WASM).
208    #[allow(dead_code)]
209    context: Option<GpuContext>,
210    /// Compositor GPU hint for adapter selection (unused on WASM).
211    #[allow(dead_code)]
212    compositor_gpu: Option<CompositorGpuHint>,
213    resources: Option<WgpuResources>,
214    surface_config: wgpu::SurfaceConfiguration,
215    atlas: Arc<WgpuAtlas>,
216    path_globals_offset: u64,
217    gamma_offset: u64,
218    instance_data_capacity: u64,
219    max_instance_data_size: u64,
220    instance_data_alignment: u64,
221    uses_webgl_instance_data: bool,
222    rendering_params: RenderingParameters,
223    is_bgr: bool,
224    dual_source_blending: bool,
225    adapter_info: wgpu::AdapterInfo,
226    transparent_alpha_mode: wgpu::CompositeAlphaMode,
227    opaque_alpha_mode: wgpu::CompositeAlphaMode,
228    max_texture_size: u32,
229    last_error: Arc<Mutex<Option<String>>>,
230    failed_frame_count: u32,
231    device_lost: std::sync::Arc<std::sync::atomic::AtomicBool>,
232    surface_configured: bool,
233    needs_redraw: bool,
234}
235
236impl WgpuRenderer {
237    fn resources(&self) -> &WgpuResources {
238        self.resources
239            .as_ref()
240            .expect("GPU resources not available")
241    }
242
243    fn resources_mut(&mut self) -> &mut WgpuResources {
244        self.resources
245            .as_mut()
246            .expect("GPU resources not available")
247    }
248
249    /// Creates a new WgpuRenderer from raw window handles.
250    ///
251    /// The `gpu_context` is a shared reference that coordinates GPU context across
252    /// multiple windows. The first window to create a renderer will initialize the
253    /// context; subsequent windows will share it.
254    ///
255    /// # Safety
256    /// The caller must ensure that the window handle remains valid for the lifetime
257    /// of the returned renderer.
258    #[cfg(not(target_family = "wasm"))]
259    pub fn new<W>(
260        gpu_context: GpuContext,
261        window: &W,
262        config: WgpuSurfaceConfig,
263        compositor_gpu: Option<CompositorGpuHint>,
264    ) -> anyhow::Result<Self>
265    where
266        W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
267    {
268        let window_handle = window
269            .window_handle()
270            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
271
272        let target = wgpu::SurfaceTargetUnsafe::RawHandle {
273            // Fall back to the display handle already provided via InstanceDescriptor::display.
274            raw_display_handle: None,
275            raw_window_handle: window_handle.as_raw(),
276        };
277
278        // Use the existing context's instance if available, otherwise create a new one.
279        // The surface must be created with the same instance that will be used for
280        // adapter selection, otherwise wgpu will panic.
281        let instance = gpu_context
282            .borrow()
283            .as_ref()
284            .map(|ctx| ctx.instance.clone())
285            .unwrap_or_else(|| WgpuContext::instance(Box::new(window.clone())));
286
287        // Safety: The caller guarantees that the window handle is valid for the
288        // lifetime of this renderer. In practice, the RawWindow struct is created
289        // from the native window handles and the surface is dropped before the window.
290        let surface = unsafe {
291            instance
292                .create_surface_unsafe(target)
293                .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
294        };
295
296        let mut ctx_ref = gpu_context.borrow_mut();
297        let context = match ctx_ref.as_mut() {
298            Some(context) => {
299                context.check_compatible_with_surface(&surface)?;
300                context
301            }
302            None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?),
303        };
304
305        let atlas = Arc::new(WgpuAtlas::from_context(context));
306
307        Self::new_internal(
308            Some(Rc::clone(&gpu_context)),
309            context,
310            surface,
311            config,
312            compositor_gpu,
313            atlas,
314        )
315    }
316
317    #[cfg(target_family = "wasm")]
318    pub fn new_from_canvas(
319        context: &WgpuContext,
320        canvas: &web_sys::HtmlCanvasElement,
321        config: WgpuSurfaceConfig,
322    ) -> anyhow::Result<Self> {
323        let surface = context
324            .instance
325            .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
326            .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
327        Self::new_from_surface(context, surface, config)
328    }
329
330    #[cfg(target_family = "wasm")]
331    #[allow(clippy::arc_with_non_send_sync)]
332    pub fn new_from_surface(
333        context: &WgpuContext,
334        surface: wgpu::Surface<'static>,
335        config: WgpuSurfaceConfig,
336    ) -> anyhow::Result<Self> {
337        let atlas = Arc::new(WgpuAtlas::from_context(context));
338        Self::new_internal(None, context, surface, config, None, atlas)
339    }
340
341    fn new_internal(
342        gpu_context: Option<GpuContext>,
343        context: &WgpuContext,
344        surface: wgpu::Surface<'static>,
345        config: WgpuSurfaceConfig,
346        compositor_gpu: Option<CompositorGpuHint>,
347        atlas: Arc<WgpuAtlas>,
348    ) -> anyhow::Result<Self> {
349        let surface_caps = surface.get_capabilities(&context.adapter);
350        let preferred_formats = [
351            wgpu::TextureFormat::Bgra8Unorm,
352            wgpu::TextureFormat::Rgba8Unorm,
353        ];
354        let surface_format = preferred_formats
355            .iter()
356            .find(|f| surface_caps.formats.contains(f))
357            .copied()
358            .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
359            .or_else(|| surface_caps.formats.first().copied())
360            .ok_or_else(|| {
361                anyhow::anyhow!(
362                    "Surface reports no supported texture formats for adapter {:?}",
363                    context.adapter.get_info().name
364                )
365            })?;
366
367        let pick_alpha_mode =
368            |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
369                preferences
370                    .iter()
371                    .find(|p| surface_caps.alpha_modes.contains(p))
372                    .copied()
373                    .or_else(|| surface_caps.alpha_modes.first().copied())
374                    .ok_or_else(|| {
375                        anyhow::anyhow!(
376                            "Surface reports no supported alpha modes for adapter {:?}",
377                            context.adapter.get_info().name
378                        )
379                    })
380            };
381
382        let transparent_alpha_mode = pick_alpha_mode(&[
383            wgpu::CompositeAlphaMode::PreMultiplied,
384            wgpu::CompositeAlphaMode::Inherit,
385        ])?;
386
387        let opaque_alpha_mode = pick_alpha_mode(&[
388            wgpu::CompositeAlphaMode::Opaque,
389            wgpu::CompositeAlphaMode::Inherit,
390        ])?;
391
392        let alpha_mode = if config.transparent {
393            transparent_alpha_mode
394        } else {
395            opaque_alpha_mode
396        };
397
398        let device = Arc::clone(&context.device);
399        let max_texture_size = device.limits().max_texture_dimension_2d;
400
401        let requested_width = config.size.width.0 as u32;
402        let requested_height = config.size.height.0 as u32;
403        let clamped_width = requested_width.min(max_texture_size);
404        let clamped_height = requested_height.min(max_texture_size);
405
406        if clamped_width != requested_width || clamped_height != requested_height {
407            warn!(
408                "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
409                 Clamping to ({}, {}). Window content may not fill the entire window.",
410                requested_width, requested_height, max_texture_size, clamped_width, clamped_height
411            );
412        }
413
414        let surface_config = wgpu::SurfaceConfiguration {
415            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
416            format: surface_format,
417            width: clamped_width.max(1),
418            height: clamped_height.max(1),
419            present_mode: config
420                .preferred_present_mode
421                .filter(|mode| surface_caps.present_modes.contains(mode))
422                .unwrap_or(wgpu::PresentMode::Fifo),
423            desired_maximum_frame_latency: 2,
424            alpha_mode,
425            view_formats: vec![],
426        };
427        // Configure the surface immediately. The adapter selection process already validated
428        // that this adapter can successfully configure this surface.
429        surface.configure(&context.device, &surface_config);
430
431        let queue = Arc::clone(&context.queue);
432        let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
433        let uses_webgl_instance_data = context.uses_webgl_instance_data();
434        let dual_source_blending =
435            context.supports_dual_source_blending() && !uses_webgl_instance_data;
436        let bind_group_layouts = Self::create_bind_group_layouts(&device, uses_webgl_instance_data);
437        let pipelines = Self::create_pipelines(
438            &device,
439            &bind_group_layouts,
440            surface_format,
441            alpha_mode,
442            rendering_params.path_sample_count,
443            dual_source_blending,
444            uses_webgl_instance_data,
445        );
446
447        let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
448            label: Some("atlas_sampler"),
449            mag_filter: wgpu::FilterMode::Linear,
450            min_filter: wgpu::FilterMode::Linear,
451            ..Default::default()
452        });
453
454        let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
455        let globals_size = std::mem::size_of::<GlobalParams>() as u64;
456        let gamma_size = std::mem::size_of::<GammaParams>() as u64;
457        let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
458        let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
459
460        let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
461            label: Some("globals_buffer"),
462            size: gamma_offset + gamma_size,
463            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
464            mapped_at_creation: false,
465        });
466
467        let (
468            instance_data,
469            instance_data_capacity,
470            max_instance_data_size,
471            instance_data_alignment,
472        ) = if uses_webgl_instance_data {
473            let max_texture_dimension = device.limits().max_texture_dimension_2d;
474            let max_instance_data_size = (u64::from(max_texture_dimension).pow(2)
475                * INSTANCE_TEXTURE_TEXEL_SIZE)
476                .min(MAX_INSTANCE_BUFFER_SIZE);
477            let initial_capacity = (2 * 1024 * 1024).min(max_instance_data_size);
478            let (instance_data, capacity) =
479                Self::create_instance_texture(&device, initial_capacity, max_texture_dimension);
480            (
481                instance_data,
482                capacity,
483                max_instance_data_size,
484                INSTANCE_TEXTURE_TEXEL_SIZE,
485            )
486        } else {
487            // Every frame allocation is exposed as one storage-buffer binding, so
488            // its backing buffer must satisfy both the allocation and binding limits.
489            let max_buffer_size = device
490                .limits()
491                .max_buffer_size
492                .min(device.limits().max_storage_buffer_binding_size)
493                .min(MAX_INSTANCE_BUFFER_SIZE);
494            let initial_capacity = (2 * 1024 * 1024).min(max_buffer_size);
495            let buffer = device.create_buffer(&wgpu::BufferDescriptor {
496                label: Some("instance_buffer"),
497                size: initial_capacity,
498                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
499                mapped_at_creation: false,
500            });
501            (
502                InstanceData::Storage(buffer),
503                initial_capacity,
504                max_buffer_size,
505                device.limits().min_storage_buffer_offset_alignment as u64,
506            )
507        };
508
509        let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
510            label: Some("globals_bind_group"),
511            layout: &bind_group_layouts.globals,
512            entries: &[
513                wgpu::BindGroupEntry {
514                    binding: 0,
515                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
516                        buffer: &globals_buffer,
517                        offset: 0,
518                        size: Some(NonZeroU64::new(globals_size).unwrap()),
519                    }),
520                },
521                wgpu::BindGroupEntry {
522                    binding: 1,
523                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
524                        buffer: &globals_buffer,
525                        offset: gamma_offset,
526                        size: Some(NonZeroU64::new(gamma_size).unwrap()),
527                    }),
528                },
529            ],
530        });
531
532        let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
533            label: Some("path_globals_bind_group"),
534            layout: &bind_group_layouts.globals,
535            entries: &[
536                wgpu::BindGroupEntry {
537                    binding: 0,
538                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
539                        buffer: &globals_buffer,
540                        offset: path_globals_offset,
541                        size: Some(NonZeroU64::new(globals_size).unwrap()),
542                    }),
543                },
544                wgpu::BindGroupEntry {
545                    binding: 1,
546                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
547                        buffer: &globals_buffer,
548                        offset: gamma_offset,
549                        size: Some(NonZeroU64::new(gamma_size).unwrap()),
550                    }),
551                },
552            ],
553        });
554
555        let adapter_info = context.adapter.get_info();
556
557        let last_error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
558        let last_error_clone = Arc::clone(&last_error);
559        device.on_uncaptured_error(Arc::new(move |error| {
560            let mut guard = last_error_clone.lock().unwrap();
561            *guard = Some(error.to_string());
562        }));
563
564        let resources = WgpuResources {
565            device,
566            queue,
567            surface,
568            pipelines,
569            bind_group_layouts,
570            atlas_sampler,
571            globals_buffer,
572            globals_bind_group,
573            path_globals_bind_group,
574            instance_data,
575            // Defer intermediate texture creation to first draw call via ensure_intermediate_textures().
576            // This avoids panics when the device/surface is in an invalid state during initialization.
577            path_intermediate_texture: None,
578            path_intermediate_view: None,
579            path_msaa_texture: None,
580            path_msaa_view: None,
581        };
582
583        Ok(Self {
584            context: gpu_context,
585            compositor_gpu,
586            resources: Some(resources),
587            surface_config,
588            atlas,
589            path_globals_offset,
590            gamma_offset,
591            instance_data_capacity,
592            max_instance_data_size,
593            instance_data_alignment,
594            uses_webgl_instance_data,
595            rendering_params,
596            is_bgr: false,
597            dual_source_blending,
598            adapter_info,
599            transparent_alpha_mode,
600            opaque_alpha_mode,
601            max_texture_size,
602            last_error,
603            failed_frame_count: 0,
604            device_lost: context.device_lost_flag(),
605            surface_configured: true,
606            needs_redraw: false,
607        })
608    }
609
610    fn create_bind_group_layouts(
611        device: &wgpu::Device,
612        uses_webgl_instance_data: bool,
613    ) -> WgpuBindGroupLayouts {
614        let globals =
615            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
616                label: Some("globals_layout"),
617                entries: &[
618                    wgpu::BindGroupLayoutEntry {
619                        binding: 0,
620                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
621                        ty: wgpu::BindingType::Buffer {
622                            ty: wgpu::BufferBindingType::Uniform,
623                            has_dynamic_offset: false,
624                            min_binding_size: NonZeroU64::new(
625                                std::mem::size_of::<GlobalParams>() as u64
626                            ),
627                        },
628                        count: None,
629                    },
630                    wgpu::BindGroupLayoutEntry {
631                        binding: 1,
632                        visibility: wgpu::ShaderStages::FRAGMENT,
633                        ty: wgpu::BindingType::Buffer {
634                            ty: wgpu::BufferBindingType::Uniform,
635                            has_dynamic_offset: false,
636                            min_binding_size: NonZeroU64::new(
637                                std::mem::size_of::<GammaParams>() as u64
638                            ),
639                        },
640                        count: None,
641                    },
642                ],
643            });
644
645        let instance_data_entry = wgpu::BindGroupLayoutEntry {
646            binding: 0,
647            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
648            ty: if uses_webgl_instance_data {
649                wgpu::BindingType::Texture {
650                    sample_type: wgpu::TextureSampleType::Uint,
651                    view_dimension: wgpu::TextureViewDimension::D2,
652                    multisampled: false,
653                }
654            } else {
655                wgpu::BindingType::Buffer {
656                    ty: wgpu::BufferBindingType::Storage { read_only: true },
657                    has_dynamic_offset: false,
658                    min_binding_size: None,
659                }
660            },
661            count: None,
662        };
663
664        let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
665            label: Some("instances_layout"),
666            entries: &[instance_data_entry],
667        });
668
669        let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
670            label: Some("texture_layout"),
671            entries: &[
672                wgpu::BindGroupLayoutEntry {
673                    binding: 0,
674                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
675                    ty: wgpu::BindingType::Texture {
676                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
677                        view_dimension: wgpu::TextureViewDimension::D2,
678                        multisampled: false,
679                    },
680                    count: None,
681                },
682                wgpu::BindGroupLayoutEntry {
683                    binding: 1,
684                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
685                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
686                    count: None,
687                },
688            ],
689        });
690
691        let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
692            label: Some("surfaces_layout"),
693            entries: &[
694                wgpu::BindGroupLayoutEntry {
695                    binding: 0,
696                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
697                    ty: wgpu::BindingType::Buffer {
698                        ty: wgpu::BufferBindingType::Uniform,
699                        has_dynamic_offset: false,
700                        min_binding_size: NonZeroU64::new(
701                            std::mem::size_of::<SurfaceParams>() as u64
702                        ),
703                    },
704                    count: None,
705                },
706                wgpu::BindGroupLayoutEntry {
707                    binding: 1,
708                    visibility: wgpu::ShaderStages::FRAGMENT,
709                    ty: wgpu::BindingType::Texture {
710                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
711                        view_dimension: wgpu::TextureViewDimension::D2,
712                        multisampled: false,
713                    },
714                    count: None,
715                },
716                wgpu::BindGroupLayoutEntry {
717                    binding: 2,
718                    visibility: wgpu::ShaderStages::FRAGMENT,
719                    ty: wgpu::BindingType::Texture {
720                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
721                        view_dimension: wgpu::TextureViewDimension::D2,
722                        multisampled: false,
723                    },
724                    count: None,
725                },
726                wgpu::BindGroupLayoutEntry {
727                    binding: 3,
728                    visibility: wgpu::ShaderStages::FRAGMENT,
729                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
730                    count: None,
731                },
732            ],
733        });
734
735        WgpuBindGroupLayouts {
736            globals,
737            instances,
738            texture,
739            surfaces,
740        }
741    }
742
743    fn create_instance_texture(
744        device: &wgpu::Device,
745        requested_capacity: u64,
746        max_texture_dimension: u32,
747    ) -> (InstanceData, u64) {
748        let texel_count = requested_capacity.div_ceil(INSTANCE_TEXTURE_TEXEL_SIZE);
749        let width = texel_count.min(u64::from(max_texture_dimension)).max(1) as u32;
750        let height = texel_count
751            .div_ceil(u64::from(width))
752            .min(u64::from(max_texture_dimension))
753            .max(1) as u32;
754        let texture = device.create_texture(&wgpu::TextureDescriptor {
755            label: Some("instance_texture"),
756            size: wgpu::Extent3d {
757                width,
758                height,
759                depth_or_array_layers: 1,
760            },
761            mip_level_count: 1,
762            sample_count: 1,
763            dimension: wgpu::TextureDimension::D2,
764            format: wgpu::TextureFormat::Rgba32Uint,
765            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
766            view_formats: &[],
767        });
768        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
769        let capacity = u64::from(width) * u64::from(height) * INSTANCE_TEXTURE_TEXEL_SIZE;
770        (
771            InstanceData::Texture {
772                texture,
773                view,
774                width,
775                height,
776            },
777            capacity,
778        )
779    }
780
781    fn create_pipelines(
782        device: &wgpu::Device,
783        layouts: &WgpuBindGroupLayouts,
784        surface_format: wgpu::TextureFormat,
785        alpha_mode: wgpu::CompositeAlphaMode,
786        path_sample_count: u32,
787        dual_source_blending: bool,
788        uses_webgl_instance_data: bool,
789    ) -> WgpuPipelines {
790        // Diagnostic guard: verify the device actually has
791        // DUAL_SOURCE_BLENDING. We have a crash report (ZED-5G1) where a
792        // feature mismatch caused a wgpu-hal abort, but we haven't
793        // identified the code path that produces the mismatch. This
794        // guard prevents the crash and logs more evidence.
795        // Remove this check once:
796        // a) We find and fix the root cause, or
797        // b) There are no reports of this warning appearing for some time.
798        let device_has_feature = device
799            .features()
800            .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
801        if dual_source_blending && !device_has_feature {
802            log::error!(
803                "BUG: dual_source_blending flag is true but device does not \
804                 have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \
805                 Falling back to mono text rendering. Please report this at \
806                 https://github.com/zed-industries/zed/issues",
807                device.features(),
808            );
809        }
810        let dual_source_blending =
811            dual_source_blending && device_has_feature && !uses_webgl_instance_data;
812
813        let shader_source = if uses_webgl_instance_data {
814            WEBGL_SHADERS
815        } else {
816            STORAGE_BUFFER_SHADERS
817        };
818        let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
819            label: Some("gpui_shaders"),
820            source: wgpu::ShaderSource::Wgsl(shader_source.into()),
821        });
822
823        let subpixel_shader_module = if dual_source_blending {
824            Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
825                label: Some("gpui_subpixel_shaders"),
826                source: wgpu::ShaderSource::Wgsl(SUBPIXEL_SHADERS.into()),
827            }))
828        } else {
829            None
830        };
831
832        let blend_mode = match alpha_mode {
833            wgpu::CompositeAlphaMode::PreMultiplied => {
834                wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
835            }
836            _ => wgpu::BlendState::ALPHA_BLENDING,
837        };
838
839        let color_target = wgpu::ColorTargetState {
840            format: surface_format,
841            blend: Some(blend_mode),
842            write_mask: wgpu::ColorWrites::ALL,
843        };
844
845        let create_pipeline = |name: &str,
846                               vs_entry: &str,
847                               fs_entry: &str,
848                               globals_layout: &wgpu::BindGroupLayout,
849                               data_layout: &wgpu::BindGroupLayout,
850                               texture_layout: Option<&wgpu::BindGroupLayout>,
851                               topology: wgpu::PrimitiveTopology,
852                               color_targets: &[Option<wgpu::ColorTargetState>],
853                               sample_count: u32,
854                               module: &wgpu::ShaderModule| {
855            let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
856            bind_group_layouts.extend(texture_layout.map(Some));
857            let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
858                label: Some(&format!("{name}_layout")),
859                bind_group_layouts: &bind_group_layouts,
860                immediate_size: 0,
861            });
862
863            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
864                label: Some(name),
865                layout: Some(&pipeline_layout),
866                vertex: wgpu::VertexState {
867                    module,
868                    entry_point: Some(vs_entry),
869                    buffers: &[],
870                    compilation_options: wgpu::PipelineCompilationOptions::default(),
871                },
872                fragment: Some(wgpu::FragmentState {
873                    module,
874                    entry_point: Some(fs_entry),
875                    targets: color_targets,
876                    compilation_options: wgpu::PipelineCompilationOptions::default(),
877                }),
878                primitive: wgpu::PrimitiveState {
879                    topology,
880                    strip_index_format: None,
881                    front_face: wgpu::FrontFace::Ccw,
882                    cull_mode: None,
883                    polygon_mode: wgpu::PolygonMode::Fill,
884                    unclipped_depth: false,
885                    conservative: false,
886                },
887                depth_stencil: None,
888                multisample: wgpu::MultisampleState {
889                    count: sample_count,
890                    mask: !0,
891                    alpha_to_coverage_enabled: false,
892                },
893                multiview_mask: None,
894                cache: None,
895            })
896        };
897
898        let quads = create_pipeline(
899            "quads",
900            "vs_quad",
901            "fs_quad",
902            &layouts.globals,
903            &layouts.instances,
904            None,
905            wgpu::PrimitiveTopology::TriangleStrip,
906            &[Some(color_target.clone())],
907            1,
908            &shader_module,
909        );
910
911        let shadows = create_pipeline(
912            "shadows",
913            "vs_shadow",
914            "fs_shadow",
915            &layouts.globals,
916            &layouts.instances,
917            None,
918            wgpu::PrimitiveTopology::TriangleStrip,
919            &[Some(color_target.clone())],
920            1,
921            &shader_module,
922        );
923
924        let path_rasterization = create_pipeline(
925            "path_rasterization",
926            "vs_path_rasterization",
927            "fs_path_rasterization",
928            &layouts.globals,
929            &layouts.instances,
930            None,
931            wgpu::PrimitiveTopology::TriangleList,
932            &[Some(wgpu::ColorTargetState {
933                format: surface_format,
934                blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
935                write_mask: wgpu::ColorWrites::ALL,
936            })],
937            path_sample_count,
938            &shader_module,
939        );
940
941        let paths_blend = wgpu::BlendState {
942            color: wgpu::BlendComponent {
943                src_factor: wgpu::BlendFactor::One,
944                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
945                operation: wgpu::BlendOperation::Add,
946            },
947            alpha: wgpu::BlendComponent {
948                src_factor: wgpu::BlendFactor::One,
949                dst_factor: wgpu::BlendFactor::One,
950                operation: wgpu::BlendOperation::Add,
951            },
952        };
953
954        let paths = create_pipeline(
955            "paths",
956            "vs_path",
957            "fs_path",
958            &layouts.globals,
959            &layouts.instances,
960            Some(&layouts.texture),
961            wgpu::PrimitiveTopology::TriangleStrip,
962            &[Some(wgpu::ColorTargetState {
963                format: surface_format,
964                blend: Some(paths_blend),
965                write_mask: wgpu::ColorWrites::ALL,
966            })],
967            1,
968            &shader_module,
969        );
970
971        let underlines = create_pipeline(
972            "underlines",
973            "vs_underline",
974            "fs_underline",
975            &layouts.globals,
976            &layouts.instances,
977            None,
978            wgpu::PrimitiveTopology::TriangleStrip,
979            &[Some(color_target.clone())],
980            1,
981            &shader_module,
982        );
983
984        let mono_sprites = create_pipeline(
985            "mono_sprites",
986            "vs_mono_sprite",
987            "fs_mono_sprite",
988            &layouts.globals,
989            &layouts.instances,
990            Some(&layouts.texture),
991            wgpu::PrimitiveTopology::TriangleStrip,
992            &[Some(color_target.clone())],
993            1,
994            &shader_module,
995        );
996
997        let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
998            let subpixel_blend = wgpu::BlendState {
999                color: wgpu::BlendComponent {
1000                    src_factor: wgpu::BlendFactor::Src1,
1001                    dst_factor: wgpu::BlendFactor::OneMinusSrc1,
1002                    operation: wgpu::BlendOperation::Add,
1003                },
1004                alpha: wgpu::BlendComponent {
1005                    src_factor: wgpu::BlendFactor::One,
1006                    dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1007                    operation: wgpu::BlendOperation::Add,
1008                },
1009            };
1010
1011            Some(create_pipeline(
1012                "subpixel_sprites",
1013                "vs_subpixel_sprite",
1014                "fs_subpixel_sprite",
1015                &layouts.globals,
1016                &layouts.instances,
1017                Some(&layouts.texture),
1018                wgpu::PrimitiveTopology::TriangleStrip,
1019                &[Some(wgpu::ColorTargetState {
1020                    format: surface_format,
1021                    blend: Some(subpixel_blend),
1022                    write_mask: wgpu::ColorWrites::COLOR,
1023                })],
1024                1,
1025                subpixel_module,
1026            ))
1027        } else {
1028            None
1029        };
1030
1031        let poly_sprites = create_pipeline(
1032            "poly_sprites",
1033            "vs_poly_sprite",
1034            "fs_poly_sprite",
1035            &layouts.globals,
1036            &layouts.instances,
1037            Some(&layouts.texture),
1038            wgpu::PrimitiveTopology::TriangleStrip,
1039            &[Some(color_target.clone())],
1040            1,
1041            &shader_module,
1042        );
1043
1044        let surfaces = create_pipeline(
1045            "surfaces",
1046            "vs_surface",
1047            "fs_surface",
1048            &layouts.globals,
1049            &layouts.surfaces,
1050            None,
1051            wgpu::PrimitiveTopology::TriangleStrip,
1052            &[Some(color_target)],
1053            1,
1054            &shader_module,
1055        );
1056
1057        WgpuPipelines {
1058            quads,
1059            shadows,
1060            path_rasterization,
1061            paths,
1062            underlines,
1063            mono_sprites,
1064            subpixel_sprites,
1065            poly_sprites,
1066            surfaces,
1067        }
1068    }
1069
1070    fn create_path_intermediate(
1071        device: &wgpu::Device,
1072        format: wgpu::TextureFormat,
1073        width: u32,
1074        height: u32,
1075    ) -> (wgpu::Texture, wgpu::TextureView) {
1076        let texture = device.create_texture(&wgpu::TextureDescriptor {
1077            label: Some("path_intermediate"),
1078            size: wgpu::Extent3d {
1079                width: width.max(1),
1080                height: height.max(1),
1081                depth_or_array_layers: 1,
1082            },
1083            mip_level_count: 1,
1084            sample_count: 1,
1085            dimension: wgpu::TextureDimension::D2,
1086            format,
1087            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1088            view_formats: &[],
1089        });
1090        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1091        (texture, view)
1092    }
1093
1094    fn create_msaa_if_needed(
1095        device: &wgpu::Device,
1096        format: wgpu::TextureFormat,
1097        width: u32,
1098        height: u32,
1099        sample_count: u32,
1100    ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
1101        if sample_count <= 1 {
1102            return None;
1103        }
1104        let texture = device.create_texture(&wgpu::TextureDescriptor {
1105            label: Some("path_msaa"),
1106            size: wgpu::Extent3d {
1107                width: width.max(1),
1108                height: height.max(1),
1109                depth_or_array_layers: 1,
1110            },
1111            mip_level_count: 1,
1112            sample_count,
1113            dimension: wgpu::TextureDimension::D2,
1114            format,
1115            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1116            view_formats: &[],
1117        });
1118        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1119        Some((texture, view))
1120    }
1121
1122    pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
1123        let width = size.width.0 as u32;
1124        let height = size.height.0 as u32;
1125
1126        if width != self.surface_config.width || height != self.surface_config.height {
1127            let clamped_width = width.min(self.max_texture_size);
1128            let clamped_height = height.min(self.max_texture_size);
1129
1130            if clamped_width != width || clamped_height != height {
1131                warn!(
1132                    "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
1133                     Clamping to ({}, {}). Window content may not fill the entire window.",
1134                    width, height, self.max_texture_size, clamped_width, clamped_height
1135                );
1136            }
1137
1138            self.surface_config.width = clamped_width.max(1);
1139            self.surface_config.height = clamped_height.max(1);
1140            let surface_config = self.surface_config.clone();
1141
1142            let Some(resources) = self.resources.as_mut() else {
1143                return;
1144            };
1145
1146            // Wait for any in-flight GPU work to complete before destroying textures
1147            if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
1148                submission_index: None,
1149                timeout: None,
1150            }) {
1151                warn!("Failed to poll device during resize: {e:?}");
1152            }
1153
1154            // Destroy old textures before allocating new ones to avoid GPU memory spikes
1155            if let Some(ref texture) = resources.path_intermediate_texture {
1156                texture.destroy();
1157            }
1158            if let Some(ref texture) = resources.path_msaa_texture {
1159                texture.destroy();
1160            }
1161
1162            resources
1163                .surface
1164                .configure(&resources.device, &surface_config);
1165
1166            // Invalidate intermediate textures - they will be lazily recreated
1167            // in draw() after we confirm the surface is healthy. This avoids
1168            // panics when the device/surface is in an invalid state during resize.
1169            resources.invalidate_intermediate_textures();
1170        }
1171    }
1172
1173    fn ensure_intermediate_textures(&mut self) {
1174        if self.resources().path_intermediate_texture.is_some() {
1175            return;
1176        }
1177
1178        let format = self.surface_config.format;
1179        let width = self.surface_config.width;
1180        let height = self.surface_config.height;
1181        let path_sample_count = self.rendering_params.path_sample_count;
1182        let resources = self.resources_mut();
1183
1184        let (t, v) = Self::create_path_intermediate(&resources.device, format, width, height);
1185        resources.path_intermediate_texture = Some(t);
1186        resources.path_intermediate_view = Some(v);
1187
1188        let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
1189            &resources.device,
1190            format,
1191            width,
1192            height,
1193            path_sample_count,
1194        )
1195        .map(|(t, v)| (Some(t), Some(v)))
1196        .unwrap_or((None, None));
1197        resources.path_msaa_texture = path_msaa_texture;
1198        resources.path_msaa_view = path_msaa_view;
1199    }
1200
1201    pub fn set_subpixel_layout(&mut self, is_bgr: bool) {
1202        self.is_bgr = is_bgr;
1203    }
1204
1205    pub fn update_transparency(&mut self, transparent: bool) {
1206        let new_alpha_mode = if transparent {
1207            self.transparent_alpha_mode
1208        } else {
1209            self.opaque_alpha_mode
1210        };
1211
1212        if new_alpha_mode != self.surface_config.alpha_mode {
1213            self.surface_config.alpha_mode = new_alpha_mode;
1214            let surface_config = self.surface_config.clone();
1215            let path_sample_count = self.rendering_params.path_sample_count;
1216            let dual_source_blending = self.dual_source_blending;
1217            let uses_webgl_instance_data = self.uses_webgl_instance_data;
1218            let Some(resources) = self.resources.as_mut() else {
1219                return;
1220            };
1221            resources
1222                .surface
1223                .configure(&resources.device, &surface_config);
1224            resources.pipelines = Self::create_pipelines(
1225                &resources.device,
1226                &resources.bind_group_layouts,
1227                surface_config.format,
1228                surface_config.alpha_mode,
1229                path_sample_count,
1230                dual_source_blending,
1231                uses_webgl_instance_data,
1232            );
1233        }
1234    }
1235
1236    #[allow(dead_code)]
1237    pub fn viewport_size(&self) -> Size<DevicePixels> {
1238        Size {
1239            width: DevicePixels(self.surface_config.width as i32),
1240            height: DevicePixels(self.surface_config.height as i32),
1241        }
1242    }
1243
1244    pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
1245        &self.atlas
1246    }
1247
1248    pub fn supports_dual_source_blending(&self) -> bool {
1249        self.dual_source_blending
1250    }
1251
1252    pub fn gpu_specs(&self) -> GpuSpecs {
1253        GpuSpecs {
1254            is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
1255            device_name: self.adapter_info.name.clone(),
1256            driver_name: self.adapter_info.driver.clone(),
1257            driver_info: self.adapter_info.driver_info.clone(),
1258        }
1259    }
1260
1261    pub fn max_texture_size(&self) -> u32 {
1262        self.max_texture_size
1263    }
1264
1265    pub fn draw(&mut self, scene: &Scene) -> bool {
1266        #[cfg(target_family = "wasm")]
1267        if self.device_lost() {
1268            if self.surface_configured {
1269                log::error!(
1270                    "Browser graphics context was lost; rendering has stopped. Reload the page to recover."
1271                );
1272                self.surface_configured = false;
1273            }
1274            return false;
1275        }
1276
1277        // Bail out early if the surface has been unconfigured (e.g. during
1278        // Android background/rotation transitions).  Attempting to acquire
1279        // a texture from an unconfigured surface can block indefinitely on
1280        // some drivers (Adreno).
1281        if !self.surface_configured {
1282            return false;
1283        }
1284
1285        let last_error = self.last_error.lock().unwrap().take();
1286        if let Some(error) = last_error {
1287            self.failed_frame_count += 1;
1288            log::error!(
1289                "GPU error during frame (failure {} of 10): {error}",
1290                self.failed_frame_count
1291            );
1292
1293            // TBD. Does retrying more actually help?
1294            if self.failed_frame_count > 10 {
1295                panic!("Too many consecutive GPU errors. Last error: {error}");
1296            } else if self.failed_frame_count > 5 {
1297                if let Some(res) = self.resources.as_mut() {
1298                    res.invalidate_intermediate_textures();
1299                }
1300                self.atlas.clear();
1301                self.needs_redraw = true;
1302                self.failed_frame_count = 0;
1303                return false;
1304            }
1305        } else {
1306            self.failed_frame_count = 0;
1307        }
1308
1309        self.atlas.before_frame();
1310
1311        let frame = match self.resources().surface.get_current_texture() {
1312            wgpu::CurrentSurfaceTexture::Success(frame) => frame,
1313            wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
1314                // Textures must be destroyed before the surface can be reconfigured.
1315                drop(frame);
1316                let surface_config = self.surface_config.clone();
1317                let resources = self.resources_mut();
1318                resources
1319                    .surface
1320                    .configure(&resources.device, &surface_config);
1321                return false;
1322            }
1323            wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
1324                let surface_config = self.surface_config.clone();
1325                let resources = self.resources_mut();
1326                resources
1327                    .surface
1328                    .configure(&resources.device, &surface_config);
1329                return false;
1330            }
1331            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
1332                return false;
1333            }
1334            wgpu::CurrentSurfaceTexture::Validation => {
1335                *self.last_error.lock().unwrap() =
1336                    Some("Surface texture validation error".to_string());
1337                return false;
1338            }
1339        };
1340
1341        // Now that we know the surface is healthy, ensure intermediate textures exist
1342        self.ensure_intermediate_textures();
1343
1344        let frame_view = frame
1345            .texture
1346            .create_view(&wgpu::TextureViewDescriptor::default());
1347
1348        let gamma_params = GammaParams {
1349            gamma_ratios: self.rendering_params.gamma_ratios,
1350            grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1351            subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1352            is_bgr: self.is_bgr as u32,
1353            _pad: 0,
1354        };
1355
1356        let globals = GlobalParams {
1357            viewport_size: [
1358                self.surface_config.width as f32,
1359                self.surface_config.height as f32,
1360            ],
1361            premultiplied_alpha: if self.surface_config.alpha_mode
1362                == wgpu::CompositeAlphaMode::PreMultiplied
1363            {
1364                1
1365            } else {
1366                0
1367            },
1368            pad: 0,
1369        };
1370
1371        let path_globals = GlobalParams {
1372            premultiplied_alpha: 0,
1373            ..globals
1374        };
1375
1376        {
1377            let resources = self.resources();
1378            resources.queue.write_buffer(
1379                &resources.globals_buffer,
1380                0,
1381                bytemuck::bytes_of(&globals),
1382            );
1383            resources.queue.write_buffer(
1384                &resources.globals_buffer,
1385                self.path_globals_offset,
1386                bytemuck::bytes_of(&path_globals),
1387            );
1388            resources.queue.write_buffer(
1389                &resources.globals_buffer,
1390                self.gamma_offset,
1391                bytemuck::bytes_of(&gamma_params),
1392            );
1393        }
1394
1395        if let Err(error) = self.record_frame(scene, &frame_view) {
1396            log::error!("{error:#}");
1397            self.resources().queue.submit(std::iter::empty());
1398            return false;
1399        }
1400
1401        frame.present();
1402        true
1403    }
1404
1405    fn record_frame(&mut self, scene: &Scene, frame_view: &wgpu::TextureView) -> Result<()> {
1406        let mut instance_offset = 0;
1407        let instance_bindings = self
1408            .write_instances(scene, &mut instance_offset)
1409            .with_context(|| {
1410                format!(
1411                    "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1412                    scene.paths.len(),
1413                    scene.shadows.len(),
1414                    scene.quads.len(),
1415                    scene.underlines.len(),
1416                    scene.monochrome_sprites.len(),
1417                    scene.subpixel_sprites.len(),
1418                    scene.polychrome_sprites.len(),
1419                )
1420            })?;
1421
1422        let mut encoder =
1423            self.resources()
1424                .device
1425                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1426                    label: Some("main_encoder"),
1427                });
1428
1429        {
1430            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1431                label: Some("main_pass"),
1432                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1433                    view: frame_view,
1434                    resolve_target: None,
1435                    ops: wgpu::Operations {
1436                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1437                        store: wgpu::StoreOp::Store,
1438                    },
1439                    depth_slice: None,
1440                })],
1441                depth_stencil_attachment: None,
1442                ..Default::default()
1443            });
1444
1445            for batch in scene.batches() {
1446                match batch {
1447                    PrimitiveBatch::Quads(range) => self.draw_instances(
1448                        &instance_bindings.quads,
1449                        &self.resources().pipelines.quads,
1450                        instance_range(range),
1451                        &mut pass,
1452                    ),
1453                    PrimitiveBatch::Shadows(range) => self.draw_instances(
1454                        &instance_bindings.shadows,
1455                        &self.resources().pipelines.shadows,
1456                        instance_range(range),
1457                        &mut pass,
1458                    ),
1459                    PrimitiveBatch::Paths(range) => {
1460                        let paths = &scene.paths[range];
1461                        if paths.is_empty() {
1462                            continue;
1463                        }
1464
1465                        drop(pass);
1466                        let rasterized = self.draw_paths_to_intermediate(
1467                            &mut encoder,
1468                            paths,
1469                            &mut instance_offset,
1470                        )?;
1471
1472                        pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1473                            label: Some("main_pass_continued"),
1474                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1475                                view: frame_view,
1476                                resolve_target: None,
1477                                ops: wgpu::Operations {
1478                                    load: wgpu::LoadOp::Load,
1479                                    store: wgpu::StoreOp::Store,
1480                                },
1481                                depth_slice: None,
1482                            })],
1483                            depth_stencil_attachment: None,
1484                            ..Default::default()
1485                        });
1486
1487                        if rasterized {
1488                            self.draw_paths_from_intermediate(
1489                                paths,
1490                                &mut instance_offset,
1491                                &mut pass,
1492                            )?;
1493                        }
1494                    }
1495                    PrimitiveBatch::Underlines(range) => self.draw_instances(
1496                        &instance_bindings.underlines,
1497                        &self.resources().pipelines.underlines,
1498                        instance_range(range),
1499                        &mut pass,
1500                    ),
1501                    PrimitiveBatch::MonochromeSprites { texture_id, range } => self.draw_sprites(
1502                        &instance_bindings.monochrome_sprites,
1503                        texture_id,
1504                        &self.resources().pipelines.mono_sprites,
1505                        instance_range(range),
1506                        &mut pass,
1507                    ),
1508                    PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1509                        let resources = self.resources();
1510                        self.draw_sprites(
1511                            &instance_bindings.subpixel_sprites,
1512                            texture_id,
1513                            resources
1514                                .pipelines
1515                                .subpixel_sprites
1516                                .as_ref()
1517                                .unwrap_or(&resources.pipelines.mono_sprites),
1518                            instance_range(range),
1519                            &mut pass,
1520                        );
1521                    }
1522                    PrimitiveBatch::PolychromeSprites { texture_id, range } => self.draw_sprites(
1523                        &instance_bindings.polychrome_sprites,
1524                        texture_id,
1525                        &self.resources().pipelines.poly_sprites,
1526                        instance_range(range),
1527                        &mut pass,
1528                    ),
1529                    // Surfaces are macOS-only for video playback and are not
1530                    // implemented by the WGPU renderer.
1531                    PrimitiveBatch::Surfaces(_surfaces) => {}
1532                }
1533            }
1534        }
1535
1536        self.resources()
1537            .queue
1538            .submit(std::iter::once(encoder.finish()));
1539        Ok(())
1540    }
1541
1542    fn write_instances(
1543        &mut self,
1544        scene: &Scene,
1545        instance_offset: &mut u64,
1546    ) -> Result<InstanceBindings> {
1547        Ok(InstanceBindings {
1548            quads: self.write_instance_binding(
1549                "quads_bind_group",
1550                instance_offset,
1551                &scene.quads,
1552            )?,
1553            shadows: self.write_instance_binding(
1554                "shadows_bind_group",
1555                instance_offset,
1556                &scene.shadows,
1557            )?,
1558            underlines: self.write_instance_binding(
1559                "underlines_bind_group",
1560                instance_offset,
1561                &scene.underlines,
1562            )?,
1563            monochrome_sprites: self.write_instance_binding(
1564                "monochrome_sprites_bind_group",
1565                instance_offset,
1566                &scene.monochrome_sprites,
1567            )?,
1568            subpixel_sprites: self.write_instance_binding(
1569                "subpixel_sprites_bind_group",
1570                instance_offset,
1571                &scene.subpixel_sprites,
1572            )?,
1573            polychrome_sprites: self.write_instance_binding(
1574                "polychrome_sprites_bind_group",
1575                instance_offset,
1576                &scene.polychrome_sprites,
1577            )?,
1578        })
1579    }
1580
1581    fn create_texture_bind_group(
1582        &self,
1583        label: &str,
1584        texture_view: &wgpu::TextureView,
1585    ) -> wgpu::BindGroup {
1586        let resources = self.resources();
1587        resources
1588            .device
1589            .create_bind_group(&wgpu::BindGroupDescriptor {
1590                label: Some(label),
1591                layout: &resources.bind_group_layouts.texture,
1592                entries: &[
1593                    wgpu::BindGroupEntry {
1594                        binding: 0,
1595                        resource: wgpu::BindingResource::TextureView(texture_view),
1596                    },
1597                    wgpu::BindGroupEntry {
1598                        binding: 1,
1599                        resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1600                    },
1601                ],
1602            })
1603    }
1604
1605    fn draw_instances(
1606        &self,
1607        instances: &InstanceBinding,
1608        pipeline: &wgpu::RenderPipeline,
1609        range: Range<u32>,
1610        pass: &mut wgpu::RenderPass<'_>,
1611    ) {
1612        if range.is_empty() {
1613            return;
1614        }
1615        pass.set_pipeline(pipeline);
1616        pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1617        pass.set_bind_group(1, &instances.bind_group, &[]);
1618        pass.draw(
1619            0..4,
1620            instances.first_instance + range.start..instances.first_instance + range.end,
1621        );
1622    }
1623
1624    fn draw_sprites(
1625        &self,
1626        sprite_instances: &InstanceBinding,
1627        texture_id: AtlasTextureId,
1628        pipeline: &wgpu::RenderPipeline,
1629        range: Range<u32>,
1630        pass: &mut wgpu::RenderPass<'_>,
1631    ) {
1632        if range.is_empty() {
1633            return;
1634        }
1635        let texture_info = self.atlas.get_texture_info(texture_id);
1636        let texture =
1637            self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
1638        pass.set_pipeline(pipeline);
1639        pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1640        pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
1641        pass.set_bind_group(2, &texture, &[]);
1642        pass.draw(
1643            0..4,
1644            sprite_instances.first_instance + range.start
1645                ..sprite_instances.first_instance + range.end,
1646        );
1647    }
1648
1649    unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
1650        unsafe {
1651            std::slice::from_raw_parts(
1652                instances.as_ptr() as *const u8,
1653                std::mem::size_of_val(instances),
1654            )
1655        }
1656    }
1657
1658    fn draw_paths_from_intermediate(
1659        &mut self,
1660        paths: &[Path<ScaledPixels>],
1661        instance_offset: &mut u64,
1662        pass: &mut wgpu::RenderPass<'_>,
1663    ) -> Result<()> {
1664        let first_path = &paths[0];
1665        let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
1666        {
1667            paths
1668                .iter()
1669                .map(|p| PathSprite {
1670                    bounds: p.clipped_bounds(),
1671                })
1672                .collect()
1673        } else {
1674            let mut bounds = first_path.clipped_bounds();
1675            for path in paths.iter().skip(1) {
1676                bounds = bounds.union(&path.clipped_bounds());
1677            }
1678            vec![PathSprite { bounds }]
1679        };
1680
1681        let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
1682            return Ok(());
1683        };
1684        let instances =
1685            self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
1686        let texture = self.create_texture_bind_group(
1687            "path_intermediate_texture_bind_group",
1688            &path_intermediate_view,
1689        );
1690        let resources = self.resources();
1691        pass.set_pipeline(&resources.pipelines.paths);
1692        pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1693        pass.set_bind_group(1, &instances.bind_group, &[]);
1694        pass.set_bind_group(2, &texture, &[]);
1695        pass.draw(
1696            0..4,
1697            instances.first_instance..instances.first_instance + sprites.len() as u32,
1698        );
1699        Ok(())
1700    }
1701
1702    fn draw_paths_to_intermediate(
1703        &mut self,
1704        encoder: &mut wgpu::CommandEncoder,
1705        paths: &[Path<ScaledPixels>],
1706        instance_offset: &mut u64,
1707    ) -> Result<bool> {
1708        let mut vertices = Vec::new();
1709        for path in paths {
1710            let bounds = path.clipped_bounds();
1711            vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
1712                xy_position: v.xy_position,
1713                st_position: v.st_position,
1714                color: path.color,
1715                bounds,
1716            }));
1717        }
1718
1719        if vertices.is_empty() {
1720            return Ok(false);
1721        }
1722
1723        let vertex_binding = self.write_instance_binding(
1724            "path_rasterization_bind_group",
1725            instance_offset,
1726            &vertices,
1727        )?;
1728
1729        let resources = self.resources();
1730        let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
1731            return Ok(false);
1732        };
1733
1734        let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
1735            (msaa_view, Some(path_intermediate_view))
1736        } else {
1737            (path_intermediate_view, None)
1738        };
1739
1740        {
1741            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1742                label: Some("path_rasterization_pass"),
1743                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1744                    view: target_view,
1745                    resolve_target,
1746                    ops: wgpu::Operations {
1747                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1748                        store: wgpu::StoreOp::Store,
1749                    },
1750                    depth_slice: None,
1751                })],
1752                depth_stencil_attachment: None,
1753                ..Default::default()
1754            });
1755
1756            pass.set_pipeline(&resources.pipelines.path_rasterization);
1757            pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
1758            pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
1759            // The path rasterization shader loads records by vertex index
1760            // rather than instance index, so the allocation's base shifts the
1761            // vertex range here.
1762            pass.draw(
1763                vertex_binding.first_instance
1764                    ..vertex_binding.first_instance + vertices.len() as u32,
1765                0..1,
1766            );
1767        }
1768
1769        Ok(true)
1770    }
1771
1772    fn write_instance_binding<T>(
1773        &mut self,
1774        label: &str,
1775        instance_offset: &mut u64,
1776        instances: &[T],
1777    ) -> Result<InstanceBinding> {
1778        let data = unsafe { Self::instance_bytes(instances) };
1779        // wgpu rejects zero-sized bindings, so empty primitive arrays still
1780        // reserve the 16-byte minimum.
1781        let size = (data.len() as u64).max(16);
1782        let stride = (std::mem::size_of::<T>() as u64).max(1);
1783        let (alignment, allocation_size) = if self.uses_webgl_instance_data {
1784            // The texture transport has no binding offset: the shader indexes
1785            // the instance texture absolutely, so each allocation must start on
1786            // a whole instance (a stride multiple) and a whole texel, and must
1787            // end on a texel boundary so the zero padding of its final partial
1788            // texel cannot overlap the next allocation.
1789            (
1790                least_common_multiple(self.instance_data_alignment, stride),
1791                size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
1792            )
1793        } else {
1794            (self.instance_data_alignment.max(1), size)
1795        };
1796        let mut offset = (*instance_offset).next_multiple_of(alignment);
1797        if offset + allocation_size > self.instance_data_capacity {
1798            self.grow_instance_data(allocation_size)?;
1799            offset = 0;
1800        }
1801        *instance_offset = offset + allocation_size;
1802
1803        let first_instance = if self.uses_webgl_instance_data {
1804            u32::try_from(offset / stride).context("instance index exceeds u32 range")?
1805        } else {
1806            0
1807        };
1808
1809        let resources = self.resources();
1810        if !data.is_empty() {
1811            match &resources.instance_data {
1812                InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
1813                InstanceData::Texture { .. } => {
1814                    Self::write_instance_texture(resources, offset, data)
1815                }
1816            }
1817        }
1818        let bind_group = resources
1819            .device
1820            .create_bind_group(&wgpu::BindGroupDescriptor {
1821                label: Some(label),
1822                layout: &resources.bind_group_layouts.instances,
1823                entries: &[wgpu::BindGroupEntry {
1824                    binding: 0,
1825                    resource: match &resources.instance_data {
1826                        InstanceData::Storage(buffer) => {
1827                            wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1828                                buffer,
1829                                offset,
1830                                size: NonZeroU64::new(size),
1831                            })
1832                        }
1833                        InstanceData::Texture { view, .. } => {
1834                            wgpu::BindingResource::TextureView(view)
1835                        }
1836                    },
1837                }],
1838            });
1839        Ok(InstanceBinding {
1840            bind_group,
1841            first_instance,
1842        })
1843    }
1844
1845    fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
1846        let InstanceData::Texture {
1847            texture,
1848            width,
1849            height,
1850            ..
1851        } = &resources.instance_data
1852        else {
1853            return;
1854        };
1855        let mut byte_offset = 0usize;
1856        let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
1857        while byte_offset < data.len() {
1858            let x = (texel_offset % u64::from(*width)) as u32;
1859            let y = (texel_offset / u64::from(*width)) as u32;
1860            if y >= *height {
1861                // The capacity check in write_instance_binding should make this
1862                // unreachable. Truncating silently would leave stale bytes in the
1863                // texture and draw garbage for the remaining instances.
1864                debug_assert!(
1865                    false,
1866                    "instance texture write out of bounds: row {y} >= height {}",
1867                    *height
1868                );
1869                log::error!(
1870                    "instance texture write out of bounds; dropping {} bytes of instance data",
1871                    data.len() - byte_offset
1872                );
1873                return;
1874            }
1875            let available_texels = u64::from(*width - x);
1876            let remaining_bytes = data.len() - byte_offset;
1877            let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
1878            let texels = complete_texels.min(available_texels);
1879            if texels > 0 {
1880                let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
1881                resources.queue.write_texture(
1882                    wgpu::TexelCopyTextureInfo {
1883                        texture,
1884                        mip_level: 0,
1885                        origin: wgpu::Origin3d { x, y, z: 0 },
1886                        aspect: wgpu::TextureAspect::All,
1887                    },
1888                    &data[byte_offset..byte_offset + byte_count],
1889                    wgpu::TexelCopyBufferLayout {
1890                        offset: 0,
1891                        bytes_per_row: Some(byte_count as u32),
1892                        rows_per_image: None,
1893                    },
1894                    wgpu::Extent3d {
1895                        width: texels as u32,
1896                        height: 1,
1897                        depth_or_array_layers: 1,
1898                    },
1899                );
1900                byte_offset += byte_count;
1901                texel_offset += texels;
1902                continue;
1903            }
1904
1905            let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
1906            final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
1907            resources.queue.write_texture(
1908                wgpu::TexelCopyTextureInfo {
1909                    texture,
1910                    mip_level: 0,
1911                    origin: wgpu::Origin3d { x, y, z: 0 },
1912                    aspect: wgpu::TextureAspect::All,
1913                },
1914                &final_texel,
1915                wgpu::TexelCopyBufferLayout {
1916                    offset: 0,
1917                    bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
1918                    rows_per_image: None,
1919                },
1920                wgpu::Extent3d {
1921                    width: 1,
1922                    height: 1,
1923                    depth_or_array_layers: 1,
1924                },
1925            );
1926            break;
1927        }
1928    }
1929
1930    fn grow_instance_data(&mut self, required: u64) -> Result<()> {
1931        let capacity = (self.instance_data_capacity * 2)
1932            .max(required.next_power_of_two())
1933            .min(self.max_instance_data_size);
1934        anyhow::ensure!(
1935            capacity >= required,
1936            "instance data needs {required} bytes, above the maximum of {}",
1937            self.max_instance_data_size
1938        );
1939        anyhow::ensure!(
1940            capacity > self.instance_data_capacity,
1941            "frame instance data exceeds the {}-byte maximum",
1942            self.max_instance_data_size
1943        );
1944        log::debug!(
1945            "instance data grown from {} to {capacity}",
1946            self.instance_data_capacity
1947        );
1948        // Bind groups created earlier in the frame keep the previous buffer or
1949        // texture alive, so allocations written before the grow remain valid;
1950        // only subsequent writes land in the new allocation.
1951        let uses_webgl_instance_data = self.uses_webgl_instance_data;
1952        let resources = self.resources_mut();
1953        if uses_webgl_instance_data {
1954            let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
1955            let (instance_data, actual_capacity) =
1956                Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
1957            resources.instance_data = instance_data;
1958            self.instance_data_capacity = actual_capacity;
1959        } else {
1960            resources.instance_data =
1961                InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
1962                    label: Some("instance_buffer"),
1963                    size: capacity,
1964                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1965                    mapped_at_creation: false,
1966                }));
1967            self.instance_data_capacity = capacity;
1968        }
1969        Ok(())
1970    }
1971
1972    /// Mark the surface as unconfigured so rendering is skipped until a new
1973    /// surface is provided via [`replace_surface`](Self::replace_surface).
1974    ///
1975    /// This does **not** drop the renderer — the device, queue, atlas, and
1976    /// pipelines stay alive.  Use this when the native window is destroyed
1977    /// (e.g. Android `TerminateWindow`) but you intend to re-create the
1978    /// surface later without losing cached atlas textures.
1979    pub fn unconfigure_surface(&mut self) {
1980        self.surface_configured = false;
1981        // Drop intermediate textures since they reference the old surface size.
1982        if let Some(res) = self.resources.as_mut() {
1983            res.invalidate_intermediate_textures();
1984        }
1985    }
1986
1987    /// Replace the wgpu surface with a new one (e.g. after Android destroys
1988    /// and recreates the native window).  Keeps the device, queue, atlas, and
1989    /// all pipelines intact so cached `AtlasTextureId`s remain valid.
1990    ///
1991    /// The `instance` **must** be the same [`wgpu::Instance`] that was used to
1992    /// create the adapter and device (i.e. from the [`WgpuContext`]).  Using a
1993    /// different instance will cause a "Device does not exist" panic because
1994    /// the wgpu device is bound to its originating instance.
1995    #[cfg(not(target_family = "wasm"))]
1996    pub fn replace_surface<W: HasWindowHandle>(
1997        &mut self,
1998        window: &W,
1999        config: WgpuSurfaceConfig,
2000        instance: &wgpu::Instance,
2001    ) -> anyhow::Result<()> {
2002        let window_handle = window
2003            .window_handle()
2004            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2005
2006        let surface = create_surface(instance, window_handle.as_raw())?;
2007
2008        let width = (config.size.width.0 as u32).max(1);
2009        let height = (config.size.height.0 as u32).max(1);
2010
2011        let alpha_mode = if config.transparent {
2012            self.transparent_alpha_mode
2013        } else {
2014            self.opaque_alpha_mode
2015        };
2016
2017        self.surface_config.width = width;
2018        self.surface_config.height = height;
2019        self.surface_config.alpha_mode = alpha_mode;
2020        if let Some(mode) = config.preferred_present_mode {
2021            self.surface_config.present_mode = mode;
2022        }
2023
2024        {
2025            let res = self
2026                .resources
2027                .as_mut()
2028                .expect("GPU resources not available");
2029            surface.configure(&res.device, &self.surface_config);
2030            res.surface = surface;
2031
2032            // Invalidate intermediate textures — they'll be recreated lazily.
2033            res.invalidate_intermediate_textures();
2034        }
2035
2036        self.surface_configured = true;
2037
2038        Ok(())
2039    }
2040
2041    pub fn destroy(&mut self) {
2042        // Release surface-bound GPU resources eagerly so the underlying native
2043        // window can be destroyed before the renderer itself is dropped.
2044        self.resources.take();
2045    }
2046
2047    /// Returns true if the GPU device was lost and recovery is needed.
2048    pub fn device_lost(&self) -> bool {
2049        self.device_lost.load(std::sync::atomic::Ordering::SeqCst)
2050    }
2051
2052    /// Returns true if a redraw is needed because GPU state was cleared.
2053    /// Calling this method clears the flag.
2054    pub fn needs_redraw(&mut self) -> bool {
2055        std::mem::take(&mut self.needs_redraw)
2056    }
2057
2058    /// Recovers from a lost GPU device by recreating the renderer with a new context.
2059    ///
2060    /// Call this after detecting `device_lost()` returns true.
2061    ///
2062    /// This method coordinates recovery across multiple windows:
2063    /// - The first window to call this will recreate the shared context
2064    /// - Subsequent windows will adopt the already-recovered context
2065    #[cfg(not(target_family = "wasm"))]
2066    pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2067    where
2068        W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2069    {
2070        let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2071
2072        // Check if another window already recovered the context
2073        let needs_new_context = gpu_context
2074            .borrow()
2075            .as_ref()
2076            .is_none_or(|ctx| ctx.device_lost());
2077
2078        let window_handle = window
2079            .window_handle()
2080            .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2081
2082        let surface = if needs_new_context {
2083            log::warn!("GPU device lost, recreating context...");
2084
2085            // Drop old resources to release Arc<Device>/Arc<Queue> and GPU resources
2086            self.resources = None;
2087            *gpu_context.borrow_mut() = None;
2088
2089            // Wait briefly for the GPU driver to stabilize, then try to
2090            // recreate the context without software renderers. If this fails
2091            // the caller should request another frame and retry — the real GPU
2092            // may need more time to come back (e.g. after suspend/resume).
2093            std::thread::sleep(std::time::Duration::from_millis(350));
2094
2095            let instance = WgpuContext::instance(Box::new(window.clone()));
2096            let surface = create_surface(&instance, window_handle.as_raw())?;
2097            let new_context =
2098                WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?;
2099            *gpu_context.borrow_mut() = Some(new_context);
2100            surface
2101        } else {
2102            let ctx_ref = gpu_context.borrow();
2103            let instance = &ctx_ref.as_ref().unwrap().instance;
2104            create_surface(instance, window_handle.as_raw())?
2105        };
2106
2107        let config = WgpuSurfaceConfig {
2108            size: gpui::Size {
2109                width: gpui::DevicePixels(self.surface_config.width as i32),
2110                height: gpui::DevicePixels(self.surface_config.height as i32),
2111            },
2112            transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2113            preferred_present_mode: Some(self.surface_config.present_mode),
2114        };
2115        let gpu_context = Rc::clone(gpu_context);
2116        let ctx_ref = gpu_context.borrow();
2117        let context = ctx_ref.as_ref().expect("context should exist");
2118
2119        self.resources = None;
2120        self.atlas.handle_device_lost(context);
2121
2122        *self = Self::new_internal(
2123            Some(gpu_context.clone()),
2124            context,
2125            surface,
2126            config,
2127            self.compositor_gpu,
2128            self.atlas.clone(),
2129        )?;
2130
2131        log::info!("GPU recovery complete");
2132        Ok(())
2133    }
2134}
2135
2136fn instance_range(range: Range<usize>) -> Range<u32> {
2137    range.start as u32..range.end as u32
2138}
2139
2140#[cfg(not(target_family = "wasm"))]
2141fn create_surface(
2142    instance: &wgpu::Instance,
2143    raw_window_handle: raw_window_handle::RawWindowHandle,
2144) -> anyhow::Result<wgpu::Surface<'static>> {
2145    unsafe {
2146        instance
2147            .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
2148                // Fall back to the display handle already provided via InstanceDescriptor::display.
2149                raw_display_handle: None,
2150                raw_window_handle,
2151            })
2152            .map_err(|e| anyhow::anyhow!("{e}"))
2153    }
2154}
2155
2156struct RenderingParameters {
2157    path_sample_count: u32,
2158    gamma_ratios: [f32; 4],
2159    grayscale_enhanced_contrast: f32,
2160    subpixel_enhanced_contrast: f32,
2161}
2162
2163impl RenderingParameters {
2164    fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
2165        use std::env;
2166
2167        let format_features = adapter.get_texture_format_features(surface_format);
2168        let path_sample_count = [4, 2, 1]
2169            .into_iter()
2170            .find(|&n| format_features.flags.sample_count_supported(n))
2171            .unwrap_or(1);
2172
2173        let gamma = env::var("ZED_FONTS_GAMMA")
2174            .ok()
2175            .and_then(|v| v.parse().ok())
2176            .unwrap_or(1.8_f32)
2177            .clamp(1.0, 2.2);
2178        let gamma_ratios = get_gamma_correction_ratios(gamma);
2179
2180        let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
2181            .ok()
2182            .and_then(|v| v.parse().ok())
2183            .unwrap_or(1.0_f32)
2184            .max(0.0);
2185
2186        let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
2187            .ok()
2188            .and_then(|v| v.parse().ok())
2189            .unwrap_or(0.5_f32)
2190            .max(0.0);
2191
2192        Self {
2193            path_sample_count,
2194            gamma_ratios,
2195            grayscale_enhanced_contrast,
2196            subpixel_enhanced_contrast,
2197        }
2198    }
2199}
2200
2201#[cfg(test)]
2202mod tests {
2203    use super::*;
2204    use gpui::{MonochromeSprite, PolychromeSprite, Quad, Shadow, SubpixelSprite, Underline};
2205
2206    #[test]
2207    fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
2208        assert!(!WEBGL_SHADERS.contains("var<storage"));
2209        validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
2210    }
2211
2212    #[test]
2213    fn storage_buffer_shader_is_valid_wgsl() {
2214        validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
2215    }
2216
2217    #[test]
2218    fn subpixel_shader_is_valid_wgsl() {
2219        validate_wgsl(
2220            SUBPIXEL_SHADERS,
2221            naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
2222        );
2223    }
2224
2225    fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
2226        let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
2227        naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
2228            .validate(&module)
2229            .expect("shader should validate");
2230    }
2231
2232    #[test]
2233    fn webgl_record_sizes_match_shader_word_strides() {
2234        assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
2235        assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
2236        assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
2237        assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
2238        assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
2239        assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
2240        assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
2241        assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
2242    }
2243}