Merge branch 'histogram'

# Conflicts:
#	ui/dr-ui/src/lib.rs
This commit is contained in:
2026-08-17 10:00:27 +02:00
11 changed files with 1541 additions and 46 deletions
+5 -44
View File
@@ -19,6 +19,7 @@ use std::collections::HashMap;
use dr_pipeline::ComposedShader;
use wgpu::util::DeviceExt;
use crate::readback::await_mapping;
use crate::{DemosaicedImage, GpuContext, GpuError};
/// Leading floats the composer reserves before any operation's own uniforms:
@@ -31,18 +32,6 @@ use crate::{DemosaicedImage, GpuContext, GpuError};
/// reads them.
const RESERVED_FIELDS: usize = dr_pipeline::RESERVED_UNIFORM_FIELDS;
/// 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 copy this
/// waits on is milliseconds — so it is reached only when something is wrong.
///
/// Ungated along with `export_pixels`: an export reads pixels back in a
/// shipping build, and the bound that stops a lost device hanging the app
/// applies at least as much there as it does to the display bridge.
const READBACK_POLL_LIMIT: u32 = 100_000;
/// Runs composed operation chains against demosaiced images.
pub struct AdjustPass {
ctx: GpuContext,
@@ -399,38 +388,10 @@ impl AdjustPass {
let _ = tx.send(r);
});
// **Polled without blocking, then checked.**
//
// `Maintain::Wait` parks the calling thread until the GPU has finished,
// and this is called from the UI thread — so that park was a frozen
// interface for the duration of the copy (~7 ms at 4K, per the note
// above). `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.
let mut mapped = None;
for _ in 0..READBACK_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.
self.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()))?;
// Polled rather than parked, and bounded rather than spun forever —
// see `readback::await_mapping`, which the histogram's own transfer
// shares for exactly the same reasons.
await_mapping(&self.ctx, &rx)?;
let data = slice.get_mapped_range();
let mut out = Vec::with_capacity((unpadded * h) as usize);