//! Waiting for a buffer mapping without parking the interface. //! //! Shared by every transfer off the device — the display bridge, an export, and //! the histogram's 4 KB of bin counts. It was written once inside `AdjustPass` //! and the reasoning below is the whole of why it is shaped this way; copying //! thirty lines of that reasoning into a second caller would have left two //! copies to keep true of each other. use crate::{GpuContext, GpuError}; /// How many non-blocking polls a readback gets before it is called failed. /// /// A bound rather than a spin forever: if the device is lost the map callback /// never arrives, and an unbounded loop would hang the interface rather than /// surfacing the error. Set far above any plausible completion — the copies /// this waits on are milliseconds — so it is reached only when something is /// wrong. const POLL_LIMIT: u32 = 100_000; /// Drive the device until a `map_async` callback lands. /// /// **Polled without blocking, then checked.** /// /// `PollType::Wait` parks the calling thread until the GPU has finished, and /// these transfers are called from the UI thread — so that park was a frozen /// interface for the duration of the copy (~7 ms at 4K for a whole frame). /// `Poll` drives the same callbacks without sleeping, so the loop below stays /// interruptible and the mapping still completes. /// /// The bounded spin matters: a lost device would otherwise never deliver the /// callback and this would hang the app instead of reporting an error. pub(crate) fn await_mapping( ctx: &GpuContext, rx: &std::sync::mpsc::Receiver>, ) -> Result<(), GpuError> { let mut mapped = None; for _ in 0..POLL_LIMIT { // A poll error is a lost device, which is exactly the case the bounded // spin exists to escape — returning here reports it immediately rather // than spinning out the full limit first. ctx.device .poll(wgpu::PollType::Poll) .map_err(|e| GpuError::Readback(e.to_string()))?; match rx.try_recv() { Ok(r) => { mapped = Some(r); break; } Err(std::sync::mpsc::TryRecvError::Empty) => continue, Err(e) => return Err(GpuError::Readback(e.to_string())), } } mapped .ok_or_else(|| GpuError::Readback("readback did not complete".into()))? .map_err(|e| GpuError::Readback(e.to_string())) }