1use crate::{CompositorGpuHint, DeviceErrorState, WgpuAtlas, WgpuContext};
3use anyhow::{Context as _, Result};
4use bytemuck::{Pod, Zeroable};
5use collections::FxHashMap;
6use gpui::{
7 AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch,
8 ScaledPixels, Scene, Size, get_gamma_correction_ratios,
9};
10use log::warn;
11#[cfg(not(target_family = "wasm"))]
12use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
13use smallvec::SmallVec;
14use std::cell::RefCell;
15use std::num::NonZeroU64;
16use std::ops::Range;
17use std::rc::Rc;
18use std::sync::Arc;
19
20const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
21
22const INSTANCE_TEXTURE_TEXEL_SIZE: u64 = 16;
23
24const STORAGE_BUFFER_SHADERS: &str = concat!(
27 include_str!("shaders.wgsl"),
28 include_str!("shaders_storage.wgsl"),
29);
30
31const WEBGL_SHADERS: &str = concat!(
34 include_str!("shaders.wgsl"),
35 include_str!("shaders_webgl.wgsl"),
36);
37
38const SUBPIXEL_SHADERS: &str = concat!(
42 "enable dual_source_blending;\n",
43 include_str!("shaders.wgsl"),
44 include_str!("shaders_storage.wgsl"),
45 include_str!("shaders_subpixel.wgsl"),
46);
47
48fn least_common_multiple(left: u64, right: u64) -> u64 {
49 let mut first = left;
50 let mut second = right;
51 while second != 0 {
52 let remainder = first % second;
53 first = second;
54 second = remainder;
55 }
56 left / first * right
57}
58
59#[repr(C)]
60#[derive(Clone, Copy, Pod, Zeroable)]
61struct GlobalParams {
62 viewport_size: [f32; 2],
63 premultiplied_alpha: u32,
64 pad: u32,
65}
66
67#[repr(C)]
68#[derive(Clone, Copy, Pod, Zeroable)]
69struct PodBounds {
70 origin: [f32; 2],
71 size: [f32; 2],
72}
73
74impl From<Bounds<ScaledPixels>> for PodBounds {
75 fn from(bounds: Bounds<ScaledPixels>) -> Self {
76 Self {
77 origin: [bounds.origin.x.0, bounds.origin.y.0],
78 size: [bounds.size.width.0, bounds.size.height.0],
79 }
80 }
81}
82
83#[repr(C)]
84#[derive(Clone, Copy, Pod, Zeroable)]
85struct SurfaceParams {
86 bounds: PodBounds,
87 content_mask: PodBounds,
88}
89
90#[repr(C)]
91#[derive(Clone, Copy, Pod, Zeroable)]
92struct GammaParams {
93 gamma_ratios: [f32; 4],
94 grayscale_enhanced_contrast: f32,
95 subpixel_enhanced_contrast: f32,
96 is_bgr: u32,
97 _pad: u32,
98}
99
100#[derive(Clone, Debug)]
101#[repr(C)]
102struct PathSprite {
103 bounds: Bounds<ScaledPixels>,
104}
105
106#[derive(Clone, Debug)]
107#[repr(C)]
108struct PathRasterizationVertex {
109 xy_position: Point<ScaledPixels>,
110 st_position: Point<f32>,
111 color: Background,
112 bounds: Bounds<ScaledPixels>,
113}
114
115pub struct WgpuSurfaceConfig {
116 pub size: Size<DevicePixels>,
117 pub transparent: bool,
118 pub preferred_present_mode: Option<wgpu::PresentMode>,
125}
126
127struct WgpuPipelines {
128 quads: wgpu::RenderPipeline,
129 shadows: wgpu::RenderPipeline,
130 path_rasterization: wgpu::RenderPipeline,
131 paths: wgpu::RenderPipeline,
132 underlines: wgpu::RenderPipeline,
133 mono_sprites: wgpu::RenderPipeline,
134 subpixel_sprites: Option<wgpu::RenderPipeline>,
135 poly_sprites: wgpu::RenderPipeline,
136 #[allow(dead_code)]
137 surfaces: wgpu::RenderPipeline,
138}
139
140struct InstanceBinding {
142 bind_group: wgpu::BindGroup,
143 first_instance: u32,
149}
150
151struct InstanceBindings {
152 quads: InstanceBinding,
153 shadows: InstanceBinding,
154 underlines: InstanceBinding,
155 monochrome_sprites: InstanceBinding,
156 subpixel_sprites: InstanceBinding,
157 polychrome_sprites: InstanceBinding,
158}
159
160struct WgpuBindGroupLayouts {
161 globals: wgpu::BindGroupLayout,
162 instances: wgpu::BindGroupLayout,
163 texture: wgpu::BindGroupLayout,
164 surfaces: wgpu::BindGroupLayout,
165}
166
167pub type GpuContext = Rc<RefCell<Option<WgpuContext>>>;
169
170enum InstanceData {
171 Storage(wgpu::Buffer),
172 Texture {
175 texture: wgpu::Texture,
176 view: wgpu::TextureView,
177 width: u32,
178 height: u32,
179 },
180}
181
182struct WgpuResources {
184 device: Arc<wgpu::Device>,
185 queue: Arc<wgpu::Queue>,
186 pipelines: WgpuPipelines,
187 bind_group_layouts: WgpuBindGroupLayouts,
188 atlas_sampler: wgpu::Sampler,
189 atlas_texture_bind_groups: FxHashMap<AtlasTextureId, CachedTextureBindGroup>,
190 globals_buffer: wgpu::Buffer,
191 globals_bind_group: wgpu::BindGroup,
192 path_globals_bind_group: wgpu::BindGroup,
193 instance_data: InstanceData,
194 path_intermediate_texture: Option<wgpu::Texture>,
195 path_intermediate_view: Option<wgpu::TextureView>,
196 path_msaa_texture: Option<wgpu::Texture>,
197 path_msaa_view: Option<wgpu::TextureView>,
198}
199
200struct CachedTextureBindGroup {
201 texture_generation: u64,
202 bind_group: wgpu::BindGroup,
203}
204
205impl WgpuResources {
206 fn invalidate_intermediate_textures(&mut self) {
207 self.path_intermediate_texture = None;
208 self.path_intermediate_view = None;
209 self.path_msaa_texture = None;
210 self.path_msaa_view = None;
211 }
212}
213
214struct WgpuRendererCore {
215 resources: WgpuResources,
216 atlas: Arc<WgpuAtlas>,
217 path_globals_offset: u64,
218 gamma_offset: u64,
219 instance_data_capacity: u64,
220 max_instance_data_size: u64,
221 instance_data_alignment: u64,
222 uses_webgl_instance_data: bool,
223 rendering_params: RenderingParameters,
224 is_bgr: bool,
225 dual_source_blending: bool,
226 adapter_info: wgpu::AdapterInfo,
227 target_format: wgpu::TextureFormat,
228 max_texture_size: u32,
229}
230
231enum RendererState {
234 Ready {
236 surface: wgpu::Surface<'static>,
237 core: WgpuRendererCore,
238 },
239 Unconfigured { core: WgpuRendererCore },
243 Released,
245}
246
247pub struct WgpuRenderer {
248 #[allow(dead_code)]
250 context: Option<GpuContext>,
251 #[allow(dead_code)]
253 compositor_gpu: Option<CompositorGpuHint>,
254 state: RendererState,
255 surface_config: wgpu::SurfaceConfiguration,
256 atlas: Arc<WgpuAtlas>,
257 transparent_alpha_mode: wgpu::CompositeAlphaMode,
258 opaque_alpha_mode: wgpu::CompositeAlphaMode,
259 max_texture_size: u32,
260 is_bgr: bool,
261 failed_frame_count: u32,
262 device_errors: Arc<DeviceErrorState>,
263 observed_error_generation: u64,
264 last_surface_error: Option<String>,
265 needs_redraw: bool,
266}
267
268impl WgpuRenderer {
269 fn core(&self) -> Option<&WgpuRendererCore> {
270 match &self.state {
271 RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
272 RendererState::Released => None,
273 }
274 }
275
276 fn core_mut(&mut self) -> Option<&mut WgpuRendererCore> {
277 match &mut self.state {
278 RendererState::Ready { core, .. } | RendererState::Unconfigured { core } => Some(core),
279 RendererState::Released => None,
280 }
281 }
282
283 #[cfg(not(target_family = "wasm"))]
293 pub fn new<W>(
294 gpu_context: GpuContext,
295 window: &W,
296 config: WgpuSurfaceConfig,
297 compositor_gpu: Option<CompositorGpuHint>,
298 ) -> anyhow::Result<Self>
299 where
300 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
301 {
302 let window_handle = window
303 .window_handle()
304 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
305
306 let target = wgpu::SurfaceTargetUnsafe::RawHandle {
307 raw_display_handle: None,
309 raw_window_handle: window_handle.as_raw(),
310 };
311
312 let instance = gpu_context
316 .borrow()
317 .as_ref()
318 .map(|ctx| ctx.instance.clone())
319 .unwrap_or_else(|| WgpuContext::instance(Some(Box::new(window.clone()))));
320
321 let surface = unsafe {
325 instance
326 .create_surface_unsafe(target)
327 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?
328 };
329
330 let mut ctx_ref = gpu_context.borrow_mut();
331 let context = match ctx_ref.as_mut() {
332 Some(context) => {
333 context.check_compatible_with_surface(&surface)?;
334 context
335 }
336 None => ctx_ref.insert(WgpuContext::new(instance, &surface, compositor_gpu)?),
337 };
338
339 let atlas = Arc::new(WgpuAtlas::from_context(context));
340
341 Self::new_internal(
342 Some(Rc::clone(&gpu_context)),
343 context,
344 surface,
345 config,
346 compositor_gpu,
347 atlas,
348 )
349 }
350
351 #[cfg(target_family = "wasm")]
352 pub fn new_from_canvas(
353 context: &WgpuContext,
354 canvas: &web_sys::HtmlCanvasElement,
355 config: WgpuSurfaceConfig,
356 ) -> anyhow::Result<Self> {
357 let surface = context
358 .instance
359 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
360 .map_err(|e| anyhow::anyhow!("Failed to create surface: {e}"))?;
361 Self::new_from_surface(context, surface, config)
362 }
363
364 #[cfg(target_family = "wasm")]
365 #[allow(clippy::arc_with_non_send_sync)]
366 pub fn new_from_surface(
367 context: &WgpuContext,
368 surface: wgpu::Surface<'static>,
369 config: WgpuSurfaceConfig,
370 ) -> anyhow::Result<Self> {
371 let atlas = Arc::new(WgpuAtlas::from_context(context));
372 Self::new_internal(None, context, surface, config, None, atlas)
373 }
374
375 fn new_internal(
376 gpu_context: Option<GpuContext>,
377 context: &WgpuContext,
378 surface: wgpu::Surface<'static>,
379 config: WgpuSurfaceConfig,
380 compositor_gpu: Option<CompositorGpuHint>,
381 atlas: Arc<WgpuAtlas>,
382 ) -> anyhow::Result<Self> {
383 let surface_caps = surface.get_capabilities(&context.adapter);
384 let preferred_formats = [
385 wgpu::TextureFormat::Bgra8Unorm,
386 wgpu::TextureFormat::Rgba8Unorm,
387 ];
388 let surface_format = preferred_formats
389 .iter()
390 .find(|f| surface_caps.formats.contains(f))
391 .copied()
392 .or_else(|| {
393 surface_caps
394 .formats
395 .iter()
396 .find(|format| !format.is_srgb())
397 .copied()
398 })
399 .or_else(|| surface_caps.formats.first().copied())
400 .ok_or_else(|| {
401 anyhow::anyhow!(
402 "Surface reports no supported texture formats for adapter {:?}",
403 context.adapter.get_info().name
404 )
405 })?;
406
407 let pick_alpha_mode =
408 |preferences: &[wgpu::CompositeAlphaMode]| -> anyhow::Result<wgpu::CompositeAlphaMode> {
409 preferences
410 .iter()
411 .find(|p| surface_caps.alpha_modes.contains(p))
412 .copied()
413 .or_else(|| surface_caps.alpha_modes.first().copied())
414 .ok_or_else(|| {
415 anyhow::anyhow!(
416 "Surface reports no supported alpha modes for adapter {:?}",
417 context.adapter.get_info().name
418 )
419 })
420 };
421
422 let transparent_alpha_mode = pick_alpha_mode(&[
423 wgpu::CompositeAlphaMode::PreMultiplied,
424 wgpu::CompositeAlphaMode::Inherit,
425 ])?;
426
427 let opaque_alpha_mode = pick_alpha_mode(&[
428 wgpu::CompositeAlphaMode::Opaque,
429 wgpu::CompositeAlphaMode::Inherit,
430 ])?;
431
432 let alpha_mode = if config.transparent {
433 transparent_alpha_mode
434 } else {
435 opaque_alpha_mode
436 };
437
438 let device = Arc::clone(&context.device);
439 let max_texture_size = device.limits().max_texture_dimension_2d;
440
441 let requested_width = config.size.width.0 as u32;
442 let requested_height = config.size.height.0 as u32;
443 let clamped_width = requested_width.min(max_texture_size);
444 let clamped_height = requested_height.min(max_texture_size);
445
446 if clamped_width != requested_width || clamped_height != requested_height {
447 warn!(
448 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
449 Clamping to ({}, {}). Window content may not fill the entire window.",
450 requested_width, requested_height, max_texture_size, clamped_width, clamped_height
451 );
452 }
453
454 let surface_config = wgpu::SurfaceConfiguration {
455 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
456 format: surface_format,
457 color_space: wgpu::SurfaceColorSpace::Auto,
458 width: clamped_width.max(1),
459 height: clamped_height.max(1),
460 present_mode: config
461 .preferred_present_mode
462 .filter(|mode| surface_caps.present_modes.contains(mode))
463 .unwrap_or(wgpu::PresentMode::Fifo),
464 desired_maximum_frame_latency: 2,
465 alpha_mode,
466 view_formats: vec![],
467 };
468 surface.configure(&context.device, &surface_config);
471
472 let core = WgpuRendererCore::new(context, atlas.clone(), surface_format, alpha_mode);
473
474 Ok(Self {
475 context: gpu_context,
476 compositor_gpu,
477 state: RendererState::Ready { surface, core },
478 surface_config,
479 atlas,
480 transparent_alpha_mode,
481 opaque_alpha_mode,
482 max_texture_size,
483 is_bgr: false,
484 failed_frame_count: 0,
485 device_errors: Arc::clone(context.errors()),
486 observed_error_generation: 0,
487 last_surface_error: None,
488 needs_redraw: false,
489 })
490 }
491}
492
493impl WgpuRendererCore {
494 fn create_bind_group_layouts(
495 device: &wgpu::Device,
496 uses_webgl_instance_data: bool,
497 ) -> WgpuBindGroupLayouts {
498 let globals =
499 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
500 label: Some("globals_layout"),
501 entries: &[
502 wgpu::BindGroupLayoutEntry {
503 binding: 0,
504 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
505 ty: wgpu::BindingType::Buffer {
506 ty: wgpu::BufferBindingType::Uniform,
507 has_dynamic_offset: false,
508 min_binding_size: NonZeroU64::new(
509 std::mem::size_of::<GlobalParams>() as u64
510 ),
511 },
512 count: None,
513 },
514 wgpu::BindGroupLayoutEntry {
515 binding: 1,
516 visibility: wgpu::ShaderStages::FRAGMENT,
517 ty: wgpu::BindingType::Buffer {
518 ty: wgpu::BufferBindingType::Uniform,
519 has_dynamic_offset: false,
520 min_binding_size: NonZeroU64::new(
521 std::mem::size_of::<GammaParams>() as u64
522 ),
523 },
524 count: None,
525 },
526 ],
527 });
528
529 let instance_data_entry = wgpu::BindGroupLayoutEntry {
530 binding: 0,
531 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
532 ty: if uses_webgl_instance_data {
533 wgpu::BindingType::Texture {
534 sample_type: wgpu::TextureSampleType::Uint,
535 view_dimension: wgpu::TextureViewDimension::D2,
536 multisampled: false,
537 }
538 } else {
539 wgpu::BindingType::Buffer {
540 ty: wgpu::BufferBindingType::Storage { read_only: true },
541 has_dynamic_offset: false,
542 min_binding_size: None,
543 }
544 },
545 count: None,
546 };
547
548 let instances = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
549 label: Some("instances_layout"),
550 entries: &[instance_data_entry],
551 });
552
553 let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
554 label: Some("texture_layout"),
555 entries: &[
556 wgpu::BindGroupLayoutEntry {
557 binding: 0,
558 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
559 ty: wgpu::BindingType::Texture {
560 sample_type: wgpu::TextureSampleType::Float { filterable: true },
561 view_dimension: wgpu::TextureViewDimension::D2,
562 multisampled: false,
563 },
564 count: None,
565 },
566 wgpu::BindGroupLayoutEntry {
567 binding: 1,
568 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
569 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
570 count: None,
571 },
572 ],
573 });
574
575 let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
576 label: Some("surfaces_layout"),
577 entries: &[
578 wgpu::BindGroupLayoutEntry {
579 binding: 0,
580 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
581 ty: wgpu::BindingType::Buffer {
582 ty: wgpu::BufferBindingType::Uniform,
583 has_dynamic_offset: false,
584 min_binding_size: NonZeroU64::new(
585 std::mem::size_of::<SurfaceParams>() as u64
586 ),
587 },
588 count: None,
589 },
590 wgpu::BindGroupLayoutEntry {
591 binding: 1,
592 visibility: wgpu::ShaderStages::FRAGMENT,
593 ty: wgpu::BindingType::Texture {
594 sample_type: wgpu::TextureSampleType::Float { filterable: true },
595 view_dimension: wgpu::TextureViewDimension::D2,
596 multisampled: false,
597 },
598 count: None,
599 },
600 wgpu::BindGroupLayoutEntry {
601 binding: 2,
602 visibility: wgpu::ShaderStages::FRAGMENT,
603 ty: wgpu::BindingType::Texture {
604 sample_type: wgpu::TextureSampleType::Float { filterable: true },
605 view_dimension: wgpu::TextureViewDimension::D2,
606 multisampled: false,
607 },
608 count: None,
609 },
610 wgpu::BindGroupLayoutEntry {
611 binding: 3,
612 visibility: wgpu::ShaderStages::FRAGMENT,
613 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
614 count: None,
615 },
616 ],
617 });
618
619 WgpuBindGroupLayouts {
620 globals,
621 instances,
622 texture,
623 surfaces,
624 }
625 }
626
627 fn create_instance_texture(
628 device: &wgpu::Device,
629 requested_capacity: u64,
630 max_texture_dimension: u32,
631 ) -> (InstanceData, u64) {
632 let texel_count = requested_capacity.div_ceil(INSTANCE_TEXTURE_TEXEL_SIZE);
633 let width = texel_count.min(u64::from(max_texture_dimension)).max(1) as u32;
634 let height = texel_count
635 .div_ceil(u64::from(width))
636 .min(u64::from(max_texture_dimension))
637 .max(1) as u32;
638 let texture = device.create_texture(&wgpu::TextureDescriptor {
639 label: Some("instance_texture"),
640 size: wgpu::Extent3d {
641 width,
642 height,
643 depth_or_array_layers: 1,
644 },
645 mip_level_count: 1,
646 sample_count: 1,
647 dimension: wgpu::TextureDimension::D2,
648 format: wgpu::TextureFormat::Rgba32Uint,
649 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
650 view_formats: &[],
651 });
652 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
653 let capacity = u64::from(width) * u64::from(height) * INSTANCE_TEXTURE_TEXEL_SIZE;
654 (
655 InstanceData::Texture {
656 texture,
657 view,
658 width,
659 height,
660 },
661 capacity,
662 )
663 }
664
665 fn create_pipelines(
666 device: &wgpu::Device,
667 layouts: &WgpuBindGroupLayouts,
668 surface_format: wgpu::TextureFormat,
669 alpha_mode: wgpu::CompositeAlphaMode,
670 path_sample_count: u32,
671 dual_source_blending: bool,
672 uses_webgl_instance_data: bool,
673 ) -> WgpuPipelines {
674 let device_has_feature = device
683 .features()
684 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
685 if dual_source_blending && !device_has_feature {
686 log::error!(
687 "BUG: dual_source_blending flag is true but device does not \
688 have DUAL_SOURCE_BLENDING enabled (device features: {:?}). \
689 Falling back to mono text rendering. Please report this at \
690 https://github.com/zed-industries/zed/issues",
691 device.features(),
692 );
693 }
694 let dual_source_blending =
695 dual_source_blending && device_has_feature && !uses_webgl_instance_data;
696
697 let shader_source = if uses_webgl_instance_data {
698 WEBGL_SHADERS
699 } else {
700 STORAGE_BUFFER_SHADERS
701 };
702 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
703 label: Some("gpui_shaders"),
704 source: wgpu::ShaderSource::Wgsl(shader_source.into()),
705 });
706
707 let subpixel_shader_module = if dual_source_blending {
708 Some(device.create_shader_module(wgpu::ShaderModuleDescriptor {
709 label: Some("gpui_subpixel_shaders"),
710 source: wgpu::ShaderSource::Wgsl(SUBPIXEL_SHADERS.into()),
711 }))
712 } else {
713 None
714 };
715
716 let blend_mode = match alpha_mode {
717 wgpu::CompositeAlphaMode::PreMultiplied => {
718 wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING
719 }
720 _ => wgpu::BlendState::ALPHA_BLENDING,
721 };
722
723 let color_target = wgpu::ColorTargetState {
724 format: surface_format,
725 blend: Some(blend_mode),
726 write_mask: wgpu::ColorWrites::ALL,
727 };
728
729 let create_pipeline = |name: &str,
730 vs_entry: &str,
731 fs_entry: &str,
732 globals_layout: &wgpu::BindGroupLayout,
733 data_layout: &wgpu::BindGroupLayout,
734 texture_layout: Option<&wgpu::BindGroupLayout>,
735 topology: wgpu::PrimitiveTopology,
736 color_targets: &[Option<wgpu::ColorTargetState>],
737 sample_count: u32,
738 module: &wgpu::ShaderModule| {
739 let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
740 bind_group_layouts.extend(texture_layout.map(Some));
741 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
742 label: Some(&format!("{name}_layout")),
743 bind_group_layouts: &bind_group_layouts,
744 immediate_size: 0,
745 });
746
747 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
748 label: Some(name),
749 layout: Some(&pipeline_layout),
750 vertex: wgpu::VertexState {
751 module,
752 entry_point: Some(vs_entry),
753 buffers: &[],
754 compilation_options: wgpu::PipelineCompilationOptions::default(),
755 },
756 fragment: Some(wgpu::FragmentState {
757 module,
758 entry_point: Some(fs_entry),
759 targets: color_targets,
760 compilation_options: wgpu::PipelineCompilationOptions::default(),
761 }),
762 primitive: wgpu::PrimitiveState {
763 topology,
764 strip_index_format: None,
765 front_face: wgpu::FrontFace::Ccw,
766 cull_mode: None,
767 polygon_mode: wgpu::PolygonMode::Fill,
768 unclipped_depth: false,
769 conservative: false,
770 },
771 depth_stencil: None,
772 multisample: wgpu::MultisampleState {
773 count: sample_count,
774 mask: !0,
775 alpha_to_coverage_enabled: false,
776 },
777 multiview_mask: None,
778 cache: None,
779 })
780 };
781
782 let quads = create_pipeline(
783 "quads",
784 "vs_quad",
785 "fs_quad",
786 &layouts.globals,
787 &layouts.instances,
788 None,
789 wgpu::PrimitiveTopology::TriangleStrip,
790 &[Some(color_target.clone())],
791 1,
792 &shader_module,
793 );
794
795 let shadows = create_pipeline(
796 "shadows",
797 "vs_shadow",
798 "fs_shadow",
799 &layouts.globals,
800 &layouts.instances,
801 None,
802 wgpu::PrimitiveTopology::TriangleStrip,
803 &[Some(color_target.clone())],
804 1,
805 &shader_module,
806 );
807
808 let path_rasterization = create_pipeline(
809 "path_rasterization",
810 "vs_path_rasterization",
811 "fs_path_rasterization",
812 &layouts.globals,
813 &layouts.instances,
814 None,
815 wgpu::PrimitiveTopology::TriangleList,
816 &[Some(wgpu::ColorTargetState {
817 format: surface_format,
818 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
819 write_mask: wgpu::ColorWrites::ALL,
820 })],
821 path_sample_count,
822 &shader_module,
823 );
824
825 let paths = create_pipeline(
826 "paths",
827 "vs_path",
828 "fs_path",
829 &layouts.globals,
830 &layouts.instances,
831 Some(&layouts.texture),
832 wgpu::PrimitiveTopology::TriangleStrip,
833 &[Some(wgpu::ColorTargetState {
834 format: surface_format,
835 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
836 write_mask: wgpu::ColorWrites::ALL,
837 })],
838 1,
839 &shader_module,
840 );
841
842 let underlines = create_pipeline(
843 "underlines",
844 "vs_underline",
845 "fs_underline",
846 &layouts.globals,
847 &layouts.instances,
848 None,
849 wgpu::PrimitiveTopology::TriangleStrip,
850 &[Some(color_target.clone())],
851 1,
852 &shader_module,
853 );
854
855 let mono_sprites = create_pipeline(
856 "mono_sprites",
857 "vs_mono_sprite",
858 "fs_mono_sprite",
859 &layouts.globals,
860 &layouts.instances,
861 Some(&layouts.texture),
862 wgpu::PrimitiveTopology::TriangleStrip,
863 &[Some(color_target.clone())],
864 1,
865 &shader_module,
866 );
867
868 let subpixel_sprites = if let Some(subpixel_module) = &subpixel_shader_module {
869 let subpixel_blend = wgpu::BlendState {
870 color: wgpu::BlendComponent {
871 src_factor: wgpu::BlendFactor::Src1,
872 dst_factor: wgpu::BlendFactor::OneMinusSrc1,
873 operation: wgpu::BlendOperation::Add,
874 },
875 alpha: wgpu::BlendComponent {
876 src_factor: wgpu::BlendFactor::One,
877 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
878 operation: wgpu::BlendOperation::Add,
879 },
880 };
881
882 Some(create_pipeline(
883 "subpixel_sprites",
884 "vs_subpixel_sprite",
885 "fs_subpixel_sprite",
886 &layouts.globals,
887 &layouts.instances,
888 Some(&layouts.texture),
889 wgpu::PrimitiveTopology::TriangleStrip,
890 &[Some(wgpu::ColorTargetState {
891 format: surface_format,
892 blend: Some(subpixel_blend),
893 write_mask: wgpu::ColorWrites::COLOR,
894 })],
895 1,
896 subpixel_module,
897 ))
898 } else {
899 None
900 };
901
902 let poly_sprites = create_pipeline(
903 "poly_sprites",
904 "vs_poly_sprite",
905 "fs_poly_sprite",
906 &layouts.globals,
907 &layouts.instances,
908 Some(&layouts.texture),
909 wgpu::PrimitiveTopology::TriangleStrip,
910 &[Some(color_target.clone())],
911 1,
912 &shader_module,
913 );
914
915 let surfaces = create_pipeline(
916 "surfaces",
917 "vs_surface",
918 "fs_surface",
919 &layouts.globals,
920 &layouts.surfaces,
921 None,
922 wgpu::PrimitiveTopology::TriangleStrip,
923 &[Some(color_target)],
924 1,
925 &shader_module,
926 );
927
928 WgpuPipelines {
929 quads,
930 shadows,
931 path_rasterization,
932 paths,
933 underlines,
934 mono_sprites,
935 subpixel_sprites,
936 poly_sprites,
937 surfaces,
938 }
939 }
940
941 fn create_path_intermediate(
942 device: &wgpu::Device,
943 format: wgpu::TextureFormat,
944 width: u32,
945 height: u32,
946 ) -> (wgpu::Texture, wgpu::TextureView) {
947 let texture = device.create_texture(&wgpu::TextureDescriptor {
948 label: Some("path_intermediate"),
949 size: wgpu::Extent3d {
950 width: width.max(1),
951 height: height.max(1),
952 depth_or_array_layers: 1,
953 },
954 mip_level_count: 1,
955 sample_count: 1,
956 dimension: wgpu::TextureDimension::D2,
957 format,
958 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
959 view_formats: &[],
960 });
961 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
962 (texture, view)
963 }
964
965 fn create_msaa_if_needed(
966 device: &wgpu::Device,
967 format: wgpu::TextureFormat,
968 width: u32,
969 height: u32,
970 sample_count: u32,
971 ) -> Option<(wgpu::Texture, wgpu::TextureView)> {
972 if sample_count <= 1 {
973 return None;
974 }
975 let texture = device.create_texture(&wgpu::TextureDescriptor {
976 label: Some("path_msaa"),
977 size: wgpu::Extent3d {
978 width: width.max(1),
979 height: height.max(1),
980 depth_or_array_layers: 1,
981 },
982 mip_level_count: 1,
983 sample_count,
984 dimension: wgpu::TextureDimension::D2,
985 format,
986 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
987 view_formats: &[],
988 });
989 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
990 Some((texture, view))
991 }
992}
993
994impl WgpuRenderer {
995 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
998 let width = size.width.0 as u32;
999 let height = size.height.0 as u32;
1000
1001 if width == self.surface_config.width && height == self.surface_config.height {
1002 return;
1003 }
1004
1005 let clamped_width = width.min(self.max_texture_size);
1006 let clamped_height = height.min(self.max_texture_size);
1007 if clamped_width != width || clamped_height != height {
1008 warn!(
1009 "Requested surface size ({}, {}) exceeds maximum texture dimension {}. \
1010 Clamping to ({}, {}). Window content may not fill the entire window.",
1011 width, height, self.max_texture_size, clamped_width, clamped_height
1012 );
1013 }
1014 self.surface_config.width = clamped_width.max(1);
1015 self.surface_config.height = clamped_height.max(1);
1016
1017 let Some(core) = self.core_mut() else {
1018 return;
1019 };
1020 let resources = &mut core.resources;
1021
1022 if let Err(e) = resources.device.poll(wgpu::PollType::Wait {
1024 submission_index: None,
1025 timeout: None,
1026 }) {
1027 warn!("Failed to poll device during resize: {e:?}");
1028 }
1029
1030 if let Some(ref texture) = resources.path_intermediate_texture {
1032 texture.destroy();
1033 }
1034 if let Some(ref texture) = resources.path_msaa_texture {
1035 texture.destroy();
1036 }
1037
1038 resources.invalidate_intermediate_textures();
1042
1043 if let RendererState::Ready { surface, core } = &self.state {
1044 surface.configure(&core.resources.device, &self.surface_config);
1045 }
1046 }
1047
1048 pub fn set_subpixel_layout(&mut self, is_bgr: bool) {
1049 self.is_bgr = is_bgr;
1050 if let Some(core) = self.core_mut() {
1051 core.is_bgr = is_bgr;
1052 }
1053 }
1054
1055 pub fn update_transparency(&mut self, transparent: bool) {
1056 let new_alpha_mode = if transparent {
1057 self.transparent_alpha_mode
1058 } else {
1059 self.opaque_alpha_mode
1060 };
1061 if new_alpha_mode == self.surface_config.alpha_mode {
1062 return;
1063 }
1064 self.surface_config.alpha_mode = new_alpha_mode;
1065 let format = self.surface_config.format;
1066
1067 let Some(core) = self.core_mut() else {
1068 return;
1069 };
1070 let resources = &mut core.resources;
1071 resources.pipelines = WgpuRendererCore::create_pipelines(
1072 &resources.device,
1073 &resources.bind_group_layouts,
1074 format,
1075 new_alpha_mode,
1076 core.rendering_params.path_sample_count,
1077 core.dual_source_blending,
1078 core.uses_webgl_instance_data,
1079 );
1080
1081 if let RendererState::Ready { surface, core } = &self.state {
1082 surface.configure(&core.resources.device, &self.surface_config);
1083 }
1084 }
1085
1086 #[allow(dead_code)]
1087 pub fn viewport_size(&self) -> Size<DevicePixels> {
1088 Size {
1089 width: DevicePixels(self.surface_config.width as i32),
1090 height: DevicePixels(self.surface_config.height as i32),
1091 }
1092 }
1093
1094 pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
1095 &self.atlas
1096 }
1097
1098 pub fn supports_dual_source_blending(&self) -> bool {
1099 self.core().is_some_and(|core| core.dual_source_blending)
1100 }
1101
1102 pub fn gpu_specs(&self) -> Option<GpuSpecs> {
1105 let adapter_info = &self.core()?.adapter_info;
1106 Some(GpuSpecs {
1107 is_software_emulated: adapter_info.device_type == wgpu::DeviceType::Cpu,
1108 device_name: adapter_info.name.clone(),
1109 driver_name: adapter_info.driver.clone(),
1110 driver_info: adapter_info.driver_info.clone(),
1111 })
1112 }
1113
1114 pub fn max_texture_size(&self) -> u32 {
1115 self.max_texture_size
1116 }
1117
1118 pub fn draw(&mut self, scene: &Scene) -> bool {
1119 #[cfg(target_family = "wasm")]
1120 if self.device_lost() {
1121 if matches!(self.state, RendererState::Ready { .. }) {
1122 log::error!(
1123 "Browser graphics context was lost; rendering has stopped. Reload the page to recover."
1124 );
1125 self.unconfigure_surface();
1126 }
1127 return false;
1128 }
1129
1130 let RendererState::Ready { surface, core } = &mut self.state else {
1135 return false;
1136 };
1137
1138 if let Some(error) = self.last_surface_error.take().or_else(|| {
1139 self.device_errors
1140 .observe_error(&mut self.observed_error_generation)
1141 }) {
1142 self.failed_frame_count += 1;
1143 log::error!(
1144 "GPU error during frame (failure {} of 10): {error}",
1145 self.failed_frame_count
1146 );
1147
1148 if self.failed_frame_count > 10 {
1150 panic!("Too many consecutive GPU errors. Last error: {error}");
1151 } else if self.failed_frame_count > 5 {
1152 core.resources.invalidate_intermediate_textures();
1153 self.atlas.clear();
1154 self.needs_redraw = true;
1155 self.failed_frame_count = 0;
1156 return false;
1157 }
1158 } else {
1159 self.failed_frame_count = 0;
1160 }
1161
1162 let frame = match surface.get_current_texture() {
1163 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
1164 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
1165 drop(frame);
1167 surface.configure(&core.resources.device, &self.surface_config);
1168 return false;
1169 }
1170 wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
1171 surface.configure(&core.resources.device, &self.surface_config);
1172 return false;
1173 }
1174 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
1175 return false;
1176 }
1177 wgpu::CurrentSurfaceTexture::Validation => {
1178 self.last_surface_error = Some("Surface texture validation error".to_string());
1179 return false;
1180 }
1181 };
1182
1183 let size = Size {
1186 width: DevicePixels(frame.texture.width() as i32),
1187 height: DevicePixels(frame.texture.height() as i32),
1188 };
1189 let frame_view = frame
1190 .texture
1191 .create_view(&wgpu::TextureViewDescriptor::default());
1192 let premultiplied_alpha =
1193 self.surface_config.alpha_mode == wgpu::CompositeAlphaMode::PreMultiplied;
1194 if let Err(error) = core.render_frame(
1195 scene,
1196 &frame_view,
1197 size,
1198 premultiplied_alpha,
1199 wgpu::Color::TRANSPARENT,
1200 ) {
1201 log::error!("{error:#}");
1202 return false;
1203 }
1204
1205 core.resources.queue.present(frame);
1206 true
1207 }
1208}
1209
1210impl WgpuRendererCore {
1211 fn new(
1212 context: &WgpuContext,
1213 atlas: Arc<WgpuAtlas>,
1214 target_format: wgpu::TextureFormat,
1215 alpha_mode: wgpu::CompositeAlphaMode,
1216 ) -> Self {
1217 let device = Arc::clone(&context.device);
1218 let queue = Arc::clone(&context.queue);
1219 let rendering_params = RenderingParameters::new(&context.adapter, target_format);
1220 let uses_webgl_instance_data = context.uses_webgl_instance_data();
1221 let dual_source_blending =
1222 context.supports_dual_source_blending() && !uses_webgl_instance_data;
1223 let bind_group_layouts = Self::create_bind_group_layouts(&device, uses_webgl_instance_data);
1224 let pipelines = Self::create_pipelines(
1225 &device,
1226 &bind_group_layouts,
1227 target_format,
1228 alpha_mode,
1229 rendering_params.path_sample_count,
1230 dual_source_blending,
1231 uses_webgl_instance_data,
1232 );
1233 let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1234 label: Some("atlas_sampler"),
1235 mag_filter: wgpu::FilterMode::Linear,
1236 min_filter: wgpu::FilterMode::Linear,
1237 ..Default::default()
1238 });
1239 let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment as u64;
1240 let globals_size = std::mem::size_of::<GlobalParams>() as u64;
1241 let gamma_size = std::mem::size_of::<GammaParams>() as u64;
1242 let path_globals_offset = globals_size.next_multiple_of(uniform_alignment);
1243 let gamma_offset = (path_globals_offset + globals_size).next_multiple_of(uniform_alignment);
1244 let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1245 label: Some("globals_buffer"),
1246 size: gamma_offset + gamma_size,
1247 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1248 mapped_at_creation: false,
1249 });
1250 let (
1251 instance_data,
1252 instance_data_capacity,
1253 max_instance_data_size,
1254 instance_data_alignment,
1255 ) = if uses_webgl_instance_data {
1256 let max_texture_dimension = device.limits().max_texture_dimension_2d;
1257 let max_instance_data_size = (u64::from(max_texture_dimension).pow(2)
1258 * INSTANCE_TEXTURE_TEXEL_SIZE)
1259 .min(MAX_INSTANCE_BUFFER_SIZE);
1260 let initial_capacity = (2 * 1024 * 1024).min(max_instance_data_size);
1261 let (instance_data, capacity) =
1262 Self::create_instance_texture(&device, initial_capacity, max_texture_dimension);
1263 (
1264 instance_data,
1265 capacity,
1266 max_instance_data_size,
1267 INSTANCE_TEXTURE_TEXEL_SIZE,
1268 )
1269 } else {
1270 let max_buffer_size = device
1271 .limits()
1272 .max_buffer_size
1273 .min(device.limits().max_storage_buffer_binding_size)
1274 .min(MAX_INSTANCE_BUFFER_SIZE);
1275 let initial_capacity = (2 * 1024 * 1024).min(max_buffer_size);
1276 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
1277 label: Some("instance_buffer"),
1278 size: initial_capacity,
1279 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1280 mapped_at_creation: false,
1281 });
1282 (
1283 InstanceData::Storage(buffer),
1284 initial_capacity,
1285 max_buffer_size,
1286 device.limits().min_storage_buffer_offset_alignment as u64,
1287 )
1288 };
1289 let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1290 label: Some("globals_bind_group"),
1291 layout: &bind_group_layouts.globals,
1292 entries: &[
1293 wgpu::BindGroupEntry {
1294 binding: 0,
1295 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1296 buffer: &globals_buffer,
1297 offset: 0,
1298 size: NonZeroU64::new(globals_size),
1299 }),
1300 },
1301 wgpu::BindGroupEntry {
1302 binding: 1,
1303 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1304 buffer: &globals_buffer,
1305 offset: gamma_offset,
1306 size: NonZeroU64::new(gamma_size),
1307 }),
1308 },
1309 ],
1310 });
1311 let path_globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1312 label: Some("path_globals_bind_group"),
1313 layout: &bind_group_layouts.globals,
1314 entries: &[
1315 wgpu::BindGroupEntry {
1316 binding: 0,
1317 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1318 buffer: &globals_buffer,
1319 offset: path_globals_offset,
1320 size: NonZeroU64::new(globals_size),
1321 }),
1322 },
1323 wgpu::BindGroupEntry {
1324 binding: 1,
1325 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1326 buffer: &globals_buffer,
1327 offset: gamma_offset,
1328 size: NonZeroU64::new(gamma_size),
1329 }),
1330 },
1331 ],
1332 });
1333 let max_texture_size = device.limits().max_texture_dimension_2d;
1334
1335 Self {
1336 resources: WgpuResources {
1337 device,
1338 queue,
1339 pipelines,
1340 bind_group_layouts,
1341 atlas_sampler,
1342 atlas_texture_bind_groups: FxHashMap::default(),
1343 globals_buffer,
1344 globals_bind_group,
1345 path_globals_bind_group,
1346 instance_data,
1347 path_intermediate_texture: None,
1348 path_intermediate_view: None,
1349 path_msaa_texture: None,
1350 path_msaa_view: None,
1351 },
1352 atlas,
1353 path_globals_offset,
1354 gamma_offset,
1355 instance_data_capacity,
1356 max_instance_data_size,
1357 instance_data_alignment,
1358 uses_webgl_instance_data,
1359 rendering_params,
1360 is_bgr: false,
1361 dual_source_blending,
1362 adapter_info: context.adapter.get_info(),
1363 target_format,
1364 max_texture_size,
1365 }
1366 }
1367
1368 fn resources(&self) -> &WgpuResources {
1369 &self.resources
1370 }
1371
1372 fn resources_mut(&mut self) -> &mut WgpuResources {
1373 &mut self.resources
1374 }
1375
1376 fn ensure_intermediate_textures(&mut self, size: Size<DevicePixels>) {
1379 let width = size.width.0 as u32;
1380 let height = size.height.0 as u32;
1381 if self
1382 .resources
1383 .path_intermediate_texture
1384 .as_ref()
1385 .is_some_and(|texture| texture.width() == width && texture.height() == height)
1386 {
1387 return;
1388 }
1389
1390 let format = self.target_format;
1391 let path_sample_count = self.rendering_params.path_sample_count;
1392 let resources = &mut self.resources;
1393
1394 let (texture, view) =
1395 Self::create_path_intermediate(&resources.device, format, width, height);
1396 resources.path_intermediate_texture = Some(texture);
1397 resources.path_intermediate_view = Some(view);
1398
1399 let (path_msaa_texture, path_msaa_view) = Self::create_msaa_if_needed(
1400 &resources.device,
1401 format,
1402 width,
1403 height,
1404 path_sample_count,
1405 )
1406 .map(|(texture, view)| (Some(texture), Some(view)))
1407 .unwrap_or((None, None));
1408 resources.path_msaa_texture = path_msaa_texture;
1409 resources.path_msaa_view = path_msaa_view;
1410 }
1411
1412 fn render_frame(
1413 &mut self,
1414 scene: &Scene,
1415 target_view: &wgpu::TextureView,
1416 size: Size<DevicePixels>,
1417 premultiplied_alpha: bool,
1418 clear_color: wgpu::Color,
1419 ) -> Result<wgpu::SubmissionIndex> {
1420 anyhow::ensure!(
1421 size.width.0 > 0 && size.height.0 > 0,
1422 "invalid render target size: {size:?}"
1423 );
1424 anyhow::ensure!(
1425 size.width.0 as u32 <= self.max_texture_size
1426 && size.height.0 as u32 <= self.max_texture_size,
1427 "render target size {size:?} exceeds maximum texture dimension {}",
1428 self.max_texture_size
1429 );
1430
1431 self.atlas.before_frame();
1432 self.ensure_intermediate_textures(size);
1433
1434 let gamma_params = GammaParams {
1435 gamma_ratios: self.rendering_params.gamma_ratios,
1436 grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast,
1437 subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast,
1438 is_bgr: self.is_bgr as u32,
1439 _pad: 0,
1440 };
1441 let globals = GlobalParams {
1442 viewport_size: [size.width.0 as f32, size.height.0 as f32],
1443 premultiplied_alpha: premultiplied_alpha as u32,
1444 pad: 0,
1445 };
1446 let path_globals = GlobalParams {
1447 premultiplied_alpha: 0,
1448 ..globals
1449 };
1450 self.resources.queue.write_buffer(
1451 &self.resources.globals_buffer,
1452 0,
1453 bytemuck::bytes_of(&globals),
1454 );
1455 self.resources.queue.write_buffer(
1456 &self.resources.globals_buffer,
1457 self.path_globals_offset,
1458 bytemuck::bytes_of(&path_globals),
1459 );
1460 self.resources.queue.write_buffer(
1461 &self.resources.globals_buffer,
1462 self.gamma_offset,
1463 bytemuck::bytes_of(&gamma_params),
1464 );
1465
1466 self.record_frame(scene, target_view, clear_color)
1467 .inspect_err(|_| {
1468 self.resources.queue.submit(std::iter::empty());
1470 })
1471 }
1472
1473 fn record_frame(
1474 &mut self,
1475 scene: &Scene,
1476 frame_view: &wgpu::TextureView,
1477 clear_color: wgpu::Color,
1478 ) -> Result<wgpu::SubmissionIndex> {
1479 let mut instance_offset = 0;
1480 let instance_bindings = self
1481 .write_instances(scene, &mut instance_offset)
1482 .with_context(|| {
1483 format!(
1484 "scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
1485 scene.paths.len(),
1486 scene.shadows.len(),
1487 scene.quads.len(),
1488 scene.underlines.len(),
1489 scene.monochrome_sprites.len(),
1490 scene.subpixel_sprites.len(),
1491 scene.polychrome_sprites.len(),
1492 )
1493 })?;
1494 self.prepare_texture_bind_groups(scene);
1495
1496 let mut encoder =
1497 self.resources()
1498 .device
1499 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1500 label: Some("main_encoder"),
1501 });
1502
1503 {
1504 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1505 label: Some("main_pass"),
1506 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1507 view: frame_view,
1508 resolve_target: None,
1509 ops: wgpu::Operations {
1510 load: wgpu::LoadOp::Clear(clear_color),
1511 store: wgpu::StoreOp::Store,
1512 },
1513 depth_slice: None,
1514 })],
1515 depth_stencil_attachment: None,
1516 ..Default::default()
1517 });
1518
1519 for batch in scene.batches() {
1520 match batch {
1521 PrimitiveBatch::Quads(range) => self.draw_instances(
1522 &instance_bindings.quads,
1523 &self.resources().pipelines.quads,
1524 instance_range(range),
1525 &mut pass,
1526 ),
1527 PrimitiveBatch::Shadows(range) => self.draw_instances(
1528 &instance_bindings.shadows,
1529 &self.resources().pipelines.shadows,
1530 instance_range(range),
1531 &mut pass,
1532 ),
1533 PrimitiveBatch::Paths(range) => {
1534 let paths = &scene.paths[range];
1535 if paths.is_empty() {
1536 continue;
1537 }
1538
1539 drop(pass);
1540 let rasterized = self.draw_paths_to_intermediate(
1541 &mut encoder,
1542 paths,
1543 &mut instance_offset,
1544 )?;
1545
1546 pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1547 label: Some("main_pass_continued"),
1548 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1549 view: frame_view,
1550 resolve_target: None,
1551 ops: wgpu::Operations {
1552 load: wgpu::LoadOp::Load,
1553 store: wgpu::StoreOp::Store,
1554 },
1555 depth_slice: None,
1556 })],
1557 depth_stencil_attachment: None,
1558 ..Default::default()
1559 });
1560
1561 if rasterized {
1562 self.draw_paths_from_intermediate(
1563 paths,
1564 &mut instance_offset,
1565 &mut pass,
1566 )?;
1567 }
1568 }
1569 PrimitiveBatch::Underlines(range) => self.draw_instances(
1570 &instance_bindings.underlines,
1571 &self.resources().pipelines.underlines,
1572 instance_range(range),
1573 &mut pass,
1574 ),
1575 PrimitiveBatch::MonochromeSprites { texture_id, range } => {
1576 self.draw_sprites(
1577 &instance_bindings.monochrome_sprites,
1578 texture_id,
1579 &self.resources().pipelines.mono_sprites,
1580 instance_range(range),
1581 &mut pass,
1582 )?;
1583 }
1584 PrimitiveBatch::SubpixelSprites { texture_id, range } => {
1585 let resources = self.resources();
1586 self.draw_sprites(
1587 &instance_bindings.subpixel_sprites,
1588 texture_id,
1589 resources
1590 .pipelines
1591 .subpixel_sprites
1592 .as_ref()
1593 .unwrap_or(&resources.pipelines.mono_sprites),
1594 instance_range(range),
1595 &mut pass,
1596 )?;
1597 }
1598 PrimitiveBatch::PolychromeSprites { texture_id, range } => {
1599 self.draw_sprites(
1600 &instance_bindings.polychrome_sprites,
1601 texture_id,
1602 &self.resources().pipelines.poly_sprites,
1603 instance_range(range),
1604 &mut pass,
1605 )?;
1606 }
1607 PrimitiveBatch::Surfaces(_surfaces) => {}
1610 }
1611 }
1612 }
1613
1614 let submission = self
1615 .resources()
1616 .queue
1617 .submit(std::iter::once(encoder.finish()));
1618 Ok(submission)
1619 }
1620
1621 fn write_instances(
1622 &mut self,
1623 scene: &Scene,
1624 instance_offset: &mut u64,
1625 ) -> Result<InstanceBindings> {
1626 Ok(InstanceBindings {
1627 quads: self.write_instance_binding(
1628 "quads_bind_group",
1629 instance_offset,
1630 &scene.quads,
1631 )?,
1632 shadows: self.write_instance_binding(
1633 "shadows_bind_group",
1634 instance_offset,
1635 &scene.shadows,
1636 )?,
1637 underlines: self.write_instance_binding(
1638 "underlines_bind_group",
1639 instance_offset,
1640 &scene.underlines,
1641 )?,
1642 monochrome_sprites: self.write_instance_binding(
1643 "monochrome_sprites_bind_group",
1644 instance_offset,
1645 &scene.monochrome_sprites,
1646 )?,
1647 subpixel_sprites: self.write_instance_binding(
1648 "subpixel_sprites_bind_group",
1649 instance_offset,
1650 &scene.subpixel_sprites,
1651 )?,
1652 polychrome_sprites: self.write_instance_binding(
1653 "polychrome_sprites_bind_group",
1654 instance_offset,
1655 &scene.polychrome_sprites,
1656 )?,
1657 })
1658 }
1659
1660 fn create_texture_bind_group(
1661 &self,
1662 label: &str,
1663 texture_view: &wgpu::TextureView,
1664 ) -> wgpu::BindGroup {
1665 let resources = self.resources();
1666 resources
1667 .device
1668 .create_bind_group(&wgpu::BindGroupDescriptor {
1669 label: Some(label),
1670 layout: &resources.bind_group_layouts.texture,
1671 entries: &[
1672 wgpu::BindGroupEntry {
1673 binding: 0,
1674 resource: wgpu::BindingResource::TextureView(texture_view),
1675 },
1676 wgpu::BindGroupEntry {
1677 binding: 1,
1678 resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
1679 },
1680 ],
1681 })
1682 }
1683
1684 fn prepare_texture_bind_groups(&mut self, scene: &Scene) {
1685 let mut texture_ids = SmallVec::<[AtlasTextureId; 8]>::new();
1686 for batch in scene.batches() {
1687 let texture_id = match batch {
1688 PrimitiveBatch::MonochromeSprites { texture_id, .. }
1689 | PrimitiveBatch::SubpixelSprites { texture_id, .. }
1690 | PrimitiveBatch::PolychromeSprites { texture_id, .. } => texture_id,
1691 _ => continue,
1692 };
1693 if !texture_ids.contains(&texture_id) {
1694 texture_ids.push(texture_id);
1695 }
1696 }
1697
1698 self.resources_mut()
1699 .atlas_texture_bind_groups
1700 .retain(|texture_id, _| texture_ids.contains(texture_id));
1701
1702 for texture_id in texture_ids {
1703 let Some(texture_info) = self.atlas.get_texture_info(texture_id) else {
1704 self.resources_mut()
1705 .atlas_texture_bind_groups
1706 .remove(&texture_id);
1707 continue;
1708 };
1709 let is_current = self
1710 .resources()
1711 .atlas_texture_bind_groups
1712 .get(&texture_id)
1713 .is_some_and(|cached| cached.texture_generation == texture_info.generation);
1714 if is_current {
1715 continue;
1716 }
1717
1718 let bind_group =
1719 self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
1720 self.resources_mut().atlas_texture_bind_groups.insert(
1721 texture_id,
1722 CachedTextureBindGroup {
1723 texture_generation: texture_info.generation,
1724 bind_group,
1725 },
1726 );
1727 }
1728 }
1729
1730 fn draw_instances(
1731 &self,
1732 instances: &InstanceBinding,
1733 pipeline: &wgpu::RenderPipeline,
1734 range: Range<u32>,
1735 pass: &mut wgpu::RenderPass<'_>,
1736 ) {
1737 if range.is_empty() {
1738 return;
1739 }
1740 pass.set_pipeline(pipeline);
1741 pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
1742 pass.set_bind_group(1, &instances.bind_group, &[]);
1743 pass.draw(
1744 0..4,
1745 instances.first_instance + range.start..instances.first_instance + range.end,
1746 );
1747 }
1748
1749 fn draw_sprites(
1750 &self,
1751 sprite_instances: &InstanceBinding,
1752 texture_id: AtlasTextureId,
1753 pipeline: &wgpu::RenderPipeline,
1754 range: Range<u32>,
1755 pass: &mut wgpu::RenderPass<'_>,
1756 ) -> Result<()> {
1757 if range.is_empty() {
1758 return Ok(());
1759 }
1760 let resources = self.resources();
1761 let Some(texture) = resources.atlas_texture_bind_groups.get(&texture_id) else {
1764 return Ok(());
1765 };
1766 pass.set_pipeline(pipeline);
1767 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1768 pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
1769 pass.set_bind_group(2, &texture.bind_group, &[]);
1770 pass.draw(
1771 0..4,
1772 sprite_instances.first_instance + range.start
1773 ..sprite_instances.first_instance + range.end,
1774 );
1775 Ok(())
1776 }
1777
1778 unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
1779 unsafe {
1780 std::slice::from_raw_parts(
1781 instances.as_ptr() as *const u8,
1782 std::mem::size_of_val(instances),
1783 )
1784 }
1785 }
1786
1787 fn draw_paths_from_intermediate(
1788 &mut self,
1789 paths: &[Path<ScaledPixels>],
1790 instance_offset: &mut u64,
1791 pass: &mut wgpu::RenderPass<'_>,
1792 ) -> Result<()> {
1793 let first_path = &paths[0];
1794 let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
1795 {
1796 paths
1797 .iter()
1798 .map(|p| PathSprite {
1799 bounds: p.clipped_bounds(),
1800 })
1801 .collect()
1802 } else {
1803 let mut bounds = first_path.clipped_bounds();
1804 for path in paths.iter().skip(1) {
1805 bounds = bounds.union(&path.clipped_bounds());
1806 }
1807 vec![PathSprite { bounds }]
1808 };
1809
1810 let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
1811 return Ok(());
1812 };
1813 let instances =
1814 self.write_instance_binding("path_sprites_bind_group", instance_offset, &sprites)?;
1815 let texture = self.create_texture_bind_group(
1816 "path_intermediate_texture_bind_group",
1817 &path_intermediate_view,
1818 );
1819 let resources = self.resources();
1820 pass.set_pipeline(&resources.pipelines.paths);
1821 pass.set_bind_group(0, &resources.globals_bind_group, &[]);
1822 pass.set_bind_group(1, &instances.bind_group, &[]);
1823 pass.set_bind_group(2, &texture, &[]);
1824 pass.draw(
1825 0..4,
1826 instances.first_instance..instances.first_instance + sprites.len() as u32,
1827 );
1828 Ok(())
1829 }
1830
1831 fn draw_paths_to_intermediate(
1832 &mut self,
1833 encoder: &mut wgpu::CommandEncoder,
1834 paths: &[Path<ScaledPixels>],
1835 instance_offset: &mut u64,
1836 ) -> Result<bool> {
1837 let mut vertices = Vec::new();
1838 for path in paths {
1839 let bounds = path.clipped_bounds();
1840 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
1841 xy_position: v.xy_position,
1842 st_position: v.st_position,
1843 color: path.color,
1844 bounds,
1845 }));
1846 }
1847
1848 if vertices.is_empty() {
1849 return Ok(false);
1850 }
1851
1852 let vertex_binding = self.write_instance_binding(
1853 "path_rasterization_bind_group",
1854 instance_offset,
1855 &vertices,
1856 )?;
1857
1858 let resources = self.resources();
1859 let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
1860 return Ok(false);
1861 };
1862
1863 let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
1864 (msaa_view, Some(path_intermediate_view))
1865 } else {
1866 (path_intermediate_view, None)
1867 };
1868
1869 {
1870 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1871 label: Some("path_rasterization_pass"),
1872 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1873 view: target_view,
1874 resolve_target,
1875 ops: wgpu::Operations {
1876 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1877 store: wgpu::StoreOp::Store,
1878 },
1879 depth_slice: None,
1880 })],
1881 depth_stencil_attachment: None,
1882 ..Default::default()
1883 });
1884
1885 pass.set_pipeline(&resources.pipelines.path_rasterization);
1886 pass.set_bind_group(0, &resources.path_globals_bind_group, &[]);
1887 pass.set_bind_group(1, &vertex_binding.bind_group, &[]);
1888 pass.draw(
1892 vertex_binding.first_instance
1893 ..vertex_binding.first_instance + vertices.len() as u32,
1894 0..1,
1895 );
1896 }
1897
1898 Ok(true)
1899 }
1900
1901 fn write_instance_binding<T>(
1902 &mut self,
1903 label: &str,
1904 instance_offset: &mut u64,
1905 instances: &[T],
1906 ) -> Result<InstanceBinding> {
1907 let data = unsafe { Self::instance_bytes(instances) };
1908 let size = (data.len() as u64).max(16);
1911 let stride = (std::mem::size_of::<T>() as u64).max(1);
1912 let (alignment, allocation_size) = if self.uses_webgl_instance_data {
1913 (
1919 least_common_multiple(self.instance_data_alignment, stride),
1920 size.next_multiple_of(INSTANCE_TEXTURE_TEXEL_SIZE),
1921 )
1922 } else {
1923 (self.instance_data_alignment.max(1), size)
1924 };
1925 let mut offset = (*instance_offset).next_multiple_of(alignment);
1926 if offset + allocation_size > self.instance_data_capacity {
1927 self.grow_instance_data(allocation_size)?;
1928 offset = 0;
1929 }
1930 *instance_offset = offset + allocation_size;
1931
1932 let first_instance = if self.uses_webgl_instance_data {
1933 u32::try_from(offset / stride).context("instance index exceeds u32 range")?
1934 } else {
1935 0
1936 };
1937
1938 let resources = self.resources();
1939 if !data.is_empty() {
1940 match &resources.instance_data {
1941 InstanceData::Storage(buffer) => resources.queue.write_buffer(buffer, offset, data),
1942 InstanceData::Texture { .. } => {
1943 Self::write_instance_texture(resources, offset, data)
1944 }
1945 }
1946 }
1947 let bind_group = resources
1948 .device
1949 .create_bind_group(&wgpu::BindGroupDescriptor {
1950 label: Some(label),
1951 layout: &resources.bind_group_layouts.instances,
1952 entries: &[wgpu::BindGroupEntry {
1953 binding: 0,
1954 resource: match &resources.instance_data {
1955 InstanceData::Storage(buffer) => {
1956 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1957 buffer,
1958 offset,
1959 size: NonZeroU64::new(size),
1960 })
1961 }
1962 InstanceData::Texture { view, .. } => {
1963 wgpu::BindingResource::TextureView(view)
1964 }
1965 },
1966 }],
1967 });
1968 Ok(InstanceBinding {
1969 bind_group,
1970 first_instance,
1971 })
1972 }
1973
1974 fn write_instance_texture(resources: &WgpuResources, offset: u64, data: &[u8]) {
1975 let InstanceData::Texture {
1976 texture,
1977 width,
1978 height,
1979 ..
1980 } = &resources.instance_data
1981 else {
1982 return;
1983 };
1984 let mut byte_offset = 0usize;
1985 let mut texel_offset = offset / INSTANCE_TEXTURE_TEXEL_SIZE;
1986 while byte_offset < data.len() {
1987 let x = (texel_offset % u64::from(*width)) as u32;
1988 let y = (texel_offset / u64::from(*width)) as u32;
1989 if y >= *height {
1990 debug_assert!(
1994 false,
1995 "instance texture write out of bounds: row {y} >= height {}",
1996 *height
1997 );
1998 log::error!(
1999 "instance texture write out of bounds; dropping {} bytes of instance data",
2000 data.len() - byte_offset
2001 );
2002 return;
2003 }
2004 let available_texels = u64::from(*width - x);
2005 let remaining_bytes = data.len() - byte_offset;
2006 let complete_texels = remaining_bytes as u64 / INSTANCE_TEXTURE_TEXEL_SIZE;
2007 let texels = complete_texels.min(available_texels);
2008 if texels > 0 {
2009 let byte_count = (texels * INSTANCE_TEXTURE_TEXEL_SIZE) as usize;
2010 resources.queue.write_texture(
2011 wgpu::TexelCopyTextureInfo {
2012 texture,
2013 mip_level: 0,
2014 origin: wgpu::Origin3d { x, y, z: 0 },
2015 aspect: wgpu::TextureAspect::All,
2016 },
2017 &data[byte_offset..byte_offset + byte_count],
2018 wgpu::TexelCopyBufferLayout {
2019 offset: 0,
2020 bytes_per_row: Some(byte_count as u32),
2021 rows_per_image: None,
2022 },
2023 wgpu::Extent3d {
2024 width: texels as u32,
2025 height: 1,
2026 depth_or_array_layers: 1,
2027 },
2028 );
2029 byte_offset += byte_count;
2030 texel_offset += texels;
2031 continue;
2032 }
2033
2034 let mut final_texel = [0; INSTANCE_TEXTURE_TEXEL_SIZE as usize];
2035 final_texel[..remaining_bytes].copy_from_slice(&data[byte_offset..]);
2036 resources.queue.write_texture(
2037 wgpu::TexelCopyTextureInfo {
2038 texture,
2039 mip_level: 0,
2040 origin: wgpu::Origin3d { x, y, z: 0 },
2041 aspect: wgpu::TextureAspect::All,
2042 },
2043 &final_texel,
2044 wgpu::TexelCopyBufferLayout {
2045 offset: 0,
2046 bytes_per_row: Some(INSTANCE_TEXTURE_TEXEL_SIZE as u32),
2047 rows_per_image: None,
2048 },
2049 wgpu::Extent3d {
2050 width: 1,
2051 height: 1,
2052 depth_or_array_layers: 1,
2053 },
2054 );
2055 break;
2056 }
2057 }
2058
2059 fn grow_instance_data(&mut self, required: u64) -> Result<()> {
2060 let capacity = (self.instance_data_capacity * 2)
2061 .max(required.next_power_of_two())
2062 .min(self.max_instance_data_size);
2063 anyhow::ensure!(
2064 capacity >= required,
2065 "instance data needs {required} bytes, above the maximum of {}",
2066 self.max_instance_data_size
2067 );
2068 anyhow::ensure!(
2069 capacity > self.instance_data_capacity,
2070 "frame instance data exceeds the {}-byte maximum",
2071 self.max_instance_data_size
2072 );
2073 log::debug!(
2074 "instance data grown from {} to {capacity}",
2075 self.instance_data_capacity
2076 );
2077 let uses_webgl_instance_data = self.uses_webgl_instance_data;
2081 let resources = self.resources_mut();
2082 if uses_webgl_instance_data {
2083 let max_texture_dimension = resources.device.limits().max_texture_dimension_2d;
2084 let (instance_data, actual_capacity) =
2085 Self::create_instance_texture(&resources.device, capacity, max_texture_dimension);
2086 resources.instance_data = instance_data;
2087 self.instance_data_capacity = actual_capacity;
2088 } else {
2089 resources.instance_data =
2090 InstanceData::Storage(resources.device.create_buffer(&wgpu::BufferDescriptor {
2091 label: Some("instance_buffer"),
2092 size: capacity,
2093 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2094 mapped_at_creation: false,
2095 }));
2096 self.instance_data_capacity = capacity;
2097 }
2098 Ok(())
2099 }
2100}
2101
2102impl WgpuRenderer {
2103 pub fn unconfigure_surface(&mut self) {
2111 self.state = match std::mem::replace(&mut self.state, RendererState::Released) {
2112 RendererState::Ready { core, surface } => {
2113 drop(surface);
2114 RendererState::Unconfigured { core }
2115 }
2116 state @ (RendererState::Unconfigured { .. } | RendererState::Released) => state,
2117 };
2118 if let Some(core) = self.core_mut() {
2120 core.resources.invalidate_intermediate_textures();
2121 }
2122 }
2123
2124 #[cfg(not(target_family = "wasm"))]
2133 pub fn replace_surface<W: HasWindowHandle>(
2134 &mut self,
2135 window: &W,
2136 config: WgpuSurfaceConfig,
2137 instance: &wgpu::Instance,
2138 ) -> anyhow::Result<()> {
2139 let window_handle = window
2140 .window_handle()
2141 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2142
2143 let surface = create_surface(instance, window_handle.as_raw())?;
2144
2145 let width = (config.size.width.0 as u32).max(1);
2146 let height = (config.size.height.0 as u32).max(1);
2147
2148 let alpha_mode = if config.transparent {
2149 self.transparent_alpha_mode
2150 } else {
2151 self.opaque_alpha_mode
2152 };
2153
2154 self.surface_config.width = width;
2155 self.surface_config.height = height;
2156 self.surface_config.alpha_mode = alpha_mode;
2157 if let Some(mode) = config.preferred_present_mode {
2158 self.surface_config.present_mode = mode;
2159 }
2160
2161 let mut core = match std::mem::replace(&mut self.state, RendererState::Released) {
2162 RendererState::Ready {
2163 core,
2164 surface: old_surface,
2165 } => {
2166 drop(old_surface);
2167 core
2168 }
2169 RendererState::Unconfigured { core } => core,
2170 RendererState::Released => {
2171 anyhow::bail!("Cannot replace the surface: GPU resources have been released")
2172 }
2173 };
2174 surface.configure(&core.resources.device, &self.surface_config);
2175 core.resources.invalidate_intermediate_textures();
2176 self.state = RendererState::Ready { surface, core };
2177
2178 Ok(())
2179 }
2180
2181 pub fn destroy(&mut self) {
2182 self.state = RendererState::Released;
2185 }
2186
2187 pub fn device_lost(&self) -> bool {
2189 self.device_errors.device_lost()
2190 }
2191
2192 pub fn needs_redraw(&mut self) -> bool {
2195 std::mem::take(&mut self.needs_redraw)
2196 }
2197
2198 #[cfg(not(target_family = "wasm"))]
2206 pub fn recover<W>(&mut self, window: &W) -> anyhow::Result<()>
2207 where
2208 W: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync + Clone + 'static,
2209 {
2210 let gpu_context = self.context.as_ref().expect("recover requires gpu_context");
2211
2212 let needs_new_context = gpu_context
2214 .borrow()
2215 .as_ref()
2216 .is_none_or(|ctx| ctx.device_lost());
2217
2218 let window_handle = window
2219 .window_handle()
2220 .map_err(|e| anyhow::anyhow!("Failed to get window handle: {e}"))?;
2221
2222 let surface = if needs_new_context {
2223 log::warn!("GPU device lost, recreating context...");
2224
2225 self.state = RendererState::Released;
2227 *gpu_context.borrow_mut() = None;
2228
2229 std::thread::sleep(std::time::Duration::from_millis(350));
2234
2235 let instance = WgpuContext::instance(Some(Box::new(window.clone())));
2236 let surface = create_surface(&instance, window_handle.as_raw())?;
2237 let new_context =
2238 WgpuContext::new_rejecting_software(instance, &surface, self.compositor_gpu)?;
2239 *gpu_context.borrow_mut() = Some(new_context);
2240 surface
2241 } else {
2242 let ctx_ref = gpu_context.borrow();
2243 let instance = &ctx_ref.as_ref().unwrap().instance;
2244 create_surface(instance, window_handle.as_raw())?
2245 };
2246
2247 let config = WgpuSurfaceConfig {
2248 size: gpui::Size {
2249 width: gpui::DevicePixels(self.surface_config.width as i32),
2250 height: gpui::DevicePixels(self.surface_config.height as i32),
2251 },
2252 transparent: self.surface_config.alpha_mode != wgpu::CompositeAlphaMode::Opaque,
2253 preferred_present_mode: Some(self.surface_config.present_mode),
2254 };
2255 let gpu_context = Rc::clone(gpu_context);
2256 let ctx_ref = gpu_context.borrow();
2257 let context = ctx_ref.as_ref().expect("context should exist");
2258
2259 self.state = RendererState::Released;
2260 self.atlas.handle_device_lost(context);
2261
2262 let is_bgr = self.is_bgr;
2263 *self = Self::new_internal(
2264 Some(gpu_context.clone()),
2265 context,
2266 surface,
2267 config,
2268 self.compositor_gpu,
2269 self.atlas.clone(),
2270 )?;
2271 self.set_subpixel_layout(is_bgr);
2272
2273 log::info!("GPU recovery complete");
2274 Ok(())
2275 }
2276}
2277
2278#[cfg(all(
2279 not(target_family = "wasm"),
2280 any(test, feature = "bench-support", feature = "test-support")
2281))]
2282struct HeadlessRenderTarget {
2283 texture: wgpu::Texture,
2284 view: wgpu::TextureView,
2285}
2286
2287#[cfg(all(
2288 not(target_family = "wasm"),
2289 any(test, feature = "bench-support", feature = "test-support")
2290))]
2291impl HeadlessRenderTarget {
2292 fn size(&self) -> Size<DevicePixels> {
2293 Size {
2294 width: DevicePixels(self.texture.width() as i32),
2295 height: DevicePixels(self.texture.height() as i32),
2296 }
2297 }
2298}
2299
2300#[cfg(all(
2301 not(target_family = "wasm"),
2302 any(test, feature = "bench-support", feature = "test-support")
2303))]
2304pub struct WgpuHeadlessRenderer {
2305 context: WgpuContext,
2306 core: WgpuRendererCore,
2307 render_target: Option<HeadlessRenderTarget>,
2308 observed_error_generation: u64,
2309}
2310
2311#[cfg(all(
2312 not(target_family = "wasm"),
2313 any(test, feature = "bench-support", feature = "test-support")
2314))]
2315impl WgpuHeadlessRenderer {
2316 pub fn new() -> anyhow::Result<Self> {
2317 let (context, target_format) = WgpuContext::new_headless()?;
2318 let observed_error_generation = context.errors().current_generation();
2321 let atlas = Arc::new(WgpuAtlas::from_context(&context));
2322 let core = WgpuRendererCore::new(
2323 &context,
2324 atlas,
2325 target_format,
2326 wgpu::CompositeAlphaMode::Opaque,
2327 );
2328
2329 Ok(Self {
2330 context,
2331 core,
2332 render_target: None,
2333 observed_error_generation,
2334 })
2335 }
2336
2337 fn ensure_render_target(&mut self, size: Size<DevicePixels>) -> anyhow::Result<()> {
2338 anyhow::ensure!(
2339 size.width.0 > 0 && size.height.0 > 0,
2340 "invalid headless render target size: {size:?}"
2341 );
2342 anyhow::ensure!(
2343 size.width.0 as u32 <= self.core.max_texture_size
2344 && size.height.0 as u32 <= self.core.max_texture_size,
2345 "headless render target size {size:?} exceeds maximum texture dimension {}",
2346 self.core.max_texture_size
2347 );
2348 if self
2349 .render_target
2350 .as_ref()
2351 .is_some_and(|target| target.size() == size)
2352 {
2353 return Ok(());
2354 }
2355
2356 let texture = self
2357 .core
2358 .resources
2359 .device
2360 .create_texture(&wgpu::TextureDescriptor {
2361 label: Some("headless_render_target"),
2362 size: wgpu::Extent3d {
2363 width: size.width.0 as u32,
2364 height: size.height.0 as u32,
2365 depth_or_array_layers: 1,
2366 },
2367 mip_level_count: 1,
2368 sample_count: 1,
2369 dimension: wgpu::TextureDimension::D2,
2370 format: self.core.target_format,
2371 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2372 view_formats: &[],
2373 });
2374 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2375 self.render_target = Some(HeadlessRenderTarget { texture, view });
2376 Ok(())
2377 }
2378
2379 fn check_gpu_errors(&mut self) -> anyhow::Result<()> {
2380 anyhow::ensure!(
2381 !self.context.device_lost(),
2382 "GPU device was lost during headless rendering"
2383 );
2384 if let Some(error) = self
2385 .context
2386 .errors()
2387 .observe_error(&mut self.observed_error_generation)
2388 {
2389 anyhow::bail!("GPU error during headless rendering: {error}");
2390 }
2391 Ok(())
2392 }
2393
2394 fn render(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2395 self.check_gpu_errors()?;
2396 self.ensure_render_target(size)?;
2397 let view = self
2398 .render_target
2399 .as_ref()
2400 .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?
2401 .view
2402 .clone();
2403 self.core
2404 .render_frame(scene, &view, size, false, wgpu::Color::BLACK)?;
2405 Ok(())
2406 }
2407
2408 fn read_image(&mut self) -> anyhow::Result<image::RgbaImage> {
2411 let target = self
2412 .render_target
2413 .as_ref()
2414 .ok_or_else(|| anyhow::anyhow!("Headless render target was not created"))?;
2415 let width = target.texture.width();
2416 let height = target.texture.height();
2417 let bytes_per_row = width
2418 .checked_mul(4)
2419 .ok_or_else(|| anyhow::anyhow!("Headless render target row size overflowed"))?;
2420 let padded_bytes_per_row = bytes_per_row
2421 .checked_next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
2422 .ok_or_else(|| anyhow::anyhow!("Headless padded row size overflowed"))?;
2423 let buffer_size = u64::from(padded_bytes_per_row)
2424 .checked_mul(u64::from(height))
2425 .ok_or_else(|| anyhow::anyhow!("Headless readback buffer size overflowed"))?;
2426 anyhow::ensure!(
2427 buffer_size <= self.core.resources.device.limits().max_buffer_size,
2428 "Headless readback buffer size {buffer_size} exceeds maximum buffer size {}",
2429 self.core.resources.device.limits().max_buffer_size
2430 );
2431 let readback_buffer = self
2432 .core
2433 .resources
2434 .device
2435 .create_buffer(&wgpu::BufferDescriptor {
2436 label: Some("headless_readback_buffer"),
2437 size: buffer_size,
2438 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2439 mapped_at_creation: false,
2440 });
2441 let mut encoder =
2442 self.core
2443 .resources
2444 .device
2445 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2446 label: Some("headless_readback_encoder"),
2447 });
2448 encoder.copy_texture_to_buffer(
2449 wgpu::TexelCopyTextureInfo {
2450 texture: &target.texture,
2451 mip_level: 0,
2452 origin: wgpu::Origin3d::ZERO,
2453 aspect: wgpu::TextureAspect::All,
2454 },
2455 wgpu::TexelCopyBufferInfo {
2456 buffer: &readback_buffer,
2457 layout: wgpu::TexelCopyBufferLayout {
2458 offset: 0,
2459 bytes_per_row: Some(padded_bytes_per_row),
2460 rows_per_image: Some(height),
2461 },
2462 },
2463 wgpu::Extent3d {
2464 width,
2465 height,
2466 depth_or_array_layers: 1,
2467 },
2468 );
2469 let submission = self
2470 .core
2471 .resources
2472 .queue
2473 .submit(std::iter::once(encoder.finish()));
2474 let (sender, receiver) = std::sync::mpsc::channel();
2475 readback_buffer
2476 .slice(..)
2477 .map_async(wgpu::MapMode::Read, move |result| {
2478 if sender.send(result).is_err() {
2479 log::error!("Headless readback receiver was dropped before mapping completed");
2480 }
2481 });
2482 self.core
2483 .resources
2484 .device
2485 .poll(wgpu::PollType::Wait {
2486 submission_index: Some(submission),
2487 timeout: Some(std::time::Duration::from_secs(30)),
2488 })
2489 .map_err(|error| anyhow::anyhow!("Failed to wait for headless rendering: {error}"))?;
2490 receiver
2491 .recv_timeout(std::time::Duration::from_secs(30))
2492 .map_err(|error| anyhow::anyhow!("Failed to receive headless mapping result: {error}"))?
2493 .map_err(|error| anyhow::anyhow!("Failed to map headless readback buffer: {error}"))?;
2494 self.check_gpu_errors()?;
2495
2496 let mapped_data = readback_buffer.slice(..).get_mapped_range()?;
2497 let pixel_capacity = usize::try_from(u64::from(bytes_per_row) * u64::from(height))
2498 .map_err(|_| anyhow::anyhow!("Headless image size exceeds addressable memory"))?;
2499 let mut pixels = Vec::with_capacity(pixel_capacity);
2500 for row in mapped_data
2501 .chunks_exact(padded_bytes_per_row as usize)
2502 .take(height as usize)
2503 {
2504 pixels.extend_from_slice(&row[..bytes_per_row as usize]);
2505 }
2506 drop(mapped_data);
2507 readback_buffer.unmap();
2508
2509 if self.core.target_format == wgpu::TextureFormat::Bgra8Unorm {
2510 for pixel in pixels.chunks_exact_mut(4) {
2511 pixel.swap(0, 2);
2512 }
2513 }
2514
2515 image::RgbaImage::from_raw(width, height, pixels)
2516 .ok_or_else(|| anyhow::anyhow!("Failed to create image from headless pixel data"))
2517 }
2518}
2519
2520#[cfg(all(
2521 not(target_family = "wasm"),
2522 any(test, feature = "bench-support", feature = "test-support")
2523))]
2524impl gpui::PlatformHeadlessRenderer for WgpuHeadlessRenderer {
2525 fn render_scene_to_image(
2526 &mut self,
2527 scene: &Scene,
2528 size: Size<DevicePixels>,
2529 ) -> anyhow::Result<image::RgbaImage> {
2530 self.render(scene, size)?;
2531 self.read_image()
2532 }
2533
2534 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> anyhow::Result<()> {
2535 self.render(scene, size)
2536 }
2537
2538 fn sprite_atlas(&self) -> Arc<dyn gpui::PlatformAtlas> {
2539 self.core.atlas.clone()
2540 }
2541}
2542
2543fn instance_range(range: Range<usize>) -> Range<u32> {
2544 range.start as u32..range.end as u32
2545}
2546
2547#[cfg(not(target_family = "wasm"))]
2548fn create_surface(
2549 instance: &wgpu::Instance,
2550 raw_window_handle: raw_window_handle::RawWindowHandle,
2551) -> anyhow::Result<wgpu::Surface<'static>> {
2552 unsafe {
2553 instance
2554 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
2555 raw_display_handle: None,
2557 raw_window_handle,
2558 })
2559 .map_err(|e| anyhow::anyhow!("{e}"))
2560 }
2561}
2562
2563struct RenderingParameters {
2564 path_sample_count: u32,
2565 gamma_ratios: [f32; 4],
2566 grayscale_enhanced_contrast: f32,
2567 subpixel_enhanced_contrast: f32,
2568}
2569
2570impl RenderingParameters {
2571 fn new(adapter: &wgpu::Adapter, surface_format: wgpu::TextureFormat) -> Self {
2572 use std::env;
2573
2574 let format_features = adapter.get_texture_format_features(surface_format);
2575 let path_sample_count = [4, 2, 1]
2576 .into_iter()
2577 .find(|&n| format_features.flags.sample_count_supported(n))
2578 .unwrap_or(1);
2579
2580 let gamma = env::var("ZED_FONTS_GAMMA")
2581 .ok()
2582 .and_then(|v| v.parse().ok())
2583 .unwrap_or(1.8_f32)
2584 .clamp(1.0, 2.2);
2585 let gamma_ratios = get_gamma_correction_ratios(gamma);
2586
2587 let grayscale_enhanced_contrast = env::var("ZED_FONTS_GRAYSCALE_ENHANCED_CONTRAST")
2588 .ok()
2589 .and_then(|v| v.parse().ok())
2590 .unwrap_or(1.0_f32)
2591 .max(0.0);
2592
2593 let subpixel_enhanced_contrast = env::var("ZED_FONTS_SUBPIXEL_ENHANCED_CONTRAST")
2594 .ok()
2595 .and_then(|v| v.parse().ok())
2596 .unwrap_or(0.5_f32)
2597 .max(0.0);
2598
2599 Self {
2600 path_sample_count,
2601 gamma_ratios,
2602 grayscale_enhanced_contrast,
2603 subpixel_enhanced_contrast,
2604 }
2605 }
2606}
2607
2608#[cfg(test)]
2609mod tests {
2610 use super::*;
2611 use gpui::{
2612 BorderStyle, ColorSpace, ContentMask, Corners, Edges, Hsla, MonochromeSprite,
2613 PolychromeSprite, Quad, Shadow, Size, SubpixelSprite, Underline, linear_color_stop,
2614 linear_gradient,
2615 };
2616 #[cfg(target_os = "linux")]
2617 use gpui::{DevicePixels, PlatformHeadlessRenderer, Scene};
2618
2619 #[cfg(target_os = "linux")]
2620 fn device_size(width: i32, height: i32) -> Size<DevicePixels> {
2621 Size {
2622 width: DevicePixels(width),
2623 height: DevicePixels(height),
2624 }
2625 }
2626
2627 #[cfg(target_os = "linux")]
2628 fn solid_quad(x: f32, y: f32, width: f32, height: f32, color: Hsla) -> Quad {
2629 let bounds = Bounds {
2630 origin: Point {
2631 x: x.into(),
2632 y: y.into(),
2633 },
2634 size: Size {
2635 width: width.into(),
2636 height: height.into(),
2637 },
2638 };
2639 Quad {
2640 order: 0,
2641 border_style: BorderStyle::Solid,
2642 bounds,
2643 content_mask: ContentMask { bounds },
2644 background: color.into(),
2645 border_color: color,
2646 corner_radii: Corners::default(),
2647 border_widths: Edges::default(),
2648 }
2649 }
2650
2651 #[cfg(target_os = "linux")]
2654 fn assert_pixel(image: &image::RgbaImage, x: u32, y: u32, expected: [u8; 4]) {
2655 let actual = image.get_pixel(x, y).0;
2656 assert!(
2657 actual
2658 .iter()
2659 .zip(expected)
2660 .all(|(actual, expected)| actual.abs_diff(expected) <= 3),
2661 "pixel ({x}, {y}) was {actual:?}, expected {expected:?}"
2662 );
2663 }
2664
2665 #[cfg(target_os = "linux")]
2666 const RED: [u8; 4] = [255, 0, 0, 255];
2667 #[cfg(target_os = "linux")]
2668 const BLUE: [u8; 4] = [0, 0, 255, 255];
2669 #[cfg(target_os = "linux")]
2670 const BLACK: [u8; 4] = [0, 0, 0, 255];
2671
2672 #[cfg(target_os = "linux")]
2673 #[test]
2674 fn headless_renderer_draws_quads_with_distinct_colors() -> anyhow::Result<()> {
2675 let mut renderer = WgpuHeadlessRenderer::new()?;
2676 let mut scene = Scene::default();
2677 scene.insert_primitive(solid_quad(0.0, 0.0, 32.0, 32.0, gpui::red()));
2678 scene.insert_primitive(solid_quad(32.0, 0.0, 32.0, 32.0, gpui::blue()));
2679 scene.finish();
2680
2681 let image = renderer.render_scene_to_image(&scene, device_size(64, 32))?;
2682 assert_eq!(image.dimensions(), (64, 32));
2683 assert_pixel(&image, 8, 16, RED);
2684 assert_pixel(&image, 24, 16, RED);
2685 assert_pixel(&image, 40, 16, BLUE);
2686 assert_pixel(&image, 56, 16, BLUE);
2687 Ok(())
2688 }
2689
2690 #[cfg(target_os = "linux")]
2691 #[test]
2692 fn headless_renderer_captures_each_requested_size() -> anyhow::Result<()> {
2693 let mut renderer = WgpuHeadlessRenderer::new()?;
2694 let mut scene = Scene::default();
2695 scene.insert_primitive(solid_quad(2.0, 2.0, 4.0, 3.0, gpui::red()));
2696 scene.finish();
2697
2698 let image = renderer.render_scene_to_image(&scene, device_size(13, 7))?;
2700 assert_eq!(image.dimensions(), (13, 7));
2701 assert_pixel(&image, 0, 0, BLACK);
2702 assert_pixel(&image, 3, 3, RED);
2703 assert_pixel(&image, 12, 6, BLACK);
2704
2705 let image = renderer.render_scene_to_image(&scene, device_size(17, 9))?;
2706 assert_eq!(image.dimensions(), (17, 9));
2707 assert_pixel(&image, 3, 3, RED);
2708 assert_pixel(&image, 16, 8, BLACK);
2709
2710 let image = renderer.render_scene_to_image(&Scene::default(), device_size(13, 7))?;
2711 assert_eq!(image.dimensions(), (13, 7));
2712 assert!(image.pixels().all(|pixel| pixel.0 == BLACK));
2713 Ok(())
2714 }
2715
2716 #[cfg(target_os = "linux")]
2717 #[test]
2718 fn headless_renderer_reuses_target_for_same_size() -> anyhow::Result<()> {
2719 let mut renderer = WgpuHeadlessRenderer::new()?;
2720 let target_texture = |renderer: &WgpuHeadlessRenderer| {
2721 renderer
2722 .render_target
2723 .as_ref()
2724 .map(|target| target.texture.clone())
2725 };
2726
2727 renderer.render_scene(&Scene::default(), device_size(16, 16))?;
2728 let first = target_texture(&renderer);
2729 assert!(first.is_some());
2730
2731 renderer.render_scene(&Scene::default(), device_size(16, 16))?;
2732 assert_eq!(target_texture(&renderer), first);
2733
2734 renderer.render_scene(&Scene::default(), device_size(16, 17))?;
2735 assert_ne!(target_texture(&renderer), first);
2736 Ok(())
2737 }
2738
2739 #[cfg(target_os = "linux")]
2740 #[test]
2741 fn headless_renderer_rejects_invalid_sizes() -> anyhow::Result<()> {
2742 let mut renderer = WgpuHeadlessRenderer::new()?;
2743 let too_large = renderer.core.max_texture_size as i32 + 1;
2744
2745 for size in [
2746 device_size(0, 8),
2747 device_size(8, 0),
2748 device_size(-1, 8),
2749 device_size(too_large, 8),
2750 ] {
2751 assert!(
2752 renderer.render_scene(&Scene::default(), size).is_err(),
2753 "{size:?} should be rejected"
2754 );
2755 assert!(
2756 renderer
2757 .render_scene_to_image(&Scene::default(), size)
2758 .is_err(),
2759 "{size:?} should be rejected"
2760 );
2761 }
2762
2763 let image = renderer.render_scene_to_image(&Scene::default(), device_size(4, 4))?;
2765 assert_eq!(image.dimensions(), (4, 4));
2766 Ok(())
2767 }
2768
2769 #[test]
2770 fn webgl_shader_is_valid_wgsl_without_storage_buffers() {
2771 assert!(!WEBGL_SHADERS.contains("var<storage"));
2772 validate_wgsl(WEBGL_SHADERS, naga::valid::Capabilities::empty());
2773 }
2774
2775 #[test]
2776 fn storage_buffer_shader_is_valid_wgsl() {
2777 validate_wgsl(STORAGE_BUFFER_SHADERS, naga::valid::Capabilities::empty());
2778 }
2779
2780 #[test]
2781 fn subpixel_shader_is_valid_wgsl() {
2782 validate_wgsl(
2783 SUBPIXEL_SHADERS,
2784 naga::valid::Capabilities::DUAL_SOURCE_BLENDING,
2785 );
2786 }
2787
2788 fn validate_wgsl(source: &str, capabilities: naga::valid::Capabilities) {
2789 let module = naga::front::wgsl::parse_str(source).expect("shader should parse");
2790 naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities)
2791 .validate(&module)
2792 .expect("shader should validate");
2793 }
2794
2795 #[test]
2796 fn webgl_record_sizes_match_shader_word_strides() {
2797 assert_eq!(std::mem::size_of::<Quad>(), 40 * 4);
2798 assert_eq!(std::mem::size_of::<Shadow>(), 28 * 4);
2799 assert_eq!(std::mem::size_of::<PathRasterizationVertex>(), 26 * 4);
2800 assert_eq!(std::mem::size_of::<PathSprite>(), 4 * 4);
2801 assert_eq!(std::mem::size_of::<Underline>(), 16 * 4);
2802 assert_eq!(std::mem::size_of::<MonochromeSprite>(), 28 * 4);
2803 assert_eq!(std::mem::size_of::<SubpixelSprite>(), 28 * 4);
2804 assert_eq!(std::mem::size_of::<PolychromeSprite>(), 24 * 4);
2805 }
2806
2807 #[test]
2808 fn webgl_quad_layout_matches_fixed_decoder() {
2809 let quad = Quad {
2810 order: 41,
2811 border_style: BorderStyle::Dashed,
2812 bounds: Bounds {
2813 origin: Point {
2814 x: 1.0.into(),
2815 y: 2.0.into(),
2816 },
2817 size: Size {
2818 width: 3.0.into(),
2819 height: 4.0.into(),
2820 },
2821 },
2822 content_mask: ContentMask {
2823 bounds: Bounds {
2824 origin: Point {
2825 x: 5.0.into(),
2826 y: 6.0.into(),
2827 },
2828 size: Size {
2829 width: 7.0.into(),
2830 height: 8.0.into(),
2831 },
2832 },
2833 },
2834 background: linear_gradient(
2835 11.0,
2836 linear_color_stop(
2837 Hsla {
2838 h: 12.0,
2839 s: 13.0,
2840 l: 14.0,
2841 a: 15.0,
2842 },
2843 16.0,
2844 ),
2845 linear_color_stop(
2846 Hsla {
2847 h: 17.0,
2848 s: 18.0,
2849 l: 19.0,
2850 a: 20.0,
2851 },
2852 21.0,
2853 ),
2854 )
2855 .color_space(ColorSpace::Oklab),
2856 border_color: Hsla {
2857 h: 22.0,
2858 s: 23.0,
2859 l: 24.0,
2860 a: 25.0,
2861 },
2862 corner_radii: Corners {
2863 top_left: 26.0.into(),
2864 top_right: 27.0.into(),
2865 bottom_right: 28.0.into(),
2866 bottom_left: 29.0.into(),
2867 },
2868 border_widths: Edges {
2869 top: 30.0.into(),
2870 right: 31.0.into(),
2871 bottom: 32.0.into(),
2872 left: 33.0.into(),
2873 },
2874 };
2875
2876 let bytes = unsafe { WgpuRendererCore::instance_bytes(std::slice::from_ref(&quad)) };
2877 let words: &[u32] = bytemuck::cast_slice(bytes);
2878 assert_eq!(
2879 words,
2880 &[
2881 41,
2882 1,
2883 1.0_f32.to_bits(),
2884 2.0_f32.to_bits(),
2885 3.0_f32.to_bits(),
2886 4.0_f32.to_bits(),
2887 5.0_f32.to_bits(),
2888 6.0_f32.to_bits(),
2889 7.0_f32.to_bits(),
2890 8.0_f32.to_bits(),
2891 1,
2892 1,
2893 0,
2894 0,
2895 0,
2896 0,
2897 11.0_f32.to_bits(),
2898 12.0_f32.to_bits(),
2899 13.0_f32.to_bits(),
2900 14.0_f32.to_bits(),
2901 15.0_f32.to_bits(),
2902 16.0_f32.to_bits(),
2903 17.0_f32.to_bits(),
2904 18.0_f32.to_bits(),
2905 19.0_f32.to_bits(),
2906 20.0_f32.to_bits(),
2907 21.0_f32.to_bits(),
2908 0,
2909 22.0_f32.to_bits(),
2910 23.0_f32.to_bits(),
2911 24.0_f32.to_bits(),
2912 25.0_f32.to_bits(),
2913 26.0_f32.to_bits(),
2914 27.0_f32.to_bits(),
2915 28.0_f32.to_bits(),
2916 29.0_f32.to_bits(),
2917 30.0_f32.to_bits(),
2918 31.0_f32.to_bits(),
2919 32.0_f32.to_bits(),
2920 33.0_f32.to_bits(),
2921 ]
2922 );
2923 }
2924}