Merge branch 'master' into worktree-faces-scrfd-mbf
Build and test / Desktop (Linux) (push) Failing after 25s
Build and test / Layer separation (push) Successful in 22s
Traceability / Requirement traces (push) Successful in 58s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Failing after 33m26s
Build and test / Desktop (Linux) (push) Failing after 25s
Build and test / Layer separation (push) Successful in 22s
Traceability / Requirement traces (push) Successful in 58s
🐳 Android image / Build and push (push) Successful in 1s
Build and test / android-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Failing after 33m26s
# Conflicts: # docs/traceability.md # ui/dr-ui/src/develop.rs # ui/dr-ui/src/segmentation.rs
This commit is contained in:
+122
-3
@@ -43,9 +43,10 @@ mod segmentation;
|
||||
mod settings_store;
|
||||
mod settings_ui;
|
||||
mod sidecar_cache;
|
||||
mod spots_ui;
|
||||
mod trash;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -295,6 +296,11 @@ fn reset_view_state(window: &AppWindow) {
|
||||
// overlay or the crosshair to the next one would offer a selection of
|
||||
// regions that are not in the picture on screen.
|
||||
masks_ui::reset(window);
|
||||
// TRACES: FR-DEV-8
|
||||
// The repairs belong to one photograph too. The next one's arrive with its
|
||||
// sidecar a moment later, and until they do the canvas must not be showing
|
||||
// the last one's.
|
||||
spots_ui::reset(window);
|
||||
}
|
||||
|
||||
/// Push the framing back to the geometry panel.
|
||||
@@ -738,6 +744,7 @@ enum PointsUpdate {
|
||||
///
|
||||
/// `None` means develop is unavailable and the viewer falls back to embedded
|
||||
/// previews — the same degradation as a machine with no adapter at all.
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
||||
let shared = match pollster::block_on(dr_gpu::GpuContext::new_shared()) {
|
||||
Ok(shared) => shared,
|
||||
@@ -778,6 +785,45 @@ fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
||||
Some(ctx)
|
||||
}
|
||||
|
||||
/// TRACES: FR-DSP-1 | AC-8
|
||||
/// Open the compute device on Android — and do **not** give it to Slint.
|
||||
///
|
||||
/// # Why this platform is different
|
||||
///
|
||||
/// The desktop version above exists so one `wgpu::Texture` can be written by
|
||||
/// the adjust pass and sampled by the compositor without a round-trip. That
|
||||
/// requires Slint to be drawing with wgpu, and on Android drawing with wgpu
|
||||
/// means drawing on wgpu's Vulkan swapchain — which hardcodes
|
||||
/// `preTransform = IDENTITY` (gfx-rs/wgpu#3345).
|
||||
///
|
||||
/// On a tablet whose panel is mounted landscape, portrait then presents an
|
||||
/// unrotated buffer, every present returns `VK_SUBOPTIMAL_KHR`, and the frames
|
||||
/// arrive torn. Measured on the device: portrait sits on `composition=CLIENT`
|
||||
/// with `bufferTransform=ROT_270`, landscape on `composition=DEVICE` with
|
||||
/// `ROT_180`, and only portrait tore. Taking Slint off wgpu — it then uses
|
||||
/// Skia over OpenGL, where the driver owns the rotation — fixed it.
|
||||
///
|
||||
/// # What it costs, and why that is the right trade here
|
||||
///
|
||||
/// Without a shared device the display path needs a readback:
|
||||
/// `AdjustPass::export_pixels` instead of `Image::try_from(texture)`. That is
|
||||
/// the transfer ARCH §6.1 and AC-8 exist to avoid, and it is still the better
|
||||
/// bargain on this platform — the alternative is not a faster develop view, it
|
||||
/// is a torn one, in the orientation a tablet is mostly held in.
|
||||
///
|
||||
/// The compute passes are untouched: demosaic and adjust still run on the GPU,
|
||||
/// on this device. Only the last hop to the screen goes through memory.
|
||||
#[cfg(target_os = "android")]
|
||||
fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
||||
match pollster::block_on(dr_gpu::GpuContext::new_headless()) {
|
||||
Ok(ctx) => Some(ctx),
|
||||
Err(e) => {
|
||||
log::warn!("no GPU for the develop passes: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TRACES: M-13 | M-14
|
||||
/// Build and run the viewer.
|
||||
pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
@@ -1325,9 +1371,16 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
//
|
||||
// Called on every slider change, so it must do no more than run the
|
||||
// adjust pass — the demosaic is not repeated.
|
||||
// TRACES: FR-DEV-5
|
||||
// The history revision the panel was last built from. See the use below:
|
||||
// the list is rebuilt when it would read differently, not when the picture
|
||||
// is redrawn, and those are very different rates.
|
||||
let drawn_history: Rc<Cell<Option<u64>>> = Rc::new(Cell::new(None));
|
||||
|
||||
let render_now: Render = {
|
||||
let session = session.clone();
|
||||
let viewport = viewport.clone();
|
||||
let drawn_history = drawn_history.clone();
|
||||
Rc::new(move |window: &AppWindow, draft: bool| {
|
||||
let mut slot = session.borrow_mut();
|
||||
let Some(s) = slot.as_mut() else { return };
|
||||
@@ -1340,6 +1393,20 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
window.set_can_undo(s.can_undo());
|
||||
window.set_can_redo(s.can_redo());
|
||||
|
||||
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||
// The list, on the same path and for the same reason — but only
|
||||
// when it would read differently. A drag ends in a redraw per
|
||||
// frame while folding into one step, so an unconditional rebuild
|
||||
// here would tear down and recreate every row of the panel sixty
|
||||
// times a second to arrive back at the list already on screen.
|
||||
let revision = s.history_revision();
|
||||
if drawn_history.get() != Some(revision) {
|
||||
drawn_history.set(Some(revision));
|
||||
window
|
||||
.set_history_rows(slint::ModelRc::new(slint::VecModel::from(s.history_rows())));
|
||||
window.set_undo_label(s.undo_label().into());
|
||||
}
|
||||
|
||||
// TRACES: FR-DEV-3
|
||||
// Which part of the region overlay the view is showing. Here
|
||||
// rather than in the panel's own sync because a pan or a zoom
|
||||
@@ -1347,6 +1414,18 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// those ends in a redraw.
|
||||
masks_ui::sync_overlay_view(window, s);
|
||||
|
||||
// TRACES: FR-DEV-8
|
||||
// And where the repairs are drawn, for the same reason: a pan or a
|
||||
// zoom moves every circle while touching no repair, and a circle
|
||||
// left where the mark used to be is worse than no circle at all.
|
||||
spots_ui::sync_handles(window, s);
|
||||
// The column's own numbers, pushed from here as well so that a
|
||||
// photograph opened with repairs already on it arrives with the
|
||||
// panel describing them — the sidecar lands after the callbacks
|
||||
// have all been installed, and a redraw is the one path every
|
||||
// arrival takes.
|
||||
spots_ui::sync_panel(window, s);
|
||||
|
||||
let (mut w, mut h) = *viewport.borrow();
|
||||
|
||||
// **Half resolution while the gesture is still moving.**
|
||||
@@ -2050,6 +2129,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
}
|
||||
|
||||
masks_ui::wire(&window, &session, &rows, &redraw);
|
||||
spots_ui::wire(&window, &session, redraw.clone());
|
||||
|
||||
// A way to land on the Identity Manager at startup, for looking at it
|
||||
// without a mouse. Off unless the variable is set, so it costs a getenv
|
||||
@@ -2139,7 +2219,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
.and_then(dr_film::find)
|
||||
.and_then(dr_film::default_print)
|
||||
.is_some();
|
||||
s.choose_film(*stock, print);
|
||||
s.pick_film(*stock, print);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
@@ -2153,7 +2233,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) {
|
||||
s.choose_film(Some(&stock), print);
|
||||
s.pick_film(Some(&stock), print);
|
||||
}
|
||||
}
|
||||
sync_film(&w, &session);
|
||||
@@ -2199,6 +2279,27 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
});
|
||||
}
|
||||
|
||||
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||
// Clicking a row. Arriving six steps away costs what arriving from one
|
||||
// does, because a step is a whole state — see `History::go_to`.
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_history_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let stepped = session
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.is_some_and(|s| s.go_to_history(index));
|
||||
if stepped {
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- zoom, pan and crop ---------------------------------------------
|
||||
//
|
||||
// Zoom and pan are viewing state and touch no parameter, so unlike the
|
||||
@@ -2275,14 +2376,32 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
w.set_crop_h(c.height);
|
||||
}
|
||||
ViewMode::Local => s.set_overlay(true),
|
||||
// TRACES: FR-DEV-8
|
||||
// Nothing to arm: the circles are drawn whenever there are
|
||||
// repairs, and what the mode changes is whether a click on
|
||||
// the photograph makes another one. Leaving the mask
|
||||
// selection behind would re-point the column at a layer's
|
||||
// chain while the canvas is showing repairs, which is the
|
||||
// fault this whole strip exists to prevent.
|
||||
ViewMode::Spots => {
|
||||
s.set_overlay(false);
|
||||
s.set_active_mask(None);
|
||||
}
|
||||
ViewMode::Photo => {
|
||||
s.set_overlay(false);
|
||||
s.set_active_mask(None);
|
||||
// A repair stays on the photograph; only the *selection*
|
||||
// goes, so the source circle does not hang about over a
|
||||
// frame nobody is repairing any more.
|
||||
s.select_spot(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
w.set_view_mode(mode);
|
||||
masks_ui::sync(&w, &session);
|
||||
if let Some(s) = session.borrow().as_ref() {
|
||||
spots_ui::sync_handles(&w, s);
|
||||
}
|
||||
// The scope may have just changed, so the panel below is now
|
||||
// describing a different chain.
|
||||
sync_rows(&w, &rows, &session);
|
||||
|
||||
Reference in New Issue
Block a user