Skip to main content

open_gpui_wgpu/
wgpu_renderer.rs

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