wgpu_renderer.rs

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