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