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