Put the region map behind the sliders that were already there
A mask layer holds a real develop chain, so the develop panel can edit one with no new controls: select a layer and the same sliders read and write its chain instead of the graph's. An operation declared in `ops/` tomorrow becomes locally adjustable by existing, which is the payoff for making a layer a chain rather than a handful of special-cased parameters. `segmentation.rs` joins the two arms into the one thing the view needs. The model reads the image through a neutral graph rather than the edited one, so a segmentation survives an exposure change instead of being invalidated by every slider. Arm B failing is not fatal: a missing or unreadable model leaves a working watershed map, because refusing to segment at all would trade a working feature for a strict one. The overlay colours groups by a golden-angle walk over hue. Deterministic rather than random, so a region keeps its colour across a level change and the eye can track it; boundaries drawn black over the fill, because two adjacent groups landing on near hues read as one region and telling them apart is the whole reason to look at it. Clicking the photograph creates the layer if none is selected — that is how a local adjustment begins, and making the user press "add layer" first would be a step with no decision in it. Shift-click extends, and clicking a region already selected removes it, so one gesture both adds and corrects. `segment-readback` is a new dr-gpu feature and not a loosening of `readback`. The region-graph transfer is once per image on a worker; the one AC-8 forbids is per frame in the render loop. Sharing a switch would have forced a build wanting local masking to unlock the other. F3 still stands and the feature name says so.
This commit is contained in:
@@ -40,7 +40,12 @@ name = "develop"
|
||||
default = []
|
||||
# Exposes read_pixels outside tests. Production must not enable this.
|
||||
readback = []
|
||||
# Exposes `Segmentation::read_field`, which builds the region adjacency graph
|
||||
# on the CPU. Separate from `readback` on purpose — see `segment.rs`. Once per
|
||||
# image on a worker, not the per-frame display round-trip AC-8 forbids; still a
|
||||
# full-resolution transfer, and still F3's open gap.
|
||||
segment-readback = []
|
||||
|
||||
[[example]]
|
||||
name = "segment"
|
||||
required-features = ["readback"]
|
||||
required-features = ["segment-readback"]
|
||||
|
||||
+31
-13
@@ -14,16 +14,17 @@
|
||||
//! # The open question this leaves
|
||||
//!
|
||||
//! [`Segmentation::read_field`] copies the label and gradient buffers back to
|
||||
//! the CPU to build the region adjacency graph, and is gated behind the
|
||||
//! `readback` feature for the same reason `read_pixels` is. That gate is not
|
||||
//! ceremony: a shipping build cannot take this path (ARCH §6.1, AC-8), so the
|
||||
//! RAG would have to be accumulated GPU-side with atomics instead.
|
||||
//! the CPU to build the region adjacency graph, behind the `segment-readback`
|
||||
//! feature. That is deliberately **not** the `readback` switch guarding the
|
||||
//! display round-trip: this transfer is once per image on a worker, where the
|
||||
//! one AC-8 forbids is per frame in the render loop, and sharing a switch
|
||||
//! would force a build wanting local masking to unlock the other.
|
||||
//!
|
||||
//! For a spike that trade is the right way round — the readback is once per
|
||||
//! image and off the frame path, and building the GPU-side RAG before knowing
|
||||
//! whether the granularity ladder is any good would be work spent on a
|
||||
//! question not yet asked. But it is a real gap between this and something
|
||||
//! shippable, and it should be read as one.
|
||||
//! It is still a real cost and still unfinished. F3 in docs/segmentation.md
|
||||
//! §12 stands: the adjacency accumulation belongs GPU-side with atomics, and
|
||||
//! until it moves there every segmentation pays a full-resolution transfer.
|
||||
//! Read the feature name as a description of a known gap rather than as
|
||||
//! permission.
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
@@ -472,9 +473,26 @@ impl Segmentation {
|
||||
|
||||
/// Build the region adjacency graph, reading the labels back to the CPU.
|
||||
///
|
||||
/// **Not a shipping path** — see this module's header. Gated so it cannot
|
||||
/// be reached from a production build by accident.
|
||||
#[cfg(any(test, feature = "readback"))]
|
||||
/// # Why this has its own feature rather than sharing `readback`
|
||||
///
|
||||
/// `readback` gates [`crate::AdjustPass::read_pixels`], which is the
|
||||
/// per-frame display round-trip AC-8 exists to forbid. This is a different
|
||||
/// transfer with different economics, and sharing one switch would have
|
||||
/// forced a build wanting local masking to also unlock the one thing the
|
||||
/// architecture is built around never doing.
|
||||
///
|
||||
/// What this transfer actually is: **once per image, on a worker, off the
|
||||
/// frame path.** Nothing in the render loop waits on it, and the result is
|
||||
/// a region graph of a few thousand nodes that every later interaction
|
||||
/// reads from the CPU anyway.
|
||||
///
|
||||
/// What it is *not* is finished. F3 in docs/segmentation.md §12 stands:
|
||||
/// the adjacency accumulation belongs on the GPU with atomics, and until
|
||||
/// it moves there a segmentation costs one full-resolution transfer of the
|
||||
/// label and gradient buffers. That is a real cost on a phone and the
|
||||
/// reason this is named for what it does rather than hidden behind the
|
||||
/// general switch.
|
||||
#[cfg(any(test, feature = "segment-readback"))]
|
||||
pub fn read_field(&self) -> Result<dr_segment::RegionField, GpuError> {
|
||||
let n = (self.width * self.height) as usize;
|
||||
let roots: Vec<u32> = read_buffer(&self.ctx, &self.labels, n)?;
|
||||
@@ -551,7 +569,7 @@ fn proxy_size(src_w: u32, src_h: u32, max_edge: u32) -> (u32, u32) {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "readback"))]
|
||||
#[cfg(any(test, feature = "segment-readback"))]
|
||||
fn read_buffer<T: bytemuck::Pod>(
|
||||
ctx: &GpuContext,
|
||||
buffer: &wgpu::Buffer,
|
||||
|
||||
Reference in New Issue
Block a user