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