wgpu_renderer.rs

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