Skip to main content

rgpui_wgpu/
wgpu_renderer.rs

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