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