Commit Graph
15 Commits
Author SHA1 Message Date
Brandon Thomas 260d03ced6 Merge PR #1777
# Conflicts:
#	docs/whats-new.md
2026-10-14 22:31:41 -04:00
Michael Anderson 1f87328c6d Never crash: Write preview cache files from one bounded writer (#1522)
Each deferred cache write started its own thread holding its image, with no limit. One writer thread per cache now takes them in turn, and a write is skipped once 128 MB is waiting.
2026-10-14 13:56:34 -05:00
Nathaniel LedfordandClaude Opus 5.5 d2523e8544 Preview cache: cached thumbnails are no longer taken for the person's photos
The thumbnail cache wrote .jpg files into the library's thumbs folder, which is usually inside
Pictures: photo galleries listed every thumbnail as a photo (issues #610, #1066), and every read
brought the file's date forward, so each launch made them all look new.

- Cache files are named <32 hex>.lcthumb (JPEG data as before). A .jpg of before is taken over
  under the new name when it is asked for; the rest when the cache is tidied, which the engine does
  once per cache folder, on a thread of its own with a row in the activity stack that shows its
  progress and can stop it. Nothing is rendered again.
- Reading writes nothing: what was read is remembered for the session (a full cache lets it go
  last), and only a file that went unused for a week has its date brought forward.
- A folder that holds nothing but the cache gets a CACHEDIR.TAG, when the cache makes it and when
  it is tidied; one that also holds somebody's files is not tagged (issue #98).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:26:17 -04:00
MarekSpodBiedry 935fabeb18 Preview disk cache: ignore symlinked shard directories (#1504) 2026-10-13 16:36:39 +02:00
bflatastic 702f90bfc9 Batch 32: close the tests two merges added at one place; cargo fmt 2026-10-13 11:08:16 +07:00
bflatastic 251397694e Merge PR #1302
# Conflicts:
#	crates/preview/src/tests.rs
2026-10-13 11:04:34 +07:00
bflatastic a681a3c667 Merge PR #1076 2026-10-13 11:02:50 +07:00
dev-null-undefinedandClaude c8f676252f M5.6: with a GPU, a slider drag's preview is drawn in the frame of the drag tick
A drag tick requested the loupe's draft and the frame ended; the draft,
done in 1.5-3 ms on the GPU, was picked up at the start of the next frame,
a frame after the slider moved. Now, in a drag, the loupe waits up to 40 %
of a frame for its draft when the last loupe render took well under that
(not with a render offload, nor zoomed in, where what changes on screen is
the window's draft). A CPU render, which would only make the frame late, is
not waited for.

JobPool::wait waits for the next finished job, which try_recv then hands
out; Renderer::poll's collection of finished jobs is split out so the wait
can turn the draft into the texture.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-13 03:40:23 +02:00
Matthew James SivakandClaude Opus 5.5 d7037d117f M5.6: a view slot renders one job at a time, so a drag's renders don't pile up
The job pool replaced a slot's queued job, but a newer job for a slot whose job was
already running started beside it on another worker. A slider drag submits faster
than renders finish, so one view had several renders on the GPU at once and about
30 % of finished renders were thrown away (61 MP raw, 1:1 Exposure drag). Now a
slot's next job waits until its running one ends (a newer submission still
replaces it in the queue); other slots are not held up, and a job that panics
frees its slot.

Measured on a 61 MP raw at 1:1, 8 s Exposure drags (M4 Max): thrown-away renders
roughly halved, and frames over 20 ms 0 % in every one of 4 runs (one run of the
overlapping pool dropped 10 %).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:20:15 +02:00
Nigel Ihza Firdausy 87be53279b fix(preview): account for replaced thumbnail cache entries 2026-10-12 01:10:31 +07:00
hxwandClaude Opus 5.5 5a537d5d39 LR-BEHAV-GPU: no render is inside the GPU driver when the app quits (issue #620)
A render still running on a worker thread while the process exits crashes in the
driver (SIGSEGV in the NVIDIA Vulkan driver on Linux), where neither the panic
hook nor catch_unwind helps: pool workers were detached, the devices are statics
that are never dropped, and nothing on the way out waited for GPU work.

- gpu: every entry into a device (render, device creation, pool trim, denoise
  runner and tiles) is counted as work in flight (exit.rs). begin_shutdown()
  lets nothing new in; wait_idle(timeout) waits for what is inside.
- preview: JobPool keeps its workers. shutdown(timeout) and Drop discard the
  queued jobs and wait, bounded, for the running ones (2 s on drop); never for
  the calling thread, and not twice.
- engine: a render job caught by the shutdown gives up instead of rendering on
  the CPU, so quitting doesn't wait for it.
- app: on_exit ends with LightcraftApp::shutdown (2 s for everything), after
  settings and library are saved; the macOS menu-bar Quit goes through on_exit.

Not changed: the export, merge, denoise and import threads are still detached;
their GPU work goes through the gate, their CPU work is cut off at exit as before.

Tests: pool drop waits / is bounded / survives a panicked job and its own job
dropping it; the gate; GPU renders refused and the device not created after
begin_shutdown; the app's shutdown leaves no render worker. The crash itself
needs Linux + NVIDIA and was not reproduced here (Windows, DX12): 9 of 9 quits
of the app mid-render exit with code 0 in under 0.8 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:46:24 -05:00
radialmonsterandClaude Opus 5.5 62325bd45e Thumbnails: retire the replaced preview cache so its old jobs can't write
Reopening a library (or opening another one, or set_disk_cache_bytes
falling back to attaching) replaced MediaCache::rendered with a new
PreviewCache, but render jobs still in flight kept the old object.
Each PreviewCache checked only its own generation, so after a clear on
the new cache the old one's generation was still current: a finishing
thumbnail job wrote its pixels through it into the directory the clear
had just emptied. The UI rejected the result, but the JPEG stayed.

attach_disk_cache now retires the cache it replaces before swapping it:
PreviewCache::retire takes the generation write lock (so it waits for a
write in progress, like clear) and marks the object retired; every
get/put/put_deferred through it is then refused and its clear() is a
no-op, as its directory may now belong to the replacement. Files on
disk are left alone, so a plain reopen still finds its thumbnails, and
resizing the budget in place keeps the same cache object as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:23:17 -04:00
f2f7a1cb26 T0: optimize thumbnail requests and reject obsolete preview work
Skip unchanged thumbnail jobs and reuse bounded settings hashes (both maps
capped at 1024 entries). Reject stale render results and cache writes after
edits, library changes and preview clears: render requests carry a request
id and cache generation, PreviewCache writes and reads hold the generation
lock that clear() takes, and library.clearPreviews also drops cached
sources. The web worker index follows the same generation rule.

The decoder guards, Windows path fixes and test fixture changes that were
bundled in a327b98 are separate commits; the benchmark follows this one.

Co-authored-by: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:04:03 -04:00
Brandon ThomasandClaude Opus 5.5 0fd0ffb783 Issue #98: thumbnail cache only lists, counts and deletes its own files
scan() now accepts only <2 hex>/<32 hex>.jpg (matching shard) and the
cache's own <32 hex>.tmp<id> temp files, so prune/clear never delete and
size() never counts foreign files in a shared thumbs/ folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:23:32 -04:00
Brandon ThomasandClaude Opus 5.5 bcecab7d84 M1.5: thumbnail/preview cache (disk + memory LRU, prioritized job pool)
- new crate lightcraft-preview (L3): 128-bit content hashing (SipHash-1-3),
  cost-bounded Lru, DiskCache (JPEG q90 files keyed by content hash +
  settings hash + size + RENDER_CACHE_VERSION; temp+rename writes; LRU prune
  by mtime to 80% of budget; corrupt files dropped), PreviewCache (memory
  LRU over disk), JobPool (per-slot dedupe, priorities, reprioritize/drop,
  inline mode for wasm).
- engine: Session::thumb_job buckets thumbnails to 128/256/384/512 and serves
  them from the cache without decoding the source; decoded thumbnail
  sources now in a 384 MB LRU (was unbounded); library attaches
  <library>/thumbs; library.info cache stats; library.clearPreviews.
- ui-egui: Renderer runs on JobPool; thumbnails scrolled out of the
  prefetch area are dropped from the queue; on-screen thumbnails get
  re-prioritized; ui.inspect reports in-flight renders + thumb textures.
- Demo library (24 photos): grid fully drawn 4.4 s cold -> 0.21 s warm
  (24/24 disk hits, no source decoded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:51:47 -04:00