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