Record where a linear DNG too large for one texture goes
ARCH §5.3 still described only a tile cache nobody built; it now says
what 0.19.0 tiles and what it does not: a linear DNG past PROXY_EDGE
opens on a reduced copy, a finer render samples a full-resolution
window, and the export is cut into halo-grown tiles. display-and-
extension.md's FR-DSP-2 row said absent, outstanding.md said the halo
had nothing to read it and that such a file fell to the embedded
preview.
rawler now builds from third_party, which ARCH's stack table and §3.2,
the root Cargo.toml's comment ("two upstream crates ... for Android")
and third_party/README.md's bump procedure did not know; the README
also names each vendored crate's licence. panorama.md and the manual
say a composite this wide develops and exports.
This commit is contained in:
+6
-5
@@ -276,11 +276,12 @@ opt-level = 0
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
# Two upstream crates carry a local patch so that the Android build can draw
|
||||
# with wgpu on a rotated display (technical-debt.md TD-1). Both are exact
|
||||
# copies of the version the lockfile already resolves, plus that patch;
|
||||
# third_party/README.md says what was changed and how to carry it forward
|
||||
# when Slint or wgpu moves.
|
||||
# Three upstream crates carry a local patch: wgpu-hal and Slint's Skia
|
||||
# renderer so that the Android build can draw with wgpu on a rotated display
|
||||
# (technical-debt.md TD-1), and rawler so that a linear DNG wider than 16 700
|
||||
# pixels decodes. Each is an exact copy of the version the lockfile already
|
||||
# resolves, plus its patch; third_party/README.md says what was changed and
|
||||
# how to carry it forward when Slint, wgpu or rawler moves.
|
||||
[patch.crates-io]
|
||||
wgpu-hal = { path = "third_party/wgpu-hal-29.0.4" }
|
||||
i-slint-renderer-skia = { path = "third_party/i-slint-renderer-skia-1.17.1" }
|
||||
|
||||
@@ -34,7 +34,7 @@ document elaborates:
|
||||
| UI | Slint | D1, D8 |
|
||||
| GPU | wgpu → Vulkan (Linux + Android) | D1 |
|
||||
| Shaders | Hand-written WGSL | D6 |
|
||||
| RAW decode | rawler; LibRaw fallback behind a trait | D2 |
|
||||
| RAW decode | rawler (0.7.2, carried patched in `third_party/`); LibRaw fallback behind a trait | D2 |
|
||||
| Catalog | SQLite (WAL) — a rebuildable index | D5, §6.12 |
|
||||
| Colour | lcms2 + GPU-side matrix/LUT transforms | D5 |
|
||||
| Network | reqwest + quick-xml | D7 |
|
||||
@@ -140,6 +140,11 @@ is *not* demosaiced. Demosaic is a GPU pipeline stage (§5.2).
|
||||
> (§3.1's `read_range`), not the decoder. `dr_decode::Rawler` is the one implementation, and only
|
||||
> the places that start a job name `dr_decode::default()`; everything below them takes a
|
||||
> `&dyn Decoder`.
|
||||
>
|
||||
> rawler itself is built from `third_party/rawler-0.7.2` since 0.19.0: the crate as published,
|
||||
> with its allocation guard raised so that a linear DNG wider than about 16 700 pixels (a
|
||||
> stitched panorama) decodes rather than being refused
|
||||
> ([third_party/README.md](../../third_party/README.md)).
|
||||
|
||||
### 3.3 Operation and descriptors
|
||||
|
||||
@@ -433,6 +438,20 @@ Tile results cache keyed by `(VersionId, tile, zoom, graph_hash_prefix)`, where
|
||||
operations up to the first `Affects` change. Adjusting exposure reuses cached demosaic and camera
|
||||
profile output for every tile.
|
||||
|
||||
> **As built (0.19.0).** The interactive path does not tile: one fused dispatch over the viewport
|
||||
> is inside the frame budget ([frame-budget.md](frame-budget.md)), and there is no scheduler or tile
|
||||
> cache. What tiles is a source too large for one texture. A linear DNG whose long edge passes
|
||||
> `PROXY_EDGE` (8192) — a stitched panorama — is held at full resolution on the CPU and opened on
|
||||
> a box-reduced copy, from which the canvas at fit, the thumbnail, the histograms and the masks
|
||||
> work. A render finer than the copy — the canvas zoomed in, a tile of the export — samples a
|
||||
> window cut from the full resolution (`DemosaicedImage::linear_rgb16_window`), which the fused
|
||||
> shader addresses through two source-window uniforms so that crops, warps and grain seeds stay
|
||||
> where they are in the frame. The canvas keeps one window while the view stays inside it. The
|
||||
> export is cut by `dr_pipeline::tiles::plan` into 4096-pixel tiles on a 16-pixel grid, each
|
||||
> grown by the detail chain's reach (`ComposedDetail::reach`, the sum of its passes' radii), and
|
||||
> reassembled; `core/dr-gpu/tests/source_window.rs` holds it to the untiled render within one code
|
||||
> value. A CFA file too large for one texture is still refused.
|
||||
|
||||
### 5.4 Mask rasterisation
|
||||
|
||||
**All masks rasterise on the GPU, including drawn brush strokes** (§6.11). Strokes arrive as
|
||||
|
||||
@@ -20,7 +20,7 @@ and the reason is that some of the work is done and untagged.
|
||||
| Requirement | Reality |
|
||||
|---|---|
|
||||
| FR-DSP-1 proxy rendering | **Done.** The develop view renders at viewport resolution, not source. |
|
||||
| FR-DSP-2 tiled computation | **Absent, and §2 now says it should stay that way.** Measured: the fused pass is inside the budget everywhere. See [frame-budget.md](frame-budget.md). |
|
||||
| FR-DSP-2 tiled computation | **Absent from the interactive path, and §2 now says it should stay that way.** Measured: the fused pass is inside the budget everywhere. See [frame-budget.md](frame-budget.md). *Since 0.19.0* the export of a linear DNG too large for one texture is drawn in halo-grown tiles, and the canvas renders such a file from a reduced copy and full-resolution windows (ARCH §5.3). |
|
||||
| FR-DSP-3 interactive latency | **Measured and asserted** for the fused path — `core/dr-gpu/tests/frame_budget.rs`. Missed by one operation, clarity, for the reason recorded as TD-4. |
|
||||
| FR-DSP-4 progressive refinement | **Built** (0.15.0), although §4's condition did not fire on the fused path: a half-resolution draft while a gesture moves, one sharp frame 120 ms after it stops, the histogram dimmed while it lags, and the draft faded out over 150 ms — `ui/dr-ui/src/refine.rs`. See [frame-budget.md](frame-budget.md). |
|
||||
| FR-DSP-5 zoom and pan | **Done and tagged**, against tests that fail if the behaviour is removed — `core/dr-gpu/tests/zoom_resolution.rs`. `Framing::view` shrinks the sampled region while the render target keeps its size, so zooming *raises* the resolution the pipeline works at. That is FR-DSP-5's requirement, arrived at without tiles. |
|
||||
|
||||
+13
-8
@@ -181,7 +181,7 @@ place.
|
||||
|
||||
## 4. The render path — FR-DSP-2, FR-DSP-4, NFR-RES-2, NFR-ARCH-1
|
||||
|
||||
**FR-DSP-2 — Tiled computation. Unbuilt, and under challenge.** [architecture.md §6.2](architecture.md)
|
||||
**FR-DSP-2 — Tiled computation. Built for one case, and otherwise under challenge.** [architecture.md §6.2](architecture.md)
|
||||
calls for tiling "from day one" on the grounds that retrofitting it is a rewrite. It was not built,
|
||||
and the evidence has since moved. `core/dr-gpu/tests/frame_budget.rs` carries the argument in its
|
||||
own header: one fused dispatch over a viewport-sized target is comfortably inside the frame budget,
|
||||
@@ -191,9 +191,10 @@ path stops being supported, and this test is what says so."
|
||||
tiled convolution at clarity's radius reads nearly twice the taps that an untiled one does, so the
|
||||
stage that looks most like it wants a tile cache is the stage that would be hurt most by one.
|
||||
|
||||
What exists is the declaration and not the mechanism: `DetailPass::radius` is documented as the halo
|
||||
a tile would have to be grown by, with a test that pins it, and there is no scheduler to read it.
|
||||
That is deliberate plumbing, not an oversight.
|
||||
What existed until 0.19.0 was the declaration and not the mechanism: `DetailPass::radius`, the halo
|
||||
a tile would have to be grown by, with a test that pins it, and nothing to read it. The export of an
|
||||
oversized linear DNG (below) is now what reads it, through `ComposedDetail::reach`; there is still
|
||||
no scheduler and no tile cache.
|
||||
|
||||
**So the open question here is not "when is tiling built" but "is FR-DSP-2 still a requirement" — and on 2026-09-19 the answer was: as written, until S6 runs.** FR-DSP-2 now carries a status note saying exactly that, and R5's note no longer claims it was rewritten.
|
||||
Two measurements say it costs more than it saves on the interactive path. Neither says anything
|
||||
@@ -201,8 +202,10 @@ about the export path or about a device under memory pressure, which is where th
|
||||
actually lives — and that is spike S6, which has not run.
|
||||
|
||||
**2026-09-27:** the export path does now tile, for the one case that forced it — a linear DNG
|
||||
wider than any texture. See the note under FR-DSP-2 in [requirements.md](requirements.md). The
|
||||
interactive path is unchanged and S6 is still unrun.
|
||||
wider than any texture, a 22 927 × 8966 Lightroom panorama in the case that prompted it. See the
|
||||
note under FR-DSP-2 in [requirements.md](requirements.md) and ARCH §5.3. The interactive path
|
||||
renders such a file from a reduced copy and one full-resolution window rather than tiles, and S6
|
||||
is still unrun.
|
||||
|
||||
**FR-DSP-4 — Progressive refinement. Built in 0.15.0.** While a gesture moves the canvas renders
|
||||
a half-resolution draft, and the sharp frame lands once, 120 ms after the last movement: the
|
||||
@@ -216,8 +219,10 @@ interface could see, and a refinement that was not a jarring swap.
|
||||
|
||||
**NFR-RES-2 — Images larger than GPU memory.** Half answered. NFR-R8's "decide explicitly" was
|
||||
decided on 2026-09-19: there is no CPU render pipeline, the degraded mode is the viewer on
|
||||
embedded previews with develop withheld, and NFR-RES-2 no longer promises a fallback render. What
|
||||
remains unbuilt is the memory half: there is no headroom budget, no allocation-failure staging,
|
||||
embedded previews with develop withheld, and NFR-RES-2 no longer promises a fallback render. Since
|
||||
0.19.0 that mode no longer catches a linear DNG larger than one texture, which develops from a
|
||||
reduced copy and exports in tiles; a CFA file that large still falls to it. What remains unbuilt is
|
||||
the memory half: there is no headroom budget, no allocation-failure staging,
|
||||
and no spill. Spike S6 — a tiled pipeline on a
|
||||
mid-range Android device with an image larger than available GPU memory — is the one that would
|
||||
settle both this and FR-DSP-2, and there is no evidence it has run.
|
||||
|
||||
@@ -606,3 +606,13 @@ the preview (`grey_if_blown` in `dr_ui::merge`) now write a blown sample,
|
||||
before the gain, as the camera value the composite's balance maps to grey —
|
||||
the develop pipeline's neutral, fading in from `CLIP_ONSET` exactly as the
|
||||
develop's does.
|
||||
|
||||
**A composite this wide is past two limits, both lifted the same day.**
|
||||
They were found on a 22 927 × 8966 panorama from Lightroom, and the fixture's
|
||||
own composite (22 993 × 5 980, §11) is past both. rawler's allocation guard,
|
||||
sized in samples but worded in pixels, refuses a three-sample DNG past about
|
||||
16 700 pixels wide; the copy in `third_party/` carries it raised
|
||||
([README](../../third_party/README.md)). And no texture holds such a frame:
|
||||
a linear DNG past 8192 pixels now opens on a box-reduced copy, a render finer
|
||||
than the copy samples a window of the full resolution, and the export is drawn
|
||||
in tiles (FR-DSP-2's note in requirements.md, ARCH §5.3).
|
||||
|
||||
@@ -351,6 +351,11 @@ it to bring it back. A frame that cannot be placed is named there with why,
|
||||
and `Merge` stays off until it is unticked. Blown sky stays white in the
|
||||
preview and in the composite.
|
||||
|
||||
A panorama is usually far wider than a graphics card can draw in one piece.
|
||||
It opens in develop all the same, on a reduced copy that is drawn from the
|
||||
full resolution wherever you zoom in, and it exports at full size. The same
|
||||
goes for a panorama Lightroom stitched and saved as a DNG.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Vendored
+12
-5
@@ -11,17 +11,24 @@ The first commit that adds a directory is the pristine copy (from
|
||||
crate's own `Cargo.lock`). Every later commit touching it is ours, so
|
||||
`git log -p -- third_party/<dir>` is the patch and nothing else.
|
||||
|
||||
Each directory keeps the crate's own licence files, and the crate keeps
|
||||
its licence: wgpu-hal is MIT or Apache-2.0, i-slint-renderer-skia is
|
||||
Slint's GPL-3.0-only or royalty-free or commercial choice, and rawler is
|
||||
LGPL-2.1.
|
||||
|
||||
## Carrying a patch forward
|
||||
|
||||
When Slint or wgpu is bumped, the version here stops matching and cargo
|
||||
warns that the patch is unused — the build then silently goes back to the
|
||||
unpatched crate. So a bump is:
|
||||
When Slint, wgpu or rawler is bumped, the version here stops matching and
|
||||
cargo warns that the patch is unused — the build then silently goes back
|
||||
to the unpatched crate. So a bump is:
|
||||
|
||||
1. Copy the new version in beside the old one, as its own commit.
|
||||
2. Re-apply the diff from `git log -p` on the old directory.
|
||||
3. Point `[patch.crates-io]` at the new directory and delete the old one.
|
||||
4. Re-check on the device (below) — both patches are behaviour that only a
|
||||
rotated Android display exercises.
|
||||
4. Re-check what the patch was for. The wgpu-hal and Skia patches are
|
||||
behaviour that only a rotated Android display exercises (below); the
|
||||
rawler one is a linear DNG wider than 16 700 pixels, such as a
|
||||
panorama the merge writes.
|
||||
|
||||
Drop a patch entirely once upstream has the fix; each section says what
|
||||
upstream change would make it unnecessary.
|
||||
|
||||
Reference in New Issue
Block a user