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