mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-13 09:20:07 -06:00
* GPU-FULL-1: LUT profiles, Refine Edges and soft proofs render on the GPU These three sent the whole loupe / compare / export render back to the CPU pipeline (media::develop and lightcraft_gpu::render declined them). - LUT profiles: the .cube table rides in the finish kernel's aux buffer (LUT_* parameters); lut_apply mirrors Lut3d::apply (trilinear, domain), applied after grain and blended by the profile amount, as on the CPU. - Refine Edges: the cross guided filter Auto Mask already ran on the device, now with a combine mode (max for strokes, mix by sqrt(refine) for Refine), applied to the combined alpha before the mask's invert and amount. - Soft proofing: proof-space gamut mapping and the destination / display gamut warnings in the kernel (PROOF_* parameters, GAMUT_WARN and warning colours generated from the CPU constants), for output spaces and displays. Equivalence tests (RTX 5070, DX12): LUT profiles at three amounts and a non-default domain, Refine 15/60/100 on two masks, proofs in 16 combinations of space, output, display and warnings - all max 1 LSB, mean <= 0.0005 LSB. The tests also check that strong refine and the destination warning change the image (so they aren't comparing two unchanged renders). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: spot removal, defringe and the histogram on the GPU Spots and defringe made every render that had them (and every white balance or spot edit) read the resampled image back, run the scene-linear stage on the CPU and upload it again. Now only the ProRAW local tone map (off by default) does. - Defringe: OkLab pass, separable lightness min/max filter, per-pixel apply. Parameters come from the new optics::defringe_params, which the CPU stage now uses too. - Spots: new spot module (spot_prep / spot_apply over each spot's box, the Heal tone correction as device Gaussians), spot by spot in place so each reads what the earlier ones left, as on the CPU. Remove's automatic source: spot_score rates the 48 candidates on the device and only the scores come back. Layout, candidates, spoke directions, radii and ranking are shared helpers in pipeline::spots (spots::apply uses them; behaviour unchanged, pipeline tests green). - Histogram: counted with atomics on the device on Histogram::of_srgb8's sampling grid (exact), instead of a CPU pass over the readback. Tests: spots (clone, heal, auto remove incl. near the edge where no candidate fits, brushed strokes, feather 0, cut by the edge, overlapping) and defringe (purple/green, hue ranges, preview and full size) max 1 LSB; the device histogram equals the CPU count of the returned image for small, sampled (>1 MP) and windowed renders; the scoring kernel picks the CPU's candidate. (The edge case caught a real bug: "doesn't fit" was written as a decimal literal that rounds below f32::MAX.) Unit tests that create the device now take turns with the crash-sentinel test, which swaps a global. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: fix the spot kernels' build; GPU tests fail when kernels don't build 07d4680b's spot module declared `const F32_MAX = bitcast<f32>(...)`, which naga can't evaluate as a constant: the kernels failed to build, the device was unavailable, and the equivalence tests skipped as if there were no adapter - so that commit's GPU tests didn't actually run. f32::MAX is now a function, and the equivalence suite (and the device unit test) fail instead of skipping when an adapter exists but the kernels don't build. Re-verified on the RTX 5070 (DX12), all on the device: 21 equivalence tests incl. the 8 spot cases (max 1 LSB), defringe, the exact device histogram, and the auto-source unit test. Adds an ignored thumbnail throughput bench (tests/thumbs_bench.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-3: thumbnails render on the GPU once the device is ready Every cached job (grid and filmstrip thumbnails, preset and profile previews, faces, quick stand-ins) was forced onto the CPU. They now use the GPU when lightcraft_gpu::ready() - so they never block on device creation at startup (1.8 s incl. kernels here) and render on the CPU until then. Views and exports keep waiting for the device as before. tests/thumbs_bench.rs (ignored): six workers rendering 256 px thumbnails of 512 px sources with preset-like settings (NR, sharpening, clarity, grading, a gradient mask): RTX 5070 3.6 ms each vs 4.3-5.2 ms on 32 CPU threads, with the cores left free for raw decoding. Engine and UI suites: unchanged (only the 12 failures that predate this branch on Windows: tests_sync paths, tests_model_setup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-4: the loupe's drafts are drawn straight from the GPU Rendering and drawing now share one device: the desktop app creates the window's wgpu device with the adapter's limits and hands it to the GPU renderer (lightcraft_gpu::adopt_window_device, on a background thread at startup, under the crash sentinel; a device that doesn't suit falls back to one of the renderer's own, as before). A drag's drafts of the views are RenderJob::on_screen: the render ends in an Rgba8Unorm texture (lightcraft_gpu::render_display -> Output::Display, new `present` kernels: unwritten-pixel count, row padding for the copy) and egui-wgpu draws it as a native texture (ui-egui ScreenTextures, implemented by the app) - no readback, no conversion to egui pixels, no re-upload. The readback's checks run on the device (atomic unwritten count, device histogram). Renders that need pixels keep the readback: the settled full render (view cache, overlays, screenshots), thumbnails, exports, overlays, windows that spots grew, a monitor profile. - engine: RenderJob::on_screen, RenderResult::frame (ScreenFrame). - ui-egui: Tex::native / Tex::id() (21 draw sites), Renderer set_screen_textures, natives per slot (freed when pixels return), ui.inspect perf.gpuWindowDevice / screenTextures / mainOnGpu. - app: WindowTextures, window device limits, LIGHTCRAFT_GPU_WINDOW=0 to opt out. Tests: crates/gpu/tests/display.rs adopts a device in its own process and checks the texture holds exactly the readback's pixels (padded and unpadded widths, draft and full), the histogram matches, and overlays / grown windows come back as pixels. In the app (RTX 5070, a 45 MP CR3 from the user's library copy): all 90 drafts of a drag drawn from GPU textures, 1.69 ms vs 2.05 ms median per draft render with the readback; the frame screenshot shows the image correctly; pixels return after the drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: the denoise runner tests take turns with the crash-sentinel tests Building a denoise runner creates a device and compiles kernels under the init marker. Running in parallel with backend::tests, it could arm the marker the test had just set, so "is removed afterwards" failed now and then. The runner tests now hold DEVICE_TEST_LOCK while they build, like the render tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-4: fix the build after merging upstream main Upstream's QuickJob panic guard builds a RenderResult (add `frame: None`), its idle-trim test builds a Tex (add `native: None`), and its on_exit test builds App (add the WindowTextures slot). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL-2: upstream's blur tests take turns with the crash-sentinel tests gaussian_matches_cpu and bench_kernels create the device, so they hold DEVICE_TEST_LOCK like every other device test in lightcraft-gpu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the same-folder export test compares paths separator-blind (Windows) The written path is the original's folder (`\` on Windows) joined with `Exports/IMG_1.jpg`; the expected one had `\Exports`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the folder-drop tests find folder widgets separator-blind (Windows) The folder tree spells its paths with `/`; the tests built widget ids from Windows paths with `\`, so none was found and every test failed on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the Windows path fixes take the same form as in the other PR branches So the branches merge into main without conflicts between their copies of the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: headless screenshots read back GPU-drawn views; LIGHTCRAFT_GPU_LIMITS caps the adopted device Review of PR #1065: - A headless `ui.screenshot` taken mid-drag showed the photo as a white rectangle: the GPU-resident draft left no CPU copy. With keep_pixels (control channel) the renderer now keeps each view's frame, and `cpu_textures` reads it back through `ScreenTextures::read` (`lightcraft_gpu::read_texture`). Drawing still never reads back, so drags keep the speed-up. - Adopting the window's device skipped LIGHTCRAFT_GPU_LIMITS; `Gpu::adopt` now caps its limits the same way `Gpu::new` does. Tests: a_headless_screenshot_reads_back_a_view_drawn_from_the_gpu (ui-egui), the_limits_override_caps_an_adopted_device (gpu); the display test uses read_texture and skips through lightcraft_testkit::skip!. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: review fixes (CodeRabbit 2026-10-21) and slideshow/print draw GPU-drawn views - the window-device adoption is announced before its thread starts: until it finishes, the settings' warm-up and renders don't create a device of their own (they raced it) - the device error / device-lost handlers go on only after the kernels build, so a declined window device keeps the window's own handler - releasing a slot frees its GPU-resident texture and kept frame - a soft proof into a colour space drafts full size on the GPU; only one to a device profile counts as proofed for `handles` - docs: Fill spots and device-profile soft proofs render on the CPU - slideshow, slide layout and print draw `Tex::id()` (the GPU texture when there is one) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the display test skips when LIGHTCRAFT_GPU=0 switches the GPU off It panicked ("GPU rendering is off") instead of skipping like the other GPU tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: a view readback never panics on a lost device read_texture (headless screenshots of GPU-drawn views) polled the device directly. On a lost device wgpu 30 panics in Device::poll; it now goes through the same guard as the renderer's own polls (poll_device): a lost device returns an error, stops the GPU and is not polled again. Review of PR #1065. Also quiets a browser-build dead-code warning on OnScreen::Frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: an HDR edit's histogram shows the HDR range on the on-screen GPU path too develop_on_screen returned the GPU's SDR histogram for both its Display and Image arms; it now applies lightcraft_pipeline::hdr_histogram as develop_reporting does (None for drafts, windows, proofs and non-HDR edits, which keep the GPU's histogram). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the Navigator draws the photo's texture as it is drawn in the loupe The Navigator falls back to the main view's texture before the photo has a thumbnail. It drew `t.tex`, which is stale (or the 1x1 placeholder) when the main view holds a GPU-resident draft; it now draws `Tex::id()`, like every other draw site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the egui 0.37 lifecycle test's texture names no native id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: rustfmt the lifecycle test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the second window fits the frame's size; freed GPU frames drop their views The second window sized its image from the texture handle, which is only a 1x1 placeholder for a GPU-resident render; it now uses the frame's size. When a GPU-resident render can't be shown and every native texture is freed, the views pointing at them (Before, Region, Compare tiles) are dropped too, so they render again as pixels instead of drawing a freed id. Test: freeing_gpu_frames_drops_the_views_drawn_from_them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(gpu): LIGHTCRAFT_GPU_ADAPTER picks the card for rendering too The variable chose the card only for AI Denoise and the face detector (two copies of the same enumerate-and-match code); photo rendering always took the first high-performance adapter, on a two-card machine the one driving the screens. One helper, backend::pick_adapter, now serves the render device (Gpu::new), the denoise network and the face detector: the first hardware card whose name contains the value (any case), else the high-performance one. A name that matches no card is logged and falls back instead of failing. backend::may_adopt_window_device says whether rendering may share the window's device (only for the window's card). Name matching is unit-tested without a GPU; the equivalence tests print the card they ran on. Documented in docs/gpu-pipeline.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: a view switching to a GPU texture releases its pixel image Review of #1065 (CodeRabbit nitpick): when a slot that had pixels switched to a GPU-resident texture, its egui texture kept the full-size image, never drawn while the native texture is set. Tex::show_native now swaps it for a 1x1 placeholder on the switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gpu): LIGHTCRAFT_GPU_ADAPTER picks among several matches in a defined order and logs once Review of #3428: - several cards match: the one whose whole name it is, then dedicated before built-in before virtual cards, then wgpu's order (backend::choose_named_adapter); the log names every match and the one used; - no match: the default choice, with a warning that lists the cards; - a name matching only a software renderer: never used, and the warning says so; - each line is logged once (backend::log_once), though the render device, AI Denoise and the face models each ask; - tests on fake adapter lists: a_fake_adapter_list_picks_in_a_defined_order, a_choice_is_logged_once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the release note gives the GPU's documented bound against the CPU (3 levels, mean under half a level) Review of PR #3544. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU-FULL: the window's device isn't adopted when LIGHTCRAFT_GPU_ADAPTER names another card With #3428 merged, Gpu::new already makes the render device on the named card; the window's device was still adopted first, so the app rendered on the screen's card whatever the variable said. backend::may_adopt_window_device (tested on names in backend.rs) now decides: another card named renders on a device of its own, and the window stays on the screen's card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>