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