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