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