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