1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
//! Swapchain pool — N-backed present leases for retained schemes.
//!
//! A swapchain pool wraps a surface and supplies drawable backings for
//! [`PresentLease`] handles. Callers may acquire a concrete drawable early
//! (classic frame timing) or let [`crate::Scheme::submit`] defer acquire until
//! the present partition is about to run.
use crate::context::Context;
use crate::handles::TextureHandle;
use crate::surface::{Frame as SurfaceFrame, Surface};
use crate::types::{ResourceAccess, SurfaceConfig};
use anyhow::Result;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
#[cfg(test)]
use std::sync::atomic::AtomicBool;
pub(crate) struct SwapchainPoolInner {
surface: RwLock<Surface>,
#[allow(dead_code)]
depth: u32,
/// Advanced on resize / present-mode change so stale claims and retained
/// physical variants cannot be reused after backing recreation.
pub(crate) generation: Arc<AtomicU64>,
/// When true, the next [`SwapchainPool::acquire_slot`] fails once (scheme tests).
#[cfg(test)]
fail_next_acquire: AtomicBool,
}
impl SwapchainPoolInner {
pub(crate) fn generation(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
fn bump_generation(&self) {
self.generation.fetch_add(1, Ordering::AcqRel);
}
}
/// Pool of OS swapchain drawables for present-on-scheme.
///
/// Constructed by [`crate::SurfaceExchange`]. Call [`Self::lease`] to obtain a
/// stable [`PresentLease`] identity for scheme recording.
pub(crate) struct SwapchainPool {
inner: Arc<SwapchainPoolInner>,
}
/// Pool-local present lease handle.
///
/// The physical backing rotates per submission. Schemes map `(pool, id)` to a
/// scheme-unique present-lease binding so two pools that both use local id `0`
/// remain distinct.
pub struct PresentLease {
pub(crate) id: u32,
pub(crate) pool: Arc<SwapchainPoolInner>,
}
impl PresentLease {
/// Shared live generation counter for this lease's pool.
pub(crate) fn generation_handle(&self) -> Arc<AtomicU64> {
Arc::clone(&self.pool.generation)
}
}
/// A swapchain drawable acquired before scheme submit (classic-like early acquire).
///
/// Dropping an unconsumed claim cancels the underlying surface frame so the
/// image is not presented.
pub struct AcquiredPresent {
/// Pool-local lease id (matches a [`PresentLease`], not the scheme binding id).
lease_id: u32,
pool: Arc<SwapchainPoolInner>,
slot_id: u32,
/// Pool generation at acquire time.
generation: u64,
handle: TextureHandle,
uav_index: u32,
frame: Option<SurfaceFrame>,
}
impl AcquiredPresent {
/// Pool-local lease id this drawable fulfills ([`PresentLease`] identity).
pub fn lease_id(&self) -> u32 {
self.lease_id
}
pub(crate) fn pool(&self) -> &Arc<SwapchainPoolInner> {
&self.pool
}
pub(crate) fn into_parts(mut self) -> (u32, Arc<SwapchainPoolInner>, u32, u64, TextureHandle, u32, SurfaceFrame) {
let frame = self.frame.take().expect("AcquiredPresent frame already taken");
let lease_id = self.lease_id;
let pool = Arc::clone(&self.pool);
let slot_id = self.slot_id;
let generation = self.generation;
let handle = self.handle;
let uav_index = self.uav_index;
(lease_id, pool, slot_id, generation, handle, uav_index, frame)
}
}
impl Drop for AcquiredPresent {
fn drop(&mut self) {
if let Some(frame) = self.frame.take() {
frame.cancel();
}
}
}
impl SwapchainPool {
/// Create a swapchain pool bound to `window` on `ctx`.
///
/// `depth` is the client's stated in-flight frame count (used for pool
/// policy; the OS may provide fewer or more swapchain images).
#[cfg_attr(not(test), allow(dead_code))]
pub fn new<W>(ctx: &Context, window: &W, depth: u32) -> Result<Self>
where
W: HasWindowHandle + HasDisplayHandle,
{
Self::new_with_config(ctx, window, depth, SurfaceConfig::default())
}
/// Create with explicit surface configuration.
pub fn new_with_config<W>(ctx: &Context, window: &W, depth: u32, config: SurfaceConfig) -> Result<Self>
where
W: HasWindowHandle + HasDisplayHandle,
{
let surface = Surface::new_with_config(ctx, window, config)?;
Ok(Self {
inner: Arc::new(SwapchainPoolInner {
surface: RwLock::new(surface),
depth: depth.max(1),
generation: Arc::new(AtomicU64::new(0)),
#[cfg(test)]
fail_next_acquire: AtomicBool::new(false),
}),
})
}
/// Acquire a stable present lease handle (v1: one lease per pool).
pub fn lease(&self) -> PresentLease {
PresentLease {
id: 0,
pool: Arc::clone(&self.inner),
}
}
/// Current backing generation (advances on resize / present-mode change).
pub fn generation(&self) -> u64 {
self.inner.generation()
}
/// Acquire the next drawable for `lease` now (blocks on DXGI / return fence).
///
/// Pass the result to [`crate::Scheme::submit_with_acquired_presents`] so submit
/// does not wait again at the present partition. Prefer this when matching
/// classic task-graph timing (acquire at the start of the frame).
pub fn acquire_present(&self, lease: &PresentLease) -> Result<AcquiredPresent> {
if !Arc::ptr_eq(&lease.pool, &self.inner) {
anyhow::bail!("PresentLease does not belong to this SwapchainPool");
}
let (slot_id, generation, frame, uav_index, handle) = Self::acquire_slot(&lease.pool)?;
Ok(AcquiredPresent {
lease_id: lease.id,
pool: Arc::clone(&lease.pool),
slot_id,
generation,
handle,
uav_index,
frame: Some(frame),
})
}
/// Current drawable extent.
pub fn size(&self) -> (u32, u32) {
let surface = self.inner.surface.read().unwrap();
surface.size()
}
pub fn width(&self) -> u32 {
self.size().0
}
pub fn height(&self) -> u32 {
self.size().1
}
pub fn format(&self) -> crate::types::TextureFormat {
let surface = self.inner.surface.read().unwrap();
surface.format()
}
/// Resize the underlying swapchain (structural edit — rebuild scheme nodes).
///
/// Advances the pool generation so claims and retained variants from the
/// previous backing cannot be reused.
pub fn resize(&self, width: u32, height: u32) -> Result<()> {
if width == 0 || height == 0 {
return Ok(());
}
let mut surface = self.inner.surface.write().unwrap();
surface.resize(width, height)?;
self.inner.bump_generation();
Ok(())
}
pub fn set_present_mode(&self, mode: crate::types::PresentMode) -> Result<()> {
let mut surface = self.inner.surface.write().unwrap();
surface.set_present_mode(mode)?;
self.inner.bump_generation();
Ok(())
}
/// Force the next deferred/eager acquire from this pool to fail (tests only).
#[cfg(test)]
pub(crate) fn fail_next_acquire(&self) {
self.inner.fail_next_acquire.store(true, Ordering::SeqCst);
}
pub(crate) fn acquire_slot(pool: &Arc<SwapchainPoolInner>) -> Result<(u32, u64, SurfaceFrame, u32, TextureHandle)> {
#[cfg(test)]
{
if pool.fail_next_acquire.swap(false, Ordering::SeqCst) {
anyhow::bail!("test-injected acquire failure");
}
}
// Read lock only: `begin()` may block on swapchain image availability without
// preventing concurrent queries or stalling resize on a held write lock.
let generation = pool.generation();
let frame = {
let surface = pool.surface.read().unwrap();
surface.begin()?
};
let slot_id = frame.frame_slot();
let (handle, uav_index) = {
let tex = frame.texture();
let uav_index = tex
.resource_index(ResourceAccess::Write)
.ok_or_else(|| anyhow::anyhow!("swapchain texture has no UAV resource index"))?;
(tex.gpu_handle(), uav_index)
};
Ok((slot_id, generation, frame, uav_index, handle))
}
}