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