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