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}