ailloli_ui_render_wgpu/backdrop_capture.rs
1//! backdrop filter: copy a region from the main framebuffer into an offscreen texture.
2
3use std::collections::HashMap;
4
5use ailloli_ui_core::Rect;
6
7use crate::offscreen_pool::{LeasedOffscreen, OffscreenSurfacePool, PoolKey};
8
9/// Blurred backdrop textures keyed by isolated pass id (keeps pool leases alive).
10///
11/// Bind groups and leases are retained together until compositing finishes, so
12/// pooled textures cannot be reused while sampled by the frame.
13///
14/// # Examples
15///
16/// ```
17/// use ailloli_ui_render_wgpu::BackdropTable;
18/// let table = BackdropTable::empty();
19/// assert!(table.bind_groups.is_empty());
20/// ```
21#[derive(Default)]
22pub struct BackdropTable {
23 /// Sample bindings indexed by the frame-local `u16` pass identifier.
24 pub bind_groups: HashMap<u16, wgpu::BindGroup>,
25 /// Offscreen leases retained until every backdrop consumer has rendered.
26 leases: Vec<LeasedOffscreen>,
27}
28
29impl BackdropTable {
30 /// Creates a table with no bindings or live pool leases.
31 ///
32 /// # Examples
33 ///
34 /// ```
35 /// use ailloli_ui_render_wgpu::BackdropTable;
36 /// assert!(BackdropTable::empty().get(0).is_none());
37 /// ```
38 pub fn empty() -> Self {
39 Self::default()
40 }
41
42 /// Returns the backdrop sample binding for `pass_id`.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use ailloli_ui_render_wgpu::BackdropTable;
48 /// assert!(BackdropTable::empty().get(7).is_none());
49 /// ```
50 pub fn get(&self, pass_id: u16) -> Option<&wgpu::BindGroup> {
51 self.bind_groups.get(&pass_id)
52 }
53
54 /// Inserts or replaces a binding and retains the associated pool lease.
55 ///
56 /// Replacing a binding does not remove the earlier lease; both remain live
57 /// until the table is dropped, preventing premature pool reuse.
58 ///
59 /// # Examples
60 ///
61 /// ```no_run
62 /// use ailloli_ui_render_wgpu::{BackdropTable, offscreen_pool::LeasedOffscreen};
63 /// fn retain(table: &mut BackdropTable, lease: LeasedOffscreen, binding: wgpu::BindGroup) {
64 /// table.insert(1, lease, binding);
65 /// }
66 /// ```
67 pub fn insert(&mut self, pass_id: u16, lease: LeasedOffscreen, bind_group: wgpu::BindGroup) {
68 self.bind_groups.insert(pass_id, bind_group);
69 self.leases.push(lease);
70 }
71}
72
73/// Physical-pixel copy from swapchain texture into a pooled offscreen target.
74///
75/// The origin is floored and clamped nonnegative. Extent is ceiled, forced to at
76/// least one pixel, and bounded by the lease dimensions. The caller must ensure
77/// the source texture also contains the resulting region and that `format`
78/// matches the lease; wgpu validation reports violations.
79///
80/// # Examples
81///
82/// ```no_run
83/// use ailloli_ui_core::Rect;
84/// use ailloli_ui_render_wgpu::{backdrop_capture::copy_swapchain_region_to_offscreen,
85/// offscreen_pool::{LeasedOffscreen, OffscreenSurfacePool}};
86/// fn copy(device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder,
87/// source: &wgpu::Texture, lease: &LeasedOffscreen, pool: &OffscreenSurfacePool) {
88/// copy_swapchain_region_to_offscreen(device, encoder, source,
89/// Rect::new(0.0, 0.0, 32.0, 32.0), lease, pool,
90/// wgpu::TextureFormat::Rgba8Unorm);
91/// }
92/// ```
93pub fn copy_swapchain_region_to_offscreen(
94 device: &wgpu::Device,
95 encoder: &mut wgpu::CommandEncoder,
96 src_texture: &wgpu::Texture,
97 rect: Rect,
98 lease: &LeasedOffscreen,
99 pool: &OffscreenSurfacePool,
100 format: wgpu::TextureFormat,
101) {
102 let x0 = rect.x.max(0.0).floor() as u32;
103 let y0 = rect.y.max(0.0).floor() as u32;
104 let w = rect.w.max(1.0).ceil() as u32;
105 let h = rect.h.max(1.0).ceil() as u32;
106 let w = w.min(lease.width.saturating_sub(x0.min(lease.width)));
107 let h = h.min(lease.height.saturating_sub(y0.min(lease.height)));
108 let w = w.max(1);
109 let h = h.max(1);
110
111 encoder.copy_texture_to_texture(
112 wgpu::ImageCopyTexture {
113 texture: src_texture,
114 mip_level: 0,
115 origin: wgpu::Origin3d { x: x0, y: y0, z: 0 },
116 aspect: wgpu::TextureAspect::All,
117 },
118 wgpu::ImageCopyTexture {
119 texture: lease.color_texture(pool),
120 mip_level: 0,
121 origin: wgpu::Origin3d::ZERO,
122 aspect: wgpu::TextureAspect::All,
123 },
124 wgpu::Extent3d {
125 width: w,
126 height: h,
127 depth_or_array_layers: 1,
128 },
129 );
130 let _ = device;
131 let _ = format;
132}
133
134/// Lease sized for a backdrop capture (no stencil).
135///
136/// Fractional width and height are ceiled and each axis is at least one physical
137/// pixel.
138///
139/// # Examples
140///
141/// ```no_run
142/// use ailloli_ui_core::Rect;
143/// use ailloli_ui_render_wgpu::{backdrop_capture::lease_backdrop_slot,
144/// offscreen_pool::OffscreenSurfacePool};
145/// fn lease(pool: &mut OffscreenSurfacePool, device: &wgpu::Device) {
146/// let slot = lease_backdrop_slot(pool, device, Rect::new(0.0, 0.0, 10.2, 4.1),
147/// wgpu::TextureFormat::Rgba8Unorm);
148/// assert_eq!((slot.width, slot.height), (11, 5));
149/// }
150/// ```
151pub fn lease_backdrop_slot(
152 pool: &mut OffscreenSurfacePool,
153 device: &wgpu::Device,
154 rect: Rect,
155 format: wgpu::TextureFormat,
156) -> LeasedOffscreen {
157 let w = rect.w.max(1.0).ceil() as u32;
158 let h = rect.h.max(1.0).ceil() as u32;
159 pool.lease(device, PoolKey::color(w, h, false), format)
160}