1use crate::{CompositorGpuHint, DeviceErrorState, WgpuAtlas, WgpuContext, WgpuImportedTexture};
2use anyhow::{Context as _, Result};
3use bytemuck::{Pod, Zeroable};
4use collections::FxHashMap;
5use gpui_engine::{
6 AtlasTextureId, CustomRenderPrimitive, PaintSurface, Path, PlatformAtlas, PrimitiveBatch, Scene,
7 SceneRenderer, get_gamma_correction_ratios,
8};
9#[cfg(all(
10 not(target_family = "wasm"),
11 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
12))]
13use gpui_engine::GpuRenderer;
14use gpui_platform::{Background, Bounds, DevicePixels, GpuPreference, GpuSpecs, Point, ScaledPixels, Size};
15#[cfg(all(
16 not(target_family = "wasm"),
17 not(target_os = "macos"),
18 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
19))]
20use gpui_platform::{PlatformRenderer, RendererTarget};
21#[cfg(all(
22 target_os = "windows",
23 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
24))]
25use gpui_platform::{WinSceneRenderer, WindowBackgroundAppearance};
26use log::warn;
27#[cfg(not(target_family = "wasm"))]
28use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
29use smallvec::SmallVec;
30#[cfg(all(
31 not(target_family = "wasm"),
32 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
33))]
34use std::any::Any;
35use std::cell::RefCell;
36use std::num::NonZeroU64;
37use std::ops::Range;
38use std::os::fd::OwnedFd;
39use std::rc::Rc;
40use std::sync::Arc;
41
42const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
43
44const INSTANCE_TEXTURE_TEXEL_SIZE: u64 = 16;
45
46const STORAGE_BUFFER_SHADERS: &str = concat!(
49 include_str!("shaders.wgsl"),
50 include_str!("shaders_storage.wgsl"),
51);
52
53const WEBGL_SHADERS: &str = concat!(
56 include_str!("shaders.wgsl"),
57 include_str!("shaders_webgl.wgsl"),
58);
59
60const SUBPIXEL_SHADERS: &str = concat!(
64 "enable dual_source_blending;\n",
65 include_str!("shaders.wgsl"),
66 include_str!("shaders_storage.wgsl"),
67 include_str!("shaders_subpixel.wgsl"),
68);
69
70fn least_common_multiple(left: u64, right: u64) -> u64 {
71 let mut first = left;
72 let mut second = right;
73 while second != 0 {
74 let remainder = first % second;
75 first = second;
76 second = remainder;
77 }
78 left / first * right
79}
80
81#[repr(C)]
82#[derive(Clone, Copy, Pod, Zeroable)]
83struct GlobalParams {
84 viewport_size: [f32; 2],
85 premultiplied_alpha: u32,
86 pad: u32,
87}
88
89#[repr(C)]
90#[derive(Clone, Copy, Pod, Zeroable)]
91struct PodBounds {
92 origin: [f32; 2],
93 size: [f32; 2],
94}
95
96impl From<Bounds<ScaledPixels>> for PodBounds {
97 fn from(bounds: Bounds<ScaledPixels>) -> Self {
98 Self {
99 origin: [bounds.origin.x.0, bounds.origin.y.0],
100 size: [bounds.size.width.0, bounds.size.height.0],
101 }
102 }
103}
104
105#[repr(C)]
106#[derive(Clone, Copy, Pod, Zeroable)]
107struct SurfaceParams {
108 bounds: PodBounds,
109 content_mask: PodBounds,
110 surface_format: u32,
114 chroma_reconstruction: u32,
115 transfer_function: u32,
117 _pad: u32,
118 ycbcr_to_rgb: [[f32; 4]; 4],
121}
122
123fn yuv_to_rgb(color_space: gpui_engine::YuvColorSpace) -> [[f32; 4]; 4] {
130 use gpui_engine::{YuvMatrix, YuvRange};
131
132 let (kr, kb) = match color_space.matrix {
133 YuvMatrix::Bt601 => (0.299f32, 0.114f32),
134 YuvMatrix::Bt709 => (0.2126f32, 0.0722f32),
135 YuvMatrix::Bt2020 => (0.2627f32, 0.0593f32),
136 };
137 let kg = 1.0 - kr - kb;
138 let red_cr = 2.0 * (1.0 - kr);
141 let blue_cb = 2.0 * (1.0 - kb);
142 let green_cb = 2.0 * kb * (1.0 - kb) / kg;
143 let green_cr = 2.0 * kr * (1.0 - kr) / kg;
144
145 let (y_scale, c_scale, y_zero, c_zero) = match color_space.range {
150 YuvRange::Full => (1.0, 1.0, 0.0, 0.5),
151 YuvRange::Limited => (255.0 / 219.0, 255.0 / 224.0, 16.0 / 255.0, 128.0 / 255.0),
152 };
153
154 let (red_cr, blue_cb) = (red_cr * c_scale, blue_cb * c_scale);
155 let (green_cb, green_cr) = (green_cb * c_scale, green_cr * c_scale);
156 [
157 [y_scale, y_scale, y_scale, 0.0],
158 [0.0, -green_cb, blue_cb, 0.0],
159 [red_cr, -green_cr, 0.0, 0.0],
160 [
161 -red_cr * c_zero - y_zero * y_scale,
162 (green_cb + green_cr) * c_zero - y_zero * y_scale,
163 -blue_cb * c_zero - y_zero * y_scale,
164 1.0,
165 ],
166 ]
167}
168
169#[repr(C)]
170#[derive(Clone, Copy, Pod, Zeroable)]
171struct GammaParams {
172 gamma_ratios: [f32; 4],
173 grayscale_enhanced_contrast: f32,
174 subpixel_enhanced_contrast: f32,
175 is_bgr: u32,
176 _pad: u32,
177}
178
179#[derive(Clone, Debug)]
180#[repr(C)]
181struct PathSprite {
182 bounds: Bounds<ScaledPixels>,
183}
184
185#[derive(Clone, Debug)]
186#[repr(C)]
187struct PathRasterizationVertex {
188 xy_position: Point<ScaledPixels>,
189 st_position: Point<f32>,
190 color: Background,
191 bounds: Bounds<ScaledPixels>,
192}
193
194pub struct WgpuSurfaceConfig {
195 pub size: Size<DevicePixels>,
196 pub transparent: bool,
197 pub preferred_present_mode: Option<wgpu::PresentMode>,
204}
205
206struct WgpuPipelines {
207 quads: wgpu::RenderPipeline,
208 shadows: wgpu::RenderPipeline,
209 path_rasterization: wgpu::RenderPipeline,
210 paths: wgpu::RenderPipeline,
211 underlines: wgpu::RenderPipeline,
212 mono_sprites: wgpu::RenderPipeline,
213 subpixel_sprites: Option<wgpu::RenderPipeline>,
214 poly_sprites: wgpu::RenderPipeline,
215 surfaces: wgpu::RenderPipeline,
216 imported_textures: wgpu::RenderPipeline,
217}
218
219struct InstanceBinding {
221 bind_group: wgpu::BindGroup,
222 first_instance: u32,
228}
229
230struct InstanceBindings {
231 quads: InstanceBinding,
232 shadows: InstanceBinding,
233 underlines: InstanceBinding,
234 monochrome_sprites: InstanceBinding,
235 subpixel_sprites: InstanceBinding,
236 polychrome_sprites: InstanceBinding,
237 imported_textures: InstanceBinding,
238}
239
240struct WgpuBindGroupLayouts {
241 globals: wgpu::BindGroupLayout,
242 instances: wgpu::BindGroupLayout,
243 texture: wgpu::BindGroupLayout,
244 surfaces: wgpu::BindGroupLayout,
245 imported_texture: wgpu::BindGroupLayout,
246}
247
248pub type WgpuContextSlot = Rc<RefCell<Option<WgpuContext>>>;
250
251enum InstanceData {
252 Storage(wgpu::Buffer),
253 Texture {
256 texture: wgpu::Texture,
257 view: wgpu::TextureView,
258 width: u32,
259 height: u32,
260 },
261}
262
263struct WgpuResources {
265 device: Arc<wgpu::Device>,
266 queue: Arc<wgpu::Queue>,
267 pipelines: WgpuPipelines,
268 bind_group_layouts: WgpuBindGroupLayouts,
269 atlas_sampler: wgpu::Sampler,
270 imported_texture_sampler: wgpu::Sampler,
271 atlas_texture_bind_groups: FxHashMap<AtlasTextureId, CachedTextureBindGroup>,
272 globals_buffer: wgpu::Buffer,
273 surface_params: wgpu::Buffer,
276 globals_bind_group: wgpu::BindGroup,
277 path_globals_bind_group: wgpu::BindGroup,
278 instance_data: InstanceData,
279 path_intermediate_texture: Option<wgpu::Texture>,
280 path_intermediate_view: Option<wgpu::TextureView>,
281 path_msaa_texture: Option<wgpu::Texture>,
282 path_msaa_view: Option<wgpu::TextureView>,
283 #[cfg(target_os = "linux")]
285 dmabuf_textures: crate::DmaBufTextureCache,
286}
287
288struct CachedTextureBindGroup {
289 texture_generation: u64,
290 bind_group: wgpu::BindGroup,
291}
292
293impl WgpuResources {
294 fn invalidate_intermediate_textures(&mut self) {
295 self.path_intermediate_texture = None;
296 self.path_intermediate_view = None;
297 self.path_msaa_texture = None;
298 self.path_msaa_view = None;
299 }
300}
301
302struct WgpuRendererCore {
303 resources: WgpuResources,
304 atlas: Arc<WgpuAtlas>,
305 path_globals_offset: u64,
306 gamma_offset: u64,
307 instance_data_capacity: u64,
308 max_instance_data_size: u64,
309 instance_data_alignment: u64,
310 uses_webgl_instance_data: bool,
311 rendering_params: RenderingParameters,
312 is_bgr: bool,
313 dual_source_blending: bool,
314 adapter_info: wgpu::AdapterInfo,
315 target_format: wgpu::TextureFormat,
316 max_texture_size: u32,
317}
318
319enum RendererState {
322 Ready {
324 surface: wgpu::Surface<'static>,
325 core: WgpuRendererCore,
326 },
327 Unconfigured { core: WgpuRendererCore },
331 Released,
333}
334
335pub struct WgpuRenderer {
336 #[allow(dead_code)]
338 context: Option<WgpuContextSlot>,
339 #[allow(dead_code)]
341 compositor_gpu: Option<CompositorGpuHint>,
342 gpu_preference: GpuPreference,
344 state: RendererState,
345 surface_config: wgpu::SurfaceConfiguration,
346 atlas: Arc<WgpuAtlas>,
347 transparent_alpha_mode: wgpu::CompositeAlphaMode,
348 opaque_alpha_mode: wgpu::CompositeAlphaMode,
349 max_texture_size: u32,
350 is_bgr: bool,
351 failed_frame_count: u32,
352 device_errors: Arc<DeviceErrorState>,
353 observed_error_generation: u64,
354 last_surface_error: Option<String>,
355 needs_redraw: bool,
356}
357
358impl WgpuRenderer {
359 fn core(&self) -> Option<&WgpuRendererCore> {
360 match &self.state {
361 RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
362 RendererState::Released => None,
363 }
364 }
365
366 fn core_mut(&mut self) -> Option<&mut WgpuRendererCore> {
367 match &mut self.state {
368 RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
369 RendererState::Released => None,
370 }
371 }
372
373 #[cfg(not(target_family = "wasm"))]
383 pub fn new<W>(
384 gpu_context: WgpuContextSlot,
385 window: &W,
386 config: WgpuSurfaceConfig,
387 compositor_gpu: Option<CompositorGpuHint>,
388 gpu_preference: GpuPreference,
389 ) -> anyhow::Result<Self>
390 where
391 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
392 {
393 let window_handle = window
394 .window_handle()
395 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
396
397 let target = wgpu::SurfaceTargetUnsafe::RawHandle {
398 raw_display_handle: None,
400 raw_window_handle: window_handle.as_raw(),
401 };
402
403 let instance = gpu_context
407 .borrow()
408 .as_ref()
409 .map(|ctx| ctx.instance.clone())
410 .unwrap_or_else(|| WgpuContext::instance(Some(Box::new(window.clone()))));
411
412 let surface = unsafe {
416 instance
417 .create_surface_unsafe(target)
418 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
419 };
420
421 let mut ctx_ref = gpu_context.borrow_mut();
422 let context = match ctx_ref.as_mut() {
423 Some(context) => {
424 context.check_compatible_with_surface(&surface)?;
425 context
426 }
427 None => ctx_ref.insert(WgpuContext::new(
428 instance,
429 &surface,
430 compositor_gpu,
431 gpu_preference.clone(),
432 )?),
433 };
434
435 let atlas = Arc::new(WgpuAtlas::from_context(context));
436
437 Self::new_internal(
438 Some(Rc::clone(&gpu_context)),
439 context,
440 surface,
441 config,
442 compositor_gpu,
443 gpu_preference,
444 atlas,
445 )
446 }
447
448 #[cfg(target_family = "wasm")]
449 pub fn new_from_canvas(
450 context: &WgpuContext,
451 canvas: &web_sys::HtmlCanvasElement,
452 config: WgpuSurfaceConfig,
453 ) -> anyhow::Result<Self> {
454 let surface = context
455 .instance
456 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
457 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
458 Self::new_from_surface(context, surface, config)
459 }
460
461 #[cfg(target_family = "wasm")]
462 #[allow(clippy::arc_with_non_send_sync)]
463 pub fn new_from_surface(
464 context: &WgpuContext,
465 surface: wgpu::Surface<'static>,
466 config: WgpuSurfaceConfig,
467 ) -> anyhow::Result<Self> {
468 let atlas = Arc::new(WgpuAtlas::from_context(context));
469 Self::new_internal(
470 None,
471 context,
472 surface,
473 config,
474 None,
475 GpuPreference::Auto,
476 atlas,
477 )
478 }
479
480 fn new_internal(
481 gpu_context: Option<WgpuContextSlot>,
482 context: &WgpuContext,
483 surface: wgpu::Surface<'static>,
484 config: WgpuSurfaceConfig,
485 compositor_gpu: Option<CompositorGpuHint>,
486 gpu_preference: GpuPreference,
487 atlas: Arc<WgpuAtlas>,
488 ) -> anyhow::Result<Self> {
489 let surface_caps = surface.get_capabilities(&context.adapter);
490 let preferred_formats = [
491 wgpu::TextureFormat::Bgra8Unorm,
492 wgpu::TextureFormat::Rgba8Unorm,
493 ];
494 let surface_format = preferred_formats
495 .iter()
496 .find(|f| surface_caps.formats.contains(f))
497 .copied()
498 .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
499 .or_else(|| surface_caps.formats.first().copied())
500 .ok_or_else(|| {
501 anyhow::anyhow!(
502 "Surface reports no supported texture formats for adapter {:?}",
503 context.adapter.get_info().name
504 )
505 })?;
506
507 let pick_alpha_mode =
508 |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
509 preferences
510 .iter()
511 .find(|p| surface_caps.alpha_modes.contains(p))
512 .copied()
513 .or_else(|| surface_caps.alpha_modes.first().copied())
514 .ok_or_else(|| {
515 anyhow::anyhow!(
516 "Surface reports no supported alpha modes for adapter {:?}",
517 context.adapter.get_info().name
518 )
519 })
520 };
521
522 let transparent_alpha_mode = pick_alpha_mode(&[
523 wgpu::CompositeAlphaMode::PreMultiplied,
524 wgpu::CompositeAlphaMode::Inherit,
525 ])?;
526
527 let opaque_alpha_mode = pick_alpha_mode(&[
528 wgpu::CompositeAlphaMode::Opaque,
529 wgpu::CompositeAlphaMode::Inherit,
530 ])?;
531
532 let alpha_mode = if config.transparent {
533 transparent_alpha_mode
534 } else {
535 opaque_alpha_mode
536 };
537
538 let device = Arc::clone(&context.device);
539 let max_texture_size = device.limits().max_texture_dimension_2d;
540
541 let requested_width = config.size.width.0 as u32;
542 let requested_height = config.size.height.0 as u32;
543 let clamped_width = requested_width.min(max_texture_size);
544 let clamped_height = requested_height.min(max_texture_size);
545
546 if clamped_width != requested_width || clamped_height != requested_height {
547 warn!(
548 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
549 Clamping to ({}, {}). Window content may not fill the entire window.",
550 requested_width, requested_height, max_texture_size, clamped_width, clamped_height
551 );
552 }
553
554 let surface_config = wgpu::SurfaceConfiguration {
555 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
556 format: surface_format,
557 width: clamped_width.max(1),
558 height: clamped_height.max(1),
559 present_mode: config
560 .preferred_present_mode
561 .filter(|mode| surface_caps.present_modes.contains(mode))
562 .unwrap_or(wgpu::PresentMode::Fifo),
563 desired_maximum_frame_latency: 2,
564 alpha_mode,
565 view_formats: vec![],
566 };
567 surface.configure(&context.device, &surface_config);
570
571 let core = WgpuRendererCore::new(context, atlas.clone(), surface_format, alpha_mode);
572
573 Ok(Self {
574 context: gpu_context,
575 compositor_gpu,
576 gpu_preference,
577 state: RendererState::Ready { surface, core },
578 surface_config,
579 atlas,
580 transparent_alpha_mode,
581 opaque_alpha_mode,
582 max_texture_size,
583 is_bgr: false,
584 failed_frame_count: 0,
585 device_errors: Arc::clone(context.errors()),
586 observed_error_generation: 0,
587 last_surface_error: None,
588 needs_redraw: false,
589 })
590 }
591}
592
593impl WgpuRendererCore {
594 fn create_bind_group_layouts(
595 device: &wgpu::Device,
596 uses_webgl_instance_data: bool,
597 ) -> WgpuBindGroupLayouts {
598 let globals =
599 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
600 label: Some("globals_layout"),
601 entries: &[
602 wgpu::BindGroupLayoutEntry {
603 binding: 0,
604 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
605 ty: wgpu::BindingType::Buffer {
606 ty: wgpu::BufferBindingType::Uniform,
607 has_dynamic_offset: false,
608 min_binding_size: NonZeroU64::new(
609 std::mem::size_of::<GlobalParams>() as u64
610 ),
611 },
612 count: None,
613 },
614 wgpu::BindGroupLayoutEntry {
615 binding: 1,
616 visibility: wgpu::ShaderStages::FRAGMENT,
617 ty: wgpu::BindingType::Buffer {
618 ty: wgpu::BufferBindingType::Uniform,
619 has_dynamic_offset: false,
620 min_binding_size: NonZeroU64::new(
621 std::mem::size_of::<GammaParams>() as u64
622 ),
623 },
624 count: None,
625 },
626 ],
627 });
628
629 let instance_data_entry = wgpu::BindGroupLayoutEntry {
630 binding: 0,
631 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
632 ty: if uses_webgl_instance_data {
633 wgpu::BindingType::Texture {
634 sample_type: wgpu::TextureSampleType::Uint,
635 view_dimension: wgpu::TextureViewDimension::D2,
636 multisampled: false,
637 }
638 } else {
639 wgpu::BindingType::Buffer {
640 ty: wgpu::BufferBindingType::Storage { read_only: true },
641 has_dynamic_offset: false,
642 min_binding_size: None,
643 }
644 },
645 count: None,
646 };
647
648 let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
649 label: Some("instances_layout"),
650 entries: &[instance_data_entry],
651 });
652
653 let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
654 label: Some("texture_layout"),
655 entries: &[
656 wgpu::BindGroupLayoutEntry {
657 binding: 0,
658 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
659 ty: wgpu::BindingType::Texture {
660 sample_type: wgpu::TextureSampleType::Float { filterable: true },
661 view_dimension: wgpu::TextureViewDimension::D2,
662 multisampled: false,
663 },
664 count: None,
665 },
666 wgpu::BindGroupLayoutEntry {
667 binding: 1,
668 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
669 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
670 count: None,
671 },
672 ],
673 });
674
675 let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
676 label: Some("surfaces_layout"),
677 entries: &[
678 wgpu::BindGroupLayoutEntry {
679 binding: 0,
680 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
681 ty: wgpu::BindingType::Buffer {
682 ty: wgpu::BufferBindingType::Uniform,
683 has_dynamic_offset: true,
684 min_binding_size: NonZeroU64::new(
685 std::mem::size_of::<SurfaceParams>() as u64
686 ),
687 },
688 count: None,
689 },
690 wgpu::BindGroupLayoutEntry {
691 binding: 1,
692 visibility: wgpu::ShaderStages::FRAGMENT,
693 ty: wgpu::BindingType::Texture {
694 sample_type: wgpu::TextureSampleType::Float { filterable: true },
695 view_dimension: wgpu::TextureViewDimension::D2,
696 multisampled: false,
697 },
698 count: None,
699 },
700 wgpu::BindGroupLayoutEntry {
701 binding: 2,
702 visibility: wgpu::ShaderStages::FRAGMENT,
703 ty: wgpu::BindingType::Texture {
704 sample_type: wgpu::TextureSampleType::Float { filterable: true },
705 view_dimension: wgpu::TextureViewDimension::D2,
706 multisampled: false,
707 },
708 count: None,
709 },
710 wgpu::BindGroupLayoutEntry {
711 binding: 3,
712 visibility: wgpu::ShaderStages::FRAGMENT,
713 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
714 count: None,
715 },
716 ],
717 });
718
719 let imported_texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
720 label: Some("imported_texture_layout"),
721 entries: &[
722 wgpu::BindGroupLayoutEntry {
723 binding: 0,
724 visibility: wgpu::ShaderStages::FRAGMENT,
725 ty: wgpu::BindingType::Texture {
726 sample_type: wgpu::TextureSampleType::Float { filterable: true },
727 view_dimension: wgpu::TextureViewDimension::D2,
728 multisampled: false,
729 },
730 count: None,
731 },
732 wgpu::BindGroupLayoutEntry {
733 binding: 1,
734 visibility: wgpu::ShaderStages::FRAGMENT,
735 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
736 count: None,
737 },
738 ],
739 });
740
741 WgpuBindGroupLayouts {
742 globals,
743 instances,
744 texture,
745 surfaces,
746 imported_texture,
747 }
748 }
749
750 fn create_instance_texture(
751 device: &wgpu::Device,
752 requested_capacity: u64,
753 max_texture_dimension: u32,
754 ) -> (InstanceData, u64) {
755 let texel_count = requested_capacity.div_ceil(INSTANCE_TEXTURE_TEXEL_SIZE);
756 let width = texel_count.min(u64::from(max_texture_dimension)).max(1) as u32;
757 let height = texel_count
758 .div_ceil(u64::from(width))
759 .min(u64::from(max_texture_dimension))
760 .max(1) as u32;
761 let texture = device.create_texture(&wgpu::TextureDescriptor {
762 label: Some("instance_texture"),
763 size: wgpu::Extent3d {
764 width,
765 height,
766 depth_or_array_layers: 1,
767 },
768 mip_level_count: 1,
769 sample_count: 1,
770 dimension: wgpu::TextureDimension::D2,
771 format: wgpu::TextureFormat::Rgba32Uint,
772 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
773 view_formats: &[],
774 });
775 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
776 let capacity = u64::from(width) * u64::from(height) * INSTANCE_TEXTURE_TEXEL_SIZE;
777 (
778 InstanceData::Texture {
779 texture,
780 view,
781 width,
782 height,
783 },
784 capacity,
785 )
786 }
787
788 fn create_pipelines(
789 device: &wgpu::Device,
790 layouts: &WgpuBindGroupLayouts,
791 surface_format: wgpu::TextureFormat,
792 alpha_mode: wgpu::CompositeAlphaMode,
793 path_sample_count: u32,
794 dual_source_blending: bool,
795 uses_webgl_instance_data: bool,
796 ) -> WgpuPipelines {
797 let device_has_feature = device
806 .features()
807 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
808 if dual_source_blending && !device_has_feature {
809 log::error!(
810 "BUG: dual_source_blending flag is true but device does not \
811 have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \
812 Falling back to mono text rendering. Please report this at \
813 https://github.com/zed-industries/zed/issues",
814 device.features(),
815 );
816 }
817 let dual_source_blending =
818 dual_source_blending && device_has_feature && !uses_webgl_instance_data;
819
820 let shader_source = if uses_webgl_instance_data {
821 WEBGL_SHADERS
822 } else {
823 STORAGE_BUFFER_SHADERS
824 };
825 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
826 label: Some("gpui_shaders"),
827 source: wgpu::ShaderSource::Wgsl(shader_source.into()),
828 });
829
830 let subpixel_shader_module = if dual_source_blending {
831 Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
832 label: Some("gpui_subpixel_shaders"),
833 source: wgpu::ShaderSource::Wgsl(SUBPIXEL_SHADERS.into()),
834 }))
835 } else {
836 None
837 };
838
839 let blend_mode = match alpha_mode {
840 wgpu::CompositeAlphaMode::PreMultiplied => {
841 wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
842 }
843 _ => wgpu::BlendState::ALPHA_BLENDING,
844 };
845
846 let color_target = wgpu::ColorTargetState {
847 format: surface_format,
848 blend: Some(blend_mode),
849 write_mask: wgpu::ColorWrites::ALL,
850 };
851
852 let create_pipeline = |name: &str,
853 vs_entry: &str,
854 fs_entry: &str,
855 globals_layout: &wgpu::BindGroupLayout,
856 data_layout: &wgpu::BindGroupLayout,
857 texture_layout: Option<&wgpu::BindGroupLayout>,
858 topology: wgpu::PrimitiveTopology,
859 color_targets: &[Option<wgpu::ColorTargetState>],
860 sample_count: u32,
861 module: &wgpu::ShaderModule| {
862 let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
863 bind_group_layouts.extend(texture_layout.map(Some));
864 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
865 label: Some(&format!("{name}_layout")),
866 bind_group_layouts: &bind_group_layouts,
867 immediate_size: 0,
868 });
869
870 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
871 label: Some(name),
872 layout: Some(&pipeline_layout),
873 vertex: wgpu::VertexState {
874 module,
875 entry_point: Some(vs_entry),
876 buffers: &[],
877 compilation_options: wgpu::PipelineCompilationOptions::default(),
878 },
879 fragment: Some(wgpu::FragmentState {
880 module,
881 entry_point: Some(fs_entry),
882 targets: color_targets,
883 compilation_options: wgpu::PipelineCompilationOptions::default(),
884 }),
885 primitive: wgpu::PrimitiveState {
886 topology,
887 strip_index_format: None,
888 front_face: wgpu::FrontFace::Ccw,
889 cull_mode: None,
890 polygon_mode: wgpu::PolygonMode::Fill,
891 unclipped_depth: false,
892 conservative: false,
893 },
894 depth_stencil: None,
895 multisample: wgpu::MultisampleState {
896 count: sample_count,
897 mask: !0,
898 alpha_to_coverage_enabled: false,
899 },
900 multiview_mask: None,
901 cache: None,
902 })
903 };
904
905 let quads = create_pipeline(
906 "quads",
907 "vs_quad",
908 "fs_quad",
909 &layouts.globals,
910 &layouts.instances,
911 None,
912 wgpu::PrimitiveTopology::TriangleStrip,
913 &[Some(color_target.clone())],
914 1,
915 &shader_module,
916 );
917
918 let shadows = create_pipeline(
919 "shadows",
920 "vs_shadow",
921 "fs_shadow",
922 &layouts.globals,
923 &layouts.instances,
924 None,
925 wgpu::PrimitiveTopology::TriangleStrip,
926 &[Some(color_target.clone())],
927 1,
928 &shader_module,
929 );
930
931 let path_rasterization = create_pipeline(
932 "path_rasterization",
933 "vs_path_rasterization",
934 "fs_path_rasterization",
935 &layouts.globals,
936 &layouts.instances,
937 None,
938 wgpu::PrimitiveTopology::TriangleList,
939 &[Some(wgpu::ColorTargetState {
940 format: surface_format,
941 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
942 write_mask: wgpu::ColorWrites::ALL,
943 })],
944 path_sample_count,
945 &shader_module,
946 );
947
948 let paths_blend = wgpu::BlendState {
949 color: wgpu::BlendComponent {
950 src_factor: wgpu::BlendFactor::One,
951 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
952 operation: wgpu::BlendOperation::Add,
953 },
954 alpha: wgpu::BlendComponent {
955 src_factor: wgpu::BlendFactor::One,
956 dst_factor: wgpu::BlendFactor::One,
957 operation: wgpu::BlendOperation::Add,
958 },
959 };
960
961 let paths = create_pipeline(
962 "paths",
963 "vs_path",
964 "fs_path",
965 &layouts.globals,
966 &layouts.instances,
967 Some(&layouts.texture),
968 wgpu::PrimitiveTopology::TriangleStrip,
969 &[Some(wgpu::ColorTargetState {
970 format: surface_format,
971 blend: Some(paths_blend),
972 write_mask: wgpu::ColorWrites::ALL,
973 })],
974 1,
975 &shader_module,
976 );
977
978 let underlines = create_pipeline(
979 "underlines",
980 "vs_underline",
981 "fs_underline",
982 &layouts.globals,
983 &layouts.instances,
984 None,
985 wgpu::PrimitiveTopology::TriangleStrip,
986 &[Some(color_target.clone())],
987 1,
988 &shader_module,
989 );
990
991 let mono_sprites = create_pipeline(
992 "mono_sprites",
993 "vs_mono_sprite",
994 "fs_mono_sprite",
995 &layouts.globals,
996 &layouts.instances,
997 Some(&layouts.texture),
998 wgpu::PrimitiveTopology::TriangleStrip,
999 &[Some(color_target.clone())],
1000 1,
1001 &shader_module,
1002 );
1003
1004 let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
1005 let subpixel_blend = wgpu::BlendState {
1006 color: wgpu::BlendComponent {
1007 src_factor: wgpu::BlendFactor::Src1,
1008 dst_factor: wgpu::BlendFactor::OneMinusSrc1,
1009 operation: wgpu::BlendOperation::Add,
1010 },
1011 alpha: wgpu::BlendComponent {
1012 src_factor: wgpu::BlendFactor::One,
1013 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1014 operation: wgpu::BlendOperation::Add,
1015 },
1016 };
1017
1018 Some(create_pipeline(
1019 "subpixel_sprites",
1020 "vs_subpixel_sprite",
1021 "fs_subpixel_sprite",
1022 &layouts.globals,
1023 &layouts.instances,
1024 Some(&layouts.texture),
1025 wgpu::PrimitiveTopology::TriangleStrip,
1026 &[Some(wgpu::ColorTargetState {
1027 format: surface_format,
1028 blend: Some(subpixel_blend),
1029 write_mask: wgpu::ColorWrites::COLOR,
1030 })],
1031 1,
1032 subpixel_module,
1033 ))
1034 } else {
1035 None
1036 };
1037
1038 let poly_sprites = create_pipeline(
1039 "poly_sprites",
1040 "vs_poly_sprite",
1041 "fs_poly_sprite",
1042 &layouts.globals,
1043 &layouts.instances,
1044 Some(&layouts.texture),
1045 wgpu::PrimitiveTopology::TriangleStrip,
1046 &[Some(color_target.clone())],
1047 1,
1048 &shader_module,
1049 );
1050
1051 let imported_textures = create_pipeline(
1052 "imported_textures",
1053 "vs_quad",
1054 "fs_imported_texture",
1055 &layouts.globals,
1056 &layouts.instances,
1057 Some(&layouts.imported_texture),
1058 wgpu::PrimitiveTopology::TriangleStrip,
1059 &[Some(color_target.clone())],
1060 1,
1061 &shader_module,
1062 );
1063
1064 let surfaces = create_pipeline(
1065 "surfaces",
1066 "vs_surface",
1067 "fs_surface",
1068 &layouts.globals,
1069 &layouts.surfaces,
1070 None,
1071 wgpu::PrimitiveTopology::TriangleStrip,
1072 &[Some(color_target)],
1073 1,
1074 &shader_module,
1075 );
1076
1077 WgpuPipelines {
1078 quads,
1079 shadows,
1080 path_rasterization,
1081 paths,
1082 underlines,
1083 mono_sprites,
1084 subpixel_sprites,
1085 poly_sprites,
1086 surfaces,
1087 imported_textures,
1088 }
1089 }
1090
1091 fn create_path_intermediate(
1092 device: &wgpu::Device,
1093 format: wgpu::TextureFormat,
1094 width: u32,
1095 height: u32,
1096 ) -> (wgpu::Texture, wgpu::TextureView) {
1097 let texture = device.create_texture(&wgpu::TextureDescriptor {
1098 label: Some("path_intermediate"),
1099 size: wgpu::Extent3d {
1100 width: width.max(1),
1101 height: height.max(1),
1102 depth_or_array_layers: 1,
1103 },
1104 mip_level_count: 1,
1105 sample_count: 1,
1106 dimension: wgpu::TextureDimension::D2,
1107 format,
1108 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1109 view_formats: &[],
1110 });
1111 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1112 (texture, view)
1113 }
1114
1115 fn create_msaa_if_needed(
1116 device: &wgpu::Device,
1117 format: wgpu::TextureFormat,
1118 width: u32,
1119 height: u32,
1120 sample_count: u32,
1121 ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
1122 if sample_count <= 1 {
1123 return None;
1124 }
1125 let texture = device.create_texture(&wgpu::TextureDescriptor {
1126 label: Some("path_msaa"),
1127 size: wgpu::Extent3d {
1128 width: width.max(1),
1129 height: height.max(1),
1130 depth_or_array_layers: 1,
1131 },
1132 mip_level_count: 1,
1133 sample_count,
1134 dimension: wgpu::TextureDimension::D2,
1135 format,
1136 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1137 view_formats: &[],
1138 });
1139 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1140 Some((texture, view))
1141 }
1142}
1143
1144impl WgpuRenderer {
1145 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
1148 let width = size.width.0 as u32;
1149 let height = size.height.0 as u32;
1150
1151 if width == self.surface_config.width && height == self.surface_config.height {
1152 return;
1153 }
1154
1155 let clamped_width = width.min(self.max_texture_size);
1156 let clamped_height = height.min(self.max_texture_size);
1157 if clamped_width != width || clamped_height != height {
1158 warn!(
1159 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
1160 Clamping to ({}, {}). Window content may not fill the entire window.",
1161 width, height, self.max_texture_size, clamped_width, clamped_height
1162 );
1163 }
1164 self.surface_config.width = clamped_width.max(1);
1165 self.surface_config.height = clamped_height.max(1);
1166
1167 let Some(core) = self.core_mut() else {
1168 return;
1169 };
1170 let resources = &mut core.resources;
1171
1172 if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
1174 submission_index: None,
1175 timeout: None,
1176 }) {
1177 warn!("Failed to poll device during resize: {e:?}");
1178 }
1179
1180 if let Some(ref texture) = resources.path_intermediate_texture {
1182 texture.destroy();
1183 }
1184 if let Some(ref texture) = resources.path_msaa_texture {
1185 texture.destroy();
1186 }
1187
1188 resources.invalidate_intermediate_textures();
1192
1193 if let RendererState::Ready { surface, core } = &self.state {
1194 surface.configure(&core.resources.device, &self.surface_config);
1195 }
1196 }
1197
1198 pub fn set_subpixel_layout(&mut self, is_bgr: bool) {
1199 self.is_bgr = is_bgr;
1200 if let Some(core) = self.core_mut() {
1201 core.is_bgr = is_bgr;
1202 }
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 if new_alpha_mode == self.surface_config.alpha_mode {
1212 return;
1213 }
1214 self.surface_config.alpha_mode = new_alpha_mode;
1215 let format = self.surface_config.format;
1216
1217 let Some(core) = self.core_mut() else {
1218 return;
1219 };
1220 let resources = &mut core.resources;
1221 resources.pipelines = WgpuRendererCore::create_pipelines(
1222 &resources.device,
1223 &resources.bind_group_layouts,
1224 format,
1225 new_alpha_mode,
1226 core.rendering_params.path_sample_count,
1227 core.dual_source_blending,
1228 core.uses_webgl_instance_data,
1229 );
1230
1231 if let RendererState::Ready { surface, core } = &self.state {
1232 surface.configure(&core.resources.device, &self.surface_config);
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.core().is_some_and(|core| core.dual_source_blending)
1250 }
1251
1252 pub fn device(&self) -> &wgpu::Device {
1259 self.core().expect("renderer has no core").resources.device.as_ref()
1260 }
1261
1262 pub fn queue(&self) -> &wgpu::Queue {
1264 self.core().expect("renderer has no core").resources.queue.as_ref()
1265 }
1266
1267 #[cfg(not(target_family = "wasm"))]
1269 pub fn gpu_context(&self) -> Option<WgpuContextSlot> {
1270 self.context.clone()
1271 }
1272
1273 pub fn gpu_specs(&self) -> Option<GpuSpecs> {
1274 let adapter_info = &self.core()?.adapter_info;
1275 Some(GpuSpecs {
1276 is_software_emulated: adapter_info.device_type == wgpu::DeviceType::Cpu,
1277 device_name: adapter_info.name.clone(),
1278 driver_name: adapter_info.driver.clone(),
1279 driver_info: adapter_info.driver_info.clone(),
1280 })
1281 }
1282
1283 pub fn max_texture_size(&self) -> u32 {
1284 self.max_texture_size
1285 }
1286
1287 pub fn draw(&mut self, scene: &Scene) -> bool {
1288 #[cfg(target_family = "wasm")]
1289 if self.device_lost() {
1290 if matches!(self.state, RendererState::Ready { .. }) {
1291 log::error!(
1292 "Browser graphics context was lost; rendering has stopped. Reload the page to recover."
1293 );
1294 self.unconfigure_surface();
1295 }
1296 return false;
1297 }
1298
1299 let RendererState::Ready { surface, core } = &mut self.state else {
1304 return false;
1305 };
1306
1307 if let Some(error) = self.last_surface_error.take().or_else(|| {
1308 self.device_errors
1309 .observe_error(&mut self.observed_error_generation)
1310 }) {
1311 self.failed_frame_count += 1;
1312 log::error!(
1313 "GPU error during frame (failure {} of 10): {error}",
1314 self.failed_frame_count
1315 );
1316
1317 if self.failed_frame_count > 10 {
1319 panic!("Too many consecutive GPU errors. Last error: {error}");
1320 } else if self.failed_frame_count > 5 {
1321 core.resources.invalidate_intermediate_textures();
1322 self.atlas.clear();
1323 self.needs_redraw = true;
1324 self.failed_frame_count = 0;
1325 return false;
1326 }
1327 } else {
1328 self.failed_frame_count = 0;
1329 }
1330
1331 let frame = match surface.get_current_texture() {
1332 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
1333 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
1334 drop(frame);
1336 surface.configure(&core.resources.device, &self.surface_config);
1337 return core.release_unpresented(scene);
1338 }
1339 wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
1340 surface.configure(&core.resources.device, &self.surface_config);
1341 return core.release_unpresented(scene);
1342 }
1343 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
1344 return core.release_unpresented(scene);
1345 }
1346 wgpu::CurrentSurfaceTexture::Validation => {
1347 self.last_surface_error = Some("Surface texture validation error".to_string());
1348 return core.release_unpresented(scene);
1349 }
1350 };
1351
1352 let size = Size {
1355 width: DevicePixels(frame.texture.width() as i32),
1356 height: DevicePixels(frame.texture.height() as i32),
1357 };
1358 let frame_view = frame
1359 .texture
1360 .create_view(&wgpu::TextureViewDescriptor::default());
1361 let premultiplied_alpha =
1362 self.surface_config.alpha_mode == wgpu::CompositeAlphaMode::PreMultiplied;
1363 if let Err(error) = core.render_frame(
1364 scene,
1365 &frame_view,
1366 size,
1367 premultiplied_alpha,
1368 wgpu::Color::TRANSPARENT,
1369 ) {
1370 log::error!("{error:#}");
1371 return core.release_unpresented(scene);
1372 }
1373
1374 frame.present();
1375 true
1376 }
1377}
1378
1379impl WgpuRendererCore {
1380 fn new(
1381 context: &WgpuContext,
1382 atlas: Arc<WgpuAtlas>,
1383 target_format: wgpu::TextureFormat,
1384 alpha_mode: wgpu::CompositeAlphaMode,
1385 ) -> Self {
1386 let device = Arc::clone(&context.device);
1387 let queue = Arc::clone(&context.queue);
1388 let rendering_params = RenderingParameters::new(&context.adapter, target_format);
1389 let uses_webgl_instance_data = context.uses_webgl_instance_data();
1390 let dual_source_blending =
1391 context.supports_dual_source_blending() && !uses_webgl_instance_data;
1392 let bind_group_layouts = Self::create_bind_group_layouts(&device, uses_webgl_instance_data);
1393 let pipelines = Self::create_pipelines(
1394 &device,
1395 &bind_group_layouts,
1396 target_format,
1397 alpha_mode,
1398 rendering_params.path_sample_count,
1399 dual_source_blending,
1400 uses_webgl_instance_data,
1401 );
1402 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1403 label: Some("atlas_sampler"),
1404 mag_filter: wgpu::FilterMode::Linear,
1405 min_filter: wgpu::FilterMode::Linear,
1406 ..Default::default()
1407 });
1408
1409 let imported_texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1412 label: Some("imported_texture_sampler"),
1413 address_mode_u: wgpu::AddressMode::ClampToEdge,
1414 address_mode_v: wgpu::AddressMode::ClampToEdge,
1415 address_mode_w: wgpu::AddressMode::ClampToEdge,
1416 mag_filter: wgpu::FilterMode::Linear,
1417 min_filter: wgpu::FilterMode::Linear,
1418 ..Default::default()
1419 });
1420 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
1421 let globals_size = std::mem::size_of::<GlobalParams>() as u64;
1422 let gamma_size = std::mem::size_of::<GammaParams>() as u64;
1423 let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
1424 let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
1425 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1426 label: Some("globals_buffer"),
1427 size: gamma_offset + gamma_size,
1428 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1429 mapped_at_creation: false,
1430 });
1431 let (
1432 instance_data,
1433 instance_data_capacity,
1434 max_instance_data_size,
1435 instance_data_alignment,
1436 ) = if uses_webgl_instance_data {
1437 let max_texture_dimension = device.limits().max_texture_dimension_2d;
1438 let max_instance_data_size = (u64::from(max_texture_dimension).pow(2)
1439 * INSTANCE_TEXTURE_TEXEL_SIZE)
1440 .min(MAX_INSTANCE_BUFFER_SIZE);
1441 let initial_capacity = (2 * 1024 * 1024).min(max_instance_data_size);
1442 let (instance_data, capacity) =
1443 Self::create_instance_texture(&device, initial_capacity, max_texture_dimension);
1444 (
1445 instance_data,
1446 capacity,
1447 max_instance_data_size,
1448 INSTANCE_TEXTURE_TEXEL_SIZE,
1449 )
1450 } else {
1451 let max_buffer_size = device
1452 .limits()
1453 .max_buffer_size
1454 .min(device.limits().max_storage_buffer_binding_size)
1455 .min(MAX_INSTANCE_BUFFER_SIZE);
1456 let initial_capacity = (2 * 1024 * 1024).min(max_buffer_size);
1457 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
1458 label: Some("instance_buffer"),
1459 size: initial_capacity,
1460 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1461 mapped_at_creation: false,
1462 });
1463 (
1464 InstanceData::Storage(buffer),
1465 initial_capacity,
1466 max_buffer_size,
1467 device.limits().min_storage_buffer_offset_alignment as u64,
1468 )
1469 };
1470 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1471 label: Some("globals_bind_group"),
1472 layout: &bind_group_layouts.globals,
1473 entries: &[
1474 wgpu::BindGroupEntry {
1475 binding: 0,
1476 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1477 buffer: &globals_buffer,
1478 offset: 0,
1479 size: NonZeroU64::new(globals_size),
1480 }),
1481 },
1482 wgpu::BindGroupEntry {
1483 binding: 1,
1484 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1485 buffer: &globals_buffer,
1486 offset: gamma_offset,
1487 size: NonZeroU64::new(gamma_size),
1488 }),
1489 },
1490 ],
1491 });
1492 let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1493 label: Some("path_globals_bind_group"),
1494 layout: &bind_group_layouts.globals,
1495 entries: &[
1496 wgpu::BindGroupEntry {
1497 binding: 0,
1498 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1499 buffer: &globals_buffer,
1500 offset: path_globals_offset,
1501 size: NonZeroU64::new(globals_size),
1502 }),
1503 },
1504 wgpu::BindGroupEntry {
1505 binding: 1,
1506 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1507 buffer: &globals_buffer,
1508 offset: gamma_offset,
1509 size: NonZeroU64::new(gamma_size),
1510 }),
1511 },
1512 ],
1513 });
1514 let max_texture_size = device.limits().max_texture_dimension_2d;
1515 let surface_params = device.create_buffer(&wgpu::BufferDescriptor {
1516 label: Some("surface_params"),
1517 size: 1 << 20,
1518 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1519 mapped_at_creation: false,
1520 });
1521
1522 Self {
1523 resources: WgpuResources {
1524 device,
1525 queue,
1526 pipelines,
1527 bind_group_layouts,
1528 atlas_sampler,
1529 imported_texture_sampler,
1530 atlas_texture_bind_groups: FxHashMap::default(),
1531 globals_buffer,
1532 surface_params,
1533 globals_bind_group,
1534 path_globals_bind_group,
1535 instance_data,
1536 path_intermediate_texture: None,
1537 path_intermediate_view: None,
1538 path_msaa_texture: None,
1539 path_msaa_view: None,
1540 #[cfg(target_os = "linux")]
1541 dmabuf_textures: crate::DmaBufTextureCache::new(),
1542 },
1543 atlas,
1544 path_globals_offset,
1545 gamma_offset,
1546 instance_data_capacity,
1547 max_instance_data_size,
1548 instance_data_alignment,
1549 uses_webgl_instance_data,
1550 rendering_params,
1551 is_bgr: false,
1552 dual_source_blending,
1553 adapter_info: context.adapter.get_info(),
1554 target_format,
1555 max_texture_size,
1556 }
1557 }
1558
1559 fn resources(&self) -> &WgpuResources {
1560 &self.resources
1561 }
1562
1563 fn resources_mut(&mut self) -> &mut WgpuResources {
1564 &mut self.resources
1565 }
1566
1567 fn ensure_intermediate_textures(&mut self, size: Size<DevicePixels>) {
1570 let width = size.width.0 as u32;
1571 let height = size.height.0 as u32;
1572 if self
1573 .resources
1574 .path_intermediate_texture
1575 .as_ref()
1576 .is_some_and(|texture| texture.width() == width && texture.height() == height)
1577 {
1578 return;
1579 }
1580
1581 let format = self.target_format;
1582 let path_sample_count = self.rendering_params.path_sample_count;
1583 let resources = &mut self.resources;
1584
1585 let (texture, view) =
1586 Self::create_path_intermediate(&resources.device, format, width, height);
1587 resources.path_intermediate_texture = Some(texture);
1588 resources.path_intermediate_view = Some(view);
1589
1590 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
1591 &resources.device,
1592 format,
1593 width,
1594 height,
1595 path_sample_count,
1596 )
1597 .map(|(texture, view)| (Some(texture), Some(view)))
1598 .unwrap_or((None, None));
1599 resources.path_msaa_texture = path_msaa_texture;
1600 resources.path_msaa_view = path_msaa_view;
1601 }
1602
1603 fn render_frame(
1604 &mut self,
1605 scene: &Scene,
1606 target_view: &wgpu::TextureView,
1607 size: Size<DevicePixels>,
1608 premultiplied_alpha: bool,
1609 clear_color: wgpu::Color,
1610 ) -> Result<wgpu::SubmissionIndex> {
1611 anyhow::ensure!(
1612 size.width.0 > 0 && size.height.0 > 0,
1613 "invalid render target size: {size:?}"
1614 );
1615 anyhow::ensure!(
1616 size.width.0 as u32 <= self.max_texture_size
1617 && size.height.0 as u32 <= self.max_texture_size,
1618 "render target size {size:?} exceeds maximum texture dimension {}",
1619 self.max_texture_size
1620 );
1621
1622 self.atlas.before_frame();
1623 self.ensure_intermediate_textures(size);
1624
1625 let gamma_params = GammaParams {
1626 gamma_ratios: self.rendering_params.gamma_ratios,
1627 grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1628 subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1629 is_bgr: self.is_bgr as u32,
1630 _pad: 0,
1631 };
1632 let globals = GlobalParams {
1633 viewport_size: [size.width.0 as f32, size.height.0 as f32],
1634 premultiplied_alpha: premultiplied_alpha as u32,
1635 pad: 0,
1636 };
1637 let path_globals = GlobalParams {
1638 premultiplied_alpha: 0,
1639 ..globals
1640 };
1641 self.resources.queue.write_buffer(
1642 &self.resources.globals_buffer,
1643 0,
1644 bytemuck::bytes_of(&globals),
1645 );
1646 self.resources.queue.write_buffer(
1647 &self.resources.globals_buffer,
1648 self.path_globals_offset,
1649 bytemuck::bytes_of(&path_globals),
1650 );
1651 self.resources.queue.write_buffer(
1652 &self.resources.globals_buffer,
1653 self.gamma_offset,
1654 bytemuck::bytes_of(&gamma_params),
1655 );
1656
1657 self.record_frame(scene, target_view, clear_color)
1658 .inspect_err(|_| {
1659 self.resources.queue.submit(std::iter::empty());
1661 })
1662 }
1663
1664 fn record_frame(
1665 &mut self,
1666 scene: &Scene,
1667 frame_view: &wgpu::TextureView,
1668 clear_color: wgpu::Color,
1669 ) -> Result<wgpu::SubmissionIndex> {
1670 let mut instance_offset = 0;
1671 let releases = Self::surface_releases(scene);
1672 let instance_bindings = self
1673 .write_instances(scene, &mut instance_offset)
1674 .with_context(|| {
1675 format!(
1676 "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1677 scene.paths.len(),
1678 scene.shadows.len(),
1679 scene.quads.len(),
1680 scene.underlines.len(),
1681 scene.monochrome_sprites.len(),
1682 scene.subpixel_sprites.len(),
1683 scene.polychrome_sprites.len(),
1684 )
1685 })?;
1686 self.prepare_texture_bind_groups(scene);
1687
1688 let mut encoder =
1689 self.resources()
1690 .device
1691 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1692 label: Some("main_encoder"),
1693 });
1694
1695 {
1696 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1697 label: Some("main_pass"),
1698 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1699 view: frame_view,
1700 resolve_target: None,
1701 ops: wgpu::Operations {
1702 load: wgpu::LoadOp::Clear(clear_color),
1703 store: wgpu::StoreOp::Store,
1704 },
1705 depth_slice: None,
1706 })],
1707 depth_stencil_attachment: None,
1708 ..Default::default()
1709 });
1710
1711 for batch in scene.batches() {
1712 match batch {
1713 PrimitiveBatch::Quads(range) => self.draw_instances(
1714 &instance_bindings.quads,
1715 &self.resources().pipelines.quads,
1716 instance_range(range),
1717 &mut pass,
1718 ),
1719 PrimitiveBatch::Shadows(range) => self.draw_instances(
1720 &instance_bindings.shadows,
1721 &self.resources().pipelines.shadows,
1722 instance_range(range),
1723 &mut pass,
1724 ),
1725 PrimitiveBatch::Paths(range) => {
1726 let paths = &scene.paths[range];
1727 if paths.is_empty() {
1728 continue;
1729 }
1730
1731 drop(pass);
1732 let rasterized = self.draw_paths_to_intermediate(
1733 &mut encoder,
1734 paths,
1735 &mut instance_offset,
1736 )?;
1737
1738 pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1739 label: Some("main_pass_continued"),
1740 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1741 view: frame_view,
1742 resolve_target: None,
1743 ops: wgpu::Operations {
1744 load: wgpu::LoadOp::Load,
1745 store: wgpu::StoreOp::Store,
1746 },
1747 depth_slice: None,
1748 })],
1749 depth_stencil_attachment: None,
1750 ..Default::default()
1751 });
1752
1753 if rasterized {
1754 self.draw_paths_from_intermediate(
1755 paths,
1756 &mut instance_offset,
1757 &mut pass,
1758 )?;
1759 }
1760 }
1761 PrimitiveBatch::Underlines(range) => self.draw_instances(
1762 &instance_bindings.underlines,
1763 &self.resources().pipelines.underlines,
1764 instance_range(range),
1765 &mut pass,
1766 ),
1767 PrimitiveBatch::MonochromeSprites { texture_id, range } => {
1768 self.draw_sprites(
1769 &instance_bindings.monochrome_sprites,
1770 texture_id,
1771 &self.resources().pipelines.mono_sprites,
1772 instance_range(range),
1773 &mut pass,
1774 )?;
1775 }
1776 PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1777 let resources = self.resources();
1778 self.draw_sprites(
1779 &instance_bindings.subpixel_sprites,
1780 texture_id,
1781 resources
1782 .pipelines
1783 .subpixel_sprites
1784 .as_ref()
1785 .unwrap_or(&resources.pipelines.mono_sprites),
1786 instance_range(range),
1787 &mut pass,
1788 )?;
1789 }
1790 PrimitiveBatch::PolychromeSprites { texture_id, range } => {
1791 self.draw_sprites(
1792 &instance_bindings.polychrome_sprites,
1793 texture_id,
1794 &self.resources().pipelines.poly_sprites,
1795 instance_range(range),
1796 &mut pass,
1797 )?;
1798 }
1799 PrimitiveBatch::Custom(range) => {
1800 self.draw_imported_textures(
1801 &instance_bindings.imported_textures,
1802 &scene.custom[range.clone()],
1803 instance_bindings.imported_textures.first_instance + range.start as u32,
1804 &mut pass,
1805 )?;
1806 }
1807 PrimitiveBatch::Surfaces(range) => {
1811 self.draw_surfaces(&scene.surfaces[range.clone()], &mut pass)?;
1812 }
1813 }
1814 }
1815 }
1816
1817 let submission = self
1818 .resources()
1819 .queue
1820 .submit(std::iter::once(encoder.finish()));
1821 if !releases.is_empty() {
1824 self.resources()
1825 .queue
1826 .on_submitted_work_done(move || {
1827 for release in &releases {
1828 crate::dmabuf::signal_release(release);
1829 }
1830 });
1831 }
1832 Ok(submission)
1833 }
1834
1835 fn write_instances(
1836 &mut self,
1837 scene: &Scene,
1838 instance_offset: &mut u64,
1839 ) -> Result<InstanceBindings> {
1840 Ok(InstanceBindings {
1841 quads: self.write_instance_binding(
1842 "quads_bind_group",
1843 instance_offset,
1844 &scene.quads,
1845 )?,
1846 shadows: self.write_instance_binding(
1847 "shadows_bind_group",
1848 instance_offset,
1849 &scene.shadows,
1850 )?,
1851 underlines: self.write_instance_binding(
1852 "underlines_bind_group",
1853 instance_offset,
1854 &scene.underlines,
1855 )?,
1856 monochrome_sprites: self.write_instance_binding(
1857 "monochrome_sprites_bind_group",
1858 instance_offset,
1859 &scene.monochrome_sprites,
1860 )?,
1861 subpixel_sprites: self.write_instance_binding(
1862 "subpixel_sprites_bind_group",
1863 instance_offset,
1864 &scene.subpixel_sprites,
1865 )?,
1866 polychrome_sprites: self.write_instance_binding(
1867 "polychrome_sprites_bind_group",
1868 instance_offset,
1869 &scene.polychrome_sprites,
1870 )?,
1871 imported_textures: self.write_instance_binding(
1872 "imported_textures_bind_group",
1873 instance_offset,
1874 &scene
1875 .custom
1876 .iter()
1877 .map(CustomRenderPrimitive::to_quad_record)
1878 .collect::<Vec<_>>(),
1879 )?,
1880 })
1881 }
1882
1883 fn create_texture_bind_group(
1884 &self,
1885 label: &str,
1886 texture_view: &wgpu::TextureView,
1887 ) -> wgpu::BindGroup {
1888 let resources = self.resources();
1889 resources
1890 .device
1891 .create_bind_group(&wgpu::BindGroupDescriptor {
1892 label: Some(label),
1893 layout: &resources.bind_group_layouts.texture,
1894 entries: &[
1895 wgpu::BindGroupEntry {
1896 binding: 0,
1897 resource: wgpu::BindingResource::TextureView(texture_view),
1898 },
1899 wgpu::BindGroupEntry {
1900 binding: 1,
1901 resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1902 },
1903 ],
1904 })
1905 }
1906
1907 fn prepare_texture_bind_groups(&mut self, scene: &Scene) {
1908 let mut texture_ids = SmallVec::<[AtlasTextureId; 8]>::new();
1909 for batch in scene.batches() {
1910 let texture_id = match batch {
1911 PrimitiveBatch::MonochromeSprites { texture_id, .. }
1912 | PrimitiveBatch::SubpixelSprites { texture_id, .. }
1913 | PrimitiveBatch::PolychromeSprites { texture_id, .. } => texture_id,
1914 _ => continue,
1915 };
1916 if !texture_ids.contains(&texture_id) {
1917 texture_ids.push(texture_id);
1918 }
1919 }
1920
1921 self.resources_mut()
1922 .atlas_texture_bind_groups
1923 .retain(|texture_id, _| texture_ids.contains(texture_id));
1924
1925 for texture_id in texture_ids {
1926 let Some(texture_info) = self.atlas.get_texture_info(texture_id) else {
1927 self.resources_mut()
1928 .atlas_texture_bind_groups
1929 .remove(&texture_id);
1930 continue;
1931 };
1932 let is_current = self
1933 .resources()
1934 .atlas_texture_bind_groups
1935 .get(&texture_id)
1936 .is_some_and(|cached| cached.texture_generation == texture_info.generation);
1937 if is_current {
1938 continue;
1939 }
1940
1941 let bind_group =
1942 self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
1943 self.resources_mut().atlas_texture_bind_groups.insert(
1944 texture_id,
1945 CachedTextureBindGroup {
1946 texture_generation: texture_info.generation,
1947 bind_group,
1948 },
1949 );
1950 }
1951 }
1952
1953 fn draw_instances(
1954 &self,
1955 instances: &InstanceBinding,
1956 pipeline: &wgpu::RenderPipeline,
1957 range: Range<u32>,
1958 pass: &mut wgpu::RenderPass<'_>,
1959 ) {
1960 if range.is_empty() {
1961 return;
1962 }
1963 pass.set_pipeline(pipeline);
1964 pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1965 pass.set_bind_group(1, &instances.bind_group, &[]);
1966 pass.draw(
1967 0..4,
1968 instances.first_instance + range.start..instances.first_instance + range.end,
1969 );
1970 }
1971
1972 fn draw_sprites(
1973 &self,
1974 sprite_instances: &InstanceBinding,
1975 texture_id: AtlasTextureId,
1976 pipeline: &wgpu::RenderPipeline,
1977 range: Range<u32>,
1978 pass: &mut wgpu::RenderPass<'_>,
1979 ) -> Result<()> {
1980 if range.is_empty() {
1981 return Ok(());
1982 }
1983 let resources = self.resources();
1984 let Some(texture) = resources.atlas_texture_bind_groups.get(&texture_id) else {
1987 return Ok(());
1988 };
1989 pass.set_pipeline(pipeline);
1990 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1991 pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
1992 pass.set_bind_group(2, &texture.bind_group, &[]);
1993 pass.draw(
1994 0..4,
1995 sprite_instances.first_instance + range.start
1996 ..sprite_instances.first_instance + range.end,
1997 );
1998 Ok(())
1999 }
2000
2001 fn draw_imported_textures(
2002 &self,
2003 instances: &InstanceBinding,
2004 primitives: &[CustomRenderPrimitive],
2005 first_instance: u32,
2006 pass: &mut wgpu::RenderPass<'_>,
2007 ) -> Result<()> {
2008 if primitives.is_empty() {
2009 return Ok(());
2010 }
2011
2012 let resources = self.resources();
2013 pass.set_pipeline(&resources.pipelines.imported_textures);
2014 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
2015 pass.set_bind_group(1, &instances.bind_group, &[]);
2016
2017 for (offset, primitive) in primitives.iter().enumerate() {
2018 let CustomRenderPrimitive::Texture { handle, .. } = primitive;
2019 let Some(texture) = handle.payload.downcast_ref::<WgpuImportedTexture>() else {
2020 anyhow::bail!("imported texture handle does not hold a wgpu texture view");
2021 };
2022
2023 let bind_group = self.create_imported_texture_bind_group(&texture.view);
2024 pass.set_bind_group(2, &bind_group, &[]);
2025
2026 let instance = first_instance + offset as u32;
2027 pass.draw(0..4, instance..instance + 1);
2028 }
2029 Ok(())
2030 }
2031
2032 fn create_imported_texture_bind_group(&self, view: &wgpu::TextureView) -> wgpu::BindGroup {
2033 let resources = self.resources();
2034 resources
2035 .device
2036 .create_bind_group(&wgpu::BindGroupDescriptor {
2037 label: Some("imported_texture_bind_group"),
2038 layout: &resources.bind_group_layouts.imported_texture,
2039 entries: &[
2040 wgpu::BindGroupEntry {
2041 binding: 0,
2042 resource: wgpu::BindingResource::TextureView(view),
2043 },
2044 wgpu::BindGroupEntry {
2045 binding: 1,
2046 resource: wgpu::BindingResource::Sampler(
2047 &resources.imported_texture_sampler,
2048 ),
2049 },
2050 ],
2051 })
2052 }
2053
2054 fn surface_releases(scene: &Scene) -> Vec<Arc<OwnedFd>> {
2059 #[cfg(target_os = "linux")]
2060 {
2061 scene
2062 .surfaces
2063 .iter()
2064 .filter_map(|surface| match &surface.source {
2065 gpui_engine::SurfaceSource::DmaBuf(handle) => handle.release.clone(),
2066 })
2067 .collect()
2068 }
2069 #[cfg(not(target_os = "linux"))]
2070 {
2071 let _ = scene;
2072 Vec::new()
2073 }
2074 }
2075
2076 fn release_unpresented(&self, scene: &Scene) -> bool {
2082 let releases = Self::surface_releases(scene);
2083 if !releases.is_empty() {
2084 self.resources()
2085 .queue
2086 .on_submitted_work_done(move || {
2087 for release in &releases {
2088 crate::dmabuf::signal_release(release);
2089 }
2090 });
2091 }
2092 false
2093 }
2094
2095 fn draw_surfaces(
2103 &mut self,
2104 surfaces: &[PaintSurface],
2105 pass: &mut wgpu::RenderPass<'_>,
2106 ) -> Result<()> {
2107 if surfaces.is_empty() {
2108 return Ok(());
2109 }
2110 pass.set_pipeline(&self.resources().pipelines.surfaces);
2111 pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
2112
2113 #[cfg(target_os = "linux")]
2116 let params_stride = {
2117 let align = u64::from(
2118 self.resources()
2119 .device
2120 .limits()
2121 .min_uniform_buffer_offset_alignment,
2122 );
2123 let size = std::mem::size_of::<SurfaceParams>() as u64;
2124 size.div_ceil(align) * align
2125 };
2126 #[cfg(target_os = "linux")]
2127 let mut params_bytes = vec![0u8; params_stride as usize * surfaces.len()];
2128 #[cfg(target_os = "linux")]
2129 for (index, surface) in surfaces.iter().enumerate() {
2130 let gpui_engine::SurfaceSource::DmaBuf(handle) = &surface.source;
2131 if let Some(fence) = &handle.acquire_fence
2134 && !crate::wait_for_acquire_fence(fence, crate::ACQUIRE_FENCE_TIMEOUT)
2135 {
2136 warn!("dropping a dma-buf surface: its acquire fence did not signal in time");
2137 continue;
2138 }
2139 let textures = {
2143 let resources = self.resources_mut();
2144 match resources
2145 .dmabuf_textures
2146 .textures(&resources.device, handle)
2147 {
2148 Ok(textures) => textures,
2149 Err(error) => {
2150 warn!("dropping a dma-buf surface: {error:#}");
2151 continue;
2152 }
2153 }
2154 };
2155 let resources = self.resources();
2156
2157 let luma = textures[0].view.clone();
2158 let (chroma, surface_format) = if textures.len() == 1 {
2162 (luma.clone(), 1u32)
2163 } else {
2164 (textures[1].view.clone(), 0u32)
2165 };
2166
2167 let chroma_reconstruction = match handle.chroma {
2168 gpui_engine::ChromaReconstruction::Bilinear => 0,
2169 gpui_engine::ChromaReconstruction::LumaGuided => 1,
2170 };
2171
2172 let transfer_function = match handle.transfer {
2173 gpui_engine::TransferFunction::Linear => 1,
2174 gpui_engine::TransferFunction::Srgb | gpui_engine::TransferFunction::Bt709 => 0,
2175 };
2176 let params = SurfaceParams {
2177 bounds: surface.bounds.into(),
2178 content_mask: surface.content_mask.bounds.into(),
2179 surface_format,
2180 chroma_reconstruction,
2181 transfer_function,
2182 _pad: 0,
2183 ycbcr_to_rgb: yuv_to_rgb(handle.color_space),
2184 };
2185 let params_offset = params_stride as usize * index;
2187 params_bytes[params_offset..params_offset + std::mem::size_of::<SurfaceParams>()]
2188 .copy_from_slice(bytemuck::bytes_of(¶ms));
2189
2190 let bind_group = resources
2191 .device
2192 .create_bind_group(&wgpu::BindGroupDescriptor {
2193 label: Some("surface_bind_group"),
2194 layout: &resources.bind_group_layouts.surfaces,
2195 entries: &[
2196 wgpu::BindGroupEntry {
2197 binding: 0,
2198 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2199 buffer: &resources.surface_params,
2200 offset: 0,
2201 size: NonZeroU64::new(
2202 std::mem::size_of::<SurfaceParams>() as u64,
2203 ),
2204 }),
2205 },
2206 wgpu::BindGroupEntry {
2207 binding: 1,
2208 resource: wgpu::BindingResource::TextureView(&luma),
2209 },
2210 wgpu::BindGroupEntry {
2211 binding: 2,
2212 resource: wgpu::BindingResource::TextureView(&chroma),
2213 },
2214 wgpu::BindGroupEntry {
2215 binding: 3,
2216 resource: wgpu::BindingResource::Sampler(
2217 &resources.imported_texture_sampler,
2218 ),
2219 },
2220 ],
2221 });
2222 pass.set_bind_group(1, &bind_group, &[params_offset as u32]);
2223 pass.draw(0..4, 0..1);
2224 }
2225
2226 #[cfg(target_os = "linux")]
2228 self.resources()
2229 .queue
2230 .write_buffer(&self.resources().surface_params, 0, ¶ms_bytes);
2231
2232 Ok(())
2233 }
2234
2235 unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
2236 unsafe {
2237 std::slice::from_raw_parts(
2238 instances.as_ptr() as *const u8,
2239 std::mem::size_of_val(instances),
2240 )
2241 }
2242 }
2243
2244 fn draw_paths_from_intermediate(
2245 &mut self,
2246 paths: &[Path<ScaledPixels>],
2247 instance_offset: &mut u64,
2248 pass: &mut wgpu::RenderPass<'_>,
2249 ) -> Result<()> {
2250 let first_path = &paths[0];
2251 let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
2252 {
2253 paths
2254 .iter()
2255 .map(|p| PathSprite {
2256 bounds: p.clipped_bounds(),
2257 })
2258 .collect()
2259 } else {
2260 let mut bounds = first_path.clipped_bounds();
2261 for path in paths.iter().skip(1) {
2262 bounds = bounds.union(&path.clipped_bounds());
2263 }
2264 vec![PathSprite { bounds }]
2265 };
2266
2267 let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
2268 return Ok(());
2269 };
2270 let instances =
2271 self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
2272 let texture = self.create_texture_bind_group(
2273 "path_intermediate_texture_bind_group",
2274 &path_intermediate_view,
2275 );
2276 let resources = self.resources();
2277 pass.set_pipeline(&resources.pipelines.paths);
2278 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
2279 pass.set_bind_group(1, &instances.bind_group, &[]);
2280 pass.set_bind_group(2, &texture, &[]);
2281 pass.draw(
2282 0..4,
2283 instances.first_instance..instances.first_instance + sprites.len() as u32,
2284 );
2285 Ok(())
2286 }
2287
2288 fn draw_paths_to_intermediate(
2289 &mut self,
2290 encoder: &mut wgpu::CommandEncoder,
2291 paths: &[Path<ScaledPixels>],
2292 instance_offset: &mut u64,
2293 ) -> Result<bool> {
2294 let mut vertices = Vec::new();
2295 for path in paths {
2296 let bounds = path.clipped_bounds();
2297 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
2298 xy_position: v.xy_position,
2299 st_position: v.st_position,
2300 color: path.color,
2301 bounds,
2302 }));
2303 }
2304
2305 if vertices.is_empty() {
2306 return Ok(false);
2307 }
2308
2309 let vertex_binding = self.write_instance_binding(
2310 "path_rasterization_bind_group",
2311 instance_offset,
2312 &vertices,
2313 )?;
2314
2315 let resources = self.resources();
2316 let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
2317 return Ok(false);
2318 };
2319
2320 let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
2321 (msaa_view, Some(path_intermediate_view))
2322 } else {
2323 (path_intermediate_view, None)
2324 };
2325
2326 {
2327 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2328 label: Some("path_rasterization_pass"),
2329 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2330 view: target_view,
2331 resolve_target,
2332 ops: wgpu::Operations {
2333 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
2334 store: wgpu::StoreOp::Store,
2335 },
2336 depth_slice: None,
2337 })],
2338 depth_stencil_attachment: None,
2339 ..Default::default()
2340 });
2341
2342 pass.set_pipeline(&resources.pipelines.path_rasterization);
2343 pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
2344 pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
2345 pass.draw(
2349 vertex_binding.first_instance
2350 ..vertex_binding.first_instance + vertices.len() as u32,
2351 0..1,
2352 );
2353 }
2354
2355 Ok(true)
2356 }
2357
2358 fn write_instance_binding<T>(
2359 &mut self,
2360 label: &str,
2361 instance_offset: &mut u64,
2362 instances: &[T],
2363 ) -> Result<InstanceBinding> {
2364 let data = unsafe { Self::instance_bytes(instances) };
2365 let size = (data.len() as u64).max(16);
2368 let stride = (std::mem::size_of::<T>() as u64).max(1);
2369 let (alignment, allocation_size) = if self.uses_webgl_instance_data {
2370 (
2376 least_common_multiple(self.instance_data_alignment, stride),
2377 size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
2378 )
2379 } else {
2380 (self.instance_data_alignment.max(1), size)
2381 };
2382 let mut offset = (*instance_offset).next_multiple_of(alignment);
2383 if offset + allocation_size > self.instance_data_capacity {
2384 self.grow_instance_data(allocation_size)?;
2385 offset = 0;
2386 }
2387 *instance_offset = offset + allocation_size;
2388
2389 let first_instance = if self.uses_webgl_instance_data {
2390 u32::try_from(offset / stride).context("instance index exceeds u32 range")?
2391 } else {
2392 0
2393 };
2394
2395 let resources = self.resources();
2396 if !data.is_empty() {
2397 match &resources.instance_data {
2398 InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
2399 InstanceData::Texture { .. } => {
2400 Self::write_instance_texture(resources, offset, data)
2401 }
2402 }
2403 }
2404 let bind_group = resources
2405 .device
2406 .create_bind_group(&wgpu::BindGroupDescriptor {
2407 label: Some(label),
2408 layout: &resources.bind_group_layouts.instances,
2409 entries: &[wgpu::BindGroupEntry {
2410 binding: 0,
2411 resource: match &resources.instance_data {
2412 InstanceData::Storage(buffer) => {
2413 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2414 buffer,
2415 offset,
2416 size: NonZeroU64::new(size),
2417 })
2418 }
2419 InstanceData::Texture { view, .. } => {
2420 wgpu::BindingResource::TextureView(view)
2421 }
2422 },
2423 }],
2424 });
2425 Ok(InstanceBinding {
2426 bind_group,
2427 first_instance,
2428 })
2429 }
2430
2431 fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
2432 let InstanceData::Texture {
2433 texture,
2434 width,
2435 height,
2436 ..
2437 } = &resources.instance_data
2438 else {
2439 return;
2440 };
2441 let mut byte_offset = 0usize;
2442 let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
2443 while byte_offset < data.len() {
2444 let x = (texel_offset % u64::from(*width)) as u32;
2445 let y = (texel_offset / u64::from(*width)) as u32;
2446 if y >= *height {
2447 debug_assert!(
2451 false,
2452 "instance texture write out of bounds: row {y} >= height {}",
2453 *height
2454 );
2455 log::error!(
2456 "instance texture write out of bounds; dropping {} bytes of instance data",
2457 data.len() - byte_offset
2458 );
2459 return;
2460 }
2461 let available_texels = u64::from(*width - x);
2462 let remaining_bytes = data.len() - byte_offset;
2463 let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
2464 let texels = complete_texels.min(available_texels);
2465 if texels > 0 {
2466 let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
2467 resources.queue.write_texture(
2468 wgpu::TexelCopyTextureInfo {
2469 texture,
2470 mip_level: 0,
2471 origin: wgpu::Origin3d { x, y, z: 0 },
2472 aspect: wgpu::TextureAspect::All,
2473 },
2474 &data[byte_offset..byte_offset + byte_count],
2475 wgpu::TexelCopyBufferLayout {
2476 offset: 0,
2477 bytes_per_row: Some(byte_count as u32),
2478 rows_per_image: None,
2479 },
2480 wgpu::Extent3d {
2481 width: texels as u32,
2482 height: 1,
2483 depth_or_array_layers: 1,
2484 },
2485 );
2486 byte_offset += byte_count;
2487 texel_offset += texels;
2488 continue;
2489 }
2490
2491 let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
2492 final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
2493 resources.queue.write_texture(
2494 wgpu::TexelCopyTextureInfo {
2495 texture,
2496 mip_level: 0,
2497 origin: wgpu::Origin3d { x, y, z: 0 },
2498 aspect: wgpu::TextureAspect::All,
2499 },
2500 &final_texel,
2501 wgpu::TexelCopyBufferLayout {
2502 offset: 0,
2503 bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
2504 rows_per_image: None,
2505 },
2506 wgpu::Extent3d {
2507 width: 1,
2508 height: 1,
2509 depth_or_array_layers: 1,
2510 },
2511 );
2512 break;
2513 }
2514 }
2515
2516 fn grow_instance_data(&mut self, required: u64) -> Result<()> {
2517 let capacity = (self.instance_data_capacity * 2)
2518 .max(required.next_power_of_two())
2519 .min(self.max_instance_data_size);
2520 anyhow::ensure!(
2521 capacity >= required,
2522 "instance data needs {required} bytes, above the maximum of {}",
2523 self.max_instance_data_size
2524 );
2525 anyhow::ensure!(
2526 capacity > self.instance_data_capacity,
2527 "frame instance data exceeds the {}-byte maximum",
2528 self.max_instance_data_size
2529 );
2530 log::debug!(
2531 "instance data grown from {} to {capacity}",
2532 self.instance_data_capacity
2533 );
2534 let uses_webgl_instance_data = self.uses_webgl_instance_data;
2538 let resources = self.resources_mut();
2539 if uses_webgl_instance_data {
2540 let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
2541 let (instance_data, actual_capacity) =
2542 Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
2543 resources.instance_data = instance_data;
2544 self.instance_data_capacity = actual_capacity;
2545 } else {
2546 resources.instance_data =
2547 InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
2548 label: Some("instance_buffer"),
2549 size: capacity,
2550 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2551 mapped_at_creation: false,
2552 }));
2553 self.instance_data_capacity = capacity;
2554 }
2555 Ok(())
2556 }
2557}
2558
2559impl WgpuRenderer {
2560 pub fn unconfigure_surface(&mut self) {
2568 self.state = match std::mem::replace(&mut self.state, RendererState::Released) {
2569 RendererState::Ready { core, surface } => {
2570 drop(surface);
2571 RendererState::Unconfigured { core }
2572 }
2573 state @ (RendererState::Unconfigured { .. } | RendererState::Released) => state,
2574 };
2575 if let Some(core) = self.core_mut() {
2577 core.resources.invalidate_intermediate_textures();
2578 }
2579 }
2580
2581 #[cfg(not(target_family = "wasm"))]
2590 pub fn replace_surface<W: HasWindowHandle>(
2591 &mut self,
2592 window: &W,
2593 config: WgpuSurfaceConfig,
2594 instance: &wgpu::Instance,
2595 ) -> anyhow::Result<()> {
2596 let window_handle = window
2597 .window_handle()
2598 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2599
2600 let surface = create_surface(instance, window_handle.as_raw())?;
2601
2602 let width = (config.size.width.0 as u32).max(1);
2603 let height = (config.size.height.0 as u32).max(1);
2604
2605 let alpha_mode = if config.transparent {
2606 self.transparent_alpha_mode
2607 } else {
2608 self.opaque_alpha_mode
2609 };
2610
2611 self.surface_config.width = width;
2612 self.surface_config.height = height;
2613 self.surface_config.alpha_mode = alpha_mode;
2614 if let Some(mode) = config.preferred_present_mode {
2615 self.surface_config.present_mode = mode;
2616 }
2617
2618 let mut core = match std::mem::replace(&mut self.state, RendererState::Released) {
2619 RendererState::Ready {
2620 core,
2621 surface: old_surface,
2622 } => {
2623 drop(old_surface);
2624 core
2625 }
2626 RendererState::Unconfigured { core } => core,
2627 RendererState::Released => {
2628 anyhow::bail!("Cannot replace the surface: GPU resources have been released")
2629 }
2630 };
2631 surface.configure(&core.resources.device, &self.surface_config);
2632 core.resources.invalidate_intermediate_textures();
2633 self.state = RendererState::Ready { surface, core };
2634
2635 Ok(())
2636 }
2637
2638 pub fn destroy(&mut self) {
2639 self.state = RendererState::Released;
2642 }
2643
2644 pub fn device_lost(&self) -> bool {
2646 self.device_errors.device_lost()
2647 }
2648
2649 pub fn needs_redraw(&mut self) -> bool {
2652 std::mem::take(&mut self.needs_redraw)
2653 }
2654
2655 #[cfg(not(target_family = "wasm"))]
2663 pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2664 where
2665 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2666 {
2667 let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2668
2669 let needs_new_context = gpu_context
2671 .borrow()
2672 .as_ref()
2673 .is_none_or(|ctx| ctx.device_lost());
2674
2675 let window_handle = window
2676 .window_handle()
2677 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2678
2679 let surface = if needs_new_context {
2680 log::warn!("GPU device lost, recreating context...");
2681
2682 self.state = RendererState::Released;
2684 *gpu_context.borrow_mut() = None;
2685
2686 std::thread::sleep(std::time::Duration::from_millis(350));
2691
2692 let instance = WgpuContext::instance(Some(Box::new(window.clone())));
2693 let surface = create_surface(&instance, window_handle.as_raw())?;
2694 let new_context = WgpuContext::new_rejecting_software(
2695 instance,
2696 &surface,
2697 self.compositor_gpu,
2698 self.gpu_preference.clone(),
2699 )?;
2700 *gpu_context.borrow_mut() = Some(new_context);
2701 surface
2702 } else {
2703 let ctx_ref = gpu_context.borrow();
2704 let instance = &ctx_ref.as_ref().unwrap().instance;
2705 create_surface(instance, window_handle.as_raw())?
2706 };
2707
2708 let config = WgpuSurfaceConfig {
2709 size: gpui_platform::Size {
2710 width: gpui_platform::DevicePixels(self.surface_config.width as i32),
2711 height: gpui_platform::DevicePixels(self.surface_config.height as i32),
2712 },
2713 transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2714 preferred_present_mode: Some(self.surface_config.present_mode),
2715 };
2716 let gpu_context = Rc::clone(gpu_context);
2717 let ctx_ref = gpu_context.borrow();
2718 let context = ctx_ref.as_ref().expect("context should exist");
2719
2720 self.state = RendererState::Released;
2721 self.atlas.handle_device_lost(context);
2722
2723 let is_bgr = self.is_bgr;
2724 *self = Self::new_internal(
2725 Some(gpu_context.clone()),
2726 context,
2727 surface,
2728 config,
2729 self.compositor_gpu,
2730 self.gpu_preference.clone(),
2731 self.atlas.clone(),
2732 )?;
2733 self.set_subpixel_layout(is_bgr);
2734
2735 log::info!("GPU recovery complete");
2736 Ok(())
2737 }
2738}
2739
2740#[cfg(all(
2741 not(target_family = "wasm"),
2742 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2743))]
2744struct HeadlessRenderTarget {
2745 texture: Arc<wgpu::Texture>,
2748 view: wgpu::TextureView,
2749}
2750
2751#[cfg(all(
2752 not(target_family = "wasm"),
2753 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2754))]
2755impl HeadlessRenderTarget {
2756 fn size(&self) -> Size<DevicePixels> {
2757 Size {
2758 width: DevicePixels(self.texture.width() as i32),
2759 height: DevicePixels(self.texture.height() as i32),
2760 }
2761 }
2762}
2763
2764#[cfg(all(
2771 not(target_family = "wasm"),
2772 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2773))]
2774pub struct WgpuOffscreenSurface {
2775 texture: Arc<wgpu::Texture>,
2776 size: Size<DevicePixels>,
2777}
2778
2779#[cfg(all(
2780 not(target_family = "wasm"),
2781 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2782))]
2783impl WgpuOffscreenSurface {
2784 pub fn texture(&self) -> &wgpu::Texture {
2786 &self.texture
2787 }
2788
2789 pub fn size(&self) -> Size<DevicePixels> {
2791 self.size
2792 }
2793}
2794
2795#[cfg(all(
2796 not(target_family = "wasm"),
2797 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2798))]
2799pub struct WgpuHeadlessRenderer {
2800 context: WgpuContext,
2801 core: WgpuRendererCore,
2802 render_target: Option<HeadlessRenderTarget>,
2803 observed_error_generation: u64,
2804 viewport_size: Option<Size<DevicePixels>>,
2805}
2806
2807#[cfg(all(
2808 not(target_family = "wasm"),
2809 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
2810))]
2811impl WgpuHeadlessRenderer {
2812 pub fn new() -> anyhow::Result<Self> {
2813 Self::with_gpu_preference(GpuPreference::Auto)
2814 }
2815
2816 pub fn with_gpu_preference(gpu_preference: GpuPreference) -> anyhow::Result<Self> {
2822 let (context, target_format) = WgpuContext::new_headless_with(gpu_preference)?;
2823 let observed_error_generation = context.errors().current_generation();
2826 let atlas = Arc::new(WgpuAtlas::from_context(&context));
2827 let core = WgpuRendererCore::new(
2828 &context,
2829 atlas,
2830 target_format,
2831 wgpu::CompositeAlphaMode::Opaque,
2832 );
2833
2834 Ok(Self {
2835 context,
2836 core,
2837 render_target: None,
2838 observed_error_generation,
2839 viewport_size: None,
2840 })
2841 }
2842
2843 fn ensure_render_target(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()> {
2844 anyhow::ensure!(
2845 size.width.0 > 0 && size.height.0 > 0,
2846 "invalid headless render target size: {size:?}"
2847 );
2848 anyhow::ensure!(
2849 size.width.0 as u32 <= self.core.max_texture_size
2850 && size.height.0 as u32 <= self.core.max_texture_size,
2851 "headless render target size {size:?} exceeds maximum texture dimension {}",
2852 self.core.max_texture_size
2853 );
2854 if self
2855 .render_target
2856 .as_ref()
2857 .is_some_and(|target| target.size() == size)
2858 {
2859 return Ok(());
2860 }
2861
2862 let texture = self
2863 .core
2864 .resources
2865 .device
2866 .create_texture(&wgpu::TextureDescriptor {
2867 label: Some("headless_render_target"),
2868 size: wgpu::Extent3d {
2869 width: size.width.0 as u32,
2870 height: size.height.0 as u32,
2871 depth_or_array_layers: 1,
2872 },
2873 mip_level_count: 1,
2874 sample_count: 1,
2875 dimension: wgpu::TextureDimension::D2,
2876 format: self.core.target_format,
2877 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2878 view_formats: &[],
2879 });
2880 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2881 self.render_target = Some(HeadlessRenderTarget {
2882 texture: Arc::new(texture),
2883 view,
2884 });
2885 Ok(())
2886 }
2887
2888 fn check_gpu_errors(&mut self) -> anyhow::Result<()> {
2889 anyhow::ensure!(
2890 !self.context.device_lost(),
2891 "GPU device was lost during headless rendering"
2892 );
2893 if let Some(error) = self
2894 .context
2895 .errors()
2896 .observe_error(&mut self.observed_error_generation)
2897 {
2898 anyhow::bail!("GPU error during headless rendering: {error}");
2899 }
2900 Ok(())
2901 }
2902
2903 fn render(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2904 self.check_gpu_errors()?;
2905 self.ensure_render_target(size)?;
2906 let view = self
2907 .render_target
2908 .as_ref()
2909 .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?
2910 .view
2911 .clone();
2912 self.core
2913 .render_frame(scene, &view, size, false, wgpu::Color::BLACK)?;
2914 Ok(())
2915 }
2916
2917 fn read_image(&mut self) -> anyhow::Result<image::RgbaImage> {
2920 let target = self
2921 .render_target
2922 .as_ref()
2923 .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?;
2924 let width = target.texture.width();
2925 let height = target.texture.height();
2926 let bytes_per_row = width
2927 .checked_mul(4)
2928 .ok_or_else(|| anyhow::anyhow!("Headless render target row size overflowed"))?;
2929 let padded_bytes_per_row = bytes_per_row
2930 .checked_next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
2931 .ok_or_else(|| anyhow::anyhow!("Headless padded row size overflowed"))?;
2932 let buffer_size = u64::from(padded_bytes_per_row)
2933 .checked_mul(u64::from(height))
2934 .ok_or_else(|| anyhow::anyhow!("Headless readback buffer size overflowed"))?;
2935 anyhow::ensure!(
2936 buffer_size <= self.core.resources.device.limits().max_buffer_size,
2937 "Headless readback buffer size {buffer_size} exceeds maximum buffer size {}",
2938 self.core.resources.device.limits().max_buffer_size
2939 );
2940 let readback_buffer = self
2941 .core
2942 .resources
2943 .device
2944 .create_buffer(&wgpu::BufferDescriptor {
2945 label: Some("headless_readback_buffer"),
2946 size: buffer_size,
2947 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2948 mapped_at_creation: false,
2949 });
2950 let mut encoder =
2951 self.core
2952 .resources
2953 .device
2954 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2955 label: Some("headless_readback_encoder"),
2956 });
2957 encoder.copy_texture_to_buffer(
2958 wgpu::TexelCopyTextureInfo {
2959 texture: &target.texture,
2960 mip_level: 0,
2961 origin: wgpu::Origin3d::ZERO,
2962 aspect: wgpu::TextureAspect::All,
2963 },
2964 wgpu::TexelCopyBufferInfo {
2965 buffer: &readback_buffer,
2966 layout: wgpu::TexelCopyBufferLayout {
2967 offset: 0,
2968 bytes_per_row: Some(padded_bytes_per_row),
2969 rows_per_image: Some(height),
2970 },
2971 },
2972 wgpu::Extent3d {
2973 width,
2974 height,
2975 depth_or_array_layers: 1,
2976 },
2977 );
2978 let submission = self
2979 .core
2980 .resources
2981 .queue
2982 .submit(std::iter::once(encoder.finish()));
2983 let (sender, receiver) = std::sync::mpsc::channel();
2984 readback_buffer
2985 .slice(..)
2986 .map_async(wgpu::MapMode::Read, move |result| {
2987 if sender.send(result).is_err() {
2988 log::error!("Headless readback receiver was dropped before mapping completed");
2989 }
2990 });
2991 self.core
2992 .resources
2993 .device
2994 .poll(wgpu::PollType::Wait {
2995 submission_index: Some(submission),
2996 timeout: Some(std::time::Duration::from_secs(30)),
2997 })
2998 .map_err(|error| anyhow::anyhow!("Failed to wait for headless rendering: {error}"))?;
2999 receiver
3000 .recv_timeout(std::time::Duration::from_secs(30))
3001 .map_err(|error| anyhow::anyhow!("Failed to receive headless mapping result: {error}"))?
3002 .map_err(|error| anyhow::anyhow!("Failed to map headless readback buffer: {error}"))?;
3003 self.check_gpu_errors()?;
3004
3005 let mapped_data = readback_buffer.slice(..).get_mapped_range();
3006 let pixel_capacity = usize::try_from(u64::from(bytes_per_row) * u64::from(height))
3007 .map_err(|_| anyhow::anyhow!("Headless image size exceeds addressable memory"))?;
3008 let mut pixels = Vec::with_capacity(pixel_capacity);
3009 for row in mapped_data
3010 .chunks_exact(padded_bytes_per_row as usize)
3011 .take(height as usize)
3012 {
3013 pixels.extend_from_slice(&row[..bytes_per_row as usize]);
3014 }
3015 drop(mapped_data);
3016 readback_buffer.unmap();
3017
3018 if self.core.target_format == wgpu::TextureFormat::Bgra8Unorm {
3019 for pixel in pixels.chunks_exact_mut(4) {
3020 pixel.swap(0, 2);
3021 }
3022 }
3023
3024 image::RgbaImage::from_raw(width, height, pixels)
3025 .ok_or_else(|| anyhow::anyhow!("Failed to create image from headless pixel data"))
3026 }
3027}
3028
3029#[cfg(all(
3030 not(target_family = "wasm"),
3031 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
3032))]
3033impl gpui_engine::SceneRenderer for WgpuHeadlessRenderer {
3034 fn draw(&mut self, scene: &Scene) -> bool {
3035 if let Some(size) = self.viewport_size {
3038 let _ = self.render(scene, size);
3039 }
3040 true
3041 }
3042
3043 fn sprite_atlas(&self) -> Arc<dyn gpui_engine::PlatformAtlas> {
3044 self.core.atlas.clone()
3045 }
3046
3047 fn set_viewport_size(&mut self, size: Size<DevicePixels>) {
3048 self.viewport_size = Some(size);
3049 }
3050
3051 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
3052 self.render(scene, size)
3053 }
3054
3055 fn read_pixels(&mut self) -> anyhow::Result<gpui_engine::PixelBuffer> {
3056 let image = self.read_image()?;
3057 let (width, height) = image.dimensions();
3058 gpui_engine::PixelBuffer::new(width, height, image.into_raw())
3059 }
3060
3061 fn offscreen_surface_handle(&self) -> Option<Arc<dyn Any + Send + Sync>> {
3062 let target = self.render_target.as_ref()?;
3063 Some(Arc::new(WgpuOffscreenSurface {
3064 texture: target.texture.clone(),
3065 size: target.size(),
3066 }))
3067 }
3068}
3069
3070#[cfg(all(
3071 target_os = "windows",
3072 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
3073))]
3074impl WinSceneRenderer for WgpuHeadlessRenderer {
3075 fn set_background_appearance(&mut self, _appearance: WindowBackgroundAppearance) {
3076 }
3078
3079 fn resize(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()> {
3080 self.viewport_size = Some(size);
3081 Ok(())
3082 }
3083
3084 fn mark_drawable(&mut self) {}
3085}
3086
3087#[cfg(all(
3099 not(target_family = "wasm"),
3100 not(target_os = "macos"),
3101 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
3102))]
3103impl PlatformRenderer for WgpuHeadlessRenderer {
3104 fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
3105 self.viewport_size = Some(size);
3106 }
3107
3108 fn max_texture_size(&self) -> u32 {
3109 self.core.max_texture_size
3110 }
3111
3112 fn gpu_specs(&self) -> Option<GpuSpecs> {
3113 let info = self.context.adapter.get_info();
3114 Some(GpuSpecs {
3115 is_software_emulated: info.device_type == wgpu::DeviceType::Cpu,
3116 device_name: info.name,
3117 driver_name: info.driver,
3118 driver_info: info.driver_info,
3119 })
3120 }
3121
3122 fn device_lost(&self) -> bool {
3123 self.context.device_lost()
3124 }
3125
3126 fn recover(&mut self, _target: RendererTarget<'_>) -> anyhow::Result<()> {
3127 anyhow::ensure!(
3130 !self.context.device_lost(),
3131 "the headless renderer's device was lost and has no surface to recover from"
3132 );
3133 Ok(())
3134 }
3135}
3136
3137#[cfg(all(
3138 not(target_family = "wasm"),
3139 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
3140))]
3141impl GpuRenderer for WgpuHeadlessRenderer {
3142 type Device = (Arc<wgpu::Device>, Arc<wgpu::Queue>);
3143
3144 fn device(&self) -> Option<Self::Device> {
3146 Some((self.context.device.clone(), self.context.queue.clone()))
3147 }
3148}
3149
3150fn instance_range(range: Range<usize>) -> Range<u32> {
3151 range.start as u32..range.end as u32
3152}
3153
3154#[cfg(not(target_family = "wasm"))]
3155fn create_surface(
3156 instance: &wgpu::Instance,
3157 raw_window_handle: raw_window_handle::RawWindowHandle,
3158) -> anyhow::Result<wgpu::Surface<'static>> {
3159 unsafe {
3160 instance
3161 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
3162 raw_display_handle: None,
3164 raw_window_handle,
3165 })
3166 .map_err(|e| anyhow::anyhow!("{e}"))
3167 }
3168}
3169
3170struct RenderingParameters {
3171 path_sample_count: u32,
3172 gamma_ratios: [f32; 4],
3173 grayscale_enhanced_contrast: f32,
3174 subpixel_enhanced_contrast: f32,
3175}
3176
3177impl RenderingParameters {
3178 fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
3179 use std::env;
3180
3181 let format_features = adapter.get_texture_format_features(surface_format);
3182 let path_sample_count = [4, 2, 1]
3183 .into_iter()
3184 .find(|&n| format_features.flags.sample_count_supported(n))
3185 .unwrap_or(1);
3186
3187 let gamma = env::var("ZED_FONTS_GAMMA")
3188 .ok()
3189 .and_then(|v| v.parse().ok())
3190 .unwrap_or(1.8_f32)
3191 .clamp(1.0, 2.2);
3192 let gamma_ratios = get_gamma_correction_ratios(gamma);
3193
3194 let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
3195 .ok()
3196 .and_then(|v| v.parse().ok())
3197 .unwrap_or(1.0_f32)
3198 .max(0.0);
3199
3200 let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
3201 .ok()
3202 .and_then(|v| v.parse().ok())
3203 .unwrap_or(0.5_f32)
3204 .max(0.0);
3205
3206 Self {
3207 path_sample_count,
3208 gamma_ratios,
3209 grayscale_enhanced_contrast,
3210 subpixel_enhanced_contrast,
3211 }
3212 }
3213}
3214
3215#[cfg(test)]
3216mod tests {
3217 use super::*;
3218 use gpui_engine::{
3219 BorderStyle, MonochromeSprite, PixelBuffer, PolychromeSprite, Quad, SubpixelSprite,
3220 Underline,
3221 };
3222 use gpui_platform::{
3223 ColorSpace, ContentMask, Corners, Edges, Hsla, Shadow, linear_color_stop, linear_gradient,
3224 };
3225 #[cfg(target_os = "linux")]
3226 use gpui::{DevicePixels, Scene};
3227
3228 #[cfg(target_os = "linux")]
3229 fn device_size(width: i32, height: i32) -> Size<DevicePixels> {
3230 Size {
3231 width: DevicePixels(width),
3232 height: DevicePixels(height),
3233 }
3234 }
3235
3236 #[cfg(target_os = "linux")]
3237 fn solid_quad(x: f32, y: f32, width: f32, height: f32, color: Hsla) -> Quad {
3238 let bounds = Bounds {
3239 origin: Point {
3240 x: x.into(),
3241 y: y.into(),
3242 },
3243 size: Size {
3244 width: width.into(),
3245 height: height.into(),
3246 },
3247 };
3248 Quad {
3249 order: 0,
3250 border_style: BorderStyle::Solid,
3251 bounds,
3252 content_mask: ContentMask { bounds },
3253 background: color.into(),
3254 border_color: color,
3255 corner_radii: Corners::default(),
3256 border_widths: Edges::default(),
3257 }
3258 }
3259
3260 #[cfg(target_os = "linux")]
3263 fn assert_pixel(image: &PixelBuffer, x: u32, y: u32, expected: [u8; 4]) {
3264 let offset = ((y * image.width() + x) * 4) as usize;
3265 let actual = &image.data()[offset..offset + 4];
3266 assert!(
3267 actual
3268 .iter()
3269 .zip(expected)
3270 .all(|(actual, expected)| actual.abs_diff(expected) <= 3),
3271 "pixel ({x}, {y}) was {actual:?}, expected {expected:?}"
3272 );
3273 }
3274
3275 #[cfg(target_os = "linux")]
3276 const RED: [u8; 4] = [255, 0, 0, 255];
3277 #[cfg(target_os = "linux")]
3278 const BLUE: [u8; 4] = [0, 0, 255, 255];
3279 #[cfg(target_os = "linux")]
3280 const BLACK: [u8; 4] = [0, 0, 0, 255];
3281
3282 #[cfg(target_os = "linux")]
3283 #[test]
3284 fn headless_renderer_draws_quads_with_distinct_colors() -> anyhow::Result<()> {
3285 let mut renderer = WgpuHeadlessRenderer::new()?;
3286 let mut scene = Scene::default();
3287 scene.insert_primitive(solid_quad(0.0, 0.0, 32.0, 32.0, gpui::red()));
3288 scene.insert_primitive(solid_quad(32.0, 0.0, 32.0, 32.0, gpui::blue()));
3289 scene.finish();
3290
3291 let image = renderer.render_scene_to_image(&scene, device_size(64, 32))?;
3292 assert_eq!((image.width(), image.height()), (64, 32));
3293 assert_pixel(&image, 8, 16, RED);
3294 assert_pixel(&image, 24, 16, RED);
3295 assert_pixel(&image, 40, 16, BLUE);
3296 assert_pixel(&image, 56, 16, BLUE);
3297 Ok(())
3298 }
3299
3300 #[cfg(target_os = "linux")]
3301 #[test]
3302 fn headless_renderer_captures_each_requested_size() -> anyhow::Result<()> {
3303 let mut renderer = WgpuHeadlessRenderer::new()?;
3304 let mut scene = Scene::default();
3305 scene.insert_primitive(solid_quad(2.0, 2.0, 4.0, 3.0, gpui::red()));
3306 scene.finish();
3307
3308 let image = renderer.render_scene_to_image(&scene, device_size(13, 7))?;
3310 assert_eq!((image.width(), image.height()), (13, 7));
3311 assert_pixel(&image, 0, 0, BLACK);
3312 assert_pixel(&image, 3, 3, RED);
3313 assert_pixel(&image, 12, 6, BLACK);
3314
3315 let image = renderer.render_scene_to_image(&scene, device_size(17, 9))?;
3316 assert_eq!((image.width(), image.height()), (17, 9));
3317 assert_pixel(&image, 3, 3, RED);
3318 assert_pixel(&image, 16, 8, BLACK);
3319
3320 let image = renderer.render_scene_to_image(&Scene::default(), device_size(13, 7))?;
3321 assert_eq!((image.width(), image.height()), (13, 7));
3322 assert!(image.data().chunks_exact(4).all(|pixel| pixel == BLACK));
3323 Ok(())
3324 }
3325
3326 #[cfg(target_os = "linux")]
3327 #[test]
3328 fn headless_renderer_reuses_target_for_same_size() -> anyhow::Result<()> {
3329 let mut renderer = WgpuHeadlessRenderer::new()?;
3330 let target_texture = |renderer: &WgpuHeadlessRenderer| {
3331 renderer
3332 .render_target
3333 .as_ref()
3334 .map(|target| target.texture.clone())
3335 };
3336
3337 renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3338 let first = target_texture(&renderer);
3339 assert!(first.is_some());
3340
3341 renderer.render_scene(&Scene::default(), device_size(16, 16))?;
3342 assert_eq!(target_texture(&renderer), first);
3343
3344 renderer.render_scene(&Scene::default(), device_size(16, 17))?;
3345 assert_ne!(target_texture(&renderer), first);
3346 Ok(())
3347 }
3348
3349 #[cfg(target_os = "linux")]
3356 #[test]
3357 fn an_offscreen_frame_crosses_a_thread_as_a_surface() -> anyhow::Result<()> {
3358 use std::sync::mpsc;
3359 use std::time::Duration;
3360
3361 let (frames, received) = mpsc::channel();
3362 let (shown, sampled) = mpsc::channel();
3363
3364 let worker = std::thread::spawn(move || -> anyhow::Result<()> {
3365 let mut renderer = WgpuHeadlessRenderer::new()?;
3366 let mut scene = Scene::default();
3367 scene.insert_primitive(solid_quad(0.0, 0.0, 32.0, 32.0, gpui::red()));
3368 scene.finish();
3369 renderer.render_scene(&scene, device_size(64, 32))?;
3370
3371 let surface = renderer
3372 .offscreen_surface_handle()
3373 .expect("a rendered target can cross a thread");
3374 let pixels = renderer.read_pixels()?;
3375 frames
3376 .send((surface, pixels))
3377 .map_err(|_| anyhow::anyhow!("the host dropped the frame channel"))?;
3378 let _ = sampled.recv();
3380 Ok(())
3381 });
3382
3383 let (surface, pixels) = received.recv()?;
3384 assert_pixel(&pixels, 8, 16, RED);
3385
3386 let surface = surface
3387 .downcast_ref::<WgpuOffscreenSurface>()
3388 .expect("the handle is this backend's surface");
3389 assert_eq!(surface.size(), device_size(64, 32));
3390
3391 let host = WgpuHeadlessRenderer::new()?;
3394 let (device, queue) = host.device().expect("the host renderer has a device");
3395 let texture = surface.texture();
3396 let width = texture.width();
3397 let height = texture.height();
3398 let padded_bytes_per_row = (width * 4)
3399 .checked_next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
3400 .expect("the row size overflows");
3401 let readback = device.create_buffer(&wgpu::BufferDescriptor {
3402 label: Some("guest_surface_readback"),
3403 size: u64::from(padded_bytes_per_row) * u64::from(height),
3404 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3405 mapped_at_creation: false,
3406 });
3407 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3408 label: Some("guest_surface_readback_encoder"),
3409 });
3410 encoder.copy_texture_to_buffer(
3411 wgpu::TexelCopyTextureInfo {
3412 texture,
3413 mip_level: 0,
3414 origin: wgpu::Origin3d::ZERO,
3415 aspect: wgpu::TextureAspect::All,
3416 },
3417 wgpu::TexelCopyBufferInfo {
3418 buffer: &readback,
3419 layout: wgpu::TexelCopyBufferLayout {
3420 offset: 0,
3421 bytes_per_row: Some(padded_bytes_per_row),
3422 rows_per_image: Some(height),
3423 },
3424 },
3425 wgpu::Extent3d {
3426 width,
3427 height,
3428 depth_or_array_layers: 1,
3429 },
3430 );
3431 let submission = queue.submit(std::iter::once(encoder.finish()));
3432 let (sender, receiver) = mpsc::channel();
3433 readback
3434 .slice(..)
3435 .map_async(wgpu::MapMode::Read, move |result| {
3436 let _ = sender.send(result);
3437 });
3438 device.poll(wgpu::PollType::Wait {
3439 submission_index: Some(submission),
3440 timeout: Some(Duration::from_secs(30)),
3441 })?;
3442 receiver.recv_timeout(Duration::from_secs(30))??;
3443
3444 let data = readback.slice(..).get_mapped_range();
3445 let offset = 16 * padded_bytes_per_row as usize + 8 * 4;
3446 let mut pixel = [
3447 data[offset],
3448 data[offset + 1],
3449 data[offset + 2],
3450 data[offset + 3],
3451 ];
3452 if host.core.target_format == wgpu::TextureFormat::Bgra8Unorm {
3453 pixel.swap(0, 2);
3454 }
3455 assert_eq!(pixel, RED, "the host samples the guest's own frame");
3456 drop(data);
3457 readback.unmap();
3458
3459 shown
3460 .send(())
3461 .map_err(|_| anyhow::anyhow!("the guest stopped early"))?;
3462 worker.join().expect("the guest thread panicked")?;
3463 Ok(())
3464 }
3465
3466 #[cfg(target_os = "linux")]
3467 #[test]
3468 fn headless_renderer_rejects_invalid_sizes() -> anyhow::Result<()> {
3469 let mut renderer = WgpuHeadlessRenderer::new()?;
3470 let too_large = renderer.core.max_texture_size as i32 + 1;
3471
3472 for size in [
3473 device_size(0, 8),
3474 device_size(8, 0),
3475 device_size(-1, 8),
3476 device_size(too_large, 8),
3477 ] {
3478 assert!(
3479 renderer.render_scene(&Scene::default(), size).is_err(),
3480 "{size:?} should be rejected"
3481 );
3482 assert!(
3483 renderer
3484 .render_scene_to_image(&Scene::default(), size)
3485 .is_err(),
3486 "{size:?} should be rejected"
3487 );
3488 }
3489
3490 let image = renderer.render_scene_to_image(&Scene::default(), device_size(4, 4))?;
3492 assert_eq!((image.width(), image.height()), (4, 4));
3493 Ok(())
3494 }
3495
3496 #[test]
3497 fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
3498 assert!(!WEBGL_SHADERS.contains("var<storage"));
3499 validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
3500 }
3501
3502 #[test]
3503 fn storage_buffer_shader_is_valid_wgsl() {
3504 validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
3505 }
3506
3507 #[test]
3508 fn subpixel_shader_is_valid_wgsl() {
3509 validate_wgsl(
3510 SUBPIXEL_SHADERS,
3511 naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
3512 );
3513 }
3514
3515 fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
3516 let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
3517 naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
3518 .validate(&module)
3519 .expect("shader should validate");
3520 }
3521
3522 #[test]
3523 fn webgl_record_sizes_match_shader_word_strides() {
3524 assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
3525 assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
3526 assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
3527 assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
3528 assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
3529 assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
3530 assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
3531 assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
3532 }
3533
3534 #[test]
3535 fn webgl_quad_layout_matches_fixed_decoder() {
3536 let quad = Quad {
3537 order: 41,
3538 border_style: BorderStyle::Dashed,
3539 bounds: Bounds {
3540 origin: Point {
3541 x: 1.0.into(),
3542 y: 2.0.into(),
3543 },
3544 size: Size {
3545 width: 3.0.into(),
3546 height: 4.0.into(),
3547 },
3548 },
3549 content_mask: ContentMask {
3550 bounds: Bounds {
3551 origin: Point {
3552 x: 5.0.into(),
3553 y: 6.0.into(),
3554 },
3555 size: Size {
3556 width: 7.0.into(),
3557 height: 8.0.into(),
3558 },
3559 },
3560 },
3561 background: linear_gradient(
3562 11.0,
3563 linear_color_stop(
3564 Hsla {
3565 h: 12.0,
3566 s: 13.0,
3567 l: 14.0,
3568 a: 15.0,
3569 },
3570 16.0,
3571 ),
3572 linear_color_stop(
3573 Hsla {
3574 h: 17.0,
3575 s: 18.0,
3576 l: 19.0,
3577 a: 20.0,
3578 },
3579 21.0,
3580 ),
3581 )
3582 .color_space(ColorSpace::Oklab),
3583 border_color: Hsla {
3584 h: 22.0,
3585 s: 23.0,
3586 l: 24.0,
3587 a: 25.0,
3588 },
3589 corner_radii: Corners {
3590 top_left: 26.0.into(),
3591 top_right: 27.0.into(),
3592 bottom_right: 28.0.into(),
3593 bottom_left: 29.0.into(),
3594 },
3595 border_widths: Edges {
3596 top: 30.0.into(),
3597 right: 31.0.into(),
3598 bottom: 32.0.into(),
3599 left: 33.0.into(),
3600 },
3601 };
3602
3603 let bytes = unsafe { WgpuRendererCore::instance_bytes(std::slice::from_ref(&quad)) };
3604 let words: &[u32] = bytemuck::cast_slice(bytes);
3605 assert_eq!(
3606 words,
3607 &[
3608 41,
3609 1,
3610 1.0_f32.to_bits(),
3611 2.0_f32.to_bits(),
3612 3.0_f32.to_bits(),
3613 4.0_f32.to_bits(),
3614 5.0_f32.to_bits(),
3615 6.0_f32.to_bits(),
3616 7.0_f32.to_bits(),
3617 8.0_f32.to_bits(),
3618 1,
3619 1,
3620 0,
3621 0,
3622 0,
3623 0,
3624 11.0_f32.to_bits(),
3625 12.0_f32.to_bits(),
3626 13.0_f32.to_bits(),
3627 14.0_f32.to_bits(),
3628 15.0_f32.to_bits(),
3629 16.0_f32.to_bits(),
3630 17.0_f32.to_bits(),
3631 18.0_f32.to_bits(),
3632 19.0_f32.to_bits(),
3633 20.0_f32.to_bits(),
3634 21.0_f32.to_bits(),
3635 0,
3636 22.0_f32.to_bits(),
3637 23.0_f32.to_bits(),
3638 24.0_f32.to_bits(),
3639 25.0_f32.to_bits(),
3640 26.0_f32.to_bits(),
3641 27.0_f32.to_bits(),
3642 28.0_f32.to_bits(),
3643 29.0_f32.to_bits(),
3644 30.0_f32.to_bits(),
3645 31.0_f32.to_bits(),
3646 32.0_f32.to_bits(),
3647 33.0_f32.to_bits(),
3648 ]
3649 );
3650 }
3651}
3652
3653impl SceneRenderer for WgpuRenderer {
3654 fn draw(&mut self, scene: &Scene) -> bool {
3655 WgpuRenderer::draw(self, scene)
3656 }
3657
3658 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
3659 self.sprite_atlas().clone()
3660 }
3661}