fix(encode): key the PyroWave plane-import cache on the capturer's ring generation
Completes the partial fix from the previous commit. The Windows PyroWave backend caches its imported plane images keyed on the D3D11 texture's COM address, and holds no reference on that texture — so once the capturer recreates its ring, those addresses can be handed straight back out by the allocator and a pointer-keyed cache hit returns an image bound to a texture that no longer exists. Adding the extent to the key ruled out same-address-different-size aliasing, but a recycle at identical dimensions still aliased. The capturer already tracks exactly the value needed: `generation`, bumped on every ring recreate. Plumbed it onto `PyroFrameShare` and the encoder now flushes every cached import when it changes, which makes cache identity independent of allocator behaviour rather than a bet against pointer reuse. Validated on the RTX box: `pyrowave_win_smoke` (forced with `--ignored`, the only test that actually exercises this path on real hardware) passes all ten configurations — 1024²/720p/1080p/1440p across SDR/HDR and 4:2:0/4:4:4 — with correct decoded chroma means, so the steady-state cache-hit path still works. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1861,6 +1861,7 @@ impl IddPushCapturer {
|
|||||||
cbcr,
|
cbcr,
|
||||||
fence_handle,
|
fence_handle,
|
||||||
fence_value,
|
fence_value,
|
||||||
|
ring_gen: self.generation,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -1919,6 +1920,7 @@ impl IddPushCapturer {
|
|||||||
cbcr: dst_cbcr,
|
cbcr: dst_cbcr,
|
||||||
fence_handle,
|
fence_handle,
|
||||||
fence_value,
|
fence_value,
|
||||||
|
ring_gen: self.generation,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
cursor: None,
|
cursor: None,
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ pub struct PyroWaveEncoder {
|
|||||||
// Imported plane textures, cached by the out-ring texture's raw pointer (stable per ring slot):
|
// Imported plane textures, cached by the out-ring texture's raw pointer (stable per ring slot):
|
||||||
// the full-res R8 Y plane and the half-res R8G8 CbCr plane, imported SEPARATELY (a single planar
|
// the full-res R8 Y plane and the half-res R8G8 CbCr plane, imported SEPARATELY (a single planar
|
||||||
// NV12 import is unreliable on NVIDIA at arbitrary sizes).
|
// NV12 import is unreliable on NVIDIA at arbitrary sizes).
|
||||||
|
/// The capturer ring generation the cached plane imports below belong to. A recreate bumps it,
|
||||||
|
/// and every cached import is destroyed — the COM addresses they are keyed on can be recycled
|
||||||
|
/// by the allocator after a recreate, so identity cannot rest on the pointer alone.
|
||||||
|
ring_gen: Option<u32>,
|
||||||
y_images: Vec<(PlaneKey, pw::pyrowave_image)>,
|
y_images: Vec<(PlaneKey, pw::pyrowave_image)>,
|
||||||
cbcr_images: Vec<(PlaneKey, pw::pyrowave_image)>,
|
cbcr_images: Vec<(PlaneKey, pw::pyrowave_image)>,
|
||||||
|
|
||||||
@@ -271,6 +275,7 @@ impl PyroWaveEncoder {
|
|||||||
pw_dev,
|
pw_dev,
|
||||||
pw_enc,
|
pw_enc,
|
||||||
sync: std::ptr::null_mut(),
|
sync: std::ptr::null_mut(),
|
||||||
|
ring_gen: None,
|
||||||
y_images: Vec::new(),
|
y_images: Vec::new(),
|
||||||
cbcr_images: Vec::new(),
|
cbcr_images: Vec::new(),
|
||||||
width,
|
width,
|
||||||
@@ -455,6 +460,25 @@ impl PyroWaveEncoder {
|
|||||||
in pyrowave mode (session_plan::output_format must set OutputFormat::pyrowave)",
|
in pyrowave mode (session_plan::output_format must set OutputFormat::pyrowave)",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Ring recreate ⇒ every cached plane import belongs to textures that no longer exist. Their
|
||||||
|
// COM addresses can be handed back out by the allocator, so a pointer-keyed hit could return
|
||||||
|
// an image bound to freed memory. Flush on the generation change rather than relying on the
|
||||||
|
// address (or the FIFO cap) to notice.
|
||||||
|
if self.ring_gen != Some(share.ring_gen) {
|
||||||
|
if self.ring_gen.is_some() {
|
||||||
|
tracing::info!(
|
||||||
|
from = ?self.ring_gen,
|
||||||
|
to = share.ring_gen,
|
||||||
|
cached = self.y_images.len() + self.cbcr_images.len(),
|
||||||
|
"pyrowave: capturer recreated its ring — flushing stale plane imports"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (_, img) in self.y_images.drain(..).chain(self.cbcr_images.drain(..)) {
|
||||||
|
pw::pyrowave_image_destroy(img);
|
||||||
|
}
|
||||||
|
self.ring_gen = Some(share.ring_gen);
|
||||||
|
}
|
||||||
|
|
||||||
// Import the fence whenever this encoder has no timeline yet — the first frame, OR a fresh
|
// Import the fence whenever this encoder has no timeline yet — the first frame, OR a fresh
|
||||||
// encoder after a client mode-switch rebuild (the capturer passes the persistent handle on
|
// encoder after a client mode-switch rebuild (the capturer passes the persistent handle on
|
||||||
// every frame precisely so a rebuilt encoder can re-import it).
|
// every frame precisely so a rebuilt encoder can re-import it).
|
||||||
@@ -1000,6 +1024,9 @@ mod tests {
|
|||||||
cbcr: cbcr_tex,
|
cbcr: cbcr_tex,
|
||||||
fence_handle: Some(fence_handle.0 as isize),
|
fence_handle: Some(fence_handle.0 as isize),
|
||||||
fence_value: 1,
|
fence_value: 1,
|
||||||
|
// One synthetic ring for the whole case: a constant generation exercises the
|
||||||
|
// steady-state cache-hit path (a changing one would flush every frame).
|
||||||
|
ring_gen: 1,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
cursor: None,
|
cursor: None,
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ pub struct PyroFrameShare {
|
|||||||
/// The fence value the capturer signalled after THIS frame's convert. The encoder's Vulkan
|
/// The fence value the capturer signalled after THIS frame's convert. The encoder's Vulkan
|
||||||
/// acquire waits on it, so the wavelet read is ordered after the D3D11 CSC.
|
/// acquire waits on it, so the wavelet read is ordered after the D3D11 CSC.
|
||||||
pub fence_value: u64,
|
pub fence_value: u64,
|
||||||
|
/// The capturer's ring generation, bumped every time it recreates its texture ring. The
|
||||||
|
/// PyroWave encoder caches its plane imports keyed on the texture's COM address, which carries
|
||||||
|
/// no reference — after a recreate those addresses can be recycled by the allocator, so a
|
||||||
|
/// cached import may describe a texture that no longer exists. The encoder flushes its import
|
||||||
|
/// cache whenever this changes, making cache identity independent of allocator behaviour.
|
||||||
|
pub ring_gen: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A GPU-resident captured texture (the Windows zero-copy path: NVENC/AMF/QSV encode it in place;
|
/// A GPU-resident captured texture (the Windows zero-copy path: NVENC/AMF/QSV encode it in place;
|
||||||
|
|||||||
Reference in New Issue
Block a user