1use super::{WgpuAtlas, WgpuContext};
2use crate::{
3 AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
4 PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
5 Underline, get_gamma_correction_ratios,
6};
7use bytemuck::{Pod, Zeroable};
8use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
9use std::num::NonZeroU64;
10use std::sync::Arc;
11
12#[repr(C)]
13#[derive(Clone, Copy, Pod, Zeroable)]
14struct GlobalParams {
15 viewport_size: [f32; 2],
16 premultiplied_alpha: u32,
17 pad: u32,
18}
19
20#[repr(C)]
21#[derive(Clone, Copy, Pod, Zeroable)]
22struct PodBounds {
23 origin: [f32; 2],
24 size: [f32; 2],
25}
26
27impl From<Bounds<ScaledPixels>> for PodBounds {
28 fn from(bounds: Bounds<ScaledPixels>) -> Self {
29 Self {
30 origin: [bounds.origin.x.0, bounds.origin.y.0],
31 size: [bounds.size.width.0, bounds.size.height.0],
32 }
33 }
34}
35
36#[repr(C)]
37#[derive(Clone, Copy, Pod, Zeroable)]
38struct SurfaceParams {
39 bounds: PodBounds,
40 content_mask: PodBounds,
41}
42
43#[repr(C)]
44#[derive(Clone, Copy, Pod, Zeroable)]
45struct GammaParams {
46 gamma_ratios: [f32; 4],
47 grayscale_enhanced_contrast: f32,
48 subpixel_enhanced_contrast: f32,
49 _pad: [f32; 2],
50}
51
52#[derive(Clone, Debug)]
53#[repr(C)]
54struct PathSprite {
55 bounds: Bounds<ScaledPixels>,
56}
57
58#[derive(Clone, Debug)]
59#[repr(C)]
60struct PathRasterizationVertex {
61 xy_position: Point<ScaledPixels>,
62 st_position: Point<f32>,
63 color: Background,
64 bounds: Bounds<ScaledPixels>,
65}
66
67pub struct WgpuSurfaceConfig {
68 pub size: Size<DevicePixels>,
69 pub transparent: bool,
70}
71
72struct WgpuPipelines {
73 quads: wgpu::RenderPipeline,
74 shadows: wgpu::RenderPipeline,
75 path_rasterization: wgpu::RenderPipeline,
76 paths: wgpu::RenderPipeline,
77 underlines: wgpu::RenderPipeline,
78 mono_sprites: wgpu::RenderPipeline,
79 subpixel_sprites: Option<wgpu::RenderPipeline>,
80 poly_sprites: wgpu::RenderPipeline,
81 #[allow(dead_code)]
82 surfaces: wgpu::RenderPipeline,
83}
84
85struct WgpuBindGroupLayouts {
86 globals: wgpu::BindGroupLayout,
87 instances: wgpu::BindGroupLayout,
88 instances_with_texture: wgpu::BindGroupLayout,
89 surfaces: wgpu::BindGroupLayout,
90}
91
92pub struct WgpuRenderer {
93 device: Arc<wgpu::Device>,
94 queue: Arc<wgpu::Queue>,
95 surface: wgpu::Surface<'static>,
96 surface_config: wgpu::SurfaceConfiguration,
97 pipelines: WgpuPipelines,
98 bind_group_layouts: WgpuBindGroupLayouts,
99 atlas: Arc<WgpuAtlas>,
100 atlas_sampler: wgpu::Sampler,
101 globals_buffer: wgpu::Buffer,
102 path_globals_offset: u64,
103 gamma_offset: u64,
104 globals_bind_group: wgpu::BindGroup,
105 path_globals_bind_group: wgpu::BindGroup,
106 instance_buffer: wgpu::Buffer,
107 instance_buffer_capacity: u64,
108 storage_buffer_alignment: u64,
109 path_intermediate_texture: wgpu::Texture,
110 path_intermediate_view: wgpu::TextureView,
111 path_msaa_texture: Option<wgpu::Texture>,
112 path_msaa_view: Option<wgpu::TextureView>,
113 rendering_params: RenderingParameters,
114 dual_source_blending: bool,
115 adapter_info: wgpu::AdapterInfo,
116 transparent_alpha_mode: wgpu::CompositeAlphaMode,
117 opaque_alpha_mode: wgpu::CompositeAlphaMode,
118}
119
120impl WgpuRenderer {
121 /// Creates a new WgpuRenderer from raw window handles.
122 ///
123 /// # Safety
124 /// The caller must ensure that the window handle remains valid for the lifetime
125 /// of the returned renderer.
126 pub fn new<W: HasWindowHandle + HasDisplayHandle>(
127 context: &WgpuContext,
128 window: &W,
129 config: WgpuSurfaceConfig,
130 ) -> anyhow::Result<Self> {
131 let window_handle = window
132 .window_handle()
133 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
134 let display_handle = window
135 .display_handle()
136 .map_err(|e| anyhow::anyhow!("Failed to get display handle: {e}"))?;
137
138 let target = wgpu::SurfaceTargetUnsafe::RawHandle {
139 raw_display_handle: display_handle.as_raw(),
140 raw_window_handle: window_handle.as_raw(),
141 };
142
143 // Safety: The caller guarantees that the window handle is valid for the
144 // lifetime of this renderer. In practice, the RawWindow struct is created
145 // from the native window handles and the surface is dropped before the window.
146 let surface = unsafe {
147 context
148 .instance
149 .create_surface_unsafe(target)
150 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
151 };
152
153 let surface_caps = surface.get_capabilities(&context.adapter);
154 // Prefer standard 8-bit non-sRGB formats that don't require special features.
155 // Other formats like Rgba16Unorm require TEXTURE_FORMAT_16BIT_NORM which may
156 // not be available on all devices.
157 let preferred_formats = [
158 wgpu::TextureFormat::Bgra8Unorm,
159 wgpu::TextureFormat::Rgba8Unorm,
160 ];
161 let surface_format = preferred_formats
162 .iter()
163 .find(|f| surface_caps.formats.contains(f))
164 .copied()
165 .or_else(|| surface_caps.formats.iter().find(|f| !f.is_srgb()).copied())
166 .unwrap_or(surface_caps.formats[0]);
167
168 let pick_alpha_mode =
169 |preferences: &[wgpu::CompositeAlphaMode]| -> wgpu::CompositeAlphaMode {
170 preferences
171 .iter()
172 .find(|p| surface_caps.alpha_modes.contains(p))
173 .copied()
174 .unwrap_or(surface_caps.alpha_modes[0])
175 };
176
177 let transparent_alpha_mode = pick_alpha_mode(&[
178 wgpu::CompositeAlphaMode::PreMultiplied,
179 wgpu::CompositeAlphaMode::Inherit,
180 ]);
181
182 let opaque_alpha_mode = pick_alpha_mode(&[
183 wgpu::CompositeAlphaMode::Opaque,
184 wgpu::CompositeAlphaMode::Inherit,
185 ]);
186
187 let alpha_mode = if config.transparent {
188 transparent_alpha_mode
189 } else {
190 opaque_alpha_mode
191 };
192
193 let surface_config = wgpu::SurfaceConfiguration {
194 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
195 format: surface_format,
196 width: config.size.width.0 as u32,
197 height: config.size.height.0 as u32,
198 present_mode: wgpu::PresentMode::Fifo,
199 desired_maximum_frame_latency: 2,
200 alpha_mode,
201 view_formats: vec![],
202 };
203 surface.configure(&context.device, &surface_config);
204
205 let device = Arc::clone(&context.device);
206 let queue = Arc::clone(&context.queue);
207 let dual_source_blending = context.supports_dual_source_blending();
208
209 let rendering_params = RenderingParameters::new(&context.adapter, surface_format);
210 let bind_group_layouts = Self::create_bind_group_layouts(&device);
211 let pipelines = Self::create_pipelines(
212 &device,
213 &bind_group_layouts,
214 surface_format,
215 alpha_mode,
216 rendering_params.path_sample_count,
217 dual_source_blending,
218 );
219
220 let atlas = Arc::new(WgpuAtlas::new(Arc::clone(&device), Arc::clone(&queue)));
221 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
222 label: Some("atlas_sampler"),
223 mag_filter: wgpu::FilterMode::Linear,
224 min_filter: wgpu::FilterMode::Linear,
225 ..Default::default()
226 });
227
228 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
229 let globals_size = std::mem::size_of::<GlobalParams>() as u64;
230 let gamma_size = std::mem::size_of::<GammaParams>() as u64;
231 let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
232 let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
233
234 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
235 label: Some("globals_buffer"),
236 size: gamma_offset + gamma_size,
237 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
238 mapped_at_creation: false,
239 });
240
241 let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64;
242 let initial_instance_buffer_capacity = 2 * 1024 * 1024;
243 let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
244 label: Some("instance_buffer"),
245 size: initial_instance_buffer_capacity,
246 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
247 mapped_at_creation: false,
248 });
249
250 let (path_intermediate_texture, path_intermediate_view) = Self::create_path_intermediate(
251 &device,
252 surface_format,
253 config.size.width.0 as u32,
254 config.size.height.0 as u32,
255 );
256
257 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
258 &device,
259 surface_format,
260 config.size.width.0 as u32,
261 config.size.height.0 as u32,
262 rendering_params.path_sample_count,
263 )
264 .map(|(t, v)| (Some(t), Some(v)))
265 .unwrap_or((None, None));
266
267 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
268 label: Some("globals_bind_group"),
269 layout: &bind_group_layouts.globals,
270 entries: &[
271 wgpu::BindGroupEntry {
272 binding: 0,
273 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
274 buffer: &globals_buffer,
275 offset: 0,
276 size: Some(NonZeroU64::new(globals_size).unwrap()),
277 }),
278 },
279 wgpu::BindGroupEntry {
280 binding: 1,
281 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
282 buffer: &globals_buffer,
283 offset: gamma_offset,
284 size: Some(NonZeroU64::new(gamma_size).unwrap()),
285 }),
286 },
287 ],
288 });
289
290 let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
291 label: Some("path_globals_bind_group"),
292 layout: &bind_group_layouts.globals,
293 entries: &[
294 wgpu::BindGroupEntry {
295 binding: 0,
296 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
297 buffer: &globals_buffer,
298 offset: path_globals_offset,
299 size: Some(NonZeroU64::new(globals_size).unwrap()),
300 }),
301 },
302 wgpu::BindGroupEntry {
303 binding: 1,
304 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
305 buffer: &globals_buffer,
306 offset: gamma_offset,
307 size: Some(NonZeroU64::new(gamma_size).unwrap()),
308 }),
309 },
310 ],
311 });
312
313 let adapter_info = context.adapter.get_info();
314
315 Ok(Self {
316 device,
317 queue,
318 surface,
319 surface_config,
320 pipelines,
321 bind_group_layouts,
322 atlas,
323 atlas_sampler,
324 globals_buffer,
325 path_globals_offset,
326 gamma_offset,
327 globals_bind_group,
328 path_globals_bind_group,
329 instance_buffer,
330 instance_buffer_capacity: initial_instance_buffer_capacity,
331 storage_buffer_alignment,
332 path_intermediate_texture,
333 path_intermediate_view,
334 path_msaa_texture,
335 path_msaa_view,
336 rendering_params,
337 dual_source_blending,
338 adapter_info,
339 transparent_alpha_mode,
340 opaque_alpha_mode,
341 })
342 }
343
344 fn create_bind_group_layouts(device: &wgpu::Device) -> WgpuBindGroupLayouts {
345 let globals =
346 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
347 label: Some("globals_layout"),
348 entries: &[
349 wgpu::BindGroupLayoutEntry {
350 binding: 0,
351 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
352 ty: wgpu::BindingType::Buffer {
353 ty: wgpu::BufferBindingType::Uniform,
354 has_dynamic_offset: false,
355 min_binding_size: NonZeroU64::new(
356 std::mem::size_of::<GlobalParams>() as u64
357 ),
358 },
359 count: None,
360 },
361 wgpu::BindGroupLayoutEntry {
362 binding: 1,
363 visibility: wgpu::ShaderStages::FRAGMENT,
364 ty: wgpu::BindingType::Buffer {
365 ty: wgpu::BufferBindingType::Uniform,
366 has_dynamic_offset: false,
367 min_binding_size: NonZeroU64::new(
368 std::mem::size_of::<GammaParams>() as u64
369 ),
370 },
371 count: None,
372 },
373 ],
374 });
375
376 let storage_buffer_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
377 binding,
378 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
379 ty: wgpu::BindingType::Buffer {
380 ty: wgpu::BufferBindingType::Storage { read_only: true },
381 has_dynamic_offset: false,
382 min_binding_size: None,
383 },
384 count: None,
385 };
386
387 let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
388 label: Some("instances_layout"),
389 entries: &[storage_buffer_entry(0)],
390 });
391
392 let instances_with_texture =
393 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
394 label: Some("instances_with_texture_layout"),
395 entries: &[
396 storage_buffer_entry(0),
397 wgpu::BindGroupLayoutEntry {
398 binding: 1,
399 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
400 ty: wgpu::BindingType::Texture {
401 sample_type: wgpu::TextureSampleType::Float { filterable: true },
402 view_dimension: wgpu::TextureViewDimension::D2,
403 multisampled: false,
404 },
405 count: None,
406 },
407 wgpu::BindGroupLayoutEntry {
408 binding: 2,
409 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
410 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
411 count: None,
412 },
413 ],
414 });
415
416 let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
417 label: Some("surfaces_layout"),
418 entries: &[
419 wgpu::BindGroupLayoutEntry {
420 binding: 0,
421 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
422 ty: wgpu::BindingType::Buffer {
423 ty: wgpu::BufferBindingType::Uniform,
424 has_dynamic_offset: false,
425 min_binding_size: NonZeroU64::new(
426 std::mem::size_of::<SurfaceParams>() as u64
427 ),
428 },
429 count: None,
430 },
431 wgpu::BindGroupLayoutEntry {
432 binding: 1,
433 visibility: wgpu::ShaderStages::FRAGMENT,
434 ty: wgpu::BindingType::Texture {
435 sample_type: wgpu::TextureSampleType::Float { filterable: true },
436 view_dimension: wgpu::TextureViewDimension::D2,
437 multisampled: false,
438 },
439 count: None,
440 },
441 wgpu::BindGroupLayoutEntry {
442 binding: 2,
443 visibility: wgpu::ShaderStages::FRAGMENT,
444 ty: wgpu::BindingType::Texture {
445 sample_type: wgpu::TextureSampleType::Float { filterable: true },
446 view_dimension: wgpu::TextureViewDimension::D2,
447 multisampled: false,
448 },
449 count: None,
450 },
451 wgpu::BindGroupLayoutEntry {
452 binding: 3,
453 visibility: wgpu::ShaderStages::FRAGMENT,
454 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
455 count: None,
456 },
457 ],
458 });
459
460 WgpuBindGroupLayouts {
461 globals,
462 instances,
463 instances_with_texture,
464 surfaces,
465 }
466 }
467
468 fn create_pipelines(
469 device: &wgpu::Device,
470 layouts: &WgpuBindGroupLayouts,
471 surface_format: wgpu::TextureFormat,
472 alpha_mode: wgpu::CompositeAlphaMode,
473 path_sample_count: u32,
474 dual_source_blending: bool,
475 ) -> WgpuPipelines {
476 let shader_source = include_str!("shaders.wgsl");
477 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
478 label: Some("gpui_shaders"),
479 source: wgpu::ShaderSource::Wgsl(shader_source.into()),
480 });
481
482 let blend_mode = match alpha_mode {
483 wgpu::CompositeAlphaMode::PreMultiplied => {
484 wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
485 }
486 _ => wgpu::BlendState::ALPHA_BLENDING,
487 };
488
489 let color_target = wgpu::ColorTargetState {
490 format: surface_format,
491 blend: Some(blend_mode),
492 write_mask: wgpu::ColorWrites::ALL,
493 };
494
495 let create_pipeline = |name: &str,
496 vs_entry: &str,
497 fs_entry: &str,
498 globals_layout: &wgpu::BindGroupLayout,
499 data_layout: &wgpu::BindGroupLayout,
500 topology: wgpu::PrimitiveTopology,
501 color_targets: &[Option<wgpu::ColorTargetState>],
502 sample_count: u32| {
503 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
504 label: Some(&format!("{name}_layout")),
505 bind_group_layouts: &[globals_layout, data_layout],
506 immediate_size: 0,
507 });
508
509 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
510 label: Some(name),
511 layout: Some(&pipeline_layout),
512 vertex: wgpu::VertexState {
513 module: &shader_module,
514 entry_point: Some(vs_entry),
515 buffers: &[],
516 compilation_options: wgpu::PipelineCompilationOptions::default(),
517 },
518 fragment: Some(wgpu::FragmentState {
519 module: &shader_module,
520 entry_point: Some(fs_entry),
521 targets: color_targets,
522 compilation_options: wgpu::PipelineCompilationOptions::default(),
523 }),
524 primitive: wgpu::PrimitiveState {
525 topology,
526 strip_index_format: None,
527 front_face: wgpu::FrontFace::Ccw,
528 cull_mode: None,
529 polygon_mode: wgpu::PolygonMode::Fill,
530 unclipped_depth: false,
531 conservative: false,
532 },
533 depth_stencil: None,
534 multisample: wgpu::MultisampleState {
535 count: sample_count,
536 mask: !0,
537 alpha_to_coverage_enabled: false,
538 },
539 multiview_mask: None,
540 cache: None,
541 })
542 };
543
544 let quads = create_pipeline(
545 "quads",
546 "vs_quad",
547 "fs_quad",
548 &layouts.globals,
549 &layouts.instances,
550 wgpu::PrimitiveTopology::TriangleStrip,
551 &[Some(color_target.clone())],
552 1,
553 );
554
555 let shadows = create_pipeline(
556 "shadows",
557 "vs_shadow",
558 "fs_shadow",
559 &layouts.globals,
560 &layouts.instances,
561 wgpu::PrimitiveTopology::TriangleStrip,
562 &[Some(color_target.clone())],
563 1,
564 );
565
566 let path_rasterization = create_pipeline(
567 "path_rasterization",
568 "vs_path_rasterization",
569 "fs_path_rasterization",
570 &layouts.globals,
571 &layouts.instances,
572 wgpu::PrimitiveTopology::TriangleList,
573 &[Some(wgpu::ColorTargetState {
574 format: surface_format,
575 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
576 write_mask: wgpu::ColorWrites::ALL,
577 })],
578 path_sample_count,
579 );
580
581 let paths_blend = wgpu::BlendState {
582 color: wgpu::BlendComponent {
583 src_factor: wgpu::BlendFactor::One,
584 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
585 operation: wgpu::BlendOperation::Add,
586 },
587 alpha: wgpu::BlendComponent {
588 src_factor: wgpu::BlendFactor::One,
589 dst_factor: wgpu::BlendFactor::One,
590 operation: wgpu::BlendOperation::Add,
591 },
592 };
593
594 let paths = create_pipeline(
595 "paths",
596 "vs_path",
597 "fs_path",
598 &layouts.globals,
599 &layouts.instances_with_texture,
600 wgpu::PrimitiveTopology::TriangleStrip,
601 &[Some(wgpu::ColorTargetState {
602 format: surface_format,
603 blend: Some(paths_blend),
604 write_mask: wgpu::ColorWrites::ALL,
605 })],
606 1,
607 );
608
609 let underlines = create_pipeline(
610 "underlines",
611 "vs_underline",
612 "fs_underline",
613 &layouts.globals,
614 &layouts.instances,
615 wgpu::PrimitiveTopology::TriangleStrip,
616 &[Some(color_target.clone())],
617 1,
618 );
619
620 let mono_sprites = create_pipeline(
621 "mono_sprites",
622 "vs_mono_sprite",
623 "fs_mono_sprite",
624 &layouts.globals,
625 &layouts.instances_with_texture,
626 wgpu::PrimitiveTopology::TriangleStrip,
627 &[Some(color_target.clone())],
628 1,
629 );
630
631 let subpixel_sprites = if dual_source_blending {
632 let subpixel_blend = wgpu::BlendState {
633 color: wgpu::BlendComponent {
634 src_factor: wgpu::BlendFactor::Src1,
635 dst_factor: wgpu::BlendFactor::OneMinusSrc1,
636 operation: wgpu::BlendOperation::Add,
637 },
638 alpha: wgpu::BlendComponent {
639 src_factor: wgpu::BlendFactor::One,
640 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
641 operation: wgpu::BlendOperation::Add,
642 },
643 };
644
645 Some(create_pipeline(
646 "subpixel_sprites",
647 "vs_subpixel_sprite",
648 "fs_subpixel_sprite",
649 &layouts.globals,
650 &layouts.instances_with_texture,
651 wgpu::PrimitiveTopology::TriangleStrip,
652 &[Some(wgpu::ColorTargetState {
653 format: surface_format,
654 blend: Some(subpixel_blend),
655 write_mask: wgpu::ColorWrites::COLOR,
656 })],
657 1,
658 ))
659 } else {
660 None
661 };
662
663 let poly_sprites = create_pipeline(
664 "poly_sprites",
665 "vs_poly_sprite",
666 "fs_poly_sprite",
667 &layouts.globals,
668 &layouts.instances_with_texture,
669 wgpu::PrimitiveTopology::TriangleStrip,
670 &[Some(color_target.clone())],
671 1,
672 );
673
674 let surfaces = create_pipeline(
675 "surfaces",
676 "vs_surface",
677 "fs_surface",
678 &layouts.globals,
679 &layouts.surfaces,
680 wgpu::PrimitiveTopology::TriangleStrip,
681 &[Some(color_target)],
682 1,
683 );
684
685 WgpuPipelines {
686 quads,
687 shadows,
688 path_rasterization,
689 paths,
690 underlines,
691 mono_sprites,
692 subpixel_sprites,
693 poly_sprites,
694 surfaces,
695 }
696 }
697
698 fn create_path_intermediate(
699 device: &wgpu::Device,
700 format: wgpu::TextureFormat,
701 width: u32,
702 height: u32,
703 ) -> (wgpu::Texture, wgpu::TextureView) {
704 let texture = device.create_texture(&wgpu::TextureDescriptor {
705 label: Some("path_intermediate"),
706 size: wgpu::Extent3d {
707 width: width.max(1),
708 height: height.max(1),
709 depth_or_array_layers: 1,
710 },
711 mip_level_count: 1,
712 sample_count: 1,
713 dimension: wgpu::TextureDimension::D2,
714 format,
715 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
716 view_formats: &[],
717 });
718 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
719 (texture, view)
720 }
721
722 fn create_msaa_if_needed(
723 device: &wgpu::Device,
724 format: wgpu::TextureFormat,
725 width: u32,
726 height: u32,
727 sample_count: u32,
728 ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
729 if sample_count <= 1 {
730 return None;
731 }
732 let texture = device.create_texture(&wgpu::TextureDescriptor {
733 label: Some("path_msaa"),
734 size: wgpu::Extent3d {
735 width: width.max(1),
736 height: height.max(1),
737 depth_or_array_layers: 1,
738 },
739 mip_level_count: 1,
740 sample_count,
741 dimension: wgpu::TextureDimension::D2,
742 format,
743 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
744 view_formats: &[],
745 });
746 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
747 Some((texture, view))
748 }
749
750 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
751 let width = size.width.0 as u32;
752 let height = size.height.0 as u32;
753
754 if width != self.surface_config.width || height != self.surface_config.height {
755 self.surface_config.width = width.max(1);
756 self.surface_config.height = height.max(1);
757 self.surface.configure(&self.device, &self.surface_config);
758
759 let (path_intermediate_texture, path_intermediate_view) =
760 Self::create_path_intermediate(
761 &self.device,
762 self.surface_config.format,
763 self.surface_config.width,
764 self.surface_config.height,
765 );
766 self.path_intermediate_texture = path_intermediate_texture;
767 self.path_intermediate_view = path_intermediate_view;
768
769 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
770 &self.device,
771 self.surface_config.format,
772 self.surface_config.width,
773 self.surface_config.height,
774 self.rendering_params.path_sample_count,
775 )
776 .map(|(t, v)| (Some(t), Some(v)))
777 .unwrap_or((None, None));
778 self.path_msaa_texture = path_msaa_texture;
779 self.path_msaa_view = path_msaa_view;
780 }
781 }
782
783 pub fn update_transparency(&mut self, transparent: bool) {
784 let new_alpha_mode = if transparent {
785 self.transparent_alpha_mode
786 } else {
787 self.opaque_alpha_mode
788 };
789
790 if new_alpha_mode != self.surface_config.alpha_mode {
791 self.surface_config.alpha_mode = new_alpha_mode;
792 self.surface.configure(&self.device, &self.surface_config);
793 self.pipelines = Self::create_pipelines(
794 &self.device,
795 &self.bind_group_layouts,
796 self.surface_config.format,
797 self.surface_config.alpha_mode,
798 self.rendering_params.path_sample_count,
799 self.dual_source_blending,
800 );
801 }
802 }
803
804 #[allow(dead_code)]
805 pub fn viewport_size(&self) -> Size<DevicePixels> {
806 Size {
807 width: DevicePixels(self.surface_config.width as i32),
808 height: DevicePixels(self.surface_config.height as i32),
809 }
810 }
811
812 pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
813 &self.atlas
814 }
815
816 pub fn gpu_specs(&self) -> GpuSpecs {
817 GpuSpecs {
818 is_software_emulated: self.adapter_info.device_type == wgpu::DeviceType::Cpu,
819 device_name: self.adapter_info.name.clone(),
820 driver_name: self.adapter_info.driver.clone(),
821 driver_info: self.adapter_info.driver_info.clone(),
822 }
823 }
824
825 pub fn draw(&mut self, scene: &Scene) {
826 self.atlas.before_frame();
827
828 let frame = match self.surface.get_current_texture() {
829 Ok(frame) => frame,
830 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
831 self.surface.configure(&self.device, &self.surface_config);
832 return;
833 }
834 Err(e) => {
835 log::error!("Failed to acquire surface texture: {e}");
836 return;
837 }
838 };
839 let frame_view = frame
840 .texture
841 .create_view(&wgpu::TextureViewDescriptor::default());
842
843 let gamma_params = GammaParams {
844 gamma_ratios: self.rendering_params.gamma_ratios,
845 grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
846 subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
847 _pad: [0.0; 2],
848 };
849
850 let globals = GlobalParams {
851 viewport_size: [
852 self.surface_config.width as f32,
853 self.surface_config.height as f32,
854 ],
855 premultiplied_alpha: if self.surface_config.alpha_mode
856 == wgpu::CompositeAlphaMode::PreMultiplied
857 {
858 1
859 } else {
860 0
861 },
862 pad: 0,
863 };
864
865 let path_globals = GlobalParams {
866 premultiplied_alpha: 0,
867 ..globals
868 };
869
870 self.queue
871 .write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals));
872 self.queue.write_buffer(
873 &self.globals_buffer,
874 self.path_globals_offset,
875 bytemuck::bytes_of(&path_globals),
876 );
877 self.queue.write_buffer(
878 &self.globals_buffer,
879 self.gamma_offset,
880 bytemuck::bytes_of(&gamma_params),
881 );
882
883 loop {
884 let mut instance_offset: u64 = 0;
885 let mut overflow = false;
886
887 let mut encoder = self
888 .device
889 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
890 label: Some("main_encoder"),
891 });
892
893 {
894 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
895 label: Some("main_pass"),
896 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
897 view: &frame_view,
898 resolve_target: None,
899 ops: wgpu::Operations {
900 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
901 store: wgpu::StoreOp::Store,
902 },
903 depth_slice: None,
904 })],
905 depth_stencil_attachment: None,
906 ..Default::default()
907 });
908
909 for batch in scene.batches() {
910 let ok = match batch {
911 PrimitiveBatch::Quads(range) => {
912 self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass)
913 }
914 PrimitiveBatch::Shadows(range) => self.draw_shadows(
915 &scene.shadows[range],
916 &mut instance_offset,
917 &mut pass,
918 ),
919 PrimitiveBatch::Paths(range) => {
920 let paths = &scene.paths[range];
921 if paths.is_empty() {
922 continue;
923 }
924
925 drop(pass);
926
927 let did_draw = self.draw_paths_to_intermediate(
928 &mut encoder,
929 paths,
930 &mut instance_offset,
931 );
932
933 pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
934 label: Some("main_pass_continued"),
935 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
936 view: &frame_view,
937 resolve_target: None,
938 ops: wgpu::Operations {
939 load: wgpu::LoadOp::Load,
940 store: wgpu::StoreOp::Store,
941 },
942 depth_slice: None,
943 })],
944 depth_stencil_attachment: None,
945 ..Default::default()
946 });
947
948 if did_draw {
949 self.draw_paths_from_intermediate(
950 paths,
951 &mut instance_offset,
952 &mut pass,
953 )
954 } else {
955 false
956 }
957 }
958 PrimitiveBatch::Underlines(range) => self.draw_underlines(
959 &scene.underlines[range],
960 &mut instance_offset,
961 &mut pass,
962 ),
963 PrimitiveBatch::MonochromeSprites { texture_id, range } => self
964 .draw_monochrome_sprites(
965 &scene.monochrome_sprites[range],
966 texture_id,
967 &mut instance_offset,
968 &mut pass,
969 ),
970 PrimitiveBatch::SubpixelSprites { texture_id, range } => self
971 .draw_subpixel_sprites(
972 &scene.subpixel_sprites[range],
973 texture_id,
974 &mut instance_offset,
975 &mut pass,
976 ),
977 PrimitiveBatch::PolychromeSprites { texture_id, range } => self
978 .draw_polychrome_sprites(
979 &scene.polychrome_sprites[range],
980 texture_id,
981 &mut instance_offset,
982 &mut pass,
983 ),
984 PrimitiveBatch::Surfaces(_surfaces) => {
985 // Surfaces are macOS-only for video playback
986 // Not implemented for Linux/wgpu
987 true
988 }
989 };
990 if !ok {
991 overflow = true;
992 break;
993 }
994 }
995 }
996
997 if overflow {
998 drop(encoder);
999 if self.instance_buffer_capacity >= 256 * 1024 * 1024 {
1000 log::error!(
1001 "instance buffer size grew too large: {}",
1002 self.instance_buffer_capacity
1003 );
1004 frame.present();
1005 return;
1006 }
1007 self.grow_instance_buffer();
1008 continue;
1009 }
1010
1011 self.queue.submit(std::iter::once(encoder.finish()));
1012 frame.present();
1013 return;
1014 }
1015 }
1016
1017 fn draw_quads(
1018 &self,
1019 quads: &[Quad],
1020 instance_offset: &mut u64,
1021 pass: &mut wgpu::RenderPass<'_>,
1022 ) -> bool {
1023 let data = unsafe { Self::instance_bytes(quads) };
1024 self.draw_instances(
1025 data,
1026 quads.len() as u32,
1027 &self.pipelines.quads,
1028 instance_offset,
1029 pass,
1030 )
1031 }
1032
1033 fn draw_shadows(
1034 &self,
1035 shadows: &[Shadow],
1036 instance_offset: &mut u64,
1037 pass: &mut wgpu::RenderPass<'_>,
1038 ) -> bool {
1039 let data = unsafe { Self::instance_bytes(shadows) };
1040 self.draw_instances(
1041 data,
1042 shadows.len() as u32,
1043 &self.pipelines.shadows,
1044 instance_offset,
1045 pass,
1046 )
1047 }
1048
1049 fn draw_underlines(
1050 &self,
1051 underlines: &[Underline],
1052 instance_offset: &mut u64,
1053 pass: &mut wgpu::RenderPass<'_>,
1054 ) -> bool {
1055 let data = unsafe { Self::instance_bytes(underlines) };
1056 self.draw_instances(
1057 data,
1058 underlines.len() as u32,
1059 &self.pipelines.underlines,
1060 instance_offset,
1061 pass,
1062 )
1063 }
1064
1065 fn draw_monochrome_sprites(
1066 &self,
1067 sprites: &[MonochromeSprite],
1068 texture_id: AtlasTextureId,
1069 instance_offset: &mut u64,
1070 pass: &mut wgpu::RenderPass<'_>,
1071 ) -> bool {
1072 let tex_info = self.atlas.get_texture_info(texture_id);
1073 let data = unsafe { Self::instance_bytes(sprites) };
1074 self.draw_instances_with_texture(
1075 data,
1076 sprites.len() as u32,
1077 &tex_info.view,
1078 &self.pipelines.mono_sprites,
1079 instance_offset,
1080 pass,
1081 )
1082 }
1083
1084 fn draw_subpixel_sprites(
1085 &self,
1086 sprites: &[SubpixelSprite],
1087 texture_id: AtlasTextureId,
1088 instance_offset: &mut u64,
1089 pass: &mut wgpu::RenderPass<'_>,
1090 ) -> bool {
1091 let tex_info = self.atlas.get_texture_info(texture_id);
1092 let data = unsafe { Self::instance_bytes(sprites) };
1093 let pipeline = self
1094 .pipelines
1095 .subpixel_sprites
1096 .as_ref()
1097 .unwrap_or(&self.pipelines.mono_sprites);
1098 self.draw_instances_with_texture(
1099 data,
1100 sprites.len() as u32,
1101 &tex_info.view,
1102 pipeline,
1103 instance_offset,
1104 pass,
1105 )
1106 }
1107
1108 fn draw_polychrome_sprites(
1109 &self,
1110 sprites: &[PolychromeSprite],
1111 texture_id: AtlasTextureId,
1112 instance_offset: &mut u64,
1113 pass: &mut wgpu::RenderPass<'_>,
1114 ) -> bool {
1115 let tex_info = self.atlas.get_texture_info(texture_id);
1116 let data = unsafe { Self::instance_bytes(sprites) };
1117 self.draw_instances_with_texture(
1118 data,
1119 sprites.len() as u32,
1120 &tex_info.view,
1121 &self.pipelines.poly_sprites,
1122 instance_offset,
1123 pass,
1124 )
1125 }
1126
1127 fn draw_instances(
1128 &self,
1129 data: &[u8],
1130 instance_count: u32,
1131 pipeline: &wgpu::RenderPipeline,
1132 instance_offset: &mut u64,
1133 pass: &mut wgpu::RenderPass<'_>,
1134 ) -> bool {
1135 if instance_count == 0 {
1136 return true;
1137 }
1138 let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
1139 return false;
1140 };
1141 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1142 label: None,
1143 layout: &self.bind_group_layouts.instances,
1144 entries: &[wgpu::BindGroupEntry {
1145 binding: 0,
1146 resource: self.instance_binding(offset, size),
1147 }],
1148 });
1149 pass.set_pipeline(pipeline);
1150 pass.set_bind_group(0, &self.globals_bind_group, &[]);
1151 pass.set_bind_group(1, &bind_group, &[]);
1152 pass.draw(0..4, 0..instance_count);
1153 true
1154 }
1155
1156 fn draw_instances_with_texture(
1157 &self,
1158 data: &[u8],
1159 instance_count: u32,
1160 texture_view: &wgpu::TextureView,
1161 pipeline: &wgpu::RenderPipeline,
1162 instance_offset: &mut u64,
1163 pass: &mut wgpu::RenderPass<'_>,
1164 ) -> bool {
1165 if instance_count == 0 {
1166 return true;
1167 }
1168 let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
1169 return false;
1170 };
1171 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1172 label: None,
1173 layout: &self.bind_group_layouts.instances_with_texture,
1174 entries: &[
1175 wgpu::BindGroupEntry {
1176 binding: 0,
1177 resource: self.instance_binding(offset, size),
1178 },
1179 wgpu::BindGroupEntry {
1180 binding: 1,
1181 resource: wgpu::BindingResource::TextureView(texture_view),
1182 },
1183 wgpu::BindGroupEntry {
1184 binding: 2,
1185 resource: wgpu::BindingResource::Sampler(&self.atlas_sampler),
1186 },
1187 ],
1188 });
1189 pass.set_pipeline(pipeline);
1190 pass.set_bind_group(0, &self.globals_bind_group, &[]);
1191 pass.set_bind_group(1, &bind_group, &[]);
1192 pass.draw(0..4, 0..instance_count);
1193 true
1194 }
1195
1196 unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
1197 unsafe {
1198 std::slice::from_raw_parts(
1199 instances.as_ptr() as *const u8,
1200 std::mem::size_of_val(instances),
1201 )
1202 }
1203 }
1204
1205 fn draw_paths_from_intermediate(
1206 &self,
1207 paths: &[Path<ScaledPixels>],
1208 instance_offset: &mut u64,
1209 pass: &mut wgpu::RenderPass<'_>,
1210 ) -> bool {
1211 let first_path = &paths[0];
1212 let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
1213 {
1214 paths
1215 .iter()
1216 .map(|p| PathSprite {
1217 bounds: p.clipped_bounds(),
1218 })
1219 .collect()
1220 } else {
1221 let mut bounds = first_path.clipped_bounds();
1222 for path in paths.iter().skip(1) {
1223 bounds = bounds.union(&path.clipped_bounds());
1224 }
1225 vec![PathSprite { bounds }]
1226 };
1227
1228 let sprite_data = unsafe { Self::instance_bytes(&sprites) };
1229 self.draw_instances_with_texture(
1230 sprite_data,
1231 sprites.len() as u32,
1232 &self.path_intermediate_view,
1233 &self.pipelines.paths,
1234 instance_offset,
1235 pass,
1236 )
1237 }
1238
1239 fn draw_paths_to_intermediate(
1240 &self,
1241 encoder: &mut wgpu::CommandEncoder,
1242 paths: &[Path<ScaledPixels>],
1243 instance_offset: &mut u64,
1244 ) -> bool {
1245 let mut vertices = Vec::new();
1246 for path in paths {
1247 let bounds = path.clipped_bounds();
1248 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
1249 xy_position: v.xy_position,
1250 st_position: v.st_position,
1251 color: path.color,
1252 bounds,
1253 }));
1254 }
1255
1256 if vertices.is_empty() {
1257 return true;
1258 }
1259
1260 let vertex_data = unsafe { Self::instance_bytes(&vertices) };
1261 let Some((vertex_offset, vertex_size)) =
1262 self.write_to_instance_buffer(instance_offset, vertex_data)
1263 else {
1264 return false;
1265 };
1266
1267 let data_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1268 label: Some("path_rasterization_bind_group"),
1269 layout: &self.bind_group_layouts.instances,
1270 entries: &[wgpu::BindGroupEntry {
1271 binding: 0,
1272 resource: self.instance_binding(vertex_offset, vertex_size),
1273 }],
1274 });
1275
1276 let (target_view, resolve_target) = if let Some(ref msaa_view) = self.path_msaa_view {
1277 (msaa_view, Some(&self.path_intermediate_view))
1278 } else {
1279 (&self.path_intermediate_view, None)
1280 };
1281
1282 {
1283 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1284 label: Some("path_rasterization_pass"),
1285 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1286 view: target_view,
1287 resolve_target,
1288 ops: wgpu::Operations {
1289 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1290 store: wgpu::StoreOp::Store,
1291 },
1292 depth_slice: None,
1293 })],
1294 depth_stencil_attachment: None,
1295 ..Default::default()
1296 });
1297
1298 pass.set_pipeline(&self.pipelines.path_rasterization);
1299 pass.set_bind_group(0, &self.path_globals_bind_group, &[]);
1300 pass.set_bind_group(1, &data_bind_group, &[]);
1301 pass.draw(0..vertices.len() as u32, 0..1);
1302 }
1303
1304 true
1305 }
1306
1307 fn grow_instance_buffer(&mut self) {
1308 let new_capacity = self.instance_buffer_capacity * 2;
1309 log::info!("increased instance buffer size to {}", new_capacity);
1310 self.instance_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1311 label: Some("instance_buffer"),
1312 size: new_capacity,
1313 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1314 mapped_at_creation: false,
1315 });
1316 self.instance_buffer_capacity = new_capacity;
1317 }
1318
1319 fn write_to_instance_buffer(
1320 &self,
1321 instance_offset: &mut u64,
1322 data: &[u8],
1323 ) -> Option<(u64, NonZeroU64)> {
1324 let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment);
1325 let size = (data.len() as u64).max(16);
1326 if offset + size > self.instance_buffer_capacity {
1327 return None;
1328 }
1329 self.queue.write_buffer(&self.instance_buffer, offset, data);
1330 *instance_offset = offset + size;
1331 Some((offset, NonZeroU64::new(size).expect("size is at least 16")))
1332 }
1333
1334 fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> {
1335 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1336 buffer: &self.instance_buffer,
1337 offset,
1338 size: Some(size),
1339 })
1340 }
1341
1342 pub fn destroy(&mut self) {
1343 // wgpu resources are automatically cleaned up when dropped
1344 }
1345}
1346
1347struct RenderingParameters {
1348 path_sample_count: u32,
1349 gamma_ratios: [f32; 4],
1350 grayscale_enhanced_contrast: f32,
1351 subpixel_enhanced_contrast: f32,
1352}
1353
1354impl RenderingParameters {
1355 fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
1356 use std::env;
1357
1358 let format_features = adapter.get_texture_format_features(surface_format);
1359 let path_sample_count = [4, 2, 1]
1360 .into_iter()
1361 .find(|&n| format_features.flags.sample_count_supported(n))
1362 .unwrap_or(1);
1363
1364 let gamma = env::var("ZED_FONTS_GAMMA")
1365 .ok()
1366 .and_then(|v| v.parse().ok())
1367 .unwrap_or(1.8_f32)
1368 .clamp(1.0, 2.2);
1369 let gamma_ratios = get_gamma_correction_ratios(gamma);
1370
1371 let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
1372 .ok()
1373 .and_then(|v| v.parse().ok())
1374 .unwrap_or(1.0_f32)
1375 .max(0.0);
1376
1377 let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
1378 .ok()
1379 .and_then(|v| v.parse().ok())
1380 .unwrap_or(0.5_f32)
1381 .max(0.0);
1382
1383 Self {
1384 path_sample_count,
1385 gamma_ratios,
1386 grayscale_enhanced_contrast,
1387 subpixel_enhanced_contrast,
1388 }
1389 }
1390}