1use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
2use anyhow::{Context as _, Result};
3use bytemuck::{Pod, Zeroable};
4use gpui::{
5 AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch,
6 ScaledPixels, Scene, Size, get_gamma_correction_ratios,
7};
8use log::warn;
9#[cfg(not(target_family = "wasm"))]
10use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
11use std::cell::RefCell;
12use std::num::NonZeroU64;
13use std::ops::Range;
14use std::rc::Rc;
15use std::sync::{Arc, Mutex};
16
17const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
18
19const INSTANCE_TEXTURE_TEXEL_SIZE: u64 = 16;
20
21const STORAGE_BUFFER_SHADERS: &str = concat!(
24 include_str!("shaders.wgsl"),
25 include_str!("shaders_storage.wgsl"),
26);
27
28const WEBGL_SHADERS: &str = concat!(
31 include_str!("shaders.wgsl"),
32 include_str!("shaders_webgl.wgsl"),
33);
34
35const SUBPIXEL_SHADERS: &str = concat!(
39 "enable dual_source_blending;\n",
40 include_str!("shaders.wgsl"),
41 include_str!("shaders_storage.wgsl"),
42 include_str!("shaders_subpixel.wgsl"),
43);
44
45fn least_common_multiple(left: u64, right: u64) -> u64 {
46 let mut first = left;
47 let mut second = right;
48 while second != 0 {
49 let remainder = first % second;
50 first = second;
51 second = remainder;
52 }
53 left / first * right
54}
55
56#[repr(C)]
57#[derive(Clone, Copy, Pod, Zeroable)]
58struct GlobalParams {
59 viewport_size: [f32; 2],
60 premultiplied_alpha: u32,
61 pad: u32,
62}
63
64#[repr(C)]
65#[derive(Clone, Copy, Pod, Zeroable)]
66struct PodBounds {
67 origin: [f32; 2],
68 size: [f32; 2],
69}
70
71impl From<Bounds<ScaledPixels>> for PodBounds {
72 fn from(bounds: Bounds<ScaledPixels>) -> Self {
73 Self {
74 origin: [bounds.origin.x.0, bounds.origin.y.0],
75 size: [bounds.size.width.0, bounds.size.height.0],
76 }
77 }
78}
79
80#[repr(C)]
81#[derive(Clone, Copy, Pod, Zeroable)]
82struct SurfaceParams {
83 bounds: PodBounds,
84 content_mask: PodBounds,
85}
86
87#[repr(C)]
88#[derive(Clone, Copy, Pod, Zeroable)]
89struct GammaParams {
90 gamma_ratios: [f32; 4],
91 grayscale_enhanced_contrast: f32,
92 subpixel_enhanced_contrast: f32,
93 is_bgr: u32,
94 _pad: u32,
95}
96
97#[derive(Clone, Debug)]
98#[repr(C)]
99struct PathSprite {
100 bounds: Bounds<ScaledPixels>,
101}
102
103#[derive(Clone, Debug)]
104#[repr(C)]
105struct PathRasterizationVertex {
106 xy_position: Point<ScaledPixels>,
107 st_position: Point<f32>,
108 color: Background,
109 bounds: Bounds<ScaledPixels>,
110}
111
112pub struct WgpuSurfaceConfig {
113 pub size: Size<DevicePixels>,
114 pub transparent: bool,
115 pub preferred_present_mode: Option<wgpu::PresentMode>,
122}
123
124struct WgpuPipelines {
125 quads: wgpu::RenderPipeline,
126 shadows: wgpu::RenderPipeline,
127 path_rasterization: wgpu::RenderPipeline,
128 paths: wgpu::RenderPipeline,
129 underlines: wgpu::RenderPipeline,
130 mono_sprites: wgpu::RenderPipeline,
131 subpixel_sprites: Option<wgpu::RenderPipeline>,
132 poly_sprites: wgpu::RenderPipeline,
133 #[allow(dead_code)]
134 surfaces: wgpu::RenderPipeline,
135}
136
137struct InstanceBinding {
139 bind_group: wgpu::BindGroup,
140 first_instance: u32,
146}
147
148struct InstanceBindings {
149 quads: InstanceBinding,
150 shadows: InstanceBinding,
151 underlines: InstanceBinding,
152 monochrome_sprites: InstanceBinding,
153 subpixel_sprites: InstanceBinding,
154 polychrome_sprites: InstanceBinding,
155}
156
157struct WgpuBindGroupLayouts {
158 globals: wgpu::BindGroupLayout,
159 instances: wgpu::BindGroupLayout,
160 texture: wgpu::BindGroupLayout,
161 surfaces: wgpu::BindGroupLayout,
162}
163
164pub type GpuContext = Rc<RefCell<Option<WgpuContext>>>;
166
167enum InstanceData {
168 Storage(wgpu::Buffer),
169 Texture {
172 texture: wgpu::Texture,
173 view: wgpu::TextureView,
174 width: u32,
175 height: u32,
176 },
177}
178
179struct WgpuResources {
181 device: Arc<wgpu::Device>,
182 queue: Arc<wgpu::Queue>,
183 surface: wgpu::Surface<'static>,
184 pipelines: WgpuPipelines,
185 bind_group_layouts: WgpuBindGroupLayouts,
186 atlas_sampler: wgpu::Sampler,
187 globals_buffer: wgpu::Buffer,
188 globals_bind_group: wgpu::BindGroup,
189 path_globals_bind_group: wgpu::BindGroup,
190 instance_data: InstanceData,
191 path_intermediate_texture: Option<wgpu::Texture>,
192 path_intermediate_view: Option<wgpu::TextureView>,
193 path_msaa_texture: Option<wgpu::Texture>,
194 path_msaa_view: Option<wgpu::TextureView>,
195}
196
197impl WgpuResources {
198 fn invalidate_intermediate_textures(&mut self) {
199 self.path_intermediate_texture = None;
200 self.path_intermediate_view = None;
201 self.path_msaa_texture = None;
202 self.path_msaa_view = None;
203 }
204}
205
206pub struct WgpuRenderer {
207 #[allow(dead_code)]
209 context: Option<GpuContext>,
210 #[allow(dead_code)]
212 compositor_gpu: Option<CompositorGpuHint>,
213 resources: Option<WgpuResources>,
214 surface_config: wgpu::SurfaceConfiguration,
215 atlas: Arc<WgpuAtlas>,
216 path_globals_offset: u64,
217 gamma_offset: u64,
218 instance_data_capacity: u64,
219 max_instance_data_size: u64,
220 instance_data_alignment: u64,
221 uses_webgl_instance_data: bool,
222 rendering_params: RenderingParameters,
223 is_bgr: bool,
224 dual_source_blending: bool,
225 adapter_info: wgpu::AdapterInfo,
226 transparent_alpha_mode: wgpu::CompositeAlphaMode,
227 opaque_alpha_mode: wgpu::CompositeAlphaMode,
228 max_texture_size: u32,
229 last_error: Arc<Mutex<Option<String>>>,
230 failed_frame_count: u32,
231 device_lost: std::sync::Arc<std::sync::atomic::AtomicBool>,
232 surface_configured: bool,
233 needs_redraw: bool,
234}
235
236impl WgpuRenderer {
237 fn resources(&self) -> &WgpuResources {
238 self.resources
239 .as_ref()
240 .expect("GPU resources not available")
241 }
242
243 fn resources_mut(&mut self) -> &mut WgpuResources {
244 self.resources
245 .as_mut()
246 .expect("GPU resources not available")
247 }
248
249 #[cfg(not(target_family = "wasm"))]
259 pub fn new<W>(
260 gpu_context: GpuContext,
261 window: &W,
262 config: WgpuSurfaceConfig,
263 compositor_gpu: Option<CompositorGpuHint>,
264 ) -> anyhow::Result<Self>
265 where
266 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
267 {
268 let window_handle = window
269 .window_handle()
270 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
271
272 let target = wgpu::SurfaceTargetUnsafe::RawHandle {
273 raw_display_handle: None,
275 raw_window_handle: window_handle.as_raw(),
276 };
277
278 let instance = gpu_context
282 .borrow()
283 .as_ref()
284 .map(|ctx| ctx.instance.clone())
285 .unwrap_or_else(|| WgpuContext::instance(Box::new(window.clone())));
286
287 let surface = unsafe {
291 instance
292 .create_surface_unsafe(target)
293 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
294 };
295
296 let mut ctx_ref = gpu_context.borrow_mut();
297 let context = match ctx_ref.as_mut() {
298 Some(context) => {
299 context.check_compatible_with_surface(&surface)?;
300 context
301 }
302 None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?),
303 };
304
305 let atlas = Arc::new(WgpuAtlas::from_context(context));
306
307 Self::new_internal(
308 Some(Rc::clone(&gpu_context)),
309 context,
310 surface,
311 config,
312 compositor_gpu,
313 atlas,
314 )
315 }
316
317 #[cfg(target_family = "wasm")]
318 pub fn new_from_canvas(
319 context: &WgpuContext,
320 canvas: &web_sys::HtmlCanvasElement,
321 config: WgpuSurfaceConfig,
322 ) -> anyhow::Result<Self> {
323 let surface = context
324 .instance
325 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
326 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
327 Self::new_from_surface(context, surface, config)
328 }
329
330 #[cfg(target_family = "wasm")]
331 #[allow(clippy::arc_with_non_send_sync)]
332 pub fn new_from_surface(
333 context: &WgpuContext,
334 surface: wgpu::Surface<'static>,
335 config: WgpuSurfaceConfig,
336 ) -> anyhow::Result<Self> {
337 let atlas = Arc::new(WgpuAtlas::from_context(context));
338 Self::new_internal(None, context, surface, config, None, atlas)
339 }
340
341 fn new_internal(
342 gpu_context: Option<GpuContext>,
343 context: &WgpuContext,
344 surface: wgpu::Surface<'static>,
345 config: WgpuSurfaceConfig,
346 compositor_gpu: Option<CompositorGpuHint>,
347 atlas: Arc<WgpuAtlas>,
348 ) -> anyhow::Result<Self> {
349 let surface_caps = surface.get_capabilities(&context.adapter);
350 let preferred_formats = [
351 wgpu::TextureFormat::Bgra8Unorm,
352 wgpu::TextureFormat::Rgba8Unorm,
353 ];
354 let surface_format = preferred_formats
355 .iter()
356 .find(|f| surface_caps.formats.contains(f))
357 .copied()
358 .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
359 .or_else(|| surface_caps.formats.first().copied())
360 .ok_or_else(|| {
361 anyhow::anyhow!(
362 "Surface reports no supported texture formats for adapter {:?}",
363 context.adapter.get_info().name
364 )
365 })?;
366
367 let pick_alpha_mode =
368 |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
369 preferences
370 .iter()
371 .find(|p| surface_caps.alpha_modes.contains(p))
372 .copied()
373 .or_else(|| surface_caps.alpha_modes.first().copied())
374 .ok_or_else(|| {
375 anyhow::anyhow!(
376 "Surface reports no supported alpha modes for adapter {:?}",
377 context.adapter.get_info().name
378 )
379 })
380 };
381
382 let transparent_alpha_mode = pick_alpha_mode(&[
383 wgpu::CompositeAlphaMode::PreMultiplied,
384 wgpu::CompositeAlphaMode::Inherit,
385 ])?;
386
387 let opaque_alpha_mode = pick_alpha_mode(&[
388 wgpu::CompositeAlphaMode::Opaque,
389 wgpu::CompositeAlphaMode::Inherit,
390 ])?;
391
392 let alpha_mode = if config.transparent {
393 transparent_alpha_mode
394 } else {
395 opaque_alpha_mode
396 };
397
398 let device = Arc::clone(&context.device);
399 let max_texture_size = device.limits().max_texture_dimension_2d;
400
401 let requested_width = config.size.width.0 as u32;
402 let requested_height = config.size.height.0 as u32;
403 let clamped_width = requested_width.min(max_texture_size);
404 let clamped_height = requested_height.min(max_texture_size);
405
406 if clamped_width != requested_width || clamped_height != requested_height {
407 warn!(
408 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
409 Clamping to ({}, {}). Window content may not fill the entire window.",
410 requested_width, requested_height, max_texture_size, clamped_width, clamped_height
411 );
412 }
413
414 let surface_config = wgpu::SurfaceConfiguration {
415 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
416 format: surface_format,
417 width: clamped_width.max(1),
418 height: clamped_height.max(1),
419 present_mode: config
420 .preferred_present_mode
421 .filter(|mode| surface_caps.present_modes.contains(mode))
422 .unwrap_or(wgpu::PresentMode::Fifo),
423 desired_maximum_frame_latency: 2,
424 alpha_mode,
425 view_formats: vec![],
426 };
427 surface.configure(&context.device, &surface_config);
430
431 let queue = Arc::clone(&context.queue);
432 let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
433 let uses_webgl_instance_data = context.uses_webgl_instance_data();
434 let dual_source_blending =
435 context.supports_dual_source_blending() && !uses_webgl_instance_data;
436 let bind_group_layouts = Self::create_bind_group_layouts(&device, uses_webgl_instance_data);
437 let pipelines = Self::create_pipelines(
438 &device,
439 &bind_group_layouts,
440 surface_format,
441 alpha_mode,
442 rendering_params.path_sample_count,
443 dual_source_blending,
444 uses_webgl_instance_data,
445 );
446
447 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
448 label: Some("atlas_sampler"),
449 mag_filter: wgpu::FilterMode::Linear,
450 min_filter: wgpu::FilterMode::Linear,
451 ..Default::default()
452 });
453
454 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
455 let globals_size = std::mem::size_of::<GlobalParams>() as u64;
456 let gamma_size = std::mem::size_of::<GammaParams>() as u64;
457 let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
458 let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
459
460 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
461 label: Some("globals_buffer"),
462 size: gamma_offset + gamma_size,
463 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
464 mapped_at_creation: false,
465 });
466
467 let (
468 instance_data,
469 instance_data_capacity,
470 max_instance_data_size,
471 instance_data_alignment,
472 ) = if uses_webgl_instance_data {
473 let max_texture_dimension = device.limits().max_texture_dimension_2d;
474 let max_instance_data_size = (u64::from(max_texture_dimension).pow(2)
475 * INSTANCE_TEXTURE_TEXEL_SIZE)
476 .min(MAX_INSTANCE_BUFFER_SIZE);
477 let initial_capacity = (2 * 1024 * 1024).min(max_instance_data_size);
478 let (instance_data, capacity) =
479 Self::create_instance_texture(&device, initial_capacity, max_texture_dimension);
480 (
481 instance_data,
482 capacity,
483 max_instance_data_size,
484 INSTANCE_TEXTURE_TEXEL_SIZE,
485 )
486 } else {
487 let max_buffer_size = device
490 .limits()
491 .max_buffer_size
492 .min(device.limits().max_storage_buffer_binding_size)
493 .min(MAX_INSTANCE_BUFFER_SIZE);
494 let initial_capacity = (2 * 1024 * 1024).min(max_buffer_size);
495 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
496 label: Some("instance_buffer"),
497 size: initial_capacity,
498 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
499 mapped_at_creation: false,
500 });
501 (
502 InstanceData::Storage(buffer),
503 initial_capacity,
504 max_buffer_size,
505 device.limits().min_storage_buffer_offset_alignment as u64,
506 )
507 };
508
509 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
510 label: Some("globals_bind_group"),
511 layout: &bind_group_layouts.globals,
512 entries: &[
513 wgpu::BindGroupEntry {
514 binding: 0,
515 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
516 buffer: &globals_buffer,
517 offset: 0,
518 size: Some(NonZeroU64::new(globals_size).unwrap()),
519 }),
520 },
521 wgpu::BindGroupEntry {
522 binding: 1,
523 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
524 buffer: &globals_buffer,
525 offset: gamma_offset,
526 size: Some(NonZeroU64::new(gamma_size).unwrap()),
527 }),
528 },
529 ],
530 });
531
532 let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
533 label: Some("path_globals_bind_group"),
534 layout: &bind_group_layouts.globals,
535 entries: &[
536 wgpu::BindGroupEntry {
537 binding: 0,
538 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
539 buffer: &globals_buffer,
540 offset: path_globals_offset,
541 size: Some(NonZeroU64::new(globals_size).unwrap()),
542 }),
543 },
544 wgpu::BindGroupEntry {
545 binding: 1,
546 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
547 buffer: &globals_buffer,
548 offset: gamma_offset,
549 size: Some(NonZeroU64::new(gamma_size).unwrap()),
550 }),
551 },
552 ],
553 });
554
555 let adapter_info = context.adapter.get_info();
556
557 let last_error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
558 let last_error_clone = Arc::clone(&last_error);
559 device.on_uncaptured_error(Arc::new(move |error| {
560 let mut guard = last_error_clone.lock().unwrap();
561 *guard = Some(error.to_string());
562 }));
563
564 let resources = WgpuResources {
565 device,
566 queue,
567 surface,
568 pipelines,
569 bind_group_layouts,
570 atlas_sampler,
571 globals_buffer,
572 globals_bind_group,
573 path_globals_bind_group,
574 instance_data,
575 path_intermediate_texture: None,
578 path_intermediate_view: None,
579 path_msaa_texture: None,
580 path_msaa_view: None,
581 };
582
583 Ok(Self {
584 context: gpu_context,
585 compositor_gpu,
586 resources: Some(resources),
587 surface_config,
588 atlas,
589 path_globals_offset,
590 gamma_offset,
591 instance_data_capacity,
592 max_instance_data_size,
593 instance_data_alignment,
594 uses_webgl_instance_data,
595 rendering_params,
596 is_bgr: false,
597 dual_source_blending,
598 adapter_info,
599 transparent_alpha_mode,
600 opaque_alpha_mode,
601 max_texture_size,
602 last_error,
603 failed_frame_count: 0,
604 device_lost: context.device_lost_flag(),
605 surface_configured: true,
606 needs_redraw: false,
607 })
608 }
609
610 fn create_bind_group_layouts(
611 device: &wgpu::Device,
612 uses_webgl_instance_data: bool,
613 ) -> WgpuBindGroupLayouts {
614 let globals =
615 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
616 label: Some("globals_layout"),
617 entries: &[
618 wgpu::BindGroupLayoutEntry {
619 binding: 0,
620 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
621 ty: wgpu::BindingType::Buffer {
622 ty: wgpu::BufferBindingType::Uniform,
623 has_dynamic_offset: false,
624 min_binding_size: NonZeroU64::new(
625 std::mem::size_of::<GlobalParams>() as u64
626 ),
627 },
628 count: None,
629 },
630 wgpu::BindGroupLayoutEntry {
631 binding: 1,
632 visibility: wgpu::ShaderStages::FRAGMENT,
633 ty: wgpu::BindingType::Buffer {
634 ty: wgpu::BufferBindingType::Uniform,
635 has_dynamic_offset: false,
636 min_binding_size: NonZeroU64::new(
637 std::mem::size_of::<GammaParams>() as u64
638 ),
639 },
640 count: None,
641 },
642 ],
643 });
644
645 let instance_data_entry = wgpu::BindGroupLayoutEntry {
646 binding: 0,
647 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
648 ty: if uses_webgl_instance_data {
649 wgpu::BindingType::Texture {
650 sample_type: wgpu::TextureSampleType::Uint,
651 view_dimension: wgpu::TextureViewDimension::D2,
652 multisampled: false,
653 }
654 } else {
655 wgpu::BindingType::Buffer {
656 ty: wgpu::BufferBindingType::Storage { read_only: true },
657 has_dynamic_offset: false,
658 min_binding_size: None,
659 }
660 },
661 count: None,
662 };
663
664 let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
665 label: Some("instances_layout"),
666 entries: &[instance_data_entry],
667 });
668
669 let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
670 label: Some("texture_layout"),
671 entries: &[
672 wgpu::BindGroupLayoutEntry {
673 binding: 0,
674 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
675 ty: wgpu::BindingType::Texture {
676 sample_type: wgpu::TextureSampleType::Float { filterable: true },
677 view_dimension: wgpu::TextureViewDimension::D2,
678 multisampled: false,
679 },
680 count: None,
681 },
682 wgpu::BindGroupLayoutEntry {
683 binding: 1,
684 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
685 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
686 count: None,
687 },
688 ],
689 });
690
691 let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
692 label: Some("surfaces_layout"),
693 entries: &[
694 wgpu::BindGroupLayoutEntry {
695 binding: 0,
696 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
697 ty: wgpu::BindingType::Buffer {
698 ty: wgpu::BufferBindingType::Uniform,
699 has_dynamic_offset: false,
700 min_binding_size: NonZeroU64::new(
701 std::mem::size_of::<SurfaceParams>() as u64
702 ),
703 },
704 count: None,
705 },
706 wgpu::BindGroupLayoutEntry {
707 binding: 1,
708 visibility: wgpu::ShaderStages::FRAGMENT,
709 ty: wgpu::BindingType::Texture {
710 sample_type: wgpu::TextureSampleType::Float { filterable: true },
711 view_dimension: wgpu::TextureViewDimension::D2,
712 multisampled: false,
713 },
714 count: None,
715 },
716 wgpu::BindGroupLayoutEntry {
717 binding: 2,
718 visibility: wgpu::ShaderStages::FRAGMENT,
719 ty: wgpu::BindingType::Texture {
720 sample_type: wgpu::TextureSampleType::Float { filterable: true },
721 view_dimension: wgpu::TextureViewDimension::D2,
722 multisampled: false,
723 },
724 count: None,
725 },
726 wgpu::BindGroupLayoutEntry {
727 binding: 3,
728 visibility: wgpu::ShaderStages::FRAGMENT,
729 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
730 count: None,
731 },
732 ],
733 });
734
735 WgpuBindGroupLayouts {
736 globals,
737 instances,
738 texture,
739 surfaces,
740 }
741 }
742
743 fn create_instance_texture(
744 device: &wgpu::Device,
745 requested_capacity: u64,
746 max_texture_dimension: u32,
747 ) -> (InstanceData, u64) {
748 let texel_count = requested_capacity.div_ceil(INSTANCE_TEXTURE_TEXEL_SIZE);
749 let width = texel_count.min(u64::from(max_texture_dimension)).max(1) as u32;
750 let height = texel_count
751 .div_ceil(u64::from(width))
752 .min(u64::from(max_texture_dimension))
753 .max(1) as u32;
754 let texture = device.create_texture(&wgpu::TextureDescriptor {
755 label: Some("instance_texture"),
756 size: wgpu::Extent3d {
757 width,
758 height,
759 depth_or_array_layers: 1,
760 },
761 mip_level_count: 1,
762 sample_count: 1,
763 dimension: wgpu::TextureDimension::D2,
764 format: wgpu::TextureFormat::Rgba32Uint,
765 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
766 view_formats: &[],
767 });
768 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
769 let capacity = u64::from(width) * u64::from(height) * INSTANCE_TEXTURE_TEXEL_SIZE;
770 (
771 InstanceData::Texture {
772 texture,
773 view,
774 width,
775 height,
776 },
777 capacity,
778 )
779 }
780
781 fn create_pipelines(
782 device: &wgpu::Device,
783 layouts: &WgpuBindGroupLayouts,
784 surface_format: wgpu::TextureFormat,
785 alpha_mode: wgpu::CompositeAlphaMode,
786 path_sample_count: u32,
787 dual_source_blending: bool,
788 uses_webgl_instance_data: bool,
789 ) -> WgpuPipelines {
790 let device_has_feature = device
799 .features()
800 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
801 if dual_source_blending && !device_has_feature {
802 log::error!(
803 "BUG: dual_source_blending flag is true but device does not \
804 have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \
805 Falling back to mono text rendering. Please report this at \
806 https://github.com/zed-industries/zed/issues",
807 device.features(),
808 );
809 }
810 let dual_source_blending =
811 dual_source_blending && device_has_feature && !uses_webgl_instance_data;
812
813 let shader_source = if uses_webgl_instance_data {
814 WEBGL_SHADERS
815 } else {
816 STORAGE_BUFFER_SHADERS
817 };
818 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
819 label: Some("gpui_shaders"),
820 source: wgpu::ShaderSource::Wgsl(shader_source.into()),
821 });
822
823 let subpixel_shader_module = if dual_source_blending {
824 Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
825 label: Some("gpui_subpixel_shaders"),
826 source: wgpu::ShaderSource::Wgsl(SUBPIXEL_SHADERS.into()),
827 }))
828 } else {
829 None
830 };
831
832 let blend_mode = match alpha_mode {
833 wgpu::CompositeAlphaMode::PreMultiplied => {
834 wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
835 }
836 _ => wgpu::BlendState::ALPHA_BLENDING,
837 };
838
839 let color_target = wgpu::ColorTargetState {
840 format: surface_format,
841 blend: Some(blend_mode),
842 write_mask: wgpu::ColorWrites::ALL,
843 };
844
845 let create_pipeline = |name: &str,
846 vs_entry: &str,
847 fs_entry: &str,
848 globals_layout: &wgpu::BindGroupLayout,
849 data_layout: &wgpu::BindGroupLayout,
850 texture_layout: Option<&wgpu::BindGroupLayout>,
851 topology: wgpu::PrimitiveTopology,
852 color_targets: &[Option<wgpu::ColorTargetState>],
853 sample_count: u32,
854 module: &wgpu::ShaderModule| {
855 let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
856 bind_group_layouts.extend(texture_layout.map(Some));
857 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
858 label: Some(&format!("{name}_layout")),
859 bind_group_layouts: &bind_group_layouts,
860 immediate_size: 0,
861 });
862
863 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
864 label: Some(name),
865 layout: Some(&pipeline_layout),
866 vertex: wgpu::VertexState {
867 module,
868 entry_point: Some(vs_entry),
869 buffers: &[],
870 compilation_options: wgpu::PipelineCompilationOptions::default(),
871 },
872 fragment: Some(wgpu::FragmentState {
873 module,
874 entry_point: Some(fs_entry),
875 targets: color_targets,
876 compilation_options: wgpu::PipelineCompilationOptions::default(),
877 }),
878 primitive: wgpu::PrimitiveState {
879 topology,
880 strip_index_format: None,
881 front_face: wgpu::FrontFace::Ccw,
882 cull_mode: None,
883 polygon_mode: wgpu::PolygonMode::Fill,
884 unclipped_depth: false,
885 conservative: false,
886 },
887 depth_stencil: None,
888 multisample: wgpu::MultisampleState {
889 count: sample_count,
890 mask: !0,
891 alpha_to_coverage_enabled: false,
892 },
893 multiview_mask: None,
894 cache: None,
895 })
896 };
897
898 let quads = create_pipeline(
899 "quads",
900 "vs_quad",
901 "fs_quad",
902 &layouts.globals,
903 &layouts.instances,
904 None,
905 wgpu::PrimitiveTopology::TriangleStrip,
906 &[Some(color_target.clone())],
907 1,
908 &shader_module,
909 );
910
911 let shadows = create_pipeline(
912 "shadows",
913 "vs_shadow",
914 "fs_shadow",
915 &layouts.globals,
916 &layouts.instances,
917 None,
918 wgpu::PrimitiveTopology::TriangleStrip,
919 &[Some(color_target.clone())],
920 1,
921 &shader_module,
922 );
923
924 let path_rasterization = create_pipeline(
925 "path_rasterization",
926 "vs_path_rasterization",
927 "fs_path_rasterization",
928 &layouts.globals,
929 &layouts.instances,
930 None,
931 wgpu::PrimitiveTopology::TriangleList,
932 &[Some(wgpu::ColorTargetState {
933 format: surface_format,
934 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
935 write_mask: wgpu::ColorWrites::ALL,
936 })],
937 path_sample_count,
938 &shader_module,
939 );
940
941 let paths_blend = wgpu::BlendState {
942 color: wgpu::BlendComponent {
943 src_factor: wgpu::BlendFactor::One,
944 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
945 operation: wgpu::BlendOperation::Add,
946 },
947 alpha: wgpu::BlendComponent {
948 src_factor: wgpu::BlendFactor::One,
949 dst_factor: wgpu::BlendFactor::One,
950 operation: wgpu::BlendOperation::Add,
951 },
952 };
953
954 let paths = create_pipeline(
955 "paths",
956 "vs_path",
957 "fs_path",
958 &layouts.globals,
959 &layouts.instances,
960 Some(&layouts.texture),
961 wgpu::PrimitiveTopology::TriangleStrip,
962 &[Some(wgpu::ColorTargetState {
963 format: surface_format,
964 blend: Some(paths_blend),
965 write_mask: wgpu::ColorWrites::ALL,
966 })],
967 1,
968 &shader_module,
969 );
970
971 let underlines = create_pipeline(
972 "underlines",
973 "vs_underline",
974 "fs_underline",
975 &layouts.globals,
976 &layouts.instances,
977 None,
978 wgpu::PrimitiveTopology::TriangleStrip,
979 &[Some(color_target.clone())],
980 1,
981 &shader_module,
982 );
983
984 let mono_sprites = create_pipeline(
985 "mono_sprites",
986 "vs_mono_sprite",
987 "fs_mono_sprite",
988 &layouts.globals,
989 &layouts.instances,
990 Some(&layouts.texture),
991 wgpu::PrimitiveTopology::TriangleStrip,
992 &[Some(color_target.clone())],
993 1,
994 &shader_module,
995 );
996
997 let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
998 let subpixel_blend = wgpu::BlendState {
999 color: wgpu::BlendComponent {
1000 src_factor: wgpu::BlendFactor::Src1,
1001 dst_factor: wgpu::BlendFactor::OneMinusSrc1,
1002 operation: wgpu::BlendOperation::Add,
1003 },
1004 alpha: wgpu::BlendComponent {
1005 src_factor: wgpu::BlendFactor::One,
1006 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1007 operation: wgpu::BlendOperation::Add,
1008 },
1009 };
1010
1011 Some(create_pipeline(
1012 "subpixel_sprites",
1013 "vs_subpixel_sprite",
1014 "fs_subpixel_sprite",
1015 &layouts.globals,
1016 &layouts.instances,
1017 Some(&layouts.texture),
1018 wgpu::PrimitiveTopology::TriangleStrip,
1019 &[Some(wgpu::ColorTargetState {
1020 format: surface_format,
1021 blend: Some(subpixel_blend),
1022 write_mask: wgpu::ColorWrites::COLOR,
1023 })],
1024 1,
1025 subpixel_module,
1026 ))
1027 } else {
1028 None
1029 };
1030
1031 let poly_sprites = create_pipeline(
1032 "poly_sprites",
1033 "vs_poly_sprite",
1034 "fs_poly_sprite",
1035 &layouts.globals,
1036 &layouts.instances,
1037 Some(&layouts.texture),
1038 wgpu::PrimitiveTopology::TriangleStrip,
1039 &[Some(color_target.clone())],
1040 1,
1041 &shader_module,
1042 );
1043
1044 let surfaces = create_pipeline(
1045 "surfaces",
1046 "vs_surface",
1047 "fs_surface",
1048 &layouts.globals,
1049 &layouts.surfaces,
1050 None,
1051 wgpu::PrimitiveTopology::TriangleStrip,
1052 &[Some(color_target)],
1053 1,
1054 &shader_module,
1055 );
1056
1057 WgpuPipelines {
1058 quads,
1059 shadows,
1060 path_rasterization,
1061 paths,
1062 underlines,
1063 mono_sprites,
1064 subpixel_sprites,
1065 poly_sprites,
1066 surfaces,
1067 }
1068 }
1069
1070 fn create_path_intermediate(
1071 device: &wgpu::Device,
1072 format: wgpu::TextureFormat,
1073 width: u32,
1074 height: u32,
1075 ) -> (wgpu::Texture, wgpu::TextureView) {
1076 let texture = device.create_texture(&wgpu::TextureDescriptor {
1077 label: Some("path_intermediate"),
1078 size: wgpu::Extent3d {
1079 width: width.max(1),
1080 height: height.max(1),
1081 depth_or_array_layers: 1,
1082 },
1083 mip_level_count: 1,
1084 sample_count: 1,
1085 dimension: wgpu::TextureDimension::D2,
1086 format,
1087 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1088 view_formats: &[],
1089 });
1090 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1091 (texture, view)
1092 }
1093
1094 fn create_msaa_if_needed(
1095 device: &wgpu::Device,
1096 format: wgpu::TextureFormat,
1097 width: u32,
1098 height: u32,
1099 sample_count: u32,
1100 ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
1101 if sample_count <= 1 {
1102 return None;
1103 }
1104 let texture = device.create_texture(&wgpu::TextureDescriptor {
1105 label: Some("path_msaa"),
1106 size: wgpu::Extent3d {
1107 width: width.max(1),
1108 height: height.max(1),
1109 depth_or_array_layers: 1,
1110 },
1111 mip_level_count: 1,
1112 sample_count,
1113 dimension: wgpu::TextureDimension::D2,
1114 format,
1115 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1116 view_formats: &[],
1117 });
1118 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1119 Some((texture, view))
1120 }
1121
1122 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
1123 let width = size.width.0 as u32;
1124 let height = size.height.0 as u32;
1125
1126 if width != self.surface_config.width || height != self.surface_config.height {
1127 let clamped_width = width.min(self.max_texture_size);
1128 let clamped_height = height.min(self.max_texture_size);
1129
1130 if clamped_width != width || clamped_height != height {
1131 warn!(
1132 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
1133 Clamping to ({}, {}). Window content may not fill the entire window.",
1134 width, height, self.max_texture_size, clamped_width, clamped_height
1135 );
1136 }
1137
1138 self.surface_config.width = clamped_width.max(1);
1139 self.surface_config.height = clamped_height.max(1);
1140 let surface_config = self.surface_config.clone();
1141
1142 let Some(resources) = self.resources.as_mut() else {
1143 return;
1144 };
1145
1146 if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
1148 submission_index: None,
1149 timeout: None,
1150 }) {
1151 warn!("Failed to poll device during resize: {e:?}");
1152 }
1153
1154 if let Some(ref texture) = resources.path_intermediate_texture {
1156 texture.destroy();
1157 }
1158 if let Some(ref texture) = resources.path_msaa_texture {
1159 texture.destroy();
1160 }
1161
1162 resources
1163 .surface
1164 .configure(&resources.device, &surface_config);
1165
1166 resources.invalidate_intermediate_textures();
1170 }
1171 }
1172
1173 fn ensure_intermediate_textures(&mut self) {
1174 if self.resources().path_intermediate_texture.is_some() {
1175 return;
1176 }
1177
1178 let format = self.surface_config.format;
1179 let width = self.surface_config.width;
1180 let height = self.surface_config.height;
1181 let path_sample_count = self.rendering_params.path_sample_count;
1182 let resources = self.resources_mut();
1183
1184 let (t, v) = Self::create_path_intermediate(&resources.device, format, width, height);
1185 resources.path_intermediate_texture = Some(t);
1186 resources.path_intermediate_view = Some(v);
1187
1188 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
1189 &resources.device,
1190 format,
1191 width,
1192 height,
1193 path_sample_count,
1194 )
1195 .map(|(t, v)| (Some(t), Some(v)))
1196 .unwrap_or((None, None));
1197 resources.path_msaa_texture = path_msaa_texture;
1198 resources.path_msaa_view = path_msaa_view;
1199 }
1200
1201 pub fn set_subpixel_layout(&mut self, is_bgr: bool) {
1202 self.is_bgr = is_bgr;
1203 }
1204
1205 pub fn update_transparency(&mut self, transparent: bool) {
1206 let new_alpha_mode = if transparent {
1207 self.transparent_alpha_mode
1208 } else {
1209 self.opaque_alpha_mode
1210 };
1211
1212 if new_alpha_mode != self.surface_config.alpha_mode {
1213 self.surface_config.alpha_mode = new_alpha_mode;
1214 let surface_config = self.surface_config.clone();
1215 let path_sample_count = self.rendering_params.path_sample_count;
1216 let dual_source_blending = self.dual_source_blending;
1217 let uses_webgl_instance_data = self.uses_webgl_instance_data;
1218 let Some(resources) = self.resources.as_mut() else {
1219 return;
1220 };
1221 resources
1222 .surface
1223 .configure(&resources.device, &surface_config);
1224 resources.pipelines = Self::create_pipelines(
1225 &resources.device,
1226 &resources.bind_group_layouts,
1227 surface_config.format,
1228 surface_config.alpha_mode,
1229 path_sample_count,
1230 dual_source_blending,
1231 uses_webgl_instance_data,
1232 );
1233 }
1234 }
1235
1236 #[allow(dead_code)]
1237 pub fn viewport_size(&self) -> Size<DevicePixels> {
1238 Size {
1239 width: DevicePixels(self.surface_config.width as i32),
1240 height: DevicePixels(self.surface_config.height as i32),
1241 }
1242 }
1243
1244 pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
1245 &self.atlas
1246 }
1247
1248 pub fn supports_dual_source_blending(&self) -> bool {
1249 self.dual_source_blending
1250 }
1251
1252 pub fn gpu_specs(&self) -> GpuSpecs {
1253 GpuSpecs {
1254 is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
1255 device_name: self.adapter_info.name.clone(),
1256 driver_name: self.adapter_info.driver.clone(),
1257 driver_info: self.adapter_info.driver_info.clone(),
1258 }
1259 }
1260
1261 pub fn max_texture_size(&self) -> u32 {
1262 self.max_texture_size
1263 }
1264
1265 pub fn draw(&mut self, scene: &Scene) -> bool {
1266 #[cfg(target_family = "wasm")]
1267 if self.device_lost() {
1268 if self.surface_configured {
1269 log::error!(
1270 "Browser graphics context was lost; rendering has stopped. Reload the page to recover."
1271 );
1272 self.surface_configured = false;
1273 }
1274 return false;
1275 }
1276
1277 if !self.surface_configured {
1282 return false;
1283 }
1284
1285 let last_error = self.last_error.lock().unwrap().take();
1286 if let Some(error) = last_error {
1287 self.failed_frame_count += 1;
1288 log::error!(
1289 "GPU error during frame (failure {} of 10): {error}",
1290 self.failed_frame_count
1291 );
1292
1293 if self.failed_frame_count > 10 {
1295 panic!("Too many consecutive GPU errors. Last error: {error}");
1296 } else if self.failed_frame_count > 5 {
1297 if let Some(res) = self.resources.as_mut() {
1298 res.invalidate_intermediate_textures();
1299 }
1300 self.atlas.clear();
1301 self.needs_redraw = true;
1302 self.failed_frame_count = 0;
1303 return false;
1304 }
1305 } else {
1306 self.failed_frame_count = 0;
1307 }
1308
1309 self.atlas.before_frame();
1310
1311 let frame = match self.resources().surface.get_current_texture() {
1312 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
1313 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
1314 drop(frame);
1316 let surface_config = self.surface_config.clone();
1317 let resources = self.resources_mut();
1318 resources
1319 .surface
1320 .configure(&resources.device, &surface_config);
1321 return false;
1322 }
1323 wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
1324 let surface_config = self.surface_config.clone();
1325 let resources = self.resources_mut();
1326 resources
1327 .surface
1328 .configure(&resources.device, &surface_config);
1329 return false;
1330 }
1331 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
1332 return false;
1333 }
1334 wgpu::CurrentSurfaceTexture::Validation => {
1335 *self.last_error.lock().unwrap() =
1336 Some("Surface texture validation error".to_string());
1337 return false;
1338 }
1339 };
1340
1341 self.ensure_intermediate_textures();
1343
1344 let frame_view = frame
1345 .texture
1346 .create_view(&wgpu::TextureViewDescriptor::default());
1347
1348 let gamma_params = GammaParams {
1349 gamma_ratios: self.rendering_params.gamma_ratios,
1350 grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1351 subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1352 is_bgr: self.is_bgr as u32,
1353 _pad: 0,
1354 };
1355
1356 let globals = GlobalParams {
1357 viewport_size: [
1358 self.surface_config.width as f32,
1359 self.surface_config.height as f32,
1360 ],
1361 premultiplied_alpha: if self.surface_config.alpha_mode
1362 == wgpu::CompositeAlphaMode::PreMultiplied
1363 {
1364 1
1365 } else {
1366 0
1367 },
1368 pad: 0,
1369 };
1370
1371 let path_globals = GlobalParams {
1372 premultiplied_alpha: 0,
1373 ..globals
1374 };
1375
1376 {
1377 let resources = self.resources();
1378 resources.queue.write_buffer(
1379 &resources.globals_buffer,
1380 0,
1381 bytemuck::bytes_of(&globals),
1382 );
1383 resources.queue.write_buffer(
1384 &resources.globals_buffer,
1385 self.path_globals_offset,
1386 bytemuck::bytes_of(&path_globals),
1387 );
1388 resources.queue.write_buffer(
1389 &resources.globals_buffer,
1390 self.gamma_offset,
1391 bytemuck::bytes_of(&gamma_params),
1392 );
1393 }
1394
1395 if let Err(error) = self.record_frame(scene, &frame_view) {
1396 log::error!("{error:#}");
1397 self.resources().queue.submit(std::iter::empty());
1398 return false;
1399 }
1400
1401 frame.present();
1402 true
1403 }
1404
1405 fn record_frame(&mut self, scene: &Scene, frame_view: &wgpu::TextureView) -> Result<()> {
1406 let mut instance_offset = 0;
1407 let instance_bindings = self
1408 .write_instances(scene, &mut instance_offset)
1409 .with_context(|| {
1410 format!(
1411 "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1412 scene.paths.len(),
1413 scene.shadows.len(),
1414 scene.quads.len(),
1415 scene.underlines.len(),
1416 scene.monochrome_sprites.len(),
1417 scene.subpixel_sprites.len(),
1418 scene.polychrome_sprites.len(),
1419 )
1420 })?;
1421
1422 let mut encoder =
1423 self.resources()
1424 .device
1425 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1426 label: Some("main_encoder"),
1427 });
1428
1429 {
1430 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1431 label: Some("main_pass"),
1432 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1433 view: frame_view,
1434 resolve_target: None,
1435 ops: wgpu::Operations {
1436 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1437 store: wgpu::StoreOp::Store,
1438 },
1439 depth_slice: None,
1440 })],
1441 depth_stencil_attachment: None,
1442 ..Default::default()
1443 });
1444
1445 for batch in scene.batches() {
1446 match batch {
1447 PrimitiveBatch::Quads(range) => self.draw_instances(
1448 &instance_bindings.quads,
1449 &self.resources().pipelines.quads,
1450 instance_range(range),
1451 &mut pass,
1452 ),
1453 PrimitiveBatch::Shadows(range) => self.draw_instances(
1454 &instance_bindings.shadows,
1455 &self.resources().pipelines.shadows,
1456 instance_range(range),
1457 &mut pass,
1458 ),
1459 PrimitiveBatch::Paths(range) => {
1460 let paths = &scene.paths[range];
1461 if paths.is_empty() {
1462 continue;
1463 }
1464
1465 drop(pass);
1466 let rasterized = self.draw_paths_to_intermediate(
1467 &mut encoder,
1468 paths,
1469 &mut instance_offset,
1470 )?;
1471
1472 pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1473 label: Some("main_pass_continued"),
1474 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1475 view: frame_view,
1476 resolve_target: None,
1477 ops: wgpu::Operations {
1478 load: wgpu::LoadOp::Load,
1479 store: wgpu::StoreOp::Store,
1480 },
1481 depth_slice: None,
1482 })],
1483 depth_stencil_attachment: None,
1484 ..Default::default()
1485 });
1486
1487 if rasterized {
1488 self.draw_paths_from_intermediate(
1489 paths,
1490 &mut instance_offset,
1491 &mut pass,
1492 )?;
1493 }
1494 }
1495 PrimitiveBatch::Underlines(range) => self.draw_instances(
1496 &instance_bindings.underlines,
1497 &self.resources().pipelines.underlines,
1498 instance_range(range),
1499 &mut pass,
1500 ),
1501 PrimitiveBatch::MonochromeSprites { texture_id, range } => self.draw_sprites(
1502 &instance_bindings.monochrome_sprites,
1503 texture_id,
1504 &self.resources().pipelines.mono_sprites,
1505 instance_range(range),
1506 &mut pass,
1507 ),
1508 PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1509 let resources = self.resources();
1510 self.draw_sprites(
1511 &instance_bindings.subpixel_sprites,
1512 texture_id,
1513 resources
1514 .pipelines
1515 .subpixel_sprites
1516 .as_ref()
1517 .unwrap_or(&resources.pipelines.mono_sprites),
1518 instance_range(range),
1519 &mut pass,
1520 );
1521 }
1522 PrimitiveBatch::PolychromeSprites { texture_id, range } => self.draw_sprites(
1523 &instance_bindings.polychrome_sprites,
1524 texture_id,
1525 &self.resources().pipelines.poly_sprites,
1526 instance_range(range),
1527 &mut pass,
1528 ),
1529 PrimitiveBatch::Surfaces(_surfaces) => {}
1532 }
1533 }
1534 }
1535
1536 self.resources()
1537 .queue
1538 .submit(std::iter::once(encoder.finish()));
1539 Ok(())
1540 }
1541
1542 fn write_instances(
1543 &mut self,
1544 scene: &Scene,
1545 instance_offset: &mut u64,
1546 ) -> Result<InstanceBindings> {
1547 Ok(InstanceBindings {
1548 quads: self.write_instance_binding(
1549 "quads_bind_group",
1550 instance_offset,
1551 &scene.quads,
1552 )?,
1553 shadows: self.write_instance_binding(
1554 "shadows_bind_group",
1555 instance_offset,
1556 &scene.shadows,
1557 )?,
1558 underlines: self.write_instance_binding(
1559 "underlines_bind_group",
1560 instance_offset,
1561 &scene.underlines,
1562 )?,
1563 monochrome_sprites: self.write_instance_binding(
1564 "monochrome_sprites_bind_group",
1565 instance_offset,
1566 &scene.monochrome_sprites,
1567 )?,
1568 subpixel_sprites: self.write_instance_binding(
1569 "subpixel_sprites_bind_group",
1570 instance_offset,
1571 &scene.subpixel_sprites,
1572 )?,
1573 polychrome_sprites: self.write_instance_binding(
1574 "polychrome_sprites_bind_group",
1575 instance_offset,
1576 &scene.polychrome_sprites,
1577 )?,
1578 })
1579 }
1580
1581 fn create_texture_bind_group(
1582 &self,
1583 label: &str,
1584 texture_view: &wgpu::TextureView,
1585 ) -> wgpu::BindGroup {
1586 let resources = self.resources();
1587 resources
1588 .device
1589 .create_bind_group(&wgpu::BindGroupDescriptor {
1590 label: Some(label),
1591 layout: &resources.bind_group_layouts.texture,
1592 entries: &[
1593 wgpu::BindGroupEntry {
1594 binding: 0,
1595 resource: wgpu::BindingResource::TextureView(texture_view),
1596 },
1597 wgpu::BindGroupEntry {
1598 binding: 1,
1599 resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1600 },
1601 ],
1602 })
1603 }
1604
1605 fn draw_instances(
1606 &self,
1607 instances: &InstanceBinding,
1608 pipeline: &wgpu::RenderPipeline,
1609 range: Range<u32>,
1610 pass: &mut wgpu::RenderPass<'_>,
1611 ) {
1612 if range.is_empty() {
1613 return;
1614 }
1615 pass.set_pipeline(pipeline);
1616 pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1617 pass.set_bind_group(1, &instances.bind_group, &[]);
1618 pass.draw(
1619 0..4,
1620 instances.first_instance + range.start..instances.first_instance + range.end,
1621 );
1622 }
1623
1624 fn draw_sprites(
1625 &self,
1626 sprite_instances: &InstanceBinding,
1627 texture_id: AtlasTextureId,
1628 pipeline: &wgpu::RenderPipeline,
1629 range: Range<u32>,
1630 pass: &mut wgpu::RenderPass<'_>,
1631 ) {
1632 if range.is_empty() {
1633 return;
1634 }
1635 let texture_info = self.atlas.get_texture_info(texture_id);
1636 let texture =
1637 self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
1638 pass.set_pipeline(pipeline);
1639 pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1640 pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
1641 pass.set_bind_group(2, &texture, &[]);
1642 pass.draw(
1643 0..4,
1644 sprite_instances.first_instance + range.start
1645 ..sprite_instances.first_instance + range.end,
1646 );
1647 }
1648
1649 unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
1650 unsafe {
1651 std::slice::from_raw_parts(
1652 instances.as_ptr() as *const u8,
1653 std::mem::size_of_val(instances),
1654 )
1655 }
1656 }
1657
1658 fn draw_paths_from_intermediate(
1659 &mut self,
1660 paths: &[Path<ScaledPixels>],
1661 instance_offset: &mut u64,
1662 pass: &mut wgpu::RenderPass<'_>,
1663 ) -> Result<()> {
1664 let first_path = &paths[0];
1665 let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
1666 {
1667 paths
1668 .iter()
1669 .map(|p| PathSprite {
1670 bounds: p.clipped_bounds(),
1671 })
1672 .collect()
1673 } else {
1674 let mut bounds = first_path.clipped_bounds();
1675 for path in paths.iter().skip(1) {
1676 bounds = bounds.union(&path.clipped_bounds());
1677 }
1678 vec![PathSprite { bounds }]
1679 };
1680
1681 let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
1682 return Ok(());
1683 };
1684 let instances =
1685 self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
1686 let texture = self.create_texture_bind_group(
1687 "path_intermediate_texture_bind_group",
1688 &path_intermediate_view,
1689 );
1690 let resources = self.resources();
1691 pass.set_pipeline(&resources.pipelines.paths);
1692 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1693 pass.set_bind_group(1, &instances.bind_group, &[]);
1694 pass.set_bind_group(2, &texture, &[]);
1695 pass.draw(
1696 0..4,
1697 instances.first_instance..instances.first_instance + sprites.len() as u32,
1698 );
1699 Ok(())
1700 }
1701
1702 fn draw_paths_to_intermediate(
1703 &mut self,
1704 encoder: &mut wgpu::CommandEncoder,
1705 paths: &[Path<ScaledPixels>],
1706 instance_offset: &mut u64,
1707 ) -> Result<bool> {
1708 let mut vertices = Vec::new();
1709 for path in paths {
1710 let bounds = path.clipped_bounds();
1711 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
1712 xy_position: v.xy_position,
1713 st_position: v.st_position,
1714 color: path.color,
1715 bounds,
1716 }));
1717 }
1718
1719 if vertices.is_empty() {
1720 return Ok(false);
1721 }
1722
1723 let vertex_binding = self.write_instance_binding(
1724 "path_rasterization_bind_group",
1725 instance_offset,
1726 &vertices,
1727 )?;
1728
1729 let resources = self.resources();
1730 let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
1731 return Ok(false);
1732 };
1733
1734 let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
1735 (msaa_view, Some(path_intermediate_view))
1736 } else {
1737 (path_intermediate_view, None)
1738 };
1739
1740 {
1741 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1742 label: Some("path_rasterization_pass"),
1743 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1744 view: target_view,
1745 resolve_target,
1746 ops: wgpu::Operations {
1747 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1748 store: wgpu::StoreOp::Store,
1749 },
1750 depth_slice: None,
1751 })],
1752 depth_stencil_attachment: None,
1753 ..Default::default()
1754 });
1755
1756 pass.set_pipeline(&resources.pipelines.path_rasterization);
1757 pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
1758 pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
1759 pass.draw(
1763 vertex_binding.first_instance
1764 ..vertex_binding.first_instance + vertices.len() as u32,
1765 0..1,
1766 );
1767 }
1768
1769 Ok(true)
1770 }
1771
1772 fn write_instance_binding<T>(
1773 &mut self,
1774 label: &str,
1775 instance_offset: &mut u64,
1776 instances: &[T],
1777 ) -> Result<InstanceBinding> {
1778 let data = unsafe { Self::instance_bytes(instances) };
1779 let size = (data.len() as u64).max(16);
1782 let stride = (std::mem::size_of::<T>() as u64).max(1);
1783 let (alignment, allocation_size) = if self.uses_webgl_instance_data {
1784 (
1790 least_common_multiple(self.instance_data_alignment, stride),
1791 size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
1792 )
1793 } else {
1794 (self.instance_data_alignment.max(1), size)
1795 };
1796 let mut offset = (*instance_offset).next_multiple_of(alignment);
1797 if offset + allocation_size > self.instance_data_capacity {
1798 self.grow_instance_data(allocation_size)?;
1799 offset = 0;
1800 }
1801 *instance_offset = offset + allocation_size;
1802
1803 let first_instance = if self.uses_webgl_instance_data {
1804 u32::try_from(offset / stride).context("instance index exceeds u32 range")?
1805 } else {
1806 0
1807 };
1808
1809 let resources = self.resources();
1810 if !data.is_empty() {
1811 match &resources.instance_data {
1812 InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
1813 InstanceData::Texture { .. } => {
1814 Self::write_instance_texture(resources, offset, data)
1815 }
1816 }
1817 }
1818 let bind_group = resources
1819 .device
1820 .create_bind_group(&wgpu::BindGroupDescriptor {
1821 label: Some(label),
1822 layout: &resources.bind_group_layouts.instances,
1823 entries: &[wgpu::BindGroupEntry {
1824 binding: 0,
1825 resource: match &resources.instance_data {
1826 InstanceData::Storage(buffer) => {
1827 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1828 buffer,
1829 offset,
1830 size: NonZeroU64::new(size),
1831 })
1832 }
1833 InstanceData::Texture { view, .. } => {
1834 wgpu::BindingResource::TextureView(view)
1835 }
1836 },
1837 }],
1838 });
1839 Ok(InstanceBinding {
1840 bind_group,
1841 first_instance,
1842 })
1843 }
1844
1845 fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
1846 let InstanceData::Texture {
1847 texture,
1848 width,
1849 height,
1850 ..
1851 } = &resources.instance_data
1852 else {
1853 return;
1854 };
1855 let mut byte_offset = 0usize;
1856 let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
1857 while byte_offset < data.len() {
1858 let x = (texel_offset % u64::from(*width)) as u32;
1859 let y = (texel_offset / u64::from(*width)) as u32;
1860 if y >= *height {
1861 debug_assert!(
1865 false,
1866 "instance texture write out of bounds: row {y} >= height {}",
1867 *height
1868 );
1869 log::error!(
1870 "instance texture write out of bounds; dropping {} bytes of instance data",
1871 data.len() - byte_offset
1872 );
1873 return;
1874 }
1875 let available_texels = u64::from(*width - x);
1876 let remaining_bytes = data.len() - byte_offset;
1877 let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
1878 let texels = complete_texels.min(available_texels);
1879 if texels > 0 {
1880 let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
1881 resources.queue.write_texture(
1882 wgpu::TexelCopyTextureInfo {
1883 texture,
1884 mip_level: 0,
1885 origin: wgpu::Origin3d { x, y, z: 0 },
1886 aspect: wgpu::TextureAspect::All,
1887 },
1888 &data[byte_offset..byte_offset + byte_count],
1889 wgpu::TexelCopyBufferLayout {
1890 offset: 0,
1891 bytes_per_row: Some(byte_count as u32),
1892 rows_per_image: None,
1893 },
1894 wgpu::Extent3d {
1895 width: texels as u32,
1896 height: 1,
1897 depth_or_array_layers: 1,
1898 },
1899 );
1900 byte_offset += byte_count;
1901 texel_offset += texels;
1902 continue;
1903 }
1904
1905 let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
1906 final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
1907 resources.queue.write_texture(
1908 wgpu::TexelCopyTextureInfo {
1909 texture,
1910 mip_level: 0,
1911 origin: wgpu::Origin3d { x, y, z: 0 },
1912 aspect: wgpu::TextureAspect::All,
1913 },
1914 &final_texel,
1915 wgpu::TexelCopyBufferLayout {
1916 offset: 0,
1917 bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
1918 rows_per_image: None,
1919 },
1920 wgpu::Extent3d {
1921 width: 1,
1922 height: 1,
1923 depth_or_array_layers: 1,
1924 },
1925 );
1926 break;
1927 }
1928 }
1929
1930 fn grow_instance_data(&mut self, required: u64) -> Result<()> {
1931 let capacity = (self.instance_data_capacity * 2)
1932 .max(required.next_power_of_two())
1933 .min(self.max_instance_data_size);
1934 anyhow::ensure!(
1935 capacity >= required,
1936 "instance data needs {required} bytes, above the maximum of {}",
1937 self.max_instance_data_size
1938 );
1939 anyhow::ensure!(
1940 capacity > self.instance_data_capacity,
1941 "frame instance data exceeds the {}-byte maximum",
1942 self.max_instance_data_size
1943 );
1944 log::debug!(
1945 "instance data grown from {} to {capacity}",
1946 self.instance_data_capacity
1947 );
1948 let uses_webgl_instance_data = self.uses_webgl_instance_data;
1952 let resources = self.resources_mut();
1953 if uses_webgl_instance_data {
1954 let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
1955 let (instance_data, actual_capacity) =
1956 Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
1957 resources.instance_data = instance_data;
1958 self.instance_data_capacity = actual_capacity;
1959 } else {
1960 resources.instance_data =
1961 InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
1962 label: Some("instance_buffer"),
1963 size: capacity,
1964 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1965 mapped_at_creation: false,
1966 }));
1967 self.instance_data_capacity = capacity;
1968 }
1969 Ok(())
1970 }
1971
1972 pub fn unconfigure_surface(&mut self) {
1980 self.surface_configured = false;
1981 if let Some(res) = self.resources.as_mut() {
1983 res.invalidate_intermediate_textures();
1984 }
1985 }
1986
1987 #[cfg(not(target_family = "wasm"))]
1996 pub fn replace_surface<W: HasWindowHandle>(
1997 &mut self,
1998 window: &W,
1999 config: WgpuSurfaceConfig,
2000 instance: &wgpu::Instance,
2001 ) -> anyhow::Result<()> {
2002 let window_handle = window
2003 .window_handle()
2004 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2005
2006 let surface = create_surface(instance, window_handle.as_raw())?;
2007
2008 let width = (config.size.width.0 as u32).max(1);
2009 let height = (config.size.height.0 as u32).max(1);
2010
2011 let alpha_mode = if config.transparent {
2012 self.transparent_alpha_mode
2013 } else {
2014 self.opaque_alpha_mode
2015 };
2016
2017 self.surface_config.width = width;
2018 self.surface_config.height = height;
2019 self.surface_config.alpha_mode = alpha_mode;
2020 if let Some(mode) = config.preferred_present_mode {
2021 self.surface_config.present_mode = mode;
2022 }
2023
2024 {
2025 let res = self
2026 .resources
2027 .as_mut()
2028 .expect("GPU resources not available");
2029 surface.configure(&res.device, &self.surface_config);
2030 res.surface = surface;
2031
2032 res.invalidate_intermediate_textures();
2034 }
2035
2036 self.surface_configured = true;
2037
2038 Ok(())
2039 }
2040
2041 pub fn destroy(&mut self) {
2042 self.resources.take();
2045 }
2046
2047 pub fn device_lost(&self) -> bool {
2049 self.device_lost.load(std::sync::atomic::Ordering::SeqCst)
2050 }
2051
2052 pub fn needs_redraw(&mut self) -> bool {
2055 std::mem::take(&mut self.needs_redraw)
2056 }
2057
2058 #[cfg(not(target_family = "wasm"))]
2066 pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2067 where
2068 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2069 {
2070 let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2071
2072 let needs_new_context = gpu_context
2074 .borrow()
2075 .as_ref()
2076 .is_none_or(|ctx| ctx.device_lost());
2077
2078 let window_handle = window
2079 .window_handle()
2080 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2081
2082 let surface = if needs_new_context {
2083 log::warn!("GPU device lost, recreating context...");
2084
2085 self.resources = None;
2087 *gpu_context.borrow_mut() = None;
2088
2089 std::thread::sleep(std::time::Duration::from_millis(350));
2094
2095 let instance = WgpuContext::instance(Box::new(window.clone()));
2096 let surface = create_surface(&instance, window_handle.as_raw())?;
2097 let new_context =
2098 WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?;
2099 *gpu_context.borrow_mut() = Some(new_context);
2100 surface
2101 } else {
2102 let ctx_ref = gpu_context.borrow();
2103 let instance = &ctx_ref.as_ref().unwrap().instance;
2104 create_surface(instance, window_handle.as_raw())?
2105 };
2106
2107 let config = WgpuSurfaceConfig {
2108 size: gpui::Size {
2109 width: gpui::DevicePixels(self.surface_config.width as i32),
2110 height: gpui::DevicePixels(self.surface_config.height as i32),
2111 },
2112 transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2113 preferred_present_mode: Some(self.surface_config.present_mode),
2114 };
2115 let gpu_context = Rc::clone(gpu_context);
2116 let ctx_ref = gpu_context.borrow();
2117 let context = ctx_ref.as_ref().expect("context should exist");
2118
2119 self.resources = None;
2120 self.atlas.handle_device_lost(context);
2121
2122 *self = Self::new_internal(
2123 Some(gpu_context.clone()),
2124 context,
2125 surface,
2126 config,
2127 self.compositor_gpu,
2128 self.atlas.clone(),
2129 )?;
2130
2131 log::info!("GPU recovery complete");
2132 Ok(())
2133 }
2134}
2135
2136fn instance_range(range: Range<usize>) -> Range<u32> {
2137 range.start as u32..range.end as u32
2138}
2139
2140#[cfg(not(target_family = "wasm"))]
2141fn create_surface(
2142 instance: &wgpu::Instance,
2143 raw_window_handle: raw_window_handle::RawWindowHandle,
2144) -> anyhow::Result<wgpu::Surface<'static>> {
2145 unsafe {
2146 instance
2147 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
2148 raw_display_handle: None,
2150 raw_window_handle,
2151 })
2152 .map_err(|e| anyhow::anyhow!("{e}"))
2153 }
2154}
2155
2156struct RenderingParameters {
2157 path_sample_count: u32,
2158 gamma_ratios: [f32; 4],
2159 grayscale_enhanced_contrast: f32,
2160 subpixel_enhanced_contrast: f32,
2161}
2162
2163impl RenderingParameters {
2164 fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
2165 use std::env;
2166
2167 let format_features = adapter.get_texture_format_features(surface_format);
2168 let path_sample_count = [4, 2, 1]
2169 .into_iter()
2170 .find(|&n| format_features.flags.sample_count_supported(n))
2171 .unwrap_or(1);
2172
2173 let gamma = env::var("ZED_FONTS_GAMMA")
2174 .ok()
2175 .and_then(|v| v.parse().ok())
2176 .unwrap_or(1.8_f32)
2177 .clamp(1.0, 2.2);
2178 let gamma_ratios = get_gamma_correction_ratios(gamma);
2179
2180 let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
2181 .ok()
2182 .and_then(|v| v.parse().ok())
2183 .unwrap_or(1.0_f32)
2184 .max(0.0);
2185
2186 let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
2187 .ok()
2188 .and_then(|v| v.parse().ok())
2189 .unwrap_or(0.5_f32)
2190 .max(0.0);
2191
2192 Self {
2193 path_sample_count,
2194 gamma_ratios,
2195 grayscale_enhanced_contrast,
2196 subpixel_enhanced_contrast,
2197 }
2198 }
2199}
2200
2201#[cfg(test)]
2202mod tests {
2203 use super::*;
2204 use gpui::{MonochromeSprite, PolychromeSprite, Quad, Shadow, SubpixelSprite, Underline};
2205
2206 #[test]
2207 fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
2208 assert!(!WEBGL_SHADERS.contains("var<storage"));
2209 validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
2210 }
2211
2212 #[test]
2213 fn storage_buffer_shader_is_valid_wgsl() {
2214 validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
2215 }
2216
2217 #[test]
2218 fn subpixel_shader_is_valid_wgsl() {
2219 validate_wgsl(
2220 SUBPIXEL_SHADERS,
2221 naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
2222 );
2223 }
2224
2225 fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
2226 let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
2227 naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
2228 .validate(&module)
2229 .expect("shader should validate");
2230 }
2231
2232 #[test]
2233 fn webgl_record_sizes_match_shader_word_strides() {
2234 assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
2235 assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
2236 assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
2237 assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
2238 assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
2239 assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
2240 assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
2241 assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
2242 }
2243}