1use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
2use bytemuck::{Pod, Zeroable};
3use gpui::{
4 AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
5 PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
6 Underline, get_gamma_correction_ratios,
7};
8use log::warn;
9#[cfg(not(target_family = "wasm"))]
10use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
11use std::cell::RefCell;
12use std::num::NonZeroU64;
13use std::rc::Rc;
14use std::sync::{Arc, Mutex};
15
16#[repr(C)]
17#[derive(Clone, Copy, Pod, Zeroable)]
18struct GlobalParams {
19 viewport_size: [f32; 2],
20 premultiplied_alpha: u32,
21 pad: u32,
22}
23
24#[repr(C)]
25#[derive(Clone, Copy, Pod, Zeroable)]
26struct PodBounds {
27 origin: [f32; 2],
28 size: [f32; 2],
29}
30
31impl From<Bounds<ScaledPixels>> for PodBounds {
32 fn from(bounds: Bounds<ScaledPixels>) -> Self {
33 Self {
34 origin: [bounds.origin.x.0, bounds.origin.y.0],
35 size: [bounds.size.width.0, bounds.size.height.0],
36 }
37 }
38}
39
40#[repr(C)]
41#[derive(Clone, Copy, Pod, Zeroable)]
42struct SurfaceParams {
43 bounds: PodBounds,
44 content_mask: PodBounds,
45}
46
47#[repr(C)]
48#[derive(Clone, Copy, Pod, Zeroable)]
49struct GammaParams {
50 gamma_ratios: [f32; 4],
51 grayscale_enhanced_contrast: f32,
52 subpixel_enhanced_contrast: f32,
53 _pad: [f32; 2],
54}
55
56#[derive(Clone, Debug)]
57#[repr(C)]
58struct PathSprite {
59 bounds: Bounds<ScaledPixels>,
60}
61
62#[derive(Clone, Debug)]
63#[repr(C)]
64struct PathRasterizationVertex {
65 xy_position: Point<ScaledPixels>,
66 st_position: Point<f32>,
67 color: Background,
68 bounds: Bounds<ScaledPixels>,
69}
70
71pub struct WgpuSurfaceConfig {
72 pub size: Size<DevicePixels>,
73 pub transparent: bool,
74}
75
76struct WgpuPipelines {
77 quads: wgpu::RenderPipeline,
78 shadows: wgpu::RenderPipeline,
79 path_rasterization: wgpu::RenderPipeline,
80 paths: wgpu::RenderPipeline,
81 underlines: wgpu::RenderPipeline,
82 mono_sprites: wgpu::RenderPipeline,
83 subpixel_sprites: Option<wgpu::RenderPipeline>,
84 poly_sprites: wgpu::RenderPipeline,
85 #[allow(dead_code)]
86 surfaces: wgpu::RenderPipeline,
87}
88
89struct WgpuBindGroupLayouts {
90 globals: wgpu::BindGroupLayout,
91 instances: wgpu::BindGroupLayout,
92 instances_with_texture: wgpu::BindGroupLayout,
93 surfaces: wgpu::BindGroupLayout,
94}
95
96/// Shared GPU context reference, used to coordinate device recovery across multiple windows.
97pub type GpuContext = Rc<RefCell<Option<WgpuContext>>>;
98
99/// GPU resources that must be dropped together during device recovery.
100struct WgpuResources {
101 device: Arc<wgpu::Device>,
102 queue: Arc<wgpu::Queue>,
103 surface: wgpu::Surface<'static>,
104 pipelines: WgpuPipelines,
105 bind_group_layouts: WgpuBindGroupLayouts,
106 atlas_sampler: wgpu::Sampler,
107 globals_buffer: wgpu::Buffer,
108 globals_bind_group: wgpu::BindGroup,
109 path_globals_bind_group: wgpu::BindGroup,
110 instance_buffer: wgpu::Buffer,
111 path_intermediate_texture: Option<wgpu::Texture>,
112 path_intermediate_view: Option<wgpu::TextureView>,
113 path_msaa_texture: Option<wgpu::Texture>,
114 path_msaa_view: Option<wgpu::TextureView>,
115}
116
117pub struct WgpuRenderer {
118 /// Shared GPU context for device recovery coordination (unused on WASM).
119 #[allow(dead_code)]
120 context: Option<GpuContext>,
121 /// Compositor GPU hint for adapter selection (unused on WASM).
122 #[allow(dead_code)]
123 compositor_gpu: Option<CompositorGpuHint>,
124 resources: Option<WgpuResources>,
125 surface_config: wgpu::SurfaceConfiguration,
126 atlas: Arc<WgpuAtlas>,
127 path_globals_offset: u64,
128 gamma_offset: u64,
129 instance_buffer_capacity: u64,
130 max_buffer_size: u64,
131 storage_buffer_alignment: u64,
132 rendering_params: RenderingParameters,
133 dual_source_blending: bool,
134 adapter_info: wgpu::AdapterInfo,
135 transparent_alpha_mode: wgpu::CompositeAlphaMode,
136 opaque_alpha_mode: wgpu::CompositeAlphaMode,
137 max_texture_size: u32,
138 last_error: Arc<Mutex<Option<String>>>,
139 failed_frame_count: u32,
140 device_lost: std::sync::Arc<std::sync::atomic::AtomicBool>,
141}
142
143impl WgpuRenderer {
144 fn resources(&self) -> &WgpuResources {
145 self.resources
146 .as_ref()
147 .expect("GPU resources not available")
148 }
149
150 fn resources_mut(&mut self) -> &mut WgpuResources {
151 self.resources
152 .as_mut()
153 .expect("GPU resources not available")
154 }
155
156 /// Creates a new WgpuRenderer from raw window handles.
157 ///
158 /// The `gpu_context` is a shared reference that coordinates GPU context across
159 /// multiple windows. The first window to create a renderer will initialize the
160 /// context; subsequent windows will share it.
161 ///
162 /// # Safety
163 /// The caller must ensure that the window handle remains valid for the lifetime
164 /// of the returned renderer.
165 #[cfg(not(target_family = "wasm"))]
166 pub fn new<W: HasWindowHandle + HasDisplayHandle>(
167 gpu_context: GpuContext,
168 window: &W,
169 config: WgpuSurfaceConfig,
170 compositor_gpu: Option<CompositorGpuHint>,
171 ) -> anyhow::Result<Self> {
172 let window_handle = window
173 .window_handle()
174 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
175 let display_handle = window
176 .display_handle()
177 .map_err(|e| anyhow::anyhow!("Failed to get display handle: {e}"))?;
178
179 let target = wgpu::SurfaceTargetUnsafe::RawHandle {
180 raw_display_handle: display_handle.as_raw(),
181 raw_window_handle: window_handle.as_raw(),
182 };
183
184 // Use the existing context's instance if available, otherwise create a new one.
185 // The surface must be created with the same instance that will be used for
186 // adapter selection, otherwise wgpu will panic.
187 let instance = gpu_context
188 .borrow()
189 .as_ref()
190 .map(|ctx| ctx.instance.clone())
191 .unwrap_or_else(WgpuContext::instance);
192
193 // Safety: The caller guarantees that the window handle is valid for the
194 // lifetime of this renderer. In practice, the RawWindow struct is created
195 // from the native window handles and the surface is dropped before the window.
196 let surface = unsafe {
197 instance
198 .create_surface_unsafe(target)
199 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
200 };
201
202 let mut ctx_ref = gpu_context.borrow_mut();
203 let context = match ctx_ref.as_mut() {
204 Some(context) => {
205 context.check_compatible_with_surface(&surface)?;
206 context
207 }
208 None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?),
209 };
210
211 let atlas = Arc::new(WgpuAtlas::new(
212 Arc::clone(&context.device),
213 Arc::clone(&context.queue),
214 ));
215
216 Self::new_internal(
217 Some(Rc::clone(&gpu_context)),
218 context,
219 surface,
220 config,
221 compositor_gpu,
222 atlas,
223 )
224 }
225
226 #[cfg(target_family = "wasm")]
227 pub fn new_from_canvas(
228 context: &WgpuContext,
229 canvas: &web_sys::HtmlCanvasElement,
230 config: WgpuSurfaceConfig,
231 ) -> anyhow::Result<Self> {
232 let surface = context
233 .instance
234 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
235 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
236
237 let atlas = Arc::new(WgpuAtlas::new(
238 Arc::clone(&context.device),
239 Arc::clone(&context.queue),
240 ));
241
242 Self::new_internal(None, context, surface, config, None, atlas)
243 }
244
245 fn new_internal(
246 gpu_context: Option<GpuContext>,
247 context: &WgpuContext,
248 surface: wgpu::Surface<'static>,
249 config: WgpuSurfaceConfig,
250 compositor_gpu: Option<CompositorGpuHint>,
251 atlas: Arc<WgpuAtlas>,
252 ) -> anyhow::Result<Self> {
253 let surface_caps = surface.get_capabilities(&context.adapter);
254 let preferred_formats = [
255 wgpu::TextureFormat::Bgra8Unorm,
256 wgpu::TextureFormat::Rgba8Unorm,
257 ];
258 let surface_format = preferred_formats
259 .iter()
260 .find(|f| surface_caps.formats.contains(f))
261 .copied()
262 .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
263 .or_else(|| surface_caps.formats.first().copied())
264 .ok_or_else(|| {
265 anyhow::anyhow!(
266 "Surface reports no supported texture formats for adapter {:?}",
267 context.adapter.get_info().name
268 )
269 })?;
270
271 let pick_alpha_mode =
272 |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
273 preferences
274 .iter()
275 .find(|p| surface_caps.alpha_modes.contains(p))
276 .copied()
277 .or_else(|| surface_caps.alpha_modes.first().copied())
278 .ok_or_else(|| {
279 anyhow::anyhow!(
280 "Surface reports no supported alpha modes for adapter {:?}",
281 context.adapter.get_info().name
282 )
283 })
284 };
285
286 let transparent_alpha_mode = pick_alpha_mode(&[
287 wgpu::CompositeAlphaMode::PreMultiplied,
288 wgpu::CompositeAlphaMode::Inherit,
289 ])?;
290
291 let opaque_alpha_mode = pick_alpha_mode(&[
292 wgpu::CompositeAlphaMode::Opaque,
293 wgpu::CompositeAlphaMode::Inherit,
294 ])?;
295
296 let alpha_mode = if config.transparent {
297 transparent_alpha_mode
298 } else {
299 opaque_alpha_mode
300 };
301
302 let device = Arc::clone(&context.device);
303 let max_texture_size = device.limits().max_texture_dimension_2d;
304
305 let requested_width = config.size.width.0 as u32;
306 let requested_height = config.size.height.0 as u32;
307 let clamped_width = requested_width.min(max_texture_size);
308 let clamped_height = requested_height.min(max_texture_size);
309
310 if clamped_width != requested_width || clamped_height != requested_height {
311 warn!(
312 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
313 Clamping to ({}, {}). Window content may not fill the entire window.",
314 requested_width, requested_height, max_texture_size, clamped_width, clamped_height
315 );
316 }
317
318 let surface_config = wgpu::SurfaceConfiguration {
319 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
320 format: surface_format,
321 width: clamped_width.max(1),
322 height: clamped_height.max(1),
323 present_mode: wgpu::PresentMode::Fifo,
324 desired_maximum_frame_latency: 2,
325 alpha_mode,
326 view_formats: vec![],
327 };
328 // Configure the surface immediately. The adapter selection process already validated
329 // that this adapter can successfully configure this surface.
330 surface.configure(&context.device, &surface_config);
331
332 let queue = Arc::clone(&context.queue);
333 let dual_source_blending = context.supports_dual_source_blending();
334
335 let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
336 let bind_group_layouts = Self::create_bind_group_layouts(&device);
337 let pipelines = Self::create_pipelines(
338 &device,
339 &bind_group_layouts,
340 surface_format,
341 alpha_mode,
342 rendering_params.path_sample_count,
343 dual_source_blending,
344 );
345
346 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
347 label: Some("atlas_sampler"),
348 mag_filter: wgpu::FilterMode::Linear,
349 min_filter: wgpu::FilterMode::Linear,
350 ..Default::default()
351 });
352
353 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
354 let globals_size = std::mem::size_of::<GlobalParams>() as u64;
355 let gamma_size = std::mem::size_of::<GammaParams>() as u64;
356 let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
357 let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
358
359 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
360 label: Some("globals_buffer"),
361 size: gamma_offset + gamma_size,
362 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
363 mapped_at_creation: false,
364 });
365
366 let max_buffer_size = device.limits().max_buffer_size;
367 let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64;
368 let initial_instance_buffer_capacity = 2 * 1024 * 1024;
369 let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
370 label: Some("instance_buffer"),
371 size: initial_instance_buffer_capacity,
372 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
373 mapped_at_creation: false,
374 });
375
376 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
377 label: Some("globals_bind_group"),
378 layout: &bind_group_layouts.globals,
379 entries: &[
380 wgpu::BindGroupEntry {
381 binding: 0,
382 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
383 buffer: &globals_buffer,
384 offset: 0,
385 size: Some(NonZeroU64::new(globals_size).unwrap()),
386 }),
387 },
388 wgpu::BindGroupEntry {
389 binding: 1,
390 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
391 buffer: &globals_buffer,
392 offset: gamma_offset,
393 size: Some(NonZeroU64::new(gamma_size).unwrap()),
394 }),
395 },
396 ],
397 });
398
399 let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
400 label: Some("path_globals_bind_group"),
401 layout: &bind_group_layouts.globals,
402 entries: &[
403 wgpu::BindGroupEntry {
404 binding: 0,
405 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
406 buffer: &globals_buffer,
407 offset: path_globals_offset,
408 size: Some(NonZeroU64::new(globals_size).unwrap()),
409 }),
410 },
411 wgpu::BindGroupEntry {
412 binding: 1,
413 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
414 buffer: &globals_buffer,
415 offset: gamma_offset,
416 size: Some(NonZeroU64::new(gamma_size).unwrap()),
417 }),
418 },
419 ],
420 });
421
422 let adapter_info = context.adapter.get_info();
423
424 let last_error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
425 let last_error_clone = Arc::clone(&last_error);
426 device.on_uncaptured_error(Arc::new(move |error| {
427 let mut guard = last_error_clone.lock().unwrap();
428 *guard = Some(error.to_string());
429 }));
430
431 let resources = WgpuResources {
432 device,
433 queue,
434 surface,
435 pipelines,
436 bind_group_layouts,
437 atlas_sampler,
438 globals_buffer,
439 globals_bind_group,
440 path_globals_bind_group,
441 instance_buffer,
442 // Defer intermediate texture creation to first draw call via ensure_intermediate_textures().
443 // This avoids panics when the device/surface is in an invalid state during initialization.
444 path_intermediate_texture: None,
445 path_intermediate_view: None,
446 path_msaa_texture: None,
447 path_msaa_view: None,
448 };
449
450 Ok(Self {
451 context: gpu_context,
452 compositor_gpu,
453 resources: Some(resources),
454 surface_config,
455 atlas,
456 path_globals_offset,
457 gamma_offset,
458 instance_buffer_capacity: initial_instance_buffer_capacity,
459 max_buffer_size,
460 storage_buffer_alignment,
461 rendering_params,
462 dual_source_blending,
463 adapter_info,
464 transparent_alpha_mode,
465 opaque_alpha_mode,
466 max_texture_size,
467 last_error,
468 failed_frame_count: 0,
469 device_lost: context.device_lost_flag(),
470 })
471 }
472
473 fn create_bind_group_layouts(device: &wgpu::Device) -> WgpuBindGroupLayouts {
474 let globals =
475 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
476 label: Some("globals_layout"),
477 entries: &[
478 wgpu::BindGroupLayoutEntry {
479 binding: 0,
480 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
481 ty: wgpu::BindingType::Buffer {
482 ty: wgpu::BufferBindingType::Uniform,
483 has_dynamic_offset: false,
484 min_binding_size: NonZeroU64::new(
485 std::mem::size_of::<GlobalParams>() as u64
486 ),
487 },
488 count: None,
489 },
490 wgpu::BindGroupLayoutEntry {
491 binding: 1,
492 visibility: wgpu::ShaderStages::FRAGMENT,
493 ty: wgpu::BindingType::Buffer {
494 ty: wgpu::BufferBindingType::Uniform,
495 has_dynamic_offset: false,
496 min_binding_size: NonZeroU64::new(
497 std::mem::size_of::<GammaParams>() as u64
498 ),
499 },
500 count: None,
501 },
502 ],
503 });
504
505 let storage_buffer_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
506 binding,
507 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
508 ty: wgpu::BindingType::Buffer {
509 ty: wgpu::BufferBindingType::Storage { read_only: true },
510 has_dynamic_offset: false,
511 min_binding_size: None,
512 },
513 count: None,
514 };
515
516 let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
517 label: Some("instances_layout"),
518 entries: &[storage_buffer_entry(0)],
519 });
520
521 let instances_with_texture =
522 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
523 label: Some("instances_with_texture_layout"),
524 entries: &[
525 storage_buffer_entry(0),
526 wgpu::BindGroupLayoutEntry {
527 binding: 1,
528 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
529 ty: wgpu::BindingType::Texture {
530 sample_type: wgpu::TextureSampleType::Float { filterable: true },
531 view_dimension: wgpu::TextureViewDimension::D2,
532 multisampled: false,
533 },
534 count: None,
535 },
536 wgpu::BindGroupLayoutEntry {
537 binding: 2,
538 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
539 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
540 count: None,
541 },
542 ],
543 });
544
545 let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
546 label: Some("surfaces_layout"),
547 entries: &[
548 wgpu::BindGroupLayoutEntry {
549 binding: 0,
550 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
551 ty: wgpu::BindingType::Buffer {
552 ty: wgpu::BufferBindingType::Uniform,
553 has_dynamic_offset: false,
554 min_binding_size: NonZeroU64::new(
555 std::mem::size_of::<SurfaceParams>() as u64
556 ),
557 },
558 count: None,
559 },
560 wgpu::BindGroupLayoutEntry {
561 binding: 1,
562 visibility: wgpu::ShaderStages::FRAGMENT,
563 ty: wgpu::BindingType::Texture {
564 sample_type: wgpu::TextureSampleType::Float { filterable: true },
565 view_dimension: wgpu::TextureViewDimension::D2,
566 multisampled: false,
567 },
568 count: None,
569 },
570 wgpu::BindGroupLayoutEntry {
571 binding: 2,
572 visibility: wgpu::ShaderStages::FRAGMENT,
573 ty: wgpu::BindingType::Texture {
574 sample_type: wgpu::TextureSampleType::Float { filterable: true },
575 view_dimension: wgpu::TextureViewDimension::D2,
576 multisampled: false,
577 },
578 count: None,
579 },
580 wgpu::BindGroupLayoutEntry {
581 binding: 3,
582 visibility: wgpu::ShaderStages::FRAGMENT,
583 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
584 count: None,
585 },
586 ],
587 });
588
589 WgpuBindGroupLayouts {
590 globals,
591 instances,
592 instances_with_texture,
593 surfaces,
594 }
595 }
596
597 fn create_pipelines(
598 device: &wgpu::Device,
599 layouts: &WgpuBindGroupLayouts,
600 surface_format: wgpu::TextureFormat,
601 alpha_mode: wgpu::CompositeAlphaMode,
602 path_sample_count: u32,
603 dual_source_blending: bool,
604 ) -> WgpuPipelines {
605 let base_shader_source = include_str!("shaders.wgsl");
606 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
607 label: Some("gpui_shaders"),
608 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(base_shader_source)),
609 });
610
611 let subpixel_shader_source = include_str!("shaders_subpixel.wgsl");
612 let subpixel_shader_module = if dual_source_blending {
613 let combined = format!(
614 "enable dual_source_blending;\n{base_shader_source}\n{subpixel_shader_source}"
615 );
616 Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
617 label: Some("gpui_subpixel_shaders"),
618 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(combined)),
619 }))
620 } else {
621 None
622 };
623
624 let blend_mode = match alpha_mode {
625 wgpu::CompositeAlphaMode::PreMultiplied => {
626 wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
627 }
628 _ => wgpu::BlendState::ALPHA_BLENDING,
629 };
630
631 let color_target = wgpu::ColorTargetState {
632 format: surface_format,
633 blend: Some(blend_mode),
634 write_mask: wgpu::ColorWrites::ALL,
635 };
636
637 let create_pipeline = |name: &str,
638 vs_entry: &str,
639 fs_entry: &str,
640 globals_layout: &wgpu::BindGroupLayout,
641 data_layout: &wgpu::BindGroupLayout,
642 topology: wgpu::PrimitiveTopology,
643 color_targets: &[Option<wgpu::ColorTargetState>],
644 sample_count: u32,
645 module: &wgpu::ShaderModule| {
646 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
647 label: Some(&format!("{name}_layout")),
648 bind_group_layouts: &[globals_layout, data_layout],
649 immediate_size: 0,
650 });
651
652 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
653 label: Some(name),
654 layout: Some(&pipeline_layout),
655 vertex: wgpu::VertexState {
656 module,
657 entry_point: Some(vs_entry),
658 buffers: &[],
659 compilation_options: wgpu::PipelineCompilationOptions::default(),
660 },
661 fragment: Some(wgpu::FragmentState {
662 module,
663 entry_point: Some(fs_entry),
664 targets: color_targets,
665 compilation_options: wgpu::PipelineCompilationOptions::default(),
666 }),
667 primitive: wgpu::PrimitiveState {
668 topology,
669 strip_index_format: None,
670 front_face: wgpu::FrontFace::Ccw,
671 cull_mode: None,
672 polygon_mode: wgpu::PolygonMode::Fill,
673 unclipped_depth: false,
674 conservative: false,
675 },
676 depth_stencil: None,
677 multisample: wgpu::MultisampleState {
678 count: sample_count,
679 mask: !0,
680 alpha_to_coverage_enabled: false,
681 },
682 multiview_mask: None,
683 cache: None,
684 })
685 };
686
687 let quads = create_pipeline(
688 "quads",
689 "vs_quad",
690 "fs_quad",
691 &layouts.globals,
692 &layouts.instances,
693 wgpu::PrimitiveTopology::TriangleStrip,
694 &[Some(color_target.clone())],
695 1,
696 &shader_module,
697 );
698
699 let shadows = create_pipeline(
700 "shadows",
701 "vs_shadow",
702 "fs_shadow",
703 &layouts.globals,
704 &layouts.instances,
705 wgpu::PrimitiveTopology::TriangleStrip,
706 &[Some(color_target.clone())],
707 1,
708 &shader_module,
709 );
710
711 let path_rasterization = create_pipeline(
712 "path_rasterization",
713 "vs_path_rasterization",
714 "fs_path_rasterization",
715 &layouts.globals,
716 &layouts.instances,
717 wgpu::PrimitiveTopology::TriangleList,
718 &[Some(wgpu::ColorTargetState {
719 format: surface_format,
720 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
721 write_mask: wgpu::ColorWrites::ALL,
722 })],
723 path_sample_count,
724 &shader_module,
725 );
726
727 let paths_blend = wgpu::BlendState {
728 color: wgpu::BlendComponent {
729 src_factor: wgpu::BlendFactor::One,
730 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
731 operation: wgpu::BlendOperation::Add,
732 },
733 alpha: wgpu::BlendComponent {
734 src_factor: wgpu::BlendFactor::One,
735 dst_factor: wgpu::BlendFactor::One,
736 operation: wgpu::BlendOperation::Add,
737 },
738 };
739
740 let paths = create_pipeline(
741 "paths",
742 "vs_path",
743 "fs_path",
744 &layouts.globals,
745 &layouts.instances_with_texture,
746 wgpu::PrimitiveTopology::TriangleStrip,
747 &[Some(wgpu::ColorTargetState {
748 format: surface_format,
749 blend: Some(paths_blend),
750 write_mask: wgpu::ColorWrites::ALL,
751 })],
752 1,
753 &shader_module,
754 );
755
756 let underlines = create_pipeline(
757 "underlines",
758 "vs_underline",
759 "fs_underline",
760 &layouts.globals,
761 &layouts.instances,
762 wgpu::PrimitiveTopology::TriangleStrip,
763 &[Some(color_target.clone())],
764 1,
765 &shader_module,
766 );
767
768 let mono_sprites = create_pipeline(
769 "mono_sprites",
770 "vs_mono_sprite",
771 "fs_mono_sprite",
772 &layouts.globals,
773 &layouts.instances_with_texture,
774 wgpu::PrimitiveTopology::TriangleStrip,
775 &[Some(color_target.clone())],
776 1,
777 &shader_module,
778 );
779
780 let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
781 let subpixel_blend = wgpu::BlendState {
782 color: wgpu::BlendComponent {
783 src_factor: wgpu::BlendFactor::Src1,
784 dst_factor: wgpu::BlendFactor::OneMinusSrc1,
785 operation: wgpu::BlendOperation::Add,
786 },
787 alpha: wgpu::BlendComponent {
788 src_factor: wgpu::BlendFactor::One,
789 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
790 operation: wgpu::BlendOperation::Add,
791 },
792 };
793
794 Some(create_pipeline(
795 "subpixel_sprites",
796 "vs_subpixel_sprite",
797 "fs_subpixel_sprite",
798 &layouts.globals,
799 &layouts.instances_with_texture,
800 wgpu::PrimitiveTopology::TriangleStrip,
801 &[Some(wgpu::ColorTargetState {
802 format: surface_format,
803 blend: Some(subpixel_blend),
804 write_mask: wgpu::ColorWrites::COLOR,
805 })],
806 1,
807 subpixel_module,
808 ))
809 } else {
810 None
811 };
812
813 let poly_sprites = create_pipeline(
814 "poly_sprites",
815 "vs_poly_sprite",
816 "fs_poly_sprite",
817 &layouts.globals,
818 &layouts.instances_with_texture,
819 wgpu::PrimitiveTopology::TriangleStrip,
820 &[Some(color_target.clone())],
821 1,
822 &shader_module,
823 );
824
825 let surfaces = create_pipeline(
826 "surfaces",
827 "vs_surface",
828 "fs_surface",
829 &layouts.globals,
830 &layouts.surfaces,
831 wgpu::PrimitiveTopology::TriangleStrip,
832 &[Some(color_target)],
833 1,
834 &shader_module,
835 );
836
837 WgpuPipelines {
838 quads,
839 shadows,
840 path_rasterization,
841 paths,
842 underlines,
843 mono_sprites,
844 subpixel_sprites,
845 poly_sprites,
846 surfaces,
847 }
848 }
849
850 fn create_path_intermediate(
851 device: &wgpu::Device,
852 format: wgpu::TextureFormat,
853 width: u32,
854 height: u32,
855 ) -> (wgpu::Texture, wgpu::TextureView) {
856 let texture = device.create_texture(&wgpu::TextureDescriptor {
857 label: Some("path_intermediate"),
858 size: wgpu::Extent3d {
859 width: width.max(1),
860 height: height.max(1),
861 depth_or_array_layers: 1,
862 },
863 mip_level_count: 1,
864 sample_count: 1,
865 dimension: wgpu::TextureDimension::D2,
866 format,
867 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
868 view_formats: &[],
869 });
870 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
871 (texture, view)
872 }
873
874 fn create_msaa_if_needed(
875 device: &wgpu::Device,
876 format: wgpu::TextureFormat,
877 width: u32,
878 height: u32,
879 sample_count: u32,
880 ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
881 if sample_count <= 1 {
882 return None;
883 }
884 let texture = device.create_texture(&wgpu::TextureDescriptor {
885 label: Some("path_msaa"),
886 size: wgpu::Extent3d {
887 width: width.max(1),
888 height: height.max(1),
889 depth_or_array_layers: 1,
890 },
891 mip_level_count: 1,
892 sample_count,
893 dimension: wgpu::TextureDimension::D2,
894 format,
895 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
896 view_formats: &[],
897 });
898 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
899 Some((texture, view))
900 }
901
902 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
903 let width = size.width.0 as u32;
904 let height = size.height.0 as u32;
905
906 if width != self.surface_config.width || height != self.surface_config.height {
907 let clamped_width = width.min(self.max_texture_size);
908 let clamped_height = height.min(self.max_texture_size);
909
910 if clamped_width != width || clamped_height != height {
911 warn!(
912 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
913 Clamping to ({}, {}). Window content may not fill the entire window.",
914 width, height, self.max_texture_size, clamped_width, clamped_height
915 );
916 }
917
918 self.surface_config.width = clamped_width.max(1);
919 self.surface_config.height = clamped_height.max(1);
920 let surface_config = self.surface_config.clone();
921
922 let resources = self.resources_mut();
923
924 // Wait for any in-flight GPU work to complete before destroying textures
925 if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
926 submission_index: None,
927 timeout: None,
928 }) {
929 warn!("Failed to poll device during resize: {e:?}");
930 }
931
932 // Destroy old textures before allocating new ones to avoid GPU memory spikes
933 if let Some(ref texture) = resources.path_intermediate_texture {
934 texture.destroy();
935 }
936 if let Some(ref texture) = resources.path_msaa_texture {
937 texture.destroy();
938 }
939
940 resources
941 .surface
942 .configure(&resources.device, &surface_config);
943
944 // Invalidate intermediate textures - they will be lazily recreated
945 // in draw() after we confirm the surface is healthy. This avoids
946 // panics when the device/surface is in an invalid state during resize.
947 resources.path_intermediate_texture = None;
948 resources.path_intermediate_view = None;
949 resources.path_msaa_texture = None;
950 resources.path_msaa_view = None;
951 }
952 }
953
954 fn ensure_intermediate_textures(&mut self) {
955 if self.resources().path_intermediate_texture.is_some() {
956 return;
957 }
958
959 let format = self.surface_config.format;
960 let width = self.surface_config.width;
961 let height = self.surface_config.height;
962 let path_sample_count = self.rendering_params.path_sample_count;
963 let resources = self.resources_mut();
964
965 let (t, v) = Self::create_path_intermediate(&resources.device, format, width, height);
966 resources.path_intermediate_texture = Some(t);
967 resources.path_intermediate_view = Some(v);
968
969 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
970 &resources.device,
971 format,
972 width,
973 height,
974 path_sample_count,
975 )
976 .map(|(t, v)| (Some(t), Some(v)))
977 .unwrap_or((None, None));
978 resources.path_msaa_texture = path_msaa_texture;
979 resources.path_msaa_view = path_msaa_view;
980 }
981
982 pub fn update_transparency(&mut self, transparent: bool) {
983 let new_alpha_mode = if transparent {
984 self.transparent_alpha_mode
985 } else {
986 self.opaque_alpha_mode
987 };
988
989 if new_alpha_mode != self.surface_config.alpha_mode {
990 self.surface_config.alpha_mode = new_alpha_mode;
991 let surface_config = self.surface_config.clone();
992 let path_sample_count = self.rendering_params.path_sample_count;
993 let dual_source_blending = self.dual_source_blending;
994 let resources = self.resources_mut();
995 resources
996 .surface
997 .configure(&resources.device, &surface_config);
998 resources.pipelines = Self::create_pipelines(
999 &resources.device,
1000 &resources.bind_group_layouts,
1001 surface_config.format,
1002 surface_config.alpha_mode,
1003 path_sample_count,
1004 dual_source_blending,
1005 );
1006 }
1007 }
1008
1009 #[allow(dead_code)]
1010 pub fn viewport_size(&self) -> Size<DevicePixels> {
1011 Size {
1012 width: DevicePixels(self.surface_config.width as i32),
1013 height: DevicePixels(self.surface_config.height as i32),
1014 }
1015 }
1016
1017 pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
1018 &self.atlas
1019 }
1020
1021 pub fn supports_dual_source_blending(&self) -> bool {
1022 self.dual_source_blending
1023 }
1024
1025 pub fn gpu_specs(&self) -> GpuSpecs {
1026 GpuSpecs {
1027 is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
1028 device_name: self.adapter_info.name.clone(),
1029 driver_name: self.adapter_info.driver.clone(),
1030 driver_info: self.adapter_info.driver_info.clone(),
1031 }
1032 }
1033
1034 pub fn max_texture_size(&self) -> u32 {
1035 self.max_texture_size
1036 }
1037
1038 pub fn draw(&mut self, scene: &Scene) {
1039 let last_error = self.last_error.lock().unwrap().take();
1040 if let Some(error) = last_error {
1041 self.failed_frame_count += 1;
1042 log::error!(
1043 "GPU error during frame (failure {} of 20): {error}",
1044 self.failed_frame_count
1045 );
1046 if self.failed_frame_count > 20 {
1047 panic!("Too many consecutive GPU errors. Last error: {error}");
1048 }
1049 } else {
1050 self.failed_frame_count = 0;
1051 }
1052
1053 self.atlas.before_frame();
1054
1055 let texture_result = self.resources().surface.get_current_texture();
1056 let frame = match texture_result {
1057 Ok(frame) => frame,
1058 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
1059 let surface_config = self.surface_config.clone();
1060 let resources = self.resources_mut();
1061 resources
1062 .surface
1063 .configure(&resources.device, &surface_config);
1064 return;
1065 }
1066 Err(e) => {
1067 *self.last_error.lock().unwrap() =
1068 Some(format!("Failed to acquire surface texture: {e}"));
1069 return;
1070 }
1071 };
1072
1073 // Now that we know the surface is healthy, ensure intermediate textures exist
1074 self.ensure_intermediate_textures();
1075
1076 let frame_view = frame
1077 .texture
1078 .create_view(&wgpu::TextureViewDescriptor::default());
1079
1080 let gamma_params = GammaParams {
1081 gamma_ratios: self.rendering_params.gamma_ratios,
1082 grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1083 subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1084 _pad: [0.0; 2],
1085 };
1086
1087 let globals = GlobalParams {
1088 viewport_size: [
1089 self.surface_config.width as f32,
1090 self.surface_config.height as f32,
1091 ],
1092 premultiplied_alpha: if self.surface_config.alpha_mode
1093 == wgpu::CompositeAlphaMode::PreMultiplied
1094 {
1095 1
1096 } else {
1097 0
1098 },
1099 pad: 0,
1100 };
1101
1102 let path_globals = GlobalParams {
1103 premultiplied_alpha: 0,
1104 ..globals
1105 };
1106
1107 {
1108 let resources = self.resources();
1109 resources.queue.write_buffer(
1110 &resources.globals_buffer,
1111 0,
1112 bytemuck::bytes_of(&globals),
1113 );
1114 resources.queue.write_buffer(
1115 &resources.globals_buffer,
1116 self.path_globals_offset,
1117 bytemuck::bytes_of(&path_globals),
1118 );
1119 resources.queue.write_buffer(
1120 &resources.globals_buffer,
1121 self.gamma_offset,
1122 bytemuck::bytes_of(&gamma_params),
1123 );
1124 }
1125
1126 loop {
1127 let mut instance_offset: u64 = 0;
1128 let mut overflow = false;
1129
1130 let mut encoder =
1131 self.resources()
1132 .device
1133 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1134 label: Some("main_encoder"),
1135 });
1136
1137 {
1138 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1139 label: Some("main_pass"),
1140 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1141 view: &frame_view,
1142 resolve_target: None,
1143 ops: wgpu::Operations {
1144 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1145 store: wgpu::StoreOp::Store,
1146 },
1147 depth_slice: None,
1148 })],
1149 depth_stencil_attachment: None,
1150 ..Default::default()
1151 });
1152
1153 for batch in scene.batches() {
1154 let ok = match batch {
1155 PrimitiveBatch::Quads(range) => {
1156 self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass)
1157 }
1158 PrimitiveBatch::Shadows(range) => self.draw_shadows(
1159 &scene.shadows[range],
1160 &mut instance_offset,
1161 &mut pass,
1162 ),
1163 PrimitiveBatch::Paths(range) => {
1164 let paths = &scene.paths[range];
1165 if paths.is_empty() {
1166 continue;
1167 }
1168
1169 drop(pass);
1170
1171 let did_draw = self.draw_paths_to_intermediate(
1172 &mut encoder,
1173 paths,
1174 &mut instance_offset,
1175 );
1176
1177 pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1178 label: Some("main_pass_continued"),
1179 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1180 view: &frame_view,
1181 resolve_target: None,
1182 ops: wgpu::Operations {
1183 load: wgpu::LoadOp::Load,
1184 store: wgpu::StoreOp::Store,
1185 },
1186 depth_slice: None,
1187 })],
1188 depth_stencil_attachment: None,
1189 ..Default::default()
1190 });
1191
1192 if did_draw {
1193 self.draw_paths_from_intermediate(
1194 paths,
1195 &mut instance_offset,
1196 &mut pass,
1197 )
1198 } else {
1199 false
1200 }
1201 }
1202 PrimitiveBatch::Underlines(range) => self.draw_underlines(
1203 &scene.underlines[range],
1204 &mut instance_offset,
1205 &mut pass,
1206 ),
1207 PrimitiveBatch::MonochromeSprites { texture_id, range } => self
1208 .draw_monochrome_sprites(
1209 &scene.monochrome_sprites[range],
1210 texture_id,
1211 &mut instance_offset,
1212 &mut pass,
1213 ),
1214 PrimitiveBatch::SubpixelSprites { texture_id, range } => self
1215 .draw_subpixel_sprites(
1216 &scene.subpixel_sprites[range],
1217 texture_id,
1218 &mut instance_offset,
1219 &mut pass,
1220 ),
1221 PrimitiveBatch::PolychromeSprites { texture_id, range } => self
1222 .draw_polychrome_sprites(
1223 &scene.polychrome_sprites[range],
1224 texture_id,
1225 &mut instance_offset,
1226 &mut pass,
1227 ),
1228 PrimitiveBatch::Surfaces(_surfaces) => {
1229 // Surfaces are macOS-only for video playback
1230 // Not implemented for Linux/wgpu
1231 true
1232 }
1233 };
1234 if !ok {
1235 overflow = true;
1236 break;
1237 }
1238 }
1239 }
1240
1241 if overflow {
1242 drop(encoder);
1243 if self.instance_buffer_capacity >= self.max_buffer_size {
1244 log::error!(
1245 "instance buffer size grew too large: {}",
1246 self.instance_buffer_capacity
1247 );
1248 frame.present();
1249 return;
1250 }
1251 self.grow_instance_buffer();
1252 continue;
1253 }
1254
1255 self.resources()
1256 .queue
1257 .submit(std::iter::once(encoder.finish()));
1258 frame.present();
1259 return;
1260 }
1261 }
1262
1263 fn draw_quads(
1264 &self,
1265 quads: &[Quad],
1266 instance_offset: &mut u64,
1267 pass: &mut wgpu::RenderPass<'_>,
1268 ) -> bool {
1269 let data = unsafe { Self::instance_bytes(quads) };
1270 self.draw_instances(
1271 data,
1272 quads.len() as u32,
1273 &self.resources().pipelines.quads,
1274 instance_offset,
1275 pass,
1276 )
1277 }
1278
1279 fn draw_shadows(
1280 &self,
1281 shadows: &[Shadow],
1282 instance_offset: &mut u64,
1283 pass: &mut wgpu::RenderPass<'_>,
1284 ) -> bool {
1285 let data = unsafe { Self::instance_bytes(shadows) };
1286 self.draw_instances(
1287 data,
1288 shadows.len() as u32,
1289 &self.resources().pipelines.shadows,
1290 instance_offset,
1291 pass,
1292 )
1293 }
1294
1295 fn draw_underlines(
1296 &self,
1297 underlines: &[Underline],
1298 instance_offset: &mut u64,
1299 pass: &mut wgpu::RenderPass<'_>,
1300 ) -> bool {
1301 let data = unsafe { Self::instance_bytes(underlines) };
1302 self.draw_instances(
1303 data,
1304 underlines.len() as u32,
1305 &self.resources().pipelines.underlines,
1306 instance_offset,
1307 pass,
1308 )
1309 }
1310
1311 fn draw_monochrome_sprites(
1312 &self,
1313 sprites: &[MonochromeSprite],
1314 texture_id: AtlasTextureId,
1315 instance_offset: &mut u64,
1316 pass: &mut wgpu::RenderPass<'_>,
1317 ) -> bool {
1318 let tex_info = self.atlas.get_texture_info(texture_id);
1319 let data = unsafe { Self::instance_bytes(sprites) };
1320 self.draw_instances_with_texture(
1321 data,
1322 sprites.len() as u32,
1323 &tex_info.view,
1324 &self.resources().pipelines.mono_sprites,
1325 instance_offset,
1326 pass,
1327 )
1328 }
1329
1330 fn draw_subpixel_sprites(
1331 &self,
1332 sprites: &[SubpixelSprite],
1333 texture_id: AtlasTextureId,
1334 instance_offset: &mut u64,
1335 pass: &mut wgpu::RenderPass<'_>,
1336 ) -> bool {
1337 let tex_info = self.atlas.get_texture_info(texture_id);
1338 let data = unsafe { Self::instance_bytes(sprites) };
1339 let resources = self.resources();
1340 let pipeline = resources
1341 .pipelines
1342 .subpixel_sprites
1343 .as_ref()
1344 .unwrap_or(&resources.pipelines.mono_sprites);
1345 self.draw_instances_with_texture(
1346 data,
1347 sprites.len() as u32,
1348 &tex_info.view,
1349 pipeline,
1350 instance_offset,
1351 pass,
1352 )
1353 }
1354
1355 fn draw_polychrome_sprites(
1356 &self,
1357 sprites: &[PolychromeSprite],
1358 texture_id: AtlasTextureId,
1359 instance_offset: &mut u64,
1360 pass: &mut wgpu::RenderPass<'_>,
1361 ) -> bool {
1362 let tex_info = self.atlas.get_texture_info(texture_id);
1363 let data = unsafe { Self::instance_bytes(sprites) };
1364 self.draw_instances_with_texture(
1365 data,
1366 sprites.len() as u32,
1367 &tex_info.view,
1368 &self.resources().pipelines.poly_sprites,
1369 instance_offset,
1370 pass,
1371 )
1372 }
1373
1374 fn draw_instances(
1375 &self,
1376 data: &[u8],
1377 instance_count: u32,
1378 pipeline: &wgpu::RenderPipeline,
1379 instance_offset: &mut u64,
1380 pass: &mut wgpu::RenderPass<'_>,
1381 ) -> bool {
1382 if instance_count == 0 {
1383 return true;
1384 }
1385 let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
1386 return false;
1387 };
1388 let resources = self.resources();
1389 let bind_group = resources
1390 .device
1391 .create_bind_group(&wgpu::BindGroupDescriptor {
1392 label: None,
1393 layout: &resources.bind_group_layouts.instances,
1394 entries: &[wgpu::BindGroupEntry {
1395 binding: 0,
1396 resource: self.instance_binding(offset, size),
1397 }],
1398 });
1399 pass.set_pipeline(pipeline);
1400 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1401 pass.set_bind_group(1, &bind_group, &[]);
1402 pass.draw(0..4, 0..instance_count);
1403 true
1404 }
1405
1406 fn draw_instances_with_texture(
1407 &self,
1408 data: &[u8],
1409 instance_count: u32,
1410 texture_view: &wgpu::TextureView,
1411 pipeline: &wgpu::RenderPipeline,
1412 instance_offset: &mut u64,
1413 pass: &mut wgpu::RenderPass<'_>,
1414 ) -> bool {
1415 if instance_count == 0 {
1416 return true;
1417 }
1418 let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
1419 return false;
1420 };
1421 let resources = self.resources();
1422 let bind_group = resources
1423 .device
1424 .create_bind_group(&wgpu::BindGroupDescriptor {
1425 label: None,
1426 layout: &resources.bind_group_layouts.instances_with_texture,
1427 entries: &[
1428 wgpu::BindGroupEntry {
1429 binding: 0,
1430 resource: self.instance_binding(offset, size),
1431 },
1432 wgpu::BindGroupEntry {
1433 binding: 1,
1434 resource: wgpu::BindingResource::TextureView(texture_view),
1435 },
1436 wgpu::BindGroupEntry {
1437 binding: 2,
1438 resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1439 },
1440 ],
1441 });
1442 pass.set_pipeline(pipeline);
1443 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1444 pass.set_bind_group(1, &bind_group, &[]);
1445 pass.draw(0..4, 0..instance_count);
1446 true
1447 }
1448
1449 unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
1450 unsafe {
1451 std::slice::from_raw_parts(
1452 instances.as_ptr() as *const u8,
1453 std::mem::size_of_val(instances),
1454 )
1455 }
1456 }
1457
1458 fn draw_paths_from_intermediate(
1459 &self,
1460 paths: &[Path<ScaledPixels>],
1461 instance_offset: &mut u64,
1462 pass: &mut wgpu::RenderPass<'_>,
1463 ) -> bool {
1464 let first_path = &paths[0];
1465 let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
1466 {
1467 paths
1468 .iter()
1469 .map(|p| PathSprite {
1470 bounds: p.clipped_bounds(),
1471 })
1472 .collect()
1473 } else {
1474 let mut bounds = first_path.clipped_bounds();
1475 for path in paths.iter().skip(1) {
1476 bounds = bounds.union(&path.clipped_bounds());
1477 }
1478 vec![PathSprite { bounds }]
1479 };
1480
1481 let resources = self.resources();
1482 let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
1483 return true;
1484 };
1485
1486 let sprite_data = unsafe { Self::instance_bytes(&sprites) };
1487 self.draw_instances_with_texture(
1488 sprite_data,
1489 sprites.len() as u32,
1490 path_intermediate_view,
1491 &resources.pipelines.paths,
1492 instance_offset,
1493 pass,
1494 )
1495 }
1496
1497 fn draw_paths_to_intermediate(
1498 &self,
1499 encoder: &mut wgpu::CommandEncoder,
1500 paths: &[Path<ScaledPixels>],
1501 instance_offset: &mut u64,
1502 ) -> bool {
1503 let mut vertices = Vec::new();
1504 for path in paths {
1505 let bounds = path.clipped_bounds();
1506 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
1507 xy_position: v.xy_position,
1508 st_position: v.st_position,
1509 color: path.color,
1510 bounds,
1511 }));
1512 }
1513
1514 if vertices.is_empty() {
1515 return true;
1516 }
1517
1518 let vertex_data = unsafe { Self::instance_bytes(&vertices) };
1519 let Some((vertex_offset, vertex_size)) =
1520 self.write_to_instance_buffer(instance_offset, vertex_data)
1521 else {
1522 return false;
1523 };
1524
1525 let resources = self.resources();
1526 let data_bind_group = resources
1527 .device
1528 .create_bind_group(&wgpu::BindGroupDescriptor {
1529 label: Some("path_rasterization_bind_group"),
1530 layout: &resources.bind_group_layouts.instances,
1531 entries: &[wgpu::BindGroupEntry {
1532 binding: 0,
1533 resource: self.instance_binding(vertex_offset, vertex_size),
1534 }],
1535 });
1536
1537 let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
1538 return true;
1539 };
1540
1541 let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
1542 (msaa_view, Some(path_intermediate_view))
1543 } else {
1544 (path_intermediate_view, None)
1545 };
1546
1547 {
1548 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1549 label: Some("path_rasterization_pass"),
1550 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1551 view: target_view,
1552 resolve_target,
1553 ops: wgpu::Operations {
1554 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1555 store: wgpu::StoreOp::Store,
1556 },
1557 depth_slice: None,
1558 })],
1559 depth_stencil_attachment: None,
1560 ..Default::default()
1561 });
1562
1563 pass.set_pipeline(&resources.pipelines.path_rasterization);
1564 pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
1565 pass.set_bind_group(1, &data_bind_group, &[]);
1566 pass.draw(0..vertices.len() as u32, 0..1);
1567 }
1568
1569 true
1570 }
1571
1572 fn grow_instance_buffer(&mut self) {
1573 let new_capacity = (self.instance_buffer_capacity * 2).min(self.max_buffer_size);
1574 log::info!("increased instance buffer size to {}", new_capacity);
1575 let resources = self.resources_mut();
1576 resources.instance_buffer = resources.device.create_buffer(&wgpu::BufferDescriptor {
1577 label: Some("instance_buffer"),
1578 size: new_capacity,
1579 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1580 mapped_at_creation: false,
1581 });
1582 self.instance_buffer_capacity = new_capacity;
1583 }
1584
1585 fn write_to_instance_buffer(
1586 &self,
1587 instance_offset: &mut u64,
1588 data: &[u8],
1589 ) -> Option<(u64, NonZeroU64)> {
1590 let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment);
1591 let size = (data.len() as u64).max(16);
1592 if offset + size > self.instance_buffer_capacity {
1593 return None;
1594 }
1595 let resources = self.resources();
1596 resources
1597 .queue
1598 .write_buffer(&resources.instance_buffer, offset, data);
1599 *instance_offset = offset + size;
1600 Some((offset, NonZeroU64::new(size).expect("size is at least 16")))
1601 }
1602
1603 fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> {
1604 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1605 buffer: &self.resources().instance_buffer,
1606 offset,
1607 size: Some(size),
1608 })
1609 }
1610
1611 pub fn destroy(&mut self) {
1612 // wgpu resources are automatically cleaned up when dropped
1613 }
1614
1615 /// Returns true if the GPU device was lost and recovery is needed.
1616 pub fn device_lost(&self) -> bool {
1617 self.device_lost.load(std::sync::atomic::Ordering::SeqCst)
1618 }
1619
1620 /// Recovers from a lost GPU device by recreating the renderer with a new context.
1621 ///
1622 /// Call this after detecting `device_lost()` returns true.
1623 ///
1624 /// This method coordinates recovery across multiple windows:
1625 /// - The first window to call this will recreate the shared context
1626 /// - Subsequent windows will adopt the already-recovered context
1627 #[cfg(not(target_family = "wasm"))]
1628 pub fn recover(
1629 &mut self,
1630 raw_display_handle: raw_window_handle::RawDisplayHandle,
1631 raw_window_handle: raw_window_handle::RawWindowHandle,
1632 ) -> anyhow::Result<()> {
1633 let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
1634
1635 // Check if another window already recovered the context
1636 let needs_new_context = gpu_context
1637 .borrow()
1638 .as_ref()
1639 .is_none_or(|ctx| ctx.device_lost());
1640
1641 let surface = if needs_new_context {
1642 log::warn!("GPU device lost, recreating context...");
1643
1644 // Drop old resources to release Arc<Device>/Arc<Queue> and GPU resources
1645 self.resources = None;
1646 *gpu_context.borrow_mut() = None;
1647
1648 // Wait for GPU driver to stabilize (350ms copied from windows :shrug:)
1649 std::thread::sleep(std::time::Duration::from_millis(350));
1650
1651 let instance = WgpuContext::instance();
1652 let surface = create_surface(&instance, raw_display_handle, raw_window_handle)?;
1653 let new_context = WgpuContext::new(instance, &surface, self.compositor_gpu)?;
1654 *gpu_context.borrow_mut() = Some(new_context);
1655 surface
1656 } else {
1657 let ctx_ref = gpu_context.borrow();
1658 let instance = &ctx_ref.as_ref().unwrap().instance;
1659 create_surface(instance, raw_display_handle, raw_window_handle)?
1660 };
1661
1662 let config = WgpuSurfaceConfig {
1663 size: gpui::Size {
1664 width: gpui::DevicePixels(self.surface_config.width as i32),
1665 height: gpui::DevicePixels(self.surface_config.height as i32),
1666 },
1667 transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
1668 };
1669 let gpu_context = Rc::clone(gpu_context);
1670 let ctx_ref = gpu_context.borrow();
1671 let context = ctx_ref.as_ref().expect("context should exist");
1672
1673 self.resources = None;
1674 self.atlas
1675 .handle_device_lost(Arc::clone(&context.device), Arc::clone(&context.queue));
1676
1677 *self = Self::new_internal(
1678 Some(gpu_context.clone()),
1679 context,
1680 surface,
1681 config,
1682 self.compositor_gpu,
1683 self.atlas.clone(),
1684 )?;
1685
1686 log::info!("GPU recovery complete");
1687 Ok(())
1688 }
1689}
1690
1691#[cfg(not(target_family = "wasm"))]
1692fn create_surface(
1693 instance: &wgpu::Instance,
1694 raw_display_handle: raw_window_handle::RawDisplayHandle,
1695 raw_window_handle: raw_window_handle::RawWindowHandle,
1696) -> anyhow::Result<wgpu::Surface<'static>> {
1697 unsafe {
1698 instance
1699 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
1700 raw_display_handle,
1701 raw_window_handle,
1702 })
1703 .map_err(|e| anyhow::anyhow!("{e}"))
1704 }
1705}
1706
1707struct RenderingParameters {
1708 path_sample_count: u32,
1709 gamma_ratios: [f32; 4],
1710 grayscale_enhanced_contrast: f32,
1711 subpixel_enhanced_contrast: f32,
1712}
1713
1714impl RenderingParameters {
1715 fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
1716 use std::env;
1717
1718 let format_features = adapter.get_texture_format_features(surface_format);
1719 let path_sample_count = [4, 2, 1]
1720 .into_iter()
1721 .find(|&n| format_features.flags.sample_count_supported(n))
1722 .unwrap_or(1);
1723
1724 let gamma = env::var("ZED_FONTS_GAMMA")
1725 .ok()
1726 .and_then(|v| v.parse().ok())
1727 .unwrap_or(1.8_f32)
1728 .clamp(1.0, 2.2);
1729 let gamma_ratios = get_gamma_correction_ratios(gamma);
1730
1731 let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
1732 .ok()
1733 .and_then(|v| v.parse().ok())
1734 .unwrap_or(1.0_f32)
1735 .max(0.0);
1736
1737 let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
1738 .ok()
1739 .and_then(|v| v.parse().ok())
1740 .unwrap_or(0.5_f32)
1741 .max(0.0);
1742
1743 Self {
1744 path_sample_count,
1745 gamma_ratios,
1746 grayscale_enhanced_contrast,
1747 subpixel_enhanced_contrast,
1748 }
1749 }
1750}