wgpu_renderer.rs

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