1use std::panic::{catch_unwind, AssertUnwindSafe};
7
8use crate::error::RendererError;
9use crate::render_target::{PhysicalExtent, RenderFrame, RenderTarget};
10use crate::vertices::{
11 BorderRRectVertex, BoxShadowVertex, RRectVertex, RingProgressVertex, StrokeVertex, TexVertex,
12 Vertex,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum SurfaceConfigDeferredReason {
27 Detached,
29 NoFormats,
31 NoPresentModes,
33}
34
35impl SurfaceConfigDeferredReason {
36 pub fn as_str(self) -> &'static str {
49 match self {
50 Self::Detached => "presentation surface is detached",
51 Self::NoFormats => "surface capabilities reported no formats",
52 Self::NoPresentModes => "surface capabilities reported no present modes",
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum SurfaceAttachmentState {
68 Attached,
70 Detached,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[non_exhaustive]
93pub enum SurfaceContextReuseFailure {
94 AdapterUnsupported,
96 CapabilitiesDeferred(SurfaceConfigDeferredReason),
98 FormatUnsupported,
100 ConfigureFailed,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[non_exhaustive]
116pub enum SurfaceReattachOutcome {
117 ReusedGpuContext,
119 RebuiltGpuContext {
121 reason: SurfaceContextReuseFailure,
123 },
124}
125
126#[derive(Debug)]
128struct SurfaceAttachmentSlot<T> {
129 value: Option<T>,
131}
132
133impl<T> SurfaceAttachmentSlot<T> {
134 fn attached(value: T) -> Self {
136 Self { value: Some(value) }
137 }
138
139 fn state(&self) -> SurfaceAttachmentState {
141 if self.value.is_some() {
142 SurfaceAttachmentState::Attached
143 } else {
144 SurfaceAttachmentState::Detached
145 }
146 }
147
148 fn as_ref(&self) -> Option<&T> {
150 self.value.as_ref()
151 }
152
153 fn as_mut(&mut self) -> Option<&mut T> {
155 self.value.as_mut()
156 }
157
158 fn detach(&mut self) -> Option<T> {
160 self.value.take()
161 }
162
163 fn attach(&mut self, value: T) -> Option<T> {
165 self.value.replace(value)
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum ResizeOutcome {
180 Applied,
182 Unchanged,
184 SkippedZero,
186 Deferred(SurfaceConfigDeferredReason),
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192enum SurfaceConfigureMode {
193 Resize,
195 Force,
197}
198
199fn surface_configure_required(
201 current: PhysicalExtent,
202 requested: PhysicalExtent,
203 mode: SurfaceConfigureMode,
204) -> bool {
205 mode == SurfaceConfigureMode::Force || current != requested
206}
207
208#[derive(Debug)]
222pub struct WgpuRenderContext {
223 pub device: wgpu::Device,
225 pub queue: wgpu::Queue,
227 pub config: wgpu::SurfaceConfiguration,
229 pub pipelines: PipelineCache,
231 pub transparent: bool,
233}
234
235impl WgpuRenderContext {
236 pub fn new_with_size(
263 device: wgpu::Device,
264 queue: wgpu::Queue,
265 format: wgpu::TextureFormat,
266 width: u32,
267 height: u32,
268 transparent: bool,
269 ) -> Self {
270 let config = wgpu::SurfaceConfiguration {
271 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
272 format,
273 width: width.max(1),
274 height: height.max(1),
275 present_mode: wgpu::PresentMode::Fifo,
276 alpha_mode: if transparent {
277 wgpu::CompositeAlphaMode::PreMultiplied
278 } else {
279 wgpu::CompositeAlphaMode::Opaque
280 },
281 view_formats: vec![],
282 desired_maximum_frame_latency: 1,
283 };
284 let pipelines = PipelineCache::new(&device, format);
285
286 Self {
287 device,
288 queue,
289 config,
290 pipelines,
291 transparent,
292 }
293 }
294
295 pub fn try_resize(&mut self, new_size: PhysicalExtent) -> ResizeOutcome {
316 if new_size.width == 0 || new_size.height == 0 {
317 return ResizeOutcome::SkippedZero;
318 }
319 if self.config.width == new_size.width && self.config.height == new_size.height {
320 return ResizeOutcome::Unchanged;
321 }
322 self.config.width = new_size.width.max(1);
323 self.config.height = new_size.height.max(1);
324 ResizeOutcome::Applied
325 }
326
327 pub fn pre_present_notify(&self) {}
339
340 pub fn surface_config_deferred_reason(&self) -> Option<SurfaceConfigDeferredReason> {
352 None
353 }
354}
355
356#[derive(Debug, Clone, Copy)]
371pub struct SurfaceBootstrapConfig {
372 pub requested_backends: wgpu::Backends,
374 pub allow_fallback_backends: bool,
376 pub preferred_backends: wgpu::Backends,
378 pub power_preference: wgpu::PowerPreference,
380 pub force_fallback_adapter: bool,
382}
383
384impl Default for SurfaceBootstrapConfig {
385 fn default() -> Self {
386 Self {
387 requested_backends: wgpu::Backends::all(),
388 allow_fallback_backends: true,
389 preferred_backends: wgpu::Backends::PRIMARY,
390 power_preference: wgpu::PowerPreference::HighPerformance,
391 force_fallback_adapter: false,
392 }
393 }
394}
395
396impl SurfaceBootstrapConfig {
397 pub fn with_requested_backends(requested: wgpu::Backends) -> Self {
409 Self {
410 requested_backends: requested,
411 preferred_backends: requested,
412 allow_fallback_backends: false,
413 ..Self::default()
414 }
415 }
416
417 pub fn vulkan_first() -> Self {
429 Self {
430 requested_backends: wgpu::Backends::VULKAN,
431 allow_fallback_backends: true,
432 preferred_backends: wgpu::Backends::VULKAN,
433 ..Self::default()
434 }
435 }
436
437 pub fn vulkan_only() -> Self {
449 Self {
450 requested_backends: wgpu::Backends::VULKAN,
451 allow_fallback_backends: false,
452 preferred_backends: wgpu::Backends::VULKAN,
453 ..Self::default()
454 }
455 }
456
457 fn preferred_backend_matches(&self, backend: wgpu::Backend) -> bool {
459 self.preferred_backends.contains(backend_to_flags(backend))
460 }
461}
462
463fn backend_to_flags(backend: wgpu::Backend) -> wgpu::Backends {
465 match backend {
466 wgpu::Backend::Empty => wgpu::Backends::empty(),
467 wgpu::Backend::Vulkan => wgpu::Backends::VULKAN,
468 wgpu::Backend::Metal => wgpu::Backends::METAL,
469 wgpu::Backend::Dx12 => wgpu::Backends::DX12,
470 wgpu::Backend::Gl => wgpu::Backends::GL,
471 wgpu::Backend::BrowserWebGpu => wgpu::Backends::BROWSER_WEBGPU,
472 }
473}
474
475fn bootstrap_adapter_rank(
477 adapter_info: &wgpu::AdapterInfo,
478 cfg: &SurfaceBootstrapConfig,
479) -> (u8, u8, &'static str) {
480 let device_type_rank = adapter_bootstrap_rank(adapter_info.device_type);
481 let backend_rank = if cfg.preferred_backend_matches(adapter_info.backend) {
482 0
483 } else {
484 1
485 };
486 let backend_name = adapter_info.backend.to_str();
487 (device_type_rank, backend_rank, backend_name)
488}
489
490impl ResizeOutcome {
491 pub fn is_deferred(self) -> bool {
504 matches!(self, Self::Deferred(_))
505 }
506}
507
508#[derive(Debug)]
522pub struct PipelineCache {
523 pub rect: wgpu::RenderPipeline,
525 pub stroke: wgpu::RenderPipeline,
527 pub textured: wgpu::RenderPipeline,
529 pub rounded_rect: wgpu::RenderPipeline,
531 pub border_rounded_rect: wgpu::RenderPipeline,
533 pub box_shadow: wgpu::RenderPipeline,
535 pub ring_progress: wgpu::RenderPipeline,
537 pub rect_passthrough_stencil: wgpu::RenderPipeline,
541 pub stroke_passthrough_stencil: wgpu::RenderPipeline,
543 pub textured_passthrough_stencil: wgpu::RenderPipeline,
545 pub rounded_rect_passthrough_stencil: wgpu::RenderPipeline,
547 pub border_rounded_rect_passthrough_stencil: wgpu::RenderPipeline,
549 pub box_shadow_passthrough_stencil: wgpu::RenderPipeline,
551 pub ring_progress_passthrough_stencil: wgpu::RenderPipeline,
553 pub rounded_rect_stencil_mask: wgpu::RenderPipeline,
555 pub rect_stencil: wgpu::RenderPipeline,
557 pub stroke_stencil: wgpu::RenderPipeline,
559 pub textured_stencil: wgpu::RenderPipeline,
561 pub rounded_rect_stencil: wgpu::RenderPipeline,
563 pub border_rounded_rect_stencil: wgpu::RenderPipeline,
565 pub box_shadow_stencil: wgpu::RenderPipeline,
567 pub ring_progress_stencil: wgpu::RenderPipeline,
569 pub rounded_rect_stencil_edge: wgpu::RenderPipeline,
571 pub clip_bind_group_layout: wgpu::BindGroupLayout,
573 pub texture_bind_group_layout: wgpu::BindGroupLayout,
575}
576
577impl PipelineCache {
578 pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
597 let clip_bind_group_layout =
598 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
599 label: Some("clip bind group layout"),
600 entries: &[wgpu::BindGroupLayoutEntry {
601 binding: 0,
602 visibility: wgpu::ShaderStages::FRAGMENT,
603 ty: wgpu::BindingType::Buffer {
604 ty: wgpu::BufferBindingType::Uniform,
605 has_dynamic_offset: false,
606 min_binding_size: None,
607 },
608 count: None,
609 }],
610 });
611
612 let shader_rect = device.create_shader_module(wgpu::ShaderModuleDescriptor {
613 label: Some("rect shader"),
614 source: wgpu::ShaderSource::Wgsl(crate::shaders::rect_shader_source().into()),
615 });
616 let shader_stroke = device.create_shader_module(wgpu::ShaderModuleDescriptor {
617 label: Some("stroke shader"),
618 source: wgpu::ShaderSource::Wgsl(crate::shaders::stroke_shader_source().into()),
619 });
620
621 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
622 label: Some("pipeline layout"),
623 bind_group_layouts: &[&clip_bind_group_layout],
624 push_constant_ranges: &[],
625 });
626
627 let rect = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
628 label: Some("rect pipeline"),
629 layout: Some(&pipeline_layout),
630 vertex: wgpu::VertexState {
631 module: &shader_rect,
632 entry_point: "vs_main",
633 compilation_options: Default::default(),
634 buffers: &[Vertex::desc()],
635 },
636 fragment: Some(wgpu::FragmentState {
637 module: &shader_rect,
638 entry_point: "fs_main",
639 compilation_options: Default::default(),
640 targets: &[Some(wgpu::ColorTargetState {
641 format: surface_format,
642 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
643 write_mask: wgpu::ColorWrites::ALL,
644 })],
645 }),
646 primitive: wgpu::PrimitiveState {
647 topology: wgpu::PrimitiveTopology::TriangleList,
648 ..Default::default()
649 },
650 depth_stencil: None,
651 multisample: wgpu::MultisampleState::default(),
652 multiview: None,
653 });
654
655 let stroke = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
656 label: Some("stroke pipeline"),
657 layout: Some(&pipeline_layout),
658 vertex: wgpu::VertexState {
659 module: &shader_stroke,
660 entry_point: "vs_main",
661 compilation_options: Default::default(),
662 buffers: &[StrokeVertex::desc()],
663 },
664 fragment: Some(wgpu::FragmentState {
665 module: &shader_stroke,
666 entry_point: "fs_main",
667 compilation_options: Default::default(),
668 targets: &[Some(wgpu::ColorTargetState {
669 format: surface_format,
670 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
671 write_mask: wgpu::ColorWrites::ALL,
672 })],
673 }),
674 primitive: wgpu::PrimitiveState {
675 topology: wgpu::PrimitiveTopology::TriangleList,
676 ..Default::default()
677 },
678 depth_stencil: None,
679 multisample: wgpu::MultisampleState::default(),
680 multiview: None,
681 });
682
683 let texture_bind_group_layout =
684 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
685 label: Some("tex bind group layout"),
686 entries: &[
687 wgpu::BindGroupLayoutEntry {
688 binding: 0,
689 visibility: wgpu::ShaderStages::FRAGMENT,
690 ty: wgpu::BindingType::Texture {
691 multisampled: false,
692 view_dimension: wgpu::TextureViewDimension::D2,
693 sample_type: wgpu::TextureSampleType::Float { filterable: true },
694 },
695 count: None,
696 },
697 wgpu::BindGroupLayoutEntry {
698 binding: 1,
699 visibility: wgpu::ShaderStages::FRAGMENT,
700 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
701 count: None,
702 },
703 ],
704 });
705
706 let tex_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
707 label: Some("tex pipeline layout"),
708 bind_group_layouts: &[&clip_bind_group_layout, &texture_bind_group_layout],
709 push_constant_ranges: &[],
710 });
711
712 let shader_textured = device.create_shader_module(wgpu::ShaderModuleDescriptor {
713 label: Some("tex shader"),
714 source: wgpu::ShaderSource::Wgsl(crate::shaders::textured_shader_source().into()),
715 });
716
717 let textured = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
718 label: Some("tex pipeline"),
719 layout: Some(&tex_pipeline_layout),
720 vertex: wgpu::VertexState {
721 module: &shader_textured,
722 entry_point: "vs_main",
723 compilation_options: Default::default(),
724 buffers: &[TexVertex::desc()],
725 },
726 fragment: Some(wgpu::FragmentState {
727 module: &shader_textured,
728 entry_point: "fs_main",
729 compilation_options: Default::default(),
730 targets: &[Some(wgpu::ColorTargetState {
731 format: surface_format,
732 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
733 write_mask: wgpu::ColorWrites::ALL,
734 })],
735 }),
736 primitive: wgpu::PrimitiveState {
737 topology: wgpu::PrimitiveTopology::TriangleList,
738 ..Default::default()
739 },
740 depth_stencil: None,
741 multisample: wgpu::MultisampleState::default(),
742 multiview: None,
743 });
744
745 let shader_rrect = device.create_shader_module(wgpu::ShaderModuleDescriptor {
746 label: Some("rrect shader"),
747 source: wgpu::ShaderSource::Wgsl(crate::shaders::rounded_rect_shader_source().into()),
748 });
749 let shader_border_rrect = device.create_shader_module(wgpu::ShaderModuleDescriptor {
750 label: Some("border rrect shader"),
751 source: wgpu::ShaderSource::Wgsl(
752 crate::shaders::border_rounded_rect_shader_source().into(),
753 ),
754 });
755 let shader_box_shadow = device.create_shader_module(wgpu::ShaderModuleDescriptor {
756 label: Some("box shadow shader"),
757 source: wgpu::ShaderSource::Wgsl(crate::shaders::box_shadow_shader_source().into()),
758 });
759 let shader_ring_progress = device.create_shader_module(wgpu::ShaderModuleDescriptor {
760 label: Some("ring progress shader"),
761 source: wgpu::ShaderSource::Wgsl(crate::shaders::ring_progress_shader_source().into()),
762 });
763
764 let rrect_pipeline_layout =
765 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
766 label: Some("rrect pipeline layout"),
767 bind_group_layouts: &[&clip_bind_group_layout],
768 push_constant_ranges: &[],
769 });
770
771 let rounded_rect = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
772 label: Some("rrect pipeline"),
773 layout: Some(&rrect_pipeline_layout),
774 vertex: wgpu::VertexState {
775 module: &shader_rrect,
776 entry_point: "vs_main",
777 compilation_options: Default::default(),
778 buffers: &[RRectVertex::desc()],
779 },
780 fragment: Some(wgpu::FragmentState {
781 module: &shader_rrect,
782 entry_point: "fs_main",
783 compilation_options: Default::default(),
784 targets: &[Some(wgpu::ColorTargetState {
785 format: surface_format,
786 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
787 write_mask: wgpu::ColorWrites::ALL,
788 })],
789 }),
790 primitive: wgpu::PrimitiveState {
791 topology: wgpu::PrimitiveTopology::TriangleList,
792 ..Default::default()
793 },
794 depth_stencil: None,
795 multisample: wgpu::MultisampleState::default(),
796 multiview: None,
797 });
798
799 let border_rounded_rect = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
800 label: Some("border rrect pipeline"),
801 layout: Some(&rrect_pipeline_layout),
802 vertex: wgpu::VertexState {
803 module: &shader_border_rrect,
804 entry_point: "vs_main",
805 compilation_options: Default::default(),
806 buffers: &[BorderRRectVertex::desc()],
807 },
808 fragment: Some(wgpu::FragmentState {
809 module: &shader_border_rrect,
810 entry_point: "fs_main",
811 compilation_options: Default::default(),
812 targets: &[Some(wgpu::ColorTargetState {
813 format: surface_format,
814 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
815 write_mask: wgpu::ColorWrites::ALL,
816 })],
817 }),
818 primitive: wgpu::PrimitiveState {
819 topology: wgpu::PrimitiveTopology::TriangleList,
820 ..Default::default()
821 },
822 depth_stencil: None,
823 multisample: wgpu::MultisampleState::default(),
824 multiview: None,
825 });
826
827 let box_shadow = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
828 label: Some("box shadow pipeline"),
829 layout: Some(&rrect_pipeline_layout),
830 vertex: wgpu::VertexState {
831 module: &shader_box_shadow,
832 entry_point: "vs_main",
833 compilation_options: Default::default(),
834 buffers: &[BoxShadowVertex::desc()],
835 },
836 fragment: Some(wgpu::FragmentState {
837 module: &shader_box_shadow,
838 entry_point: "fs_main",
839 compilation_options: Default::default(),
840 targets: &[Some(wgpu::ColorTargetState {
841 format: surface_format,
842 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
843 write_mask: wgpu::ColorWrites::ALL,
844 })],
845 }),
846 primitive: wgpu::PrimitiveState {
847 topology: wgpu::PrimitiveTopology::TriangleList,
848 ..Default::default()
849 },
850 depth_stencil: None,
851 multisample: wgpu::MultisampleState::default(),
852 multiview: None,
853 });
854
855 let ring_progress = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
856 label: Some("ring progress pipeline"),
857 layout: Some(&rrect_pipeline_layout),
858 vertex: wgpu::VertexState {
859 module: &shader_ring_progress,
860 entry_point: "vs_main",
861 compilation_options: Default::default(),
862 buffers: &[RingProgressVertex::desc()],
863 },
864 fragment: Some(wgpu::FragmentState {
865 module: &shader_ring_progress,
866 entry_point: "fs_main",
867 compilation_options: Default::default(),
868 targets: &[Some(wgpu::ColorTargetState {
869 format: surface_format,
870 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
871 write_mask: wgpu::ColorWrites::ALL,
872 })],
873 }),
874 primitive: wgpu::PrimitiveState {
875 topology: wgpu::PrimitiveTopology::TriangleList,
876 ..Default::default()
877 },
878 depth_stencil: None,
879 multisample: wgpu::MultisampleState::default(),
880 multiview: None,
881 });
882
883 let stencil_write = wgpu::StencilState {
884 front: wgpu::StencilFaceState {
885 compare: wgpu::CompareFunction::Always,
886 fail_op: wgpu::StencilOperation::Keep,
887 depth_fail_op: wgpu::StencilOperation::Keep,
888 pass_op: wgpu::StencilOperation::Replace,
889 },
890 back: wgpu::StencilFaceState::default(),
891 read_mask: 0xff,
892 write_mask: 0xff,
893 };
894 let stencil_test = wgpu::StencilState {
895 front: wgpu::StencilFaceState {
896 compare: wgpu::CompareFunction::Equal,
897 fail_op: wgpu::StencilOperation::Keep,
898 depth_fail_op: wgpu::StencilOperation::Keep,
899 pass_op: wgpu::StencilOperation::Keep,
900 },
901 back: wgpu::StencilFaceState::default(),
902 read_mask: 0xff,
903 write_mask: 0x00,
904 };
905 let stencil_edge = wgpu::StencilState {
906 front: wgpu::StencilFaceState {
907 compare: wgpu::CompareFunction::NotEqual,
908 fail_op: wgpu::StencilOperation::Keep,
909 depth_fail_op: wgpu::StencilOperation::Keep,
910 pass_op: wgpu::StencilOperation::Keep,
911 },
912 back: wgpu::StencilFaceState::default(),
913 read_mask: 0xff,
914 write_mask: 0x00,
915 };
916 let stencil_passthrough = wgpu::StencilState {
921 front: wgpu::StencilFaceState {
922 compare: wgpu::CompareFunction::Always,
923 fail_op: wgpu::StencilOperation::Keep,
924 depth_fail_op: wgpu::StencilOperation::Keep,
925 pass_op: wgpu::StencilOperation::Keep,
926 },
927 back: wgpu::StencilFaceState::default(),
928 read_mask: 0xff,
929 write_mask: 0x00,
930 };
931 let depth_stencil_passthrough = Some(wgpu::DepthStencilState {
932 format: crate::stencil::StencilTarget::FORMAT,
933 depth_write_enabled: false,
934 depth_compare: wgpu::CompareFunction::Always,
935 stencil: stencil_passthrough,
936 bias: wgpu::DepthBiasState::default(),
937 });
938 let depth_stencil_write = Some(wgpu::DepthStencilState {
939 format: crate::stencil::StencilTarget::FORMAT,
940 depth_write_enabled: false,
941 depth_compare: wgpu::CompareFunction::Always,
942 stencil: stencil_write,
943 bias: wgpu::DepthBiasState::default(),
944 });
945 let depth_stencil_test = Some(wgpu::DepthStencilState {
946 format: crate::stencil::StencilTarget::FORMAT,
947 depth_write_enabled: false,
948 depth_compare: wgpu::CompareFunction::Always,
949 stencil: stencil_test,
950 bias: wgpu::DepthBiasState::default(),
951 });
952 let depth_stencil_edge = Some(wgpu::DepthStencilState {
953 format: crate::stencil::StencilTarget::FORMAT,
954 depth_write_enabled: false,
955 depth_compare: wgpu::CompareFunction::Always,
956 stencil: stencil_edge,
957 bias: wgpu::DepthBiasState::default(),
958 });
959
960 let rounded_rect_stencil_mask =
961 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
962 label: Some("rounded rect stencil mask"),
963 layout: Some(&rrect_pipeline_layout),
964 vertex: wgpu::VertexState {
965 module: &shader_rrect,
966 entry_point: "vs_main",
967 compilation_options: Default::default(),
968 buffers: &[RRectVertex::desc()],
969 },
970 fragment: Some(wgpu::FragmentState {
971 module: &shader_rrect,
972 entry_point: "fs_main",
973 compilation_options: Default::default(),
974 targets: &[Some(wgpu::ColorTargetState {
975 format: surface_format,
976 blend: None,
977 write_mask: wgpu::ColorWrites::empty(),
978 })],
979 }),
980 primitive: wgpu::PrimitiveState::default(),
981 depth_stencil: depth_stencil_write,
982 multisample: wgpu::MultisampleState::default(),
983 multiview: None,
984 });
985
986 let rect_stencil = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
987 label: Some("rect stencil content"),
988 layout: Some(&pipeline_layout),
989 vertex: wgpu::VertexState {
990 module: &shader_rect,
991 entry_point: "vs_main",
992 compilation_options: Default::default(),
993 buffers: &[Vertex::desc()],
994 },
995 fragment: Some(wgpu::FragmentState {
996 module: &shader_rect,
997 entry_point: "fs_main",
998 compilation_options: Default::default(),
999 targets: &[Some(wgpu::ColorTargetState {
1000 format: surface_format,
1001 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1002 write_mask: wgpu::ColorWrites::ALL,
1003 })],
1004 }),
1005 primitive: wgpu::PrimitiveState::default(),
1006 depth_stencil: depth_stencil_test.clone(),
1007 multisample: wgpu::MultisampleState::default(),
1008 multiview: None,
1009 });
1010
1011 let stroke_stencil = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1012 label: Some("stroke stencil content"),
1013 layout: Some(&pipeline_layout),
1014 vertex: wgpu::VertexState {
1015 module: &shader_stroke,
1016 entry_point: "vs_main",
1017 compilation_options: Default::default(),
1018 buffers: &[StrokeVertex::desc()],
1019 },
1020 fragment: Some(wgpu::FragmentState {
1021 module: &shader_stroke,
1022 entry_point: "fs_main",
1023 compilation_options: Default::default(),
1024 targets: &[Some(wgpu::ColorTargetState {
1025 format: surface_format,
1026 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1027 write_mask: wgpu::ColorWrites::ALL,
1028 })],
1029 }),
1030 primitive: wgpu::PrimitiveState::default(),
1031 depth_stencil: depth_stencil_test.clone(),
1032 multisample: wgpu::MultisampleState::default(),
1033 multiview: None,
1034 });
1035
1036 let textured_stencil = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1037 label: Some("textured stencil content"),
1038 layout: Some(&tex_pipeline_layout),
1039 vertex: wgpu::VertexState {
1040 module: &shader_textured,
1041 entry_point: "vs_main",
1042 compilation_options: Default::default(),
1043 buffers: &[TexVertex::desc()],
1044 },
1045 fragment: Some(wgpu::FragmentState {
1046 module: &shader_textured,
1047 entry_point: "fs_main",
1048 compilation_options: Default::default(),
1049 targets: &[Some(wgpu::ColorTargetState {
1050 format: surface_format,
1051 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1052 write_mask: wgpu::ColorWrites::ALL,
1053 })],
1054 }),
1055 primitive: wgpu::PrimitiveState::default(),
1056 depth_stencil: depth_stencil_test.clone(),
1057 multisample: wgpu::MultisampleState::default(),
1058 multiview: None,
1059 });
1060
1061 let rounded_rect_stencil = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1062 label: Some("rrect stencil content"),
1063 layout: Some(&rrect_pipeline_layout),
1064 vertex: wgpu::VertexState {
1065 module: &shader_rrect,
1066 entry_point: "vs_main",
1067 compilation_options: Default::default(),
1068 buffers: &[RRectVertex::desc()],
1069 },
1070 fragment: Some(wgpu::FragmentState {
1071 module: &shader_rrect,
1072 entry_point: "fs_main",
1073 compilation_options: Default::default(),
1074 targets: &[Some(wgpu::ColorTargetState {
1075 format: surface_format,
1076 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1077 write_mask: wgpu::ColorWrites::ALL,
1078 })],
1079 }),
1080 primitive: wgpu::PrimitiveState::default(),
1081 depth_stencil: depth_stencil_test.clone(),
1082 multisample: wgpu::MultisampleState::default(),
1083 multiview: None,
1084 });
1085
1086 let border_rounded_rect_stencil =
1087 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1088 label: Some("border rrect stencil content"),
1089 layout: Some(&rrect_pipeline_layout),
1090 vertex: wgpu::VertexState {
1091 module: &shader_border_rrect,
1092 entry_point: "vs_main",
1093 compilation_options: Default::default(),
1094 buffers: &[BorderRRectVertex::desc()],
1095 },
1096 fragment: Some(wgpu::FragmentState {
1097 module: &shader_border_rrect,
1098 entry_point: "fs_main",
1099 compilation_options: Default::default(),
1100 targets: &[Some(wgpu::ColorTargetState {
1101 format: surface_format,
1102 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1103 write_mask: wgpu::ColorWrites::ALL,
1104 })],
1105 }),
1106 primitive: wgpu::PrimitiveState::default(),
1107 depth_stencil: depth_stencil_test.clone(),
1108 multisample: wgpu::MultisampleState::default(),
1109 multiview: None,
1110 });
1111
1112 let box_shadow_stencil = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1113 label: Some("box shadow stencil content"),
1114 layout: Some(&rrect_pipeline_layout),
1115 vertex: wgpu::VertexState {
1116 module: &shader_box_shadow,
1117 entry_point: "vs_main",
1118 compilation_options: Default::default(),
1119 buffers: &[BoxShadowVertex::desc()],
1120 },
1121 fragment: Some(wgpu::FragmentState {
1122 module: &shader_box_shadow,
1123 entry_point: "fs_main",
1124 compilation_options: Default::default(),
1125 targets: &[Some(wgpu::ColorTargetState {
1126 format: surface_format,
1127 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1128 write_mask: wgpu::ColorWrites::ALL,
1129 })],
1130 }),
1131 primitive: wgpu::PrimitiveState::default(),
1132 depth_stencil: depth_stencil_test.clone(),
1133 multisample: wgpu::MultisampleState::default(),
1134 multiview: None,
1135 });
1136
1137 let ring_progress_stencil =
1138 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1139 label: Some("ring progress stencil content"),
1140 layout: Some(&rrect_pipeline_layout),
1141 vertex: wgpu::VertexState {
1142 module: &shader_ring_progress,
1143 entry_point: "vs_main",
1144 compilation_options: Default::default(),
1145 buffers: &[RingProgressVertex::desc()],
1146 },
1147 fragment: Some(wgpu::FragmentState {
1148 module: &shader_ring_progress,
1149 entry_point: "fs_main",
1150 compilation_options: Default::default(),
1151 targets: &[Some(wgpu::ColorTargetState {
1152 format: surface_format,
1153 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1154 write_mask: wgpu::ColorWrites::ALL,
1155 })],
1156 }),
1157 primitive: wgpu::PrimitiveState::default(),
1158 depth_stencil: depth_stencil_test.clone(),
1159 multisample: wgpu::MultisampleState::default(),
1160 multiview: None,
1161 });
1162
1163 let rounded_rect_stencil_edge =
1164 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1165 label: Some("rrect stencil edge aa"),
1166 layout: Some(&rrect_pipeline_layout),
1167 vertex: wgpu::VertexState {
1168 module: &shader_rrect,
1169 entry_point: "vs_main",
1170 compilation_options: Default::default(),
1171 buffers: &[RRectVertex::desc()],
1172 },
1173 fragment: Some(wgpu::FragmentState {
1174 module: &shader_rrect,
1175 entry_point: "fs_main",
1176 compilation_options: Default::default(),
1177 targets: &[Some(wgpu::ColorTargetState {
1178 format: surface_format,
1179 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1180 write_mask: wgpu::ColorWrites::ALL,
1181 })],
1182 }),
1183 primitive: wgpu::PrimitiveState::default(),
1184 depth_stencil: depth_stencil_edge,
1185 multisample: wgpu::MultisampleState::default(),
1186 multiview: None,
1187 });
1188
1189 let rect_passthrough_stencil =
1190 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1191 label: Some("rect pipeline (passthrough stencil)"),
1192 layout: Some(&pipeline_layout),
1193 vertex: wgpu::VertexState {
1194 module: &shader_rect,
1195 entry_point: "vs_main",
1196 compilation_options: Default::default(),
1197 buffers: &[Vertex::desc()],
1198 },
1199 fragment: Some(wgpu::FragmentState {
1200 module: &shader_rect,
1201 entry_point: "fs_main",
1202 compilation_options: Default::default(),
1203 targets: &[Some(wgpu::ColorTargetState {
1204 format: surface_format,
1205 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1206 write_mask: wgpu::ColorWrites::ALL,
1207 })],
1208 }),
1209 primitive: wgpu::PrimitiveState {
1210 topology: wgpu::PrimitiveTopology::TriangleList,
1211 ..Default::default()
1212 },
1213 depth_stencil: depth_stencil_passthrough.clone(),
1214 multisample: wgpu::MultisampleState::default(),
1215 multiview: None,
1216 });
1217
1218 let stroke_passthrough_stencil =
1219 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1220 label: Some("stroke pipeline (passthrough stencil)"),
1221 layout: Some(&pipeline_layout),
1222 vertex: wgpu::VertexState {
1223 module: &shader_stroke,
1224 entry_point: "vs_main",
1225 compilation_options: Default::default(),
1226 buffers: &[StrokeVertex::desc()],
1227 },
1228 fragment: Some(wgpu::FragmentState {
1229 module: &shader_stroke,
1230 entry_point: "fs_main",
1231 compilation_options: Default::default(),
1232 targets: &[Some(wgpu::ColorTargetState {
1233 format: surface_format,
1234 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1235 write_mask: wgpu::ColorWrites::ALL,
1236 })],
1237 }),
1238 primitive: wgpu::PrimitiveState {
1239 topology: wgpu::PrimitiveTopology::TriangleList,
1240 ..Default::default()
1241 },
1242 depth_stencil: depth_stencil_passthrough.clone(),
1243 multisample: wgpu::MultisampleState::default(),
1244 multiview: None,
1245 });
1246
1247 let textured_passthrough_stencil =
1248 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1249 label: Some("tex pipeline (passthrough stencil)"),
1250 layout: Some(&tex_pipeline_layout),
1251 vertex: wgpu::VertexState {
1252 module: &shader_textured,
1253 entry_point: "vs_main",
1254 compilation_options: Default::default(),
1255 buffers: &[TexVertex::desc()],
1256 },
1257 fragment: Some(wgpu::FragmentState {
1258 module: &shader_textured,
1259 entry_point: "fs_main",
1260 compilation_options: Default::default(),
1261 targets: &[Some(wgpu::ColorTargetState {
1262 format: surface_format,
1263 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1264 write_mask: wgpu::ColorWrites::ALL,
1265 })],
1266 }),
1267 primitive: wgpu::PrimitiveState {
1268 topology: wgpu::PrimitiveTopology::TriangleList,
1269 ..Default::default()
1270 },
1271 depth_stencil: depth_stencil_passthrough.clone(),
1272 multisample: wgpu::MultisampleState::default(),
1273 multiview: None,
1274 });
1275
1276 let rounded_rect_passthrough_stencil =
1277 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1278 label: Some("rrect pipeline (passthrough stencil)"),
1279 layout: Some(&rrect_pipeline_layout),
1280 vertex: wgpu::VertexState {
1281 module: &shader_rrect,
1282 entry_point: "vs_main",
1283 compilation_options: Default::default(),
1284 buffers: &[RRectVertex::desc()],
1285 },
1286 fragment: Some(wgpu::FragmentState {
1287 module: &shader_rrect,
1288 entry_point: "fs_main",
1289 compilation_options: Default::default(),
1290 targets: &[Some(wgpu::ColorTargetState {
1291 format: surface_format,
1292 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1293 write_mask: wgpu::ColorWrites::ALL,
1294 })],
1295 }),
1296 primitive: wgpu::PrimitiveState {
1297 topology: wgpu::PrimitiveTopology::TriangleList,
1298 ..Default::default()
1299 },
1300 depth_stencil: depth_stencil_passthrough.clone(),
1301 multisample: wgpu::MultisampleState::default(),
1302 multiview: None,
1303 });
1304
1305 let border_rounded_rect_passthrough_stencil =
1306 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1307 label: Some("border rrect pipeline (passthrough stencil)"),
1308 layout: Some(&rrect_pipeline_layout),
1309 vertex: wgpu::VertexState {
1310 module: &shader_border_rrect,
1311 entry_point: "vs_main",
1312 compilation_options: Default::default(),
1313 buffers: &[BorderRRectVertex::desc()],
1314 },
1315 fragment: Some(wgpu::FragmentState {
1316 module: &shader_border_rrect,
1317 entry_point: "fs_main",
1318 compilation_options: Default::default(),
1319 targets: &[Some(wgpu::ColorTargetState {
1320 format: surface_format,
1321 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1322 write_mask: wgpu::ColorWrites::ALL,
1323 })],
1324 }),
1325 primitive: wgpu::PrimitiveState {
1326 topology: wgpu::PrimitiveTopology::TriangleList,
1327 ..Default::default()
1328 },
1329 depth_stencil: depth_stencil_passthrough.clone(),
1330 multisample: wgpu::MultisampleState::default(),
1331 multiview: None,
1332 });
1333
1334 let box_shadow_passthrough_stencil =
1335 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1336 label: Some("box shadow pipeline (passthrough stencil)"),
1337 layout: Some(&rrect_pipeline_layout),
1338 vertex: wgpu::VertexState {
1339 module: &shader_box_shadow,
1340 entry_point: "vs_main",
1341 compilation_options: Default::default(),
1342 buffers: &[BoxShadowVertex::desc()],
1343 },
1344 fragment: Some(wgpu::FragmentState {
1345 module: &shader_box_shadow,
1346 entry_point: "fs_main",
1347 compilation_options: Default::default(),
1348 targets: &[Some(wgpu::ColorTargetState {
1349 format: surface_format,
1350 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1351 write_mask: wgpu::ColorWrites::ALL,
1352 })],
1353 }),
1354 primitive: wgpu::PrimitiveState {
1355 topology: wgpu::PrimitiveTopology::TriangleList,
1356 ..Default::default()
1357 },
1358 depth_stencil: depth_stencil_passthrough.clone(),
1359 multisample: wgpu::MultisampleState::default(),
1360 multiview: None,
1361 });
1362
1363 let ring_progress_passthrough_stencil =
1364 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1365 label: Some("ring progress pipeline (passthrough stencil)"),
1366 layout: Some(&rrect_pipeline_layout),
1367 vertex: wgpu::VertexState {
1368 module: &shader_ring_progress,
1369 entry_point: "vs_main",
1370 compilation_options: Default::default(),
1371 buffers: &[RingProgressVertex::desc()],
1372 },
1373 fragment: Some(wgpu::FragmentState {
1374 module: &shader_ring_progress,
1375 entry_point: "fs_main",
1376 compilation_options: Default::default(),
1377 targets: &[Some(wgpu::ColorTargetState {
1378 format: surface_format,
1379 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1380 write_mask: wgpu::ColorWrites::ALL,
1381 })],
1382 }),
1383 primitive: wgpu::PrimitiveState {
1384 topology: wgpu::PrimitiveTopology::TriangleList,
1385 ..Default::default()
1386 },
1387 depth_stencil: depth_stencil_passthrough,
1388 multisample: wgpu::MultisampleState::default(),
1389 multiview: None,
1390 });
1391
1392 Self {
1393 rect,
1394 stroke,
1395 textured,
1396 rounded_rect,
1397 border_rounded_rect,
1398 box_shadow,
1399 ring_progress,
1400 rect_passthrough_stencil,
1401 stroke_passthrough_stencil,
1402 textured_passthrough_stencil,
1403 rounded_rect_passthrough_stencil,
1404 border_rounded_rect_passthrough_stencil,
1405 box_shadow_passthrough_stencil,
1406 ring_progress_passthrough_stencil,
1407 rounded_rect_stencil_mask,
1408 rect_stencil,
1409 stroke_stencil,
1410 textured_stencil,
1411 rounded_rect_stencil,
1412 border_rounded_rect_stencil,
1413 box_shadow_stencil,
1414 ring_progress_stencil,
1415 rounded_rect_stencil_edge,
1416 clip_bind_group_layout,
1417 texture_bind_group_layout,
1418 }
1419 }
1420}
1421
1422pub fn gpu_debug_enabled() -> bool {
1437 crate::env_control::truthy("AILLOLI_UI_GPU_DEBUG", "OCTAVUI_GPU_DEBUG")
1438}
1439
1440fn adapter_bootstrap_rank(device_type: wgpu::DeviceType) -> u8 {
1445 match device_type {
1446 wgpu::DeviceType::DiscreteGpu => 0,
1447 wgpu::DeviceType::IntegratedGpu => 1,
1448 wgpu::DeviceType::Other => 2,
1449 wgpu::DeviceType::VirtualGpu => 3,
1450 wgpu::DeviceType::Cpu => 4,
1451 }
1452}
1453
1454pub struct SurfaceGpuContext {
1467 instance: wgpu::Instance,
1469 adapter: wgpu::Adapter,
1471 device: wgpu::Device,
1473 queue: wgpu::Queue,
1475 pipelines: PipelineCache,
1477 format: wgpu::TextureFormat,
1479}
1480
1481impl SurfaceGpuContext {
1482 pub fn instance(&self) -> &wgpu::Instance {
1494 &self.instance
1495 }
1496
1497 pub fn adapter(&self) -> &wgpu::Adapter {
1509 &self.adapter
1510 }
1511
1512 pub fn device(&self) -> &wgpu::Device {
1524 &self.device
1525 }
1526
1527 pub fn queue(&self) -> &wgpu::Queue {
1539 &self.queue
1540 }
1541
1542 pub fn pipelines(&self) -> &PipelineCache {
1554 &self.pipelines
1555 }
1556
1557 pub fn format(&self) -> wgpu::TextureFormat {
1569 self.format
1570 }
1571
1572 pub fn adapter_info(&self) -> wgpu::AdapterInfo {
1584 self.adapter.get_info()
1585 }
1586}
1587
1588pub struct SurfaceAttachment {
1602 surface: wgpu::Surface<'static>,
1604 config: wgpu::SurfaceConfiguration,
1606 capabilities: wgpu::SurfaceCapabilities,
1608 pre_present: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
1610}
1611
1612impl std::fmt::Debug for SurfaceAttachment {
1613 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1614 formatter
1615 .debug_struct("SurfaceAttachment")
1616 .field("surface", &self.surface)
1617 .field("config", &self.config)
1618 .field("capabilities", &self.capabilities)
1619 .field("has_pre_present", &self.pre_present.is_some())
1620 .finish()
1621 }
1622}
1623
1624impl SurfaceAttachment {
1625 pub fn config(&self) -> &wgpu::SurfaceConfiguration {
1640 &self.config
1641 }
1642
1643 pub fn capabilities(&self) -> &wgpu::SurfaceCapabilities {
1658 &self.capabilities
1659 }
1660
1661 fn pre_present_notify(&self) {
1663 if let Some(pre_present) = &self.pre_present {
1664 pre_present();
1665 }
1666 }
1667}
1668
1669enum SurfaceAttachAttemptError {
1672 Fatal(RendererError),
1674 RequiresRebuild(SurfaceContextReuseFailure),
1676}
1677
1678pub struct WgpuSurfaceBundle {
1692 attachment: SurfaceAttachmentSlot<SurfaceAttachment>,
1696 context: SurfaceGpuContext,
1698 last_extent: PhysicalExtent,
1700 transparent: bool,
1702 bootstrap: SurfaceBootstrapConfig,
1704}
1705
1706impl WgpuSurfaceBundle {
1707 pub fn new_with_surface_target<T>(
1752 target: std::sync::Arc<T>,
1753 size: PhysicalExtent,
1754 transparent: bool,
1755 bootstrap: SurfaceBootstrapConfig,
1756 pre_present: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
1757 ) -> Result<Self, RendererError>
1758 where
1759 T: wgpu::rwh::HasWindowHandle + wgpu::rwh::HasDisplayHandle + Send + Sync + 'static,
1760 {
1761 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
1762 let surface = instance
1763 .create_surface(target)
1764 .map_err(|_| RendererError::SurfaceConfigFailed)?;
1765 let gpu_debug = gpu_debug_enabled();
1766
1767 let mut candidates: Vec<wgpu::Adapter> = instance
1768 .enumerate_adapters(bootstrap.requested_backends)
1769 .into_iter()
1770 .filter(|adapter| adapter.is_surface_supported(&surface))
1771 .collect();
1772
1773 if candidates.is_empty() && bootstrap.allow_fallback_backends {
1774 candidates = instance
1775 .enumerate_adapters(wgpu::Backends::all())
1776 .into_iter()
1777 .filter(|adapter| adapter.is_surface_supported(&surface))
1778 .collect();
1779 }
1780
1781 if candidates.is_empty() {
1782 if let Some(adapter) =
1783 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1784 compatible_surface: Some(&surface),
1785 power_preference: bootstrap.power_preference,
1786 force_fallback_adapter: bootstrap.force_fallback_adapter,
1787 }))
1788 {
1789 if adapter.is_surface_supported(&surface) {
1790 candidates.push(adapter);
1791 }
1792 }
1793 }
1794
1795 if candidates.is_empty() {
1796 return Err(RendererError::RequestAdapterFailed);
1797 }
1798
1799 candidates.sort_by(|a, b| {
1800 let ia = a.get_info();
1801 let ib = b.get_info();
1802 bootstrap_adapter_rank(&ia, &bootstrap)
1803 .cmp(&bootstrap_adapter_rank(&ib, &bootstrap))
1804 .then_with(|| ia.name.cmp(&ib.name))
1805 });
1806
1807 let device_desc = wgpu::DeviceDescriptor {
1808 label: Some("device"),
1809 required_features: wgpu::Features::empty(),
1810 required_limits: wgpu::Limits::default(),
1811 };
1812
1813 for adapter in candidates {
1814 let info = adapter.get_info();
1815 if gpu_debug {
1816 eprintln!(
1817 "ailloli_ui_render_wgpu: trying adapter name={} backend={:?} device_type={:?}",
1818 info.name, info.backend, info.device_type
1819 );
1820 }
1821
1822 let (device, queue) =
1823 match pollster::block_on(adapter.request_device(&device_desc, None)) {
1824 Ok(pair) => pair,
1825 Err(error) => {
1826 if gpu_debug {
1827 eprintln!(
1828 "ailloli_ui_render_wgpu: request_device failed for {}: {error:?}",
1829 info.name
1830 );
1831 }
1832 continue;
1833 }
1834 };
1835
1836 let capabilities = surface.get_capabilities(&adapter);
1837 let surface_config = match build_surface_config(&capabilities, size, 1, transparent) {
1838 Ok(config) => config,
1839 Err(reason) => {
1840 if gpu_debug {
1841 eprintln!(
1842 "ailloli_ui_render_wgpu: build_surface_config deferred {:?} for {}",
1843 reason, info.name
1844 );
1845 }
1846 continue;
1847 }
1848 };
1849
1850 if gpu_debug {
1851 eprintln!(
1852 "ailloli_ui_render_wgpu: configuring surface for adapter name={} backend={:?} device_type={:?}",
1853 info.name, info.backend, info.device_type
1854 );
1855 }
1856
1857 let configured_ok = catch_unwind(AssertUnwindSafe(|| {
1860 surface.configure(&device, &surface_config);
1861 }))
1862 .is_ok();
1863
1864 if !configured_ok {
1865 if gpu_debug {
1866 eprintln!(
1867 "ailloli_ui_render_wgpu: surface.configure panicked for adapter name={}",
1868 info.name
1869 );
1870 }
1871 continue;
1872 }
1873
1874 let format = surface_config.format;
1875 let pipelines = PipelineCache::new(&device, format);
1876 let context = SurfaceGpuContext {
1877 instance,
1878 adapter,
1879 device,
1880 queue,
1881 pipelines,
1882 format,
1883 };
1884 let attachment = SurfaceAttachment {
1885 surface,
1886 config: surface_config,
1887 capabilities,
1888 pre_present,
1889 };
1890
1891 return Ok(Self {
1892 attachment: SurfaceAttachmentSlot::attached(attachment),
1893 context,
1894 last_extent: size,
1895 transparent,
1896 bootstrap,
1897 });
1898 }
1899
1900 Err(RendererError::SurfaceConfigureExhausted)
1901 }
1902
1903 pub fn context(&self) -> &SurfaceGpuContext {
1915 &self.context
1916 }
1917
1918 pub fn attachment(&self) -> Option<&SurfaceAttachment> {
1930 self.attachment.as_ref()
1931 }
1932
1933 pub fn attachment_state(&self) -> SurfaceAttachmentState {
1947 self.attachment.state()
1948 }
1949
1950 pub fn device(&self) -> &wgpu::Device {
1962 self.context.device()
1963 }
1964
1965 pub fn queue(&self) -> &wgpu::Queue {
1977 self.context.queue()
1978 }
1979
1980 pub fn pipelines(&self) -> &PipelineCache {
1992 self.context.pipelines()
1993 }
1994
1995 pub fn format(&self) -> wgpu::TextureFormat {
2007 self.context.format()
2008 }
2009
2010 pub fn extent(&self) -> PhysicalExtent {
2026 self.attachment
2027 .as_ref()
2028 .map(|attachment| {
2029 PhysicalExtent::new(attachment.config.width, attachment.config.height)
2030 })
2031 .unwrap_or(self.last_extent)
2032 }
2033
2034 pub fn config(&self) -> Option<&wgpu::SurfaceConfiguration> {
2046 self.attachment.as_ref().map(SurfaceAttachment::config)
2047 }
2048
2049 pub fn detach_surface(&mut self) -> bool {
2068 let Some(attachment) = self.attachment.detach() else {
2069 return false;
2070 };
2071 self.last_extent = PhysicalExtent::new(attachment.config.width, attachment.config.height);
2072 drop(attachment);
2073 true
2074 }
2075
2076 pub fn reattach_surface_target<T>(
2119 &mut self,
2120 target: std::sync::Arc<T>,
2121 size: PhysicalExtent,
2122 pre_present: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
2123 ) -> Result<SurfaceReattachOutcome, RendererError>
2124 where
2125 T: wgpu::rwh::HasWindowHandle + wgpu::rwh::HasDisplayHandle + Send + Sync + 'static,
2126 {
2127 self.detach_surface();
2128
2129 match Self::create_attachment_with_context(
2130 &self.context,
2131 target.clone(),
2132 size,
2133 self.transparent,
2134 pre_present.clone(),
2135 ) {
2136 Ok(attachment) => {
2137 let previous_attachment = self.attachment.attach(attachment);
2138 debug_assert!(previous_attachment.is_none());
2139 drop(previous_attachment);
2140 self.last_extent = size;
2141 Ok(SurfaceReattachOutcome::ReusedGpuContext)
2142 }
2143 Err(SurfaceAttachAttemptError::Fatal(error)) => Err(error),
2144 Err(SurfaceAttachAttemptError::RequiresRebuild(reason)) => {
2145 let replacement = Self::new_with_surface_target(
2146 target,
2147 size,
2148 self.transparent,
2149 self.bootstrap,
2150 pre_present,
2151 )?;
2152 *self = replacement;
2153 Ok(SurfaceReattachOutcome::RebuiltGpuContext { reason })
2154 }
2155 }
2156 }
2157
2158 fn create_attachment_with_context<T>(
2170 context: &SurfaceGpuContext,
2171 target: std::sync::Arc<T>,
2172 size: PhysicalExtent,
2173 transparent: bool,
2174 pre_present: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
2175 ) -> Result<SurfaceAttachment, SurfaceAttachAttemptError>
2176 where
2177 T: wgpu::rwh::HasWindowHandle + wgpu::rwh::HasDisplayHandle + Send + Sync + 'static,
2178 {
2179 let surface = context
2180 .instance
2181 .create_surface(target)
2182 .map_err(|_| SurfaceAttachAttemptError::Fatal(RendererError::SurfaceConfigFailed))?;
2183 let adapter_supported = context.adapter.is_surface_supported(&surface);
2184 if !adapter_supported {
2185 return Err(SurfaceAttachAttemptError::RequiresRebuild(
2186 SurfaceContextReuseFailure::AdapterUnsupported,
2187 ));
2188 }
2189 let capabilities = surface.get_capabilities(&context.adapter);
2190 if let Some(reason) = surface_context_reuse_failure(true, context.format, &capabilities) {
2191 return Err(SurfaceAttachAttemptError::RequiresRebuild(reason));
2192 }
2193
2194 let config =
2195 build_surface_config_for_format(&capabilities, size, 1, transparent, context.format)
2196 .map_err(|reason| {
2197 SurfaceAttachAttemptError::RequiresRebuild(
2198 SurfaceContextReuseFailure::CapabilitiesDeferred(reason),
2199 )
2200 })?;
2201 let configured = catch_unwind(AssertUnwindSafe(|| {
2202 surface.configure(&context.device, &config);
2203 }));
2204 if configured.is_err() {
2205 return Err(SurfaceAttachAttemptError::RequiresRebuild(
2206 SurfaceContextReuseFailure::ConfigureFailed,
2207 ));
2208 }
2209
2210 Ok(SurfaceAttachment {
2211 surface,
2212 config,
2213 capabilities,
2214 pre_present,
2215 })
2216 }
2217
2218 pub fn resize(&mut self, new_size: PhysicalExtent) {
2234 let _ = self.try_resize(new_size);
2235 }
2236
2237 pub fn try_resize(&mut self, new_size: PhysicalExtent) -> Result<ResizeOutcome, RendererError> {
2260 self.configure_surface(new_size, SurfaceConfigureMode::Resize)
2261 }
2262
2263 pub fn try_reconfigure(
2284 &mut self,
2285 new_size: PhysicalExtent,
2286 ) -> Result<ResizeOutcome, RendererError> {
2287 self.configure_surface(new_size, SurfaceConfigureMode::Force)
2288 }
2289
2290 fn configure_surface(
2304 &mut self,
2305 new_size: PhysicalExtent,
2306 mode: SurfaceConfigureMode,
2307 ) -> Result<ResizeOutcome, RendererError> {
2308 if new_size.is_zero() {
2309 return Ok(ResizeOutcome::SkippedZero);
2310 }
2311 let Some(attachment) = self.attachment.as_mut() else {
2312 return Err(RendererError::RenderTargetUnavailable(
2313 "presentation surface is detached",
2314 ));
2315 };
2316 let capabilities = attachment.surface.get_capabilities(&self.context.adapter);
2317 if let Some(reason) = surface_config_deferred_reason(&capabilities) {
2318 attachment.capabilities = capabilities;
2319 return Ok(ResizeOutcome::Deferred(reason));
2320 }
2321 if !capabilities.formats.contains(&self.context.format) {
2322 attachment.capabilities = capabilities;
2323 return Err(RendererError::SurfaceRecreationRequired(
2324 "surface no longer supports the pipeline format",
2325 ));
2326 }
2327 let current_size = PhysicalExtent::new(attachment.config.width, attachment.config.height);
2328 if !surface_configure_required(current_size, new_size, mode) {
2329 return Ok(ResizeOutcome::Unchanged);
2330 }
2331 let next_config = match build_surface_config_for_format(
2332 &capabilities,
2333 new_size,
2334 attachment.config.desired_maximum_frame_latency,
2335 self.transparent,
2336 self.context.format,
2337 ) {
2338 Ok(config) => config,
2339 Err(reason) => {
2340 attachment.capabilities = capabilities;
2341 return Ok(ResizeOutcome::Deferred(reason));
2342 }
2343 };
2344 let start = std::time::Instant::now();
2345 let configured = catch_unwind(AssertUnwindSafe(|| {
2346 attachment
2347 .surface
2348 .configure(&self.context.device, &next_config);
2349 }));
2350 if configured.is_err() {
2351 return Err(RendererError::SurfaceConfigFailed);
2352 }
2353 attachment.config = next_config;
2354 attachment.capabilities = attachment.surface.get_capabilities(&self.context.adapter);
2355 self.last_extent = new_size;
2356 ailloli_ui_bench::record(ailloli_ui_bench::Event::SurfaceConfigure {
2357 ts_ms: now_ms(),
2358 w: new_size.width,
2359 h: new_size.height,
2360 dur_us: start.elapsed().as_micros(),
2361 });
2362 if let Some(reason) = surface_config_deferred_reason(&attachment.capabilities) {
2363 return Ok(ResizeOutcome::Deferred(reason));
2364 }
2365 Ok(ResizeOutcome::Applied)
2366 }
2367
2368 pub fn reconfigure(&mut self) -> Result<ResizeOutcome, RendererError> {
2387 self.try_reconfigure(self.extent())
2388 }
2389
2390 pub fn surface_capabilities(&self) -> wgpu::SurfaceCapabilities {
2405 self.attachment
2406 .as_ref()
2407 .map(|attachment| attachment.surface.get_capabilities(&self.context.adapter))
2408 .unwrap_or_default()
2409 }
2410
2411 pub fn adapter_info(&self) -> wgpu::AdapterInfo {
2423 self.context.adapter_info()
2424 }
2425
2426 pub fn surface_config_deferred_reason(&self) -> Option<SurfaceConfigDeferredReason> {
2445 let Some(attachment) = self.attachment.as_ref() else {
2446 return Some(SurfaceConfigDeferredReason::Detached);
2447 };
2448 let capabilities = attachment.surface.get_capabilities(&self.context.adapter);
2449 surface_config_deferred_reason(&capabilities)
2450 }
2451
2452 pub fn pre_present_notify(&self) {
2466 if let Some(attachment) = self.attachment.as_ref() {
2467 attachment.pre_present_notify();
2468 }
2469 }
2470}
2471
2472impl RenderTarget for WgpuSurfaceBundle {
2473 fn size(&self) -> PhysicalExtent {
2474 self.extent()
2475 }
2476
2477 fn format(&self) -> wgpu::TextureFormat {
2478 self.context.format
2479 }
2480
2481 fn acquire_frame(&mut self) -> Result<RenderFrame, RendererError> {
2482 let size = self.extent();
2483 let format = self.format();
2484 let Some(attachment) = self.attachment.as_mut() else {
2485 return Err(RendererError::RenderTargetUnavailable(
2486 "presentation surface is detached",
2487 ));
2488 };
2489 let acquire_start = std::time::Instant::now();
2490 let frame = match attachment.surface.get_current_texture() {
2491 Ok(frame) => frame,
2492 Err(error) => {
2493 ailloli_ui_bench::metric(
2494 "get_current_texture_us",
2495 acquire_start.elapsed().as_micros() as f64,
2496 );
2497 ailloli_ui_bench::record(ailloli_ui_bench::Event::GetCurrentTextureErr {
2498 ts_ms: now_ms(),
2499 err: format!("{error:?}"),
2500 });
2501 return Err(RendererError::from_surface_error(error));
2502 }
2503 };
2504 ailloli_ui_bench::metric(
2505 "get_current_texture_us",
2506 acquire_start.elapsed().as_micros() as f64,
2507 );
2508 let view = frame
2509 .texture
2510 .create_view(&wgpu::TextureViewDescriptor::default());
2511 Ok(RenderFrame::from_surface_texture(
2512 view, frame, size, format, None,
2513 ))
2514 }
2515
2516 fn pre_present_notify(&self) {
2517 self.pre_present_notify();
2518 }
2519}
2520
2521pub fn choose_surface_format(caps: &wgpu::SurfaceCapabilities) -> Option<wgpu::TextureFormat> {
2539 caps.formats
2540 .iter()
2541 .copied()
2542 .find(|format| format.is_srgb())
2543 .or_else(|| caps.formats.first().copied())
2544}
2545
2546pub fn choose_present_mode(modes: &[wgpu::PresentMode]) -> Option<wgpu::PresentMode> {
2562 modes
2563 .iter()
2564 .copied()
2565 .filter(is_concrete_present_mode)
2566 .find(|mode| *mode == wgpu::PresentMode::Fifo)
2567 .or_else(|| modes.iter().copied().find(is_concrete_present_mode))
2568}
2569
2570fn is_concrete_present_mode(mode: &wgpu::PresentMode) -> bool {
2572 !matches!(
2573 mode,
2574 wgpu::PresentMode::AutoVsync | wgpu::PresentMode::AutoNoVsync
2575 )
2576}
2577
2578pub fn choose_surface_usage(usages: wgpu::TextureUsages) -> wgpu::TextureUsages {
2593 let mut usage = wgpu::TextureUsages::RENDER_ATTACHMENT;
2594 if usages.contains(wgpu::TextureUsages::COPY_SRC) {
2595 usage |= wgpu::TextureUsages::COPY_SRC;
2596 }
2597 usage
2598}
2599
2600pub fn choose_alpha_mode(
2621 modes: &[wgpu::CompositeAlphaMode],
2622 transparent: bool,
2623) -> wgpu::CompositeAlphaMode {
2624 let preferred: &[wgpu::CompositeAlphaMode] = if transparent {
2625 &[
2626 wgpu::CompositeAlphaMode::PreMultiplied,
2627 wgpu::CompositeAlphaMode::PostMultiplied,
2628 wgpu::CompositeAlphaMode::Inherit,
2629 wgpu::CompositeAlphaMode::Auto,
2630 wgpu::CompositeAlphaMode::Opaque,
2631 ]
2632 } else {
2633 &[
2634 wgpu::CompositeAlphaMode::Opaque,
2635 wgpu::CompositeAlphaMode::Auto,
2636 wgpu::CompositeAlphaMode::Inherit,
2637 wgpu::CompositeAlphaMode::PreMultiplied,
2638 wgpu::CompositeAlphaMode::PostMultiplied,
2639 ]
2640 };
2641
2642 preferred
2643 .iter()
2644 .copied()
2645 .find(|mode| modes.contains(mode))
2646 .or_else(|| modes.first().copied())
2647 .unwrap_or(wgpu::CompositeAlphaMode::Opaque)
2648}
2649
2650fn surface_context_reuse_failure(
2655 adapter_supported: bool,
2656 pipeline_format: wgpu::TextureFormat,
2657 capabilities: &wgpu::SurfaceCapabilities,
2658) -> Option<SurfaceContextReuseFailure> {
2659 if !adapter_supported {
2660 return Some(SurfaceContextReuseFailure::AdapterUnsupported);
2661 }
2662 if let Some(reason) = surface_config_deferred_reason(capabilities) {
2663 return Some(SurfaceContextReuseFailure::CapabilitiesDeferred(reason));
2664 }
2665 if !capabilities.formats.contains(&pipeline_format) {
2666 return Some(SurfaceContextReuseFailure::FormatUnsupported);
2667 }
2668 None
2669}
2670
2671fn build_surface_config_for_format(
2682 capabilities: &wgpu::SurfaceCapabilities,
2683 size: PhysicalExtent,
2684 desired_maximum_frame_latency: u32,
2685 transparent: bool,
2686 format: wgpu::TextureFormat,
2687) -> Result<wgpu::SurfaceConfiguration, SurfaceConfigDeferredReason> {
2688 let mut config = build_surface_config(
2689 capabilities,
2690 size,
2691 desired_maximum_frame_latency,
2692 transparent,
2693 )?;
2694 config.format = format;
2695 Ok(config)
2696}
2697
2698pub fn build_surface_config(
2730 caps: &wgpu::SurfaceCapabilities,
2731 size: PhysicalExtent,
2732 desired_maximum_frame_latency: u32,
2733 transparent: bool,
2734) -> Result<wgpu::SurfaceConfiguration, SurfaceConfigDeferredReason> {
2735 if let Some(reason) = surface_config_deferred_reason(caps) {
2736 return Err(reason);
2737 }
2738 let format = choose_surface_format(caps).ok_or(SurfaceConfigDeferredReason::NoFormats)?;
2739 let present_mode = choose_present_mode(&caps.present_modes)
2740 .ok_or(SurfaceConfigDeferredReason::NoPresentModes)?;
2741 let alpha_mode = choose_alpha_mode(&caps.alpha_modes, transparent);
2742
2743 Ok(wgpu::SurfaceConfiguration {
2744 usage: choose_surface_usage(caps.usages),
2745 format,
2746 width: size.width.max(1),
2747 height: size.height.max(1),
2748 present_mode,
2749 alpha_mode,
2750 view_formats: vec![],
2751 desired_maximum_frame_latency,
2752 })
2753}
2754
2755pub fn surface_config_deferred_reason(
2773 caps: &wgpu::SurfaceCapabilities,
2774) -> Option<SurfaceConfigDeferredReason> {
2775 if caps.formats.is_empty() {
2776 return Some(SurfaceConfigDeferredReason::NoFormats);
2777 }
2778 if choose_present_mode(&caps.present_modes).is_none() {
2779 return Some(SurfaceConfigDeferredReason::NoPresentModes);
2780 }
2781 None
2782}
2783
2784fn now_ms() -> u128 {
2788 use std::time::{SystemTime, UNIX_EPOCH};
2789 SystemTime::now()
2790 .duration_since(UNIX_EPOCH)
2791 .unwrap_or_default()
2792 .as_millis()
2793}
2794
2795#[cfg(test)]
2796mod tests {
2798 use super::*;
2799
2800 fn caps_with_present_modes(modes: Vec<wgpu::PresentMode>) -> wgpu::SurfaceCapabilities {
2802 wgpu::SurfaceCapabilities {
2803 formats: vec![
2804 wgpu::TextureFormat::Bgra8Unorm,
2805 wgpu::TextureFormat::Bgra8UnormSrgb,
2806 ],
2807 present_modes: modes,
2808 alpha_modes: vec![wgpu::CompositeAlphaMode::Opaque],
2809 usages: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
2810 }
2811 }
2812
2813 #[test]
2814 fn present_mode_empty_capabilities_defer_instead_of_autovsync() {
2815 let caps = caps_with_present_modes(Vec::new());
2816
2817 let result = build_surface_config(&caps, PhysicalExtent::new(100, 100), 1, false);
2818
2819 assert_eq!(result, Err(SurfaceConfigDeferredReason::NoPresentModes));
2820 }
2821
2822 #[test]
2823 fn present_mode_prefers_fifo_when_available() {
2824 let modes = vec![wgpu::PresentMode::Immediate, wgpu::PresentMode::Fifo];
2825
2826 assert_eq!(choose_present_mode(&modes), Some(wgpu::PresentMode::Fifo));
2827 }
2828
2829 #[test]
2830 fn surface_usage_includes_copy_src_only_when_supported() {
2831 assert_eq!(
2832 choose_surface_usage(wgpu::TextureUsages::RENDER_ATTACHMENT),
2833 wgpu::TextureUsages::RENDER_ATTACHMENT
2834 );
2835 assert_eq!(
2836 choose_surface_usage(
2837 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
2838 ),
2839 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
2840 );
2841 }
2842
2843 #[test]
2844 fn alpha_mode_prefers_opaque_for_non_transparent_surfaces() {
2845 let modes = vec![
2846 wgpu::CompositeAlphaMode::PreMultiplied,
2847 wgpu::CompositeAlphaMode::Opaque,
2848 ];
2849
2850 assert_eq!(
2851 choose_alpha_mode(&modes, false),
2852 wgpu::CompositeAlphaMode::Opaque
2853 );
2854 }
2855
2856 #[test]
2857 fn alpha_mode_prefers_premultiplied_for_transparent_surfaces() {
2858 let mut caps = caps_with_present_modes(vec![wgpu::PresentMode::Fifo]);
2859 caps.alpha_modes = vec![
2860 wgpu::CompositeAlphaMode::Opaque,
2861 wgpu::CompositeAlphaMode::PostMultiplied,
2862 wgpu::CompositeAlphaMode::PreMultiplied,
2863 ];
2864
2865 let config =
2866 build_surface_config(&caps, PhysicalExtent::new(100, 100), 1, true).expect("config");
2867
2868 assert_eq!(config.alpha_mode, wgpu::CompositeAlphaMode::PreMultiplied);
2869 }
2870
2871 #[test]
2872 fn alpha_mode_falls_back_to_opaque_when_transparency_is_not_supported() {
2873 let caps = caps_with_present_modes(vec![wgpu::PresentMode::Fifo]);
2874
2875 let config =
2876 build_surface_config(&caps, PhysicalExtent::new(100, 100), 1, true).expect("config");
2877
2878 assert_eq!(config.alpha_mode, wgpu::CompositeAlphaMode::Opaque);
2879 }
2880
2881 #[test]
2882 fn present_mode_ignores_automatic_modes() {
2883 let modes = vec![wgpu::PresentMode::AutoVsync, wgpu::PresentMode::Mailbox];
2884
2885 assert_eq!(
2886 choose_present_mode(&modes),
2887 Some(wgpu::PresentMode::Mailbox)
2888 );
2889 assert_eq!(choose_present_mode(&[wgpu::PresentMode::AutoVsync]), None);
2890 }
2891
2892 #[test]
2893 fn adapter_bootstrap_rank_prefers_discrete_then_integrated_then_cpu() {
2894 assert!(
2895 super::adapter_bootstrap_rank(wgpu::DeviceType::DiscreteGpu)
2896 < super::adapter_bootstrap_rank(wgpu::DeviceType::IntegratedGpu)
2897 );
2898 assert!(
2899 super::adapter_bootstrap_rank(wgpu::DeviceType::IntegratedGpu)
2900 < super::adapter_bootstrap_rank(wgpu::DeviceType::VirtualGpu)
2901 );
2902 assert!(
2903 super::adapter_bootstrap_rank(wgpu::DeviceType::VirtualGpu)
2904 < super::adapter_bootstrap_rank(wgpu::DeviceType::Cpu)
2905 );
2906 }
2907
2908 #[test]
2909 fn same_size_resize_skips_but_surface_recovery_forces_configure() {
2910 let size = PhysicalExtent::new(1280, 720);
2911
2912 assert!(!surface_configure_required(
2913 size,
2914 size,
2915 SurfaceConfigureMode::Resize
2916 ));
2917 assert!(surface_configure_required(
2918 size,
2919 size,
2920 SurfaceConfigureMode::Force
2921 ));
2922 }
2923
2924 #[test]
2925 fn attachment_slot_transitions_detach_and_reattach_without_native_handles() {
2926 let mut slot = SurfaceAttachmentSlot::attached("first");
2927
2928 assert_eq!(slot.state(), SurfaceAttachmentState::Attached);
2929 assert_eq!(slot.detach(), Some("first"));
2930 assert_eq!(slot.state(), SurfaceAttachmentState::Detached);
2931 assert_eq!(slot.detach(), None);
2932 assert_eq!(slot.attach("second"), None);
2933 assert_eq!(slot.state(), SurfaceAttachmentState::Attached);
2934 assert_eq!(slot.as_ref(), Some(&"second"));
2935 }
2936
2937 #[test]
2938 fn surface_context_reuse_requires_adapter_and_pipeline_format_compatibility() {
2939 let caps = caps_with_present_modes(vec![wgpu::PresentMode::Fifo]);
2940
2941 assert_eq!(
2942 surface_context_reuse_failure(true, wgpu::TextureFormat::Bgra8UnormSrgb, &caps),
2943 None
2944 );
2945 assert_eq!(
2946 surface_context_reuse_failure(false, wgpu::TextureFormat::Bgra8UnormSrgb, &caps),
2947 Some(SurfaceContextReuseFailure::AdapterUnsupported)
2948 );
2949 assert_eq!(
2950 surface_context_reuse_failure(true, wgpu::TextureFormat::Rgba16Float, &caps),
2951 Some(SurfaceContextReuseFailure::FormatUnsupported)
2952 );
2953 }
2954
2955 #[test]
2956 fn surface_context_reuse_defers_empty_capabilities_before_format_check() {
2957 let caps = wgpu::SurfaceCapabilities::default();
2958
2959 assert_eq!(
2960 surface_context_reuse_failure(true, wgpu::TextureFormat::Bgra8UnormSrgb, &caps),
2961 Some(SurfaceContextReuseFailure::CapabilitiesDeferred(
2962 SurfaceConfigDeferredReason::NoFormats
2963 ))
2964 );
2965 }
2966
2967 #[test]
2968 fn reusable_surface_config_keeps_the_existing_pipeline_format() {
2969 let caps = caps_with_present_modes(vec![wgpu::PresentMode::Fifo]);
2970 let config = build_surface_config_for_format(
2971 &caps,
2972 PhysicalExtent::new(640, 480),
2973 1,
2974 false,
2975 wgpu::TextureFormat::Bgra8Unorm,
2976 )
2977 .expect("surface config");
2978
2979 assert_eq!(config.format, wgpu::TextureFormat::Bgra8Unorm);
2980 assert_eq!((config.width, config.height), (640, 480));
2981 }
2982}