1use std::sync::Arc;
9
10pub use raw_window_handle;
11pub use wgpu;
12pub use winit;
13
14use winit::window::Window;
15
16#[derive(Debug)]
18pub enum SurfaceError {
19 Lost,
20 Outdated,
21 OutOfMemory,
22 Timeout,
23 Other(String),
24}
25
26impl std::fmt::Display for SurfaceError {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::Lost => write!(f, "surface lost"),
30 Self::Outdated => write!(f, "surface outdated"),
31 Self::OutOfMemory => write!(f, "surface out of memory"),
32 Self::Timeout => write!(f, "surface timeout"),
33 Self::Other(s) => write!(f, "surface error: {s}"),
34 }
35 }
36}
37
38impl std::error::Error for SurfaceError {}
39
40#[derive(Debug)]
42pub enum HalError {
43 NoAdapter,
44 RequestDevice(String),
45 CreateSurface(String),
46}
47
48impl std::fmt::Display for HalError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::NoAdapter => write!(f, "no GPU adapter available"),
52 Self::RequestDevice(s) => write!(f, "request_device failed: {s}"),
53 Self::CreateSurface(s) => write!(f, "create_surface failed: {s}"),
54 }
55 }
56}
57
58impl std::error::Error for HalError {}
59
60pub trait Surface {
71 fn size(&self) -> (u32, u32);
72 fn resize(&mut self, width: u32, height: u32);
73 fn acquire(&mut self) -> Result<Frame, SurfaceError>;
75 fn present(&mut self, frame: Frame, hal: &Hal);
77}
78
79pub struct Frame {
82 surface_texture: wgpu::SurfaceTexture,
83 surface_view: wgpu::TextureView,
84 intermediate_view: wgpu::TextureView,
85 overlay_view: wgpu::TextureView,
91 width: u32,
92 height: u32,
93}
94
95impl Frame {
96 pub fn view(&self) -> &wgpu::TextureView {
97 &self.intermediate_view
98 }
99
100 pub fn overlay_view(&self) -> &wgpu::TextureView {
103 &self.overlay_view
104 }
105
106 pub fn size(&self) -> (u32, u32) {
107 (self.width, self.height)
108 }
109}
110
111pub struct Hal {
114 pub instance: wgpu::Instance,
115 pub adapter: wgpu::Adapter,
116 pub device: wgpu::Device,
117 pub queue: wgpu::Queue,
118}
119
120impl Hal {
121 pub async fn new(
125 compatible_surface: Option<&wgpu::Surface<'static>>,
126 ) -> Result<Self, HalError> {
127 let opts = wgpu::RequestAdapterOptions {
128 power_preference: wgpu::PowerPreference::HighPerformance,
129 force_fallback_adapter: false,
130 compatible_surface,
131 };
132 let primary = wgpu::Instance::new(&wgpu::InstanceDescriptor {
144 backends: wgpu::Backends::PRIMARY,
145 ..Default::default()
146 });
147 let (instance, adapter) = match primary.request_adapter(&opts).await {
148 Ok(a) => (primary, a),
149 Err(_) => {
150 let all = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
151 let a = all
152 .request_adapter(&opts)
153 .await
154 .map_err(|_| HalError::NoAdapter)?;
155 (all, a)
156 }
157 };
158 let limits = wgpu::Limits::default().using_resolution(adapter.limits());
163 let (device, queue) = adapter
164 .request_device(&wgpu::DeviceDescriptor {
165 label: Some("llimphi-hal-device"),
166 required_features: wgpu::Features::empty(),
167 required_limits: limits,
168 memory_hints: wgpu::MemoryHints::Performance,
169 experimental_features: wgpu::ExperimentalFeatures::default(),
170 trace: wgpu::Trace::Off,
171 })
172 .await
173 .map_err(|e| HalError::RequestDevice(e.to_string()))?;
174 Ok(Self {
175 instance,
176 adapter,
177 device,
178 queue,
179 })
180 }
181
182 pub async unsafe fn new_for_raw_surface(
205 make_target: impl Fn() -> wgpu::SurfaceTargetUnsafe,
206 width: u32,
207 height: u32,
208 ) -> Result<(Self, RawSurface), HalError> {
209 let primary = wgpu::Instance::new(&wgpu::InstanceDescriptor {
212 backends: wgpu::Backends::PRIMARY,
213 ..Default::default()
214 });
215 let prim_surface = unsafe { primary.create_surface_unsafe(make_target()) }
216 .map_err(|e| HalError::CreateSurface(e.to_string()))?;
217 let prim_adapter = primary
218 .request_adapter(&wgpu::RequestAdapterOptions {
219 power_preference: wgpu::PowerPreference::HighPerformance,
220 force_fallback_adapter: false,
221 compatible_surface: Some(&prim_surface),
222 })
223 .await;
224 let (instance, adapter, wgpu_surface) = match prim_adapter {
225 Ok(a) => (primary, a, prim_surface),
226 Err(_) => {
227 let all = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
228 let surface = unsafe { all.create_surface_unsafe(make_target()) }
229 .map_err(|e| HalError::CreateSurface(e.to_string()))?;
230 let a = all
231 .request_adapter(&wgpu::RequestAdapterOptions {
232 power_preference: wgpu::PowerPreference::HighPerformance,
233 force_fallback_adapter: false,
234 compatible_surface: Some(&surface),
235 })
236 .await
237 .map_err(|_| HalError::NoAdapter)?;
238 (all, a, surface)
239 }
240 };
241 let limits = wgpu::Limits::default().using_resolution(adapter.limits());
242 let (device, queue) = adapter
243 .request_device(&wgpu::DeviceDescriptor {
244 label: Some("llimphi-hal-device"),
245 required_features: wgpu::Features::empty(),
246 required_limits: limits,
247 memory_hints: wgpu::MemoryHints::Performance,
248 experimental_features: wgpu::ExperimentalFeatures::default(),
249 trace: wgpu::Trace::Off,
250 })
251 .await
252 .map_err(|e| HalError::RequestDevice(e.to_string()))?;
253 let hal = Self {
254 instance,
255 adapter,
256 device,
257 queue,
258 };
259 let surface = RawSurface::from_surface(&hal, wgpu_surface, width, height)?;
260 Ok((hal, surface))
261 }
262}
263
264pub struct WinitSurface {
268 _window: Arc<Window>,
269 surface: wgpu::Surface<'static>,
270 config: wgpu::SurfaceConfiguration,
271 device: wgpu::Device,
272 intermediate: wgpu::Texture,
273 intermediate_view: wgpu::TextureView,
274 overlay: wgpu::Texture,
276 overlay_view: wgpu::TextureView,
277 blitter: wgpu::util::TextureBlitter,
278}
279
280const INTERMEDIATE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
281
282impl WinitSurface {
283 pub fn new(hal: &Hal, window: Arc<Window>) -> Result<Self, HalError> {
291 let surface = hal
292 .instance
293 .create_surface(window.clone())
294 .map_err(|e| HalError::CreateSurface(e.to_string()))?;
295 Self::from_surface(hal, window, surface)
296 }
297
298 pub fn from_surface(
311 hal: &Hal,
312 window: Arc<Window>,
313 surface: wgpu::Surface<'static>,
314 ) -> Result<Self, HalError> {
315 let size = window.inner_size();
316 let caps = surface.get_capabilities(&hal.adapter);
317 let format = caps
320 .formats
321 .iter()
322 .copied()
323 .find(|f| matches!(f, wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm))
324 .unwrap_or(caps.formats[0]);
325 let config = wgpu::SurfaceConfiguration {
326 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
329 format,
330 width: size.width.max(1),
331 height: size.height.max(1),
332 present_mode: choose_present_mode(&caps),
333 desired_maximum_frame_latency: 2,
334 alpha_mode: caps.alpha_modes[0],
335 view_formats: vec![],
336 };
337 surface.configure(&hal.device, &config);
338 let (intermediate, intermediate_view) =
339 create_intermediate(&hal.device, config.width, config.height);
340 let (overlay, overlay_view) =
341 create_intermediate(&hal.device, config.width, config.height);
342 let blitter = wgpu::util::TextureBlitter::new(&hal.device, format);
343 Ok(Self {
344 _window: window,
345 surface,
346 config,
347 device: hal.device.clone(),
348 intermediate,
349 intermediate_view,
350 overlay,
351 overlay_view,
352 blitter,
353 })
354 }
355
356 pub fn format(&self) -> wgpu::TextureFormat {
357 self.config.format
358 }
359}
360
361pub struct RawSurface {
369 surface: wgpu::Surface<'static>,
370 config: wgpu::SurfaceConfiguration,
371 device: wgpu::Device,
372 intermediate: wgpu::Texture,
373 intermediate_view: wgpu::TextureView,
374 overlay: wgpu::Texture,
375 overlay_view: wgpu::TextureView,
376 blitter: wgpu::util::TextureBlitter,
377}
378
379impl RawSurface {
380 pub fn from_surface(
382 hal: &Hal,
383 surface: wgpu::Surface<'static>,
384 width: u32,
385 height: u32,
386 ) -> Result<Self, HalError> {
387 let caps = surface.get_capabilities(&hal.adapter);
388 let info = hal.adapter.get_info();
389 let format = match caps
393 .formats
394 .iter()
395 .copied()
396 .find(|f| matches!(f, wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm))
397 .or_else(|| caps.formats.first().copied())
398 {
399 Some(f) => f,
400 None => {
401 return Err(HalError::CreateSurface(format!(
402 "la superficie no expone formatos (adapter {:?}/{:?}): el compositor no la soporta por {:?} WSI",
403 info.backend, info.device_type, info.backend
404 )))
405 }
406 };
407 let alpha_mode = {
419 use wgpu::CompositeAlphaMode as Mode;
420 let want = [
421 Mode::PreMultiplied,
422 Mode::PostMultiplied,
423 Mode::Inherit,
424 Mode::Auto,
425 ];
426 want.iter()
427 .copied()
428 .find(|m| caps.alpha_modes.contains(m))
429 .or_else(|| caps.alpha_modes.first().copied())
430 .unwrap_or(Mode::Auto)
431 };
432 let config = wgpu::SurfaceConfiguration {
433 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
434 format,
435 width: width.max(1),
436 height: height.max(1),
437 present_mode: choose_present_mode(&caps),
438 desired_maximum_frame_latency: 2,
439 alpha_mode,
440 view_formats: vec![],
441 };
442 surface.configure(&hal.device, &config);
443 let (intermediate, intermediate_view) =
444 create_intermediate(&hal.device, config.width, config.height);
445 let (overlay, overlay_view) =
446 create_intermediate(&hal.device, config.width, config.height);
447 let blitter = wgpu::util::TextureBlitter::new(&hal.device, format);
448 Ok(Self {
449 surface,
450 config,
451 device: hal.device.clone(),
452 intermediate,
453 intermediate_view,
454 overlay,
455 overlay_view,
456 blitter,
457 })
458 }
459
460 pub fn format(&self) -> wgpu::TextureFormat {
461 self.config.format
462 }
463}
464
465impl Surface for RawSurface {
466 fn size(&self) -> (u32, u32) {
467 (self.config.width, self.config.height)
468 }
469
470 fn resize(&mut self, width: u32, height: u32) {
471 let (w, h) = (width.max(1), height.max(1));
472 if self.config.width == w && self.config.height == h {
479 return;
480 }
481 self.config.width = w;
482 self.config.height = h;
483 self.surface.configure(&self.device, &self.config);
484 let (tex, view) = create_intermediate(&self.device, self.config.width, self.config.height);
485 self.intermediate = tex;
486 self.intermediate_view = view;
487 let (otex, oview) =
488 create_intermediate(&self.device, self.config.width, self.config.height);
489 self.overlay = otex;
490 self.overlay_view = oview;
491 }
492
493 fn acquire(&mut self) -> Result<Frame, SurfaceError> {
494 let texture = match self.surface.get_current_texture() {
495 Ok(t) => t,
496 Err(e @ (wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost)) => {
501 self.surface.configure(&self.device, &self.config);
502 self.surface.get_current_texture().map_err(|_| match e {
503 wgpu::SurfaceError::Lost => SurfaceError::Lost,
504 _ => SurfaceError::Outdated,
505 })?
506 }
507 Err(wgpu::SurfaceError::OutOfMemory) => return Err(SurfaceError::OutOfMemory),
508 Err(wgpu::SurfaceError::Timeout) => return Err(SurfaceError::Timeout),
509 Err(other) => return Err(SurfaceError::Other(format!("{other:?}"))),
510 };
511 let surface_view = texture
512 .texture
513 .create_view(&wgpu::TextureViewDescriptor::default());
514 Ok(Frame {
515 surface_texture: texture,
516 surface_view,
517 intermediate_view: self.intermediate_view.clone(),
518 overlay_view: self.overlay_view.clone(),
519 width: self.config.width,
520 height: self.config.height,
521 })
522 }
523
524 fn present(&mut self, frame: Frame, hal: &Hal) {
525 let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
526 label: Some("llimphi-blit-raw"),
527 });
528 self.blitter.copy(
529 &hal.device,
530 &mut encoder,
531 &frame.intermediate_view,
532 &frame.surface_view,
533 );
534 hal.queue.submit(std::iter::once(encoder.finish()));
535 frame.surface_texture.present();
536 }
537}
538
539fn choose_present_mode(caps: &wgpu::SurfaceCapabilities) -> wgpu::PresentMode {
555 use wgpu::PresentMode::{Fifo, FifoRelaxed, Immediate, Mailbox};
556 if let Ok(v) = std::env::var("LLIMPHI_PRESENT_MODE") {
557 let want = match v.trim().to_ascii_lowercase().as_str() {
558 "fifo" | "vsync" => Some(Fifo),
559 "fifo_relaxed" | "fiforelaxed" => Some(FifoRelaxed),
560 "mailbox" => Some(Mailbox),
561 "immediate" | "novsync" => Some(Immediate),
562 _ => None,
563 };
564 if let Some(m) = want {
565 if caps.present_modes.contains(&m) {
566 return m;
567 }
568 }
569 }
570 if caps.present_modes.contains(&Mailbox) {
571 Mailbox
572 } else {
573 Fifo
574 }
575}
576
577fn create_intermediate(
578 device: &wgpu::Device,
579 width: u32,
580 height: u32,
581) -> (wgpu::Texture, wgpu::TextureView) {
582 let texture = device.create_texture(&wgpu::TextureDescriptor {
583 label: Some("llimphi-intermediate"),
584 size: wgpu::Extent3d {
585 width,
586 height,
587 depth_or_array_layers: 1,
588 },
589 mip_level_count: 1,
590 sample_count: 1,
591 dimension: wgpu::TextureDimension::D2,
592 format: INTERMEDIATE_FORMAT,
593 usage: wgpu::TextureUsages::STORAGE_BINDING
599 | wgpu::TextureUsages::TEXTURE_BINDING
600 | wgpu::TextureUsages::RENDER_ATTACHMENT,
601 view_formats: &[],
602 });
603 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
604 (texture, view)
605}
606
607pub struct OverlayCompositor {
620 pipeline: wgpu::RenderPipeline,
621 sampler: wgpu::Sampler,
622 bind_layout: wgpu::BindGroupLayout,
623}
624
625impl OverlayCompositor {
626 pub fn new(device: &wgpu::Device) -> Self {
627 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
628 label: Some("llimphi-overlay-composite"),
629 source: wgpu::ShaderSource::Wgsl(OVERLAY_COMPOSITE_WGSL.into()),
630 });
631 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
632 label: Some("llimphi-overlay-bgl"),
633 entries: &[
634 wgpu::BindGroupLayoutEntry {
635 binding: 0,
636 visibility: wgpu::ShaderStages::FRAGMENT,
637 ty: wgpu::BindingType::Texture {
638 sample_type: wgpu::TextureSampleType::Float { filterable: true },
639 view_dimension: wgpu::TextureViewDimension::D2,
640 multisampled: false,
641 },
642 count: None,
643 },
644 wgpu::BindGroupLayoutEntry {
645 binding: 1,
646 visibility: wgpu::ShaderStages::FRAGMENT,
647 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
648 count: None,
649 },
650 ],
651 });
652 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
653 label: Some("llimphi-overlay-pl"),
654 bind_group_layouts: &[&bind_layout],
655 push_constant_ranges: &[],
656 });
657 let straight = std::env::var("LLIMPHI_OVERLAY_BLEND")
660 .map(|v| v.trim().eq_ignore_ascii_case("straight"))
661 .unwrap_or(false);
662 let color_src = if straight {
663 wgpu::BlendFactor::SrcAlpha
664 } else {
665 wgpu::BlendFactor::One
666 };
667 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
668 label: Some("llimphi-overlay-pipe"),
669 layout: Some(&pipeline_layout),
670 vertex: wgpu::VertexState {
671 module: &shader,
672 entry_point: Some("vs"),
673 buffers: &[],
674 compilation_options: Default::default(),
675 },
676 fragment: Some(wgpu::FragmentState {
677 module: &shader,
678 entry_point: Some("fs"),
679 targets: &[Some(wgpu::ColorTargetState {
680 format: INTERMEDIATE_FORMAT,
681 blend: Some(wgpu::BlendState {
682 color: wgpu::BlendComponent {
683 src_factor: color_src,
684 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
685 operation: wgpu::BlendOperation::Add,
686 },
687 alpha: wgpu::BlendComponent {
688 src_factor: wgpu::BlendFactor::One,
689 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
690 operation: wgpu::BlendOperation::Add,
691 },
692 }),
693 write_mask: wgpu::ColorWrites::ALL,
694 })],
695 compilation_options: Default::default(),
696 }),
697 primitive: wgpu::PrimitiveState::default(),
698 depth_stencil: None,
699 multisample: wgpu::MultisampleState::default(),
700 multiview: None,
701 cache: None,
702 });
703 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
704 label: Some("llimphi-overlay-sampler"),
705 ..Default::default()
706 });
707 OverlayCompositor {
708 pipeline,
709 sampler,
710 bind_layout,
711 }
712 }
713
714 pub fn composite(
718 &self,
719 device: &wgpu::Device,
720 encoder: &mut wgpu::CommandEncoder,
721 target: &wgpu::TextureView,
722 source: &wgpu::TextureView,
723 ) {
724 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
725 label: Some("llimphi-overlay-bg"),
726 layout: &self.bind_layout,
727 entries: &[
728 wgpu::BindGroupEntry {
729 binding: 0,
730 resource: wgpu::BindingResource::TextureView(source),
731 },
732 wgpu::BindGroupEntry {
733 binding: 1,
734 resource: wgpu::BindingResource::Sampler(&self.sampler),
735 },
736 ],
737 });
738 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
739 label: Some("llimphi-overlay-composite-pass"),
740 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
741 view: target,
742 resolve_target: None,
743 depth_slice: None,
744 ops: wgpu::Operations {
745 load: wgpu::LoadOp::Load,
746 store: wgpu::StoreOp::Store,
747 },
748 })],
749 depth_stencil_attachment: None,
750 timestamp_writes: None,
751 occlusion_query_set: None,
752 });
753 pass.set_pipeline(&self.pipeline);
754 pass.set_bind_group(0, &bind_group, &[]);
755 pass.draw(0..3, 0..1);
756 }
757}
758
759const OVERLAY_COMPOSITE_WGSL: &str = r#"
763struct VsOut {
764 @builtin(position) pos: vec4<f32>,
765 @location(0) uv: vec2<f32>,
766};
767
768@vertex
769fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
770 var corners = array<vec2<f32>, 3>(
771 vec2<f32>(-1.0, -1.0),
772 vec2<f32>( 3.0, -1.0),
773 vec2<f32>(-1.0, 3.0),
774 );
775 let xy = corners[vi];
776 var out: VsOut;
777 out.pos = vec4<f32>(xy, 0.0, 1.0);
778 out.uv = vec2<f32>((xy.x + 1.0) * 0.5, (1.0 - xy.y) * 0.5);
779 return out;
780}
781
782@group(0) @binding(0) var src_tex: texture_2d<f32>;
783@group(0) @binding(1) var src_samp: sampler;
784
785@fragment
786fn fs(in: VsOut) -> @location(0) vec4<f32> {
787 return textureSample(src_tex, src_samp, in.uv);
788}
789"#;
790
791pub struct BlurCompositor {
814 pipeline: wgpu::RenderPipeline,
815 sampler: wgpu::Sampler,
816 bind_layout: wgpu::BindGroupLayout,
817 scratch: Option<BlurScratch>,
818}
819
820struct BlurScratch {
821 _texture: wgpu::Texture,
822 view: wgpu::TextureView,
823 width: u32,
824 height: u32,
825}
826
827#[repr(C)]
831#[derive(Clone, Copy)]
832struct BlurUniforms {
833 direction: [f32; 2],
834 pixel_size: [f32; 2],
835 sigma: f32,
836 radius: f32,
837 _pad: [f32; 2],
838}
839
840const BLUR_UBO_SIZE: u64 = std::mem::size_of::<BlurUniforms>() as u64;
841const BLUR_MAX_RADIUS: f32 = 32.0;
842
843impl BlurCompositor {
844 pub fn new(device: &wgpu::Device) -> Self {
845 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
846 label: Some("llimphi-blur-shader"),
847 source: wgpu::ShaderSource::Wgsl(BLUR_WGSL.into()),
848 });
849 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
850 label: Some("llimphi-blur-bgl"),
851 entries: &[
852 wgpu::BindGroupLayoutEntry {
853 binding: 0,
854 visibility: wgpu::ShaderStages::FRAGMENT,
855 ty: wgpu::BindingType::Texture {
856 sample_type: wgpu::TextureSampleType::Float { filterable: true },
857 view_dimension: wgpu::TextureViewDimension::D2,
858 multisampled: false,
859 },
860 count: None,
861 },
862 wgpu::BindGroupLayoutEntry {
863 binding: 1,
864 visibility: wgpu::ShaderStages::FRAGMENT,
865 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
866 count: None,
867 },
868 wgpu::BindGroupLayoutEntry {
869 binding: 2,
870 visibility: wgpu::ShaderStages::FRAGMENT,
871 ty: wgpu::BindingType::Buffer {
872 ty: wgpu::BufferBindingType::Uniform,
873 has_dynamic_offset: false,
874 min_binding_size: None,
875 },
876 count: None,
877 },
878 ],
879 });
880 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
881 label: Some("llimphi-blur-pl"),
882 bind_group_layouts: &[&bind_layout],
883 push_constant_ranges: &[],
884 });
885 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
886 label: Some("llimphi-blur-pipe"),
887 layout: Some(&pipeline_layout),
888 vertex: wgpu::VertexState {
889 module: &shader,
890 entry_point: Some("vs"),
891 buffers: &[],
892 compilation_options: Default::default(),
893 },
894 fragment: Some(wgpu::FragmentState {
895 module: &shader,
896 entry_point: Some("fs"),
897 targets: &[Some(wgpu::ColorTargetState {
898 format: INTERMEDIATE_FORMAT,
899 blend: None,
903 write_mask: wgpu::ColorWrites::ALL,
904 })],
905 compilation_options: Default::default(),
906 }),
907 primitive: wgpu::PrimitiveState::default(),
908 depth_stencil: None,
909 multisample: wgpu::MultisampleState::default(),
910 multiview: None,
911 cache: None,
912 });
913 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
914 label: Some("llimphi-blur-sampler"),
915 address_mode_u: wgpu::AddressMode::ClampToEdge,
916 address_mode_v: wgpu::AddressMode::ClampToEdge,
917 address_mode_w: wgpu::AddressMode::ClampToEdge,
918 mag_filter: wgpu::FilterMode::Linear,
919 min_filter: wgpu::FilterMode::Linear,
920 mipmap_filter: wgpu::FilterMode::Nearest,
921 ..Default::default()
922 });
923 BlurCompositor {
924 pipeline,
925 sampler,
926 bind_layout,
927 scratch: None,
928 }
929 }
930
931 pub fn blur(
939 &mut self,
940 device: &wgpu::Device,
941 queue: &wgpu::Queue,
942 encoder: &mut wgpu::CommandEncoder,
943 target: &wgpu::TextureView,
944 viewport: (u32, u32),
945 rect: (f32, f32, f32, f32),
946 sigma: f32,
947 ) {
948 let (vw, vh) = viewport;
949 if vw == 0 || vh == 0 || sigma <= 0.0 {
950 return;
951 }
952 let (rx, ry, rw, rh) = rect;
953 let x0 = rx.max(0.0) as u32;
956 let y0 = ry.max(0.0) as u32;
957 let x1 = (rx + rw).min(vw as f32).max(0.0) as u32;
958 let y1 = (ry + rh).min(vh as f32).max(0.0) as u32;
959 if x1 <= x0 || y1 <= y0 {
960 return;
961 }
962 let scissor = (x0, y0, x1 - x0, y1 - y0);
963
964 let need_new = match &self.scratch {
966 Some(s) => s.width != vw || s.height != vh,
967 None => true,
968 };
969 if need_new {
970 let texture = device.create_texture(&wgpu::TextureDescriptor {
971 label: Some("llimphi-blur-scratch"),
972 size: wgpu::Extent3d {
973 width: vw,
974 height: vh,
975 depth_or_array_layers: 1,
976 },
977 mip_level_count: 1,
978 sample_count: 1,
979 dimension: wgpu::TextureDimension::D2,
980 format: INTERMEDIATE_FORMAT,
981 usage: wgpu::TextureUsages::TEXTURE_BINDING
982 | wgpu::TextureUsages::RENDER_ATTACHMENT,
983 view_formats: &[],
984 });
985 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
986 self.scratch = Some(BlurScratch {
987 _texture: texture,
988 view,
989 width: vw,
990 height: vh,
991 });
992 }
993 let scratch_view = &self.scratch.as_ref().expect("scratch creado arriba").view;
994
995 let radius = (sigma * 3.0).ceil().min(BLUR_MAX_RADIUS);
996 let pixel_size = [1.0 / vw as f32, 1.0 / vh as f32];
997 let ubo_h_data = BlurUniforms {
998 direction: [1.0, 0.0],
999 pixel_size,
1000 sigma,
1001 radius,
1002 _pad: [0.0, 0.0],
1003 };
1004 let ubo_v_data = BlurUniforms {
1005 direction: [0.0, 1.0],
1006 pixel_size,
1007 sigma,
1008 radius,
1009 _pad: [0.0, 0.0],
1010 };
1011 let ubo_h = device.create_buffer(&wgpu::BufferDescriptor {
1015 label: Some("llimphi-blur-ubo-h"),
1016 size: BLUR_UBO_SIZE,
1017 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1018 mapped_at_creation: false,
1019 });
1020 let ubo_v = device.create_buffer(&wgpu::BufferDescriptor {
1021 label: Some("llimphi-blur-ubo-v"),
1022 size: BLUR_UBO_SIZE,
1023 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1024 mapped_at_creation: false,
1025 });
1026 queue.write_buffer(&ubo_h, 0, bytemuck_cast(&ubo_h_data));
1027 queue.write_buffer(&ubo_v, 0, bytemuck_cast(&ubo_v_data));
1028
1029 let bg_h = device.create_bind_group(&wgpu::BindGroupDescriptor {
1031 label: Some("llimphi-blur-bg-h"),
1032 layout: &self.bind_layout,
1033 entries: &[
1034 wgpu::BindGroupEntry {
1035 binding: 0,
1036 resource: wgpu::BindingResource::TextureView(target),
1037 },
1038 wgpu::BindGroupEntry {
1039 binding: 1,
1040 resource: wgpu::BindingResource::Sampler(&self.sampler),
1041 },
1042 wgpu::BindGroupEntry {
1043 binding: 2,
1044 resource: ubo_h.as_entire_binding(),
1045 },
1046 ],
1047 });
1048 {
1049 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1050 label: Some("llimphi-blur-pass-h"),
1051 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1052 view: scratch_view,
1053 resolve_target: None,
1054 depth_slice: None,
1055 ops: wgpu::Operations {
1056 load: wgpu::LoadOp::Load,
1059 store: wgpu::StoreOp::Store,
1060 },
1061 })],
1062 depth_stencil_attachment: None,
1063 timestamp_writes: None,
1064 occlusion_query_set: None,
1065 });
1066 pass.set_pipeline(&self.pipeline);
1067 pass.set_bind_group(0, &bg_h, &[]);
1068 pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
1069 pass.draw(0..3, 0..1);
1070 }
1071
1072 let bg_v = device.create_bind_group(&wgpu::BindGroupDescriptor {
1074 label: Some("llimphi-blur-bg-v"),
1075 layout: &self.bind_layout,
1076 entries: &[
1077 wgpu::BindGroupEntry {
1078 binding: 0,
1079 resource: wgpu::BindingResource::TextureView(scratch_view),
1080 },
1081 wgpu::BindGroupEntry {
1082 binding: 1,
1083 resource: wgpu::BindingResource::Sampler(&self.sampler),
1084 },
1085 wgpu::BindGroupEntry {
1086 binding: 2,
1087 resource: ubo_v.as_entire_binding(),
1088 },
1089 ],
1090 });
1091 {
1092 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1093 label: Some("llimphi-blur-pass-v"),
1094 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1095 view: target,
1096 resolve_target: None,
1097 depth_slice: None,
1098 ops: wgpu::Operations {
1099 load: wgpu::LoadOp::Load,
1100 store: wgpu::StoreOp::Store,
1101 },
1102 })],
1103 depth_stencil_attachment: None,
1104 timestamp_writes: None,
1105 occlusion_query_set: None,
1106 });
1107 pass.set_pipeline(&self.pipeline);
1108 pass.set_bind_group(0, &bg_v, &[]);
1109 pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
1110 pass.draw(0..3, 0..1);
1111 }
1112 }
1113}
1114
1115pub struct ColorFilterCompositor {
1123 pipeline: wgpu::RenderPipeline,
1124 sampler: wgpu::Sampler,
1125 bind_layout: wgpu::BindGroupLayout,
1126 scratch: Option<BlurScratch>,
1127}
1128
1129#[repr(C)]
1132#[derive(Clone, Copy)]
1133struct ColorUniforms {
1134 r: [f32; 4],
1135 g: [f32; 4],
1136 b: [f32; 4],
1137 a: [f32; 4],
1138 bias: [f32; 4],
1139}
1140
1141const COLOR_UBO_SIZE: u64 = std::mem::size_of::<ColorUniforms>() as u64;
1142
1143const COLOR_IDENTITY: ColorUniforms = ColorUniforms {
1145 r: [1.0, 0.0, 0.0, 0.0],
1146 g: [0.0, 1.0, 0.0, 0.0],
1147 b: [0.0, 0.0, 1.0, 0.0],
1148 a: [0.0, 0.0, 0.0, 1.0],
1149 bias: [0.0, 0.0, 0.0, 0.0],
1150};
1151
1152impl ColorFilterCompositor {
1153 pub fn new(device: &wgpu::Device) -> Self {
1154 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1155 label: Some("llimphi-color-filter-shader"),
1156 source: wgpu::ShaderSource::Wgsl(COLOR_WGSL.into()),
1157 });
1158 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1159 label: Some("llimphi-color-filter-bgl"),
1160 entries: &[
1161 wgpu::BindGroupLayoutEntry {
1162 binding: 0,
1163 visibility: wgpu::ShaderStages::FRAGMENT,
1164 ty: wgpu::BindingType::Texture {
1165 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1166 view_dimension: wgpu::TextureViewDimension::D2,
1167 multisampled: false,
1168 },
1169 count: None,
1170 },
1171 wgpu::BindGroupLayoutEntry {
1172 binding: 1,
1173 visibility: wgpu::ShaderStages::FRAGMENT,
1174 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1175 count: None,
1176 },
1177 wgpu::BindGroupLayoutEntry {
1178 binding: 2,
1179 visibility: wgpu::ShaderStages::FRAGMENT,
1180 ty: wgpu::BindingType::Buffer {
1181 ty: wgpu::BufferBindingType::Uniform,
1182 has_dynamic_offset: false,
1183 min_binding_size: None,
1184 },
1185 count: None,
1186 },
1187 ],
1188 });
1189 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1190 label: Some("llimphi-color-filter-pl"),
1191 bind_group_layouts: &[&bind_layout],
1192 push_constant_ranges: &[],
1193 });
1194 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1195 label: Some("llimphi-color-filter-pipe"),
1196 layout: Some(&pipeline_layout),
1197 vertex: wgpu::VertexState {
1198 module: &shader,
1199 entry_point: Some("vs"),
1200 buffers: &[],
1201 compilation_options: Default::default(),
1202 },
1203 fragment: Some(wgpu::FragmentState {
1204 module: &shader,
1205 entry_point: Some("fs"),
1206 targets: &[Some(wgpu::ColorTargetState {
1207 format: INTERMEDIATE_FORMAT,
1208 blend: None,
1211 write_mask: wgpu::ColorWrites::ALL,
1212 })],
1213 compilation_options: Default::default(),
1214 }),
1215 primitive: wgpu::PrimitiveState::default(),
1216 depth_stencil: None,
1217 multisample: wgpu::MultisampleState::default(),
1218 multiview: None,
1219 cache: None,
1220 });
1221 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1222 label: Some("llimphi-color-filter-sampler"),
1223 address_mode_u: wgpu::AddressMode::ClampToEdge,
1224 address_mode_v: wgpu::AddressMode::ClampToEdge,
1225 address_mode_w: wgpu::AddressMode::ClampToEdge,
1226 mag_filter: wgpu::FilterMode::Nearest,
1227 min_filter: wgpu::FilterMode::Nearest,
1228 mipmap_filter: wgpu::FilterMode::Nearest,
1229 ..Default::default()
1230 });
1231 ColorFilterCompositor {
1232 pipeline,
1233 sampler,
1234 bind_layout,
1235 scratch: None,
1236 }
1237 }
1238
1239 pub fn apply(
1244 &mut self,
1245 device: &wgpu::Device,
1246 queue: &wgpu::Queue,
1247 encoder: &mut wgpu::CommandEncoder,
1248 target: &wgpu::TextureView,
1249 viewport: (u32, u32),
1250 rect: (f32, f32, f32, f32),
1251 matrix: [f32; 20],
1252 ) {
1253 let (vw, vh) = viewport;
1254 if vw == 0 || vh == 0 {
1255 return;
1256 }
1257 let (rx, ry, rw, rh) = rect;
1258 let x0 = rx.max(0.0) as u32;
1259 let y0 = ry.max(0.0) as u32;
1260 let x1 = (rx + rw).min(vw as f32).max(0.0) as u32;
1261 let y1 = (ry + rh).min(vh as f32).max(0.0) as u32;
1262 if x1 <= x0 || y1 <= y0 {
1263 return;
1264 }
1265 let scissor = (x0, y0, x1 - x0, y1 - y0);
1266
1267 let need_new = match &self.scratch {
1268 Some(s) => s.width != vw || s.height != vh,
1269 None => true,
1270 };
1271 if need_new {
1272 let texture = device.create_texture(&wgpu::TextureDescriptor {
1273 label: Some("llimphi-color-filter-scratch"),
1274 size: wgpu::Extent3d {
1275 width: vw,
1276 height: vh,
1277 depth_or_array_layers: 1,
1278 },
1279 mip_level_count: 1,
1280 sample_count: 1,
1281 dimension: wgpu::TextureDimension::D2,
1282 format: INTERMEDIATE_FORMAT,
1283 usage: wgpu::TextureUsages::TEXTURE_BINDING
1284 | wgpu::TextureUsages::RENDER_ATTACHMENT,
1285 view_formats: &[],
1286 });
1287 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1288 self.scratch = Some(BlurScratch {
1289 _texture: texture,
1290 view,
1291 width: vw,
1292 height: vh,
1293 });
1294 }
1295 let scratch_view = &self.scratch.as_ref().expect("scratch creado arriba").view;
1296
1297 let apply = ColorUniforms {
1300 r: [matrix[0], matrix[1], matrix[2], matrix[3]],
1301 g: [matrix[5], matrix[6], matrix[7], matrix[8]],
1302 b: [matrix[10], matrix[11], matrix[12], matrix[13]],
1303 a: [matrix[15], matrix[16], matrix[17], matrix[18]],
1304 bias: [matrix[4], matrix[9], matrix[14], matrix[19]],
1305 };
1306 let ubo_apply = device.create_buffer(&wgpu::BufferDescriptor {
1312 label: Some("llimphi-color-filter-ubo-apply"),
1313 size: COLOR_UBO_SIZE,
1314 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1315 mapped_at_creation: false,
1316 });
1317 let ubo_copy = device.create_buffer(&wgpu::BufferDescriptor {
1318 label: Some("llimphi-color-filter-ubo-copy"),
1319 size: COLOR_UBO_SIZE,
1320 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1321 mapped_at_creation: false,
1322 });
1323 queue.write_buffer(&ubo_apply, 0, bytemuck_cast(&apply));
1324 queue.write_buffer(&ubo_copy, 0, bytemuck_cast(&COLOR_IDENTITY));
1325
1326 let bg_apply = device.create_bind_group(&wgpu::BindGroupDescriptor {
1328 label: Some("llimphi-color-filter-bg-apply"),
1329 layout: &self.bind_layout,
1330 entries: &[
1331 wgpu::BindGroupEntry {
1332 binding: 0,
1333 resource: wgpu::BindingResource::TextureView(target),
1334 },
1335 wgpu::BindGroupEntry {
1336 binding: 1,
1337 resource: wgpu::BindingResource::Sampler(&self.sampler),
1338 },
1339 wgpu::BindGroupEntry {
1340 binding: 2,
1341 resource: ubo_apply.as_entire_binding(),
1342 },
1343 ],
1344 });
1345 {
1346 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1347 label: Some("llimphi-color-filter-pass-apply"),
1348 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1349 view: scratch_view,
1350 resolve_target: None,
1351 depth_slice: None,
1352 ops: wgpu::Operations {
1353 load: wgpu::LoadOp::Load,
1354 store: wgpu::StoreOp::Store,
1355 },
1356 })],
1357 depth_stencil_attachment: None,
1358 timestamp_writes: None,
1359 occlusion_query_set: None,
1360 });
1361 pass.set_pipeline(&self.pipeline);
1362 pass.set_bind_group(0, &bg_apply, &[]);
1363 pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
1364 pass.draw(0..3, 0..1);
1365 }
1366
1367 let bg_copy = device.create_bind_group(&wgpu::BindGroupDescriptor {
1369 label: Some("llimphi-color-filter-bg-copy"),
1370 layout: &self.bind_layout,
1371 entries: &[
1372 wgpu::BindGroupEntry {
1373 binding: 0,
1374 resource: wgpu::BindingResource::TextureView(scratch_view),
1375 },
1376 wgpu::BindGroupEntry {
1377 binding: 1,
1378 resource: wgpu::BindingResource::Sampler(&self.sampler),
1379 },
1380 wgpu::BindGroupEntry {
1381 binding: 2,
1382 resource: ubo_copy.as_entire_binding(),
1383 },
1384 ],
1385 });
1386 {
1387 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1388 label: Some("llimphi-color-filter-pass-copy"),
1389 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1390 view: target,
1391 resolve_target: None,
1392 depth_slice: None,
1393 ops: wgpu::Operations {
1394 load: wgpu::LoadOp::Load,
1395 store: wgpu::StoreOp::Store,
1396 },
1397 })],
1398 depth_stencil_attachment: None,
1399 timestamp_writes: None,
1400 occlusion_query_set: None,
1401 });
1402 pass.set_pipeline(&self.pipeline);
1403 pass.set_bind_group(0, &bg_copy, &[]);
1404 pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
1405 pass.draw(0..3, 0..1);
1406 }
1407 }
1408}
1409
1410fn bytemuck_cast<T: Copy>(v: &T) -> &[u8] {
1413 unsafe {
1414 std::slice::from_raw_parts(
1415 v as *const T as *const u8,
1416 std::mem::size_of::<T>(),
1417 )
1418 }
1419}
1420
1421const BLUR_WGSL: &str = r#"
1426struct VsOut {
1427 @builtin(position) pos: vec4<f32>,
1428 @location(0) uv: vec2<f32>,
1429};
1430
1431@vertex
1432fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
1433 var corners = array<vec2<f32>, 3>(
1434 vec2<f32>(-1.0, -1.0),
1435 vec2<f32>( 3.0, -1.0),
1436 vec2<f32>(-1.0, 3.0),
1437 );
1438 let xy = corners[vi];
1439 var out: VsOut;
1440 out.pos = vec4<f32>(xy, 0.0, 1.0);
1441 out.uv = vec2<f32>((xy.x + 1.0) * 0.5, (1.0 - xy.y) * 0.5);
1442 return out;
1443}
1444
1445struct BlurParams {
1446 direction: vec2<f32>,
1447 pixel_size: vec2<f32>,
1448 sigma: f32,
1449 radius: f32,
1450 _pad: vec2<f32>,
1451};
1452
1453@group(0) @binding(0) var src_tex: texture_2d<f32>;
1454@group(0) @binding(1) var src_samp: sampler;
1455@group(0) @binding(2) var<uniform> params: BlurParams;
1456
1457@fragment
1458fn fs(in: VsOut) -> @location(0) vec4<f32> {
1459 let dir = params.direction * params.pixel_size;
1460 let r = i32(params.radius);
1461 let two_sigma_sq = 2.0 * params.sigma * params.sigma;
1462 var acc = vec4<f32>(0.0);
1463 var weight_sum = 0.0;
1464 for (var i = -r; i <= r; i = i + 1) {
1465 let fi = f32(i);
1466 let w = exp(-(fi * fi) / two_sigma_sq);
1467 acc = acc + textureSample(src_tex, src_samp, in.uv + dir * fi) * w;
1468 weight_sum = weight_sum + w;
1469 }
1470 return acc / weight_sum;
1471}
1472"#;
1473
1474const COLOR_WGSL: &str = r#"
1477struct VsOut {
1478 @builtin(position) pos: vec4<f32>,
1479 @location(0) uv: vec2<f32>,
1480};
1481
1482@vertex
1483fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
1484 var corners = array<vec2<f32>, 3>(
1485 vec2<f32>(-1.0, -1.0),
1486 vec2<f32>( 3.0, -1.0),
1487 vec2<f32>(-1.0, 3.0),
1488 );
1489 let xy = corners[vi];
1490 var out: VsOut;
1491 out.pos = vec4<f32>(xy, 0.0, 1.0);
1492 out.uv = vec2<f32>((xy.x + 1.0) * 0.5, (1.0 - xy.y) * 0.5);
1493 return out;
1494}
1495
1496struct ColorParams {
1497 r: vec4<f32>,
1498 g: vec4<f32>,
1499 b: vec4<f32>,
1500 a: vec4<f32>,
1501 bias: vec4<f32>,
1502};
1503
1504@group(0) @binding(0) var src_tex: texture_2d<f32>;
1505@group(0) @binding(1) var src_samp: sampler;
1506@group(0) @binding(2) var<uniform> params: ColorParams;
1507
1508@fragment
1509fn fs(in: VsOut) -> @location(0) vec4<f32> {
1510 let c = textureSample(src_tex, src_samp, in.uv);
1511 var o: vec4<f32>;
1512 o.r = dot(params.r, c) + params.bias.r;
1513 o.g = dot(params.g, c) + params.bias.g;
1514 o.b = dot(params.b, c) + params.bias.b;
1515 o.a = dot(params.a, c) + params.bias.a;
1516 return clamp(o, vec4<f32>(0.0), vec4<f32>(1.0));
1517}
1518"#;
1519
1520impl Surface for WinitSurface {
1521 fn size(&self) -> (u32, u32) {
1522 (self.config.width, self.config.height)
1523 }
1524
1525 fn resize(&mut self, width: u32, height: u32) {
1526 self.config.width = width.max(1);
1527 self.config.height = height.max(1);
1528 self.surface.configure(&self.device, &self.config);
1529 let (tex, view) = create_intermediate(&self.device, self.config.width, self.config.height);
1530 self.intermediate = tex;
1531 self.intermediate_view = view;
1532 let (otex, oview) =
1533 create_intermediate(&self.device, self.config.width, self.config.height);
1534 self.overlay = otex;
1535 self.overlay_view = oview;
1536 }
1537
1538 fn acquire(&mut self) -> Result<Frame, SurfaceError> {
1539 let texture = self.surface.get_current_texture().map_err(|e| match e {
1540 wgpu::SurfaceError::Lost => SurfaceError::Lost,
1541 wgpu::SurfaceError::Outdated => SurfaceError::Outdated,
1542 wgpu::SurfaceError::OutOfMemory => SurfaceError::OutOfMemory,
1543 wgpu::SurfaceError::Timeout => SurfaceError::Timeout,
1544 other => SurfaceError::Other(format!("{other:?}")),
1545 })?;
1546 let surface_view = texture
1547 .texture
1548 .create_view(&wgpu::TextureViewDescriptor::default());
1549 Ok(Frame {
1552 surface_texture: texture,
1553 surface_view,
1554 intermediate_view: self.intermediate_view.clone(),
1555 overlay_view: self.overlay_view.clone(),
1556 width: self.config.width,
1557 height: self.config.height,
1558 })
1559 }
1560
1561 fn present(&mut self, frame: Frame, hal: &Hal) {
1562 let mut encoder = hal.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1563 label: Some("llimphi-blit"),
1564 });
1565 self.blitter.copy(
1566 &hal.device,
1567 &mut encoder,
1568 &frame.intermediate_view,
1569 &frame.surface_view,
1570 );
1571 hal.queue.submit(std::iter::once(encoder.finish()));
1572 frame.surface_texture.present();
1573 }
1574}