atagen

monolith

public
Star0
Watch0

Commit f8e6bc3e5529

tarball

atagen <boss@atagen.co>2026-10-19 07:56 UTC

2 files changed+45-0
render-vk: flush and invalidate non-coherent staging memory

read_back read GPU-written staging memory and stage_rows wrote memory the GPU then read without vkInvalidateMappedMemoryRanges or vkFlushMappedMemoryRanges, which the spec requires when the allocator falls back to a memory type without HOST_COHERENT. Staging::bytes now invalidates and stage_rows flushes.
Modified file: crates/render-vk/src/renderer/shm.rs+1 additions
@@ -116,6 +116,7 @@ pub(super) fn stage_rows<'a>(
116116 for i in 0..h {
117117 dst[i * line..(i + 1) * line].copy_from_slice(row(region.loc.y as usize + i));
118118 }
119+ staging.flush()?;
119120 Ok(staging)
120121 }
121122
Modified file: crates/render-vk/src/staging.rs+44 additions
@@ -25,13 +25,23 @@ struct Pooled {
2525 pub(crate) struct StagingPool {
2626 shared: Arc<Shared>,
2727 free: Mutex<Vec<Pooled>>,
28+ /// Granularity of flushes and invalidations of non-coherent memory.
29+ atom: u64,
2830 }
2931
3032 impl StagingPool {
3133 pub fn new(shared: &Arc<Shared>) -> Arc<Self> {
34+ let limits = unsafe {
35+ shared
36+ .device
37+ .instance
38+ .get_physical_device_properties(shared.device.physical)
39+ }
40+ .limits;
3241 Arc::new(Self {
3342 shared: shared.clone(),
3443 free: Mutex::new(Vec::new()),
44+ atom: limits.non_coherent_atom_size.max(1),
3545 })
3646 }
3747
@@ -149,13 +159,47 @@ impl Staging {
149159 })
150160 }
151161
162+ /// The mapped bytes, with whatever the GPU wrote to them made visible.
152163 pub fn bytes(&self) -> Result<&[u8], Error> {
164+ self.host_sync(false)?;
153165 self.allocation
154166 .as_ref()
155167 .and_then(|a| a.mapped_slice())
156168 .ok_or(Error::StagingUnmapped)
157169 }
158170
171+ /// Makes what the host wrote through `bytes_mut` visible to the GPU.
172+ pub fn flush(&self) -> Result<(), Error> {
173+ self.host_sync(true)
174+ }
175+
176+ /// Flushes or invalidates the mapping. Coherent memory needs neither.
177+ fn host_sync(&self, flush: bool) -> Result<(), Error> {
178+ let Some(allocation) = self.allocation.as_ref() else {
179+ return Ok(());
180+ };
181+ if allocation
182+ .memory_properties()
183+ .contains(vk::MemoryPropertyFlags::HOST_COHERENT)
184+ {
185+ return Ok(());
186+ }
187+ let atom = self.pool.atom;
188+ let range = [vk::MappedMemoryRange::default()
189+ .memory(unsafe { allocation.memory() })
190+ .offset(allocation.offset() / atom * atom)
191+ .size(vk::WHOLE_SIZE)];
192+ let d = &self.pool.shared.device.device;
193+ unsafe {
194+ if flush {
195+ d.flush_mapped_memory_ranges(&range)?;
196+ } else {
197+ d.invalidate_mapped_memory_ranges(&range)?;
198+ }
199+ }
200+ Ok(())
201+ }
202+
159203 pub fn bytes_mut(&mut self) -> Result<&mut [u8], Error> {
160204 self.allocation
161205 .as_mut()