wgpu_renderer.rs

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