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