Step 3 of #6916. Dehaze, Clarity and Texture run as fragment passes
(box, minimum and guided filters) around step 2's Shadows and
Highlights, so every setting but Detail develops on the GPU with one
readback.
- algo: camera_raw::spatial resolves Dehaze, Clarity and Texture per
frame; develop builds them the same way (130 output hashes unchanged,
same speed). PerPixel gains the three stages, per_pixel() refuses only
Detail, and Whites/Blacks are measured after any spatial stage.
- gpu: camera_raw_filters.wgsl; the tone frame's pyramid is made on
demand; plans record four segments around the main shader's passes.
Dehaze's light uses two exact selections (cutoff value, then cutoff
index among its ties). Its <=768 px copy comes from the CPU
(Dehaze::cells, develop's own code), cached per source and white
balance: on WARP the GPU's copy split CPU ties at the cutoff and moved
the light by 5.7e-3.
- GPU vs CPU at most 7.6e-5 (Vulkan, DX12, WARP), tolerance 1/4096.
858x572: Dehaze +40 6.0 ms vs 29 ms, Clarity +50 3.5 vs 16 ms,
Texture +50 3.4 vs 10 ms, everything 19 ms vs 650-690 ms (Vulkan).
Part of #6916.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 2 of #6916: the local Laplacian filter behind Shadows and Highlights
(Paris, Hasinoff, Kautz 2011, sampled as in Aubry et al. 2014, as
local_tone.rs does) as wgpu fragment passes, chained in front of step 1's
per-pixel pass with no readback in between.
- algo: local_tone exposes its constants and resolves the sliders into
LocalTone / TonePass (the remapping is a method); the CPU filter runs
those passes. PerPixel gains local_tone and measure_whites_blacks;
whites_blacks_model() exposes the Whites and Blacks tables;
quantile_rank() is the one rank rule. CPU output unchanged bit for bit
(86 hashes, 8-bit and float, 171x113 to 2100x1300) and as fast.
- gpu: camera_raw_tone.rs/.wgsl. Pyramid levels are R32Float textures; the
remapped pyramids of up to 96 references sit in one Rgba32Float atlas per
level, four per tile. The filter's quantiles, and Whites and Blacks'
points after it, are exact order statistics on the GPU (16 thresholds per
round, 8 rounds); a pass renders the Whites and Blacks rows into the
table texture. Textures, passes and bind groups are planned once per
frame size. Pipelines build with the rest on the existing thread.
- ui-egui: the proxy worker sends Shadows and Highlights frames to the GPU;
Dehaze, Texture, Clarity and Detail still use the CPU for the frame.
GPU vs CPU at 8, 16 and 32 bits: at most 9.9e-6 (RTX 5060 Ti, Vulkan and
DX12), 1.03e-5 (WARP); tests keep 1/4096. 858x572 proxy, release: Shadows
+50 8.7 ms (Vulkan) / 10.6 ms (DX12) vs 147-300 ms on the CPU.
Part of #6916.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Prepare GPU compositor in background
* Add compositor startup performance scenarios
* Generate scorecard in scratch CI
* Fix pipeline build type inference
* Address compositor startup review and document measurements
* Run startup performance checks on scratch CI
* Remove scratch performance job
* Count failed GPU attempts in frame timing
* Make shader fingerprint test portable
* Address review: run first-document scenario as a guarded group; share COMPOSITOR_BUILDING constant
* Handle compositor startup benchmark failures
* Fix frame timing after upstream merge
* Keep GPU startup failures on CPU fallback
* Record the failed GPU attempt of a deferred redraw, and bound --reps
A redraw deferred after a failed GPU composite carries that attempt's time to its end, where it is
recorded once beside the CPU time. Perf::record clears the attempt so a direct CPU refresh doesn't
inherit the last one's. perf_scenarios caps --reps.
* Refresh cached canvases after compositor build
* Expose canvas cache key to GPU completion handler
* ci: drop the GPU compositor probe job
AGENTS.md: feature and fix PRs don't add or change CI, and app workflows
only call the shared craft-common-actions workflows. The probe example
stays as a manual tool (cargo run -p photocraft-gpu --example
compositor_build_probe).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Loading the Intel Vulkan driver on Windows (igvk64.dll) crashes the process
with STATUS_ACCESS_VIOLATION (#4). The app leaves Vulkan out on Intel, but
the tests use wgpu's defaults, so on such a machine `cargo test` dies in the
first GPU test (ui-egui's background_redraw, adjust_preview_gpu, ...).
WGPU_BACKEND=dx12 avoids it for the kittest harnesses, but seven tests and
four benches built their instance with wgpu::Instance::default(), which takes
every backend and ignores the environment. They now use
InstanceDescriptor::new_without_display_handle_from_env(): the same defaults
with WGPU_BACKEND and friends applied, so nothing changes where it is unset.
Document the workaround in docs/development.md and the testkit driver notes.
* perf(gpu): reuse filled-shape effect maps during translation
Hash filled-shape outlines in local coordinates so whole-pixel movement can use the existing effect-map translation reuse path. Keep its region-size and exact coverage checks, and invalidate the key when the outline deforms.
Add a shape-key regression and a synchronized styled-shape group benchmark with fresh-render parity verification. On RTX 4070 Laptop, the 1600x1200 fixture improves from 61.336 to 33.120 ms median; a 24 MP full-canvas fixture improves from 223.018 to 184.289 ms. Warm frames rebuild zero effect programs, and the quick fixture matches fresh rendering exactly.
* fix(gpu): stabilize fractional shape effect fingerprints
Quantize only origin-normalized fingerprint coordinates to a 1/65536 pixel grid and canonicalize signed zero. Preserve the render path and exact coverage guard.
Cover fractional anchors and handles, positive and negative integer translations and handle deformation. Exercise fractional origins in the benchmark and document its entry points. All 40 GPU tests, Clippy and WASM checks pass; reused output matches a fresh compositor exactly.
---------
Co-authored-by: kotulsky <7805437+kotulsky@users.noreply.github.com>
bench_camera_raw dropped each GPU develop's Result with .ok(), so a
device fault or a refused size would have been timed and printed as a
GPU figure. try_time returns the first error instead, and main
propagates it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 1 of #6916: one wgpu fragment pass for develop's per-pixel stages
(white balance and exposure, Contrast without Texture or Clarity, Whites
and Blacks, Vibrance and Saturation, tone curves, Color Mixer, Color
Grading, vignette, grain in full-resolution coordinates).
- algo: camera_raw::per_pixel resolves those stages' constants and
tables; the CPU develop builds its own from the same functions, so its
output is unchanged bit for bit.
- gpu: CameraRawDevelop (camera_raw.rs, camera_raw.wgsl), curves and
Whites/Blacks rows in one R32Float table texture, readback for the
histogram and scopes. Built on demand, not with the compositor (#6419).
- ui-egui: the proxy worker develops on the GPU when the settings have no
spatial stage; Rendering Mode CPU, no GPU canvas, a software adapter, a
fault or a failed develop use the CPU develop. The pipeline builds on a
thread at the first open. describe() reports developMs and
developBackend.
GPU vs CPU at 8, 16 and 32 bits: at most 2.7e-5 (RTX 5060 Ti, Vulkan and
DX12), 6.7e-6 (WARP); tests hold 1/4096. 858x572 proxy, release: CPU
20.7-28.1 ms, GPU 2.7 ms.
Part of #6916.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a machine with Iris Xe and a discrete GPU the default request can pick
the discrete one, so the probe would not test the affected adapter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wgpu-hal 30 creates Iris Xe textures as committed resources (its
gfx-rs/wgpu#3552 workaround) with D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, and
egui-wgpu fills each new texture with a single copy. On this driver the
copy leaves 4x4 blocks of all-zero texels showing stale memory, so the
empty corners of Brush Preset picker strokes and tool icons show freed
textures' pixels (often red).
ui-egui's new texel_guard plugin rewrites fully transparent texels in
each frame's texture uploads as premultiplied (0, 0, 0, 1) before eframe
renders them (under one 8-bit level of difference). The desktop app
installs it only for DX12 on "Iris(R) Xe", the same adapter test wgpu
uses; PHOTOCRAFT_TEXEL_GUARD=1/0 forces it on or off.
The gpu example zero_block_probe reproduces the driver bug without the
UI: 630/1500 egui-style uploads read back wrong, 0/1500 with the guard.
Driving the release app (tool cycling, picker rescaled): 6/40
screenshots with red blocks before, 0/120 after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(gpu): reuse unchanged lower-stack composite chunks
* test(gpu): verify half-float composite checkpoint reuse
* docs: record measured GPU composite reuse gains and limits
* perf(gpu): admit whole grid chunks, serve partial damage as sub-rect copies
Cache entries were keyed by the exact damage rectangle, so brush strokes
(a new partial rect every frame) filled the budget with entries that
never hit again. Entries are now whole chunk-aligned, chunk-sized grid
cells: a capture merges into the cell it overlaps at the rectangle's
offset, a read is served as a sub-rect copy of the covered part, and
coverage is tracked per cell so a resume only happens where every pixel
of the requested rectangle is cached. After a full render, any in-bounds
partial rectangle hits.
Regression test: two series of disjoint strokes inside the same chunks
leave composite_cache_bytes unchanged (no entry-per-rectangle growth),
a full refresh still finds its chunks and a top-layer edit afterwards
reuses them. The old partial-damage test now expects the covered-chunk
hit and checks outside-the-document renders admit nothing.
* docs: record the GPU reuse measurement in target-app-parity.md
The honest assessment moved from roadmap.md to target-app-parity.md on
main; keep main's roadmap and add only the dated measurement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>