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