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