Commit Graph
178 Commits
Author SHA1 Message Date
aiongg 576ba0e02b Integration: track t12
# Conflicts:
#	contributors/people.toml
#	crates/compose/src/lib.rs
#	crates/compose/src/tests.rs
#	crates/engine/src/print_cmds.rs
#	crates/engine/src/print_cmds/tests.rs
#	docs/target-app-parity.md
2026-10-22 15:38:33 +00:00
aiongg 71991b0a03 Integration: track t10
# Conflicts:
#	contributors/people.toml
#	crates/engine/src/prefs.rs
#	crates/ui-egui/src/layer_style/contour.rs
#	docs/ui-parity.md
2026-10-22 15:38:28 +00:00
aiongg f72c3c2187 Integration: track t08
# Conflicts:
#	crates/ui-egui/src/camera_raw_ui.rs
#	crates/ui-egui/src/jobs_ui.rs
#	crates/ui-egui/src/widgets.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-22 15:38:23 +00:00
aiongg 446b74a8de Integration: track t07
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	crates/ui-egui/src/layer_shortcut_tests.rs
2026-10-22 15:38:21 +00:00
aiongg 0e80156397 Integration: track t16
Resolutions: CLI usage, flag lists and flag parsing carry both --tiff-compression (t15) and
--jpeg-format (t16); file.saveACopy, batch, imageProcessor and layersToFiles param specs carry
both tiffCompression and jpegFormat; file_cmds keeps tiff_compression_param and
jpeg_format_param; atn_cmds keeps flag_set/applies_at_canvas_bounds (t15) and UNHOLDABLE (t16),
edit.transform uses the pixel_target guard (t15) with layer_ref(p)? (t16); ui-egui open paths
build ImportOptions (t16) and apply raw_defaults (t15); docs headers/rows unioned; atn tests keep
both sides; tr.tsv "Keep Using CPU" kept once.
2026-10-22 15:38:13 +00:00
aiongg a7c80a83d2 Merge PR #6791: Intel Iris Xe on DX12: no more red blocks in brush previews and tool icons (#6790) 2026-10-22 15:37:46 +00:00
aiongg dc0094fa78 Merge PR #5781: 32-bit Hue/Saturation as the reference app does it: unclipped saturation and Colorize (two rgb32 oracles pass)
crates/io/tests/corpus.rs: kept main's pass_floor 188 (the PR's 187 would lower it; not re-measured here)
docs/file-format-parity.md: kept main's header and PSD/PSB rows; added the PR's change-log row without its floor figure
docs/gaps.md: kept main's header and evidence line; added the PR's change-log row without its floor figure
docs/scorecard.md: ours
2026-10-22 15:37:35 +00:00
aiongg d8865fc682 Merge PR #7638: Map the bevel's shading through its gloss contour, not its light (#7360)
crates/compose/src/effects.rs: kept main's par_units row loop in bevel_maps and ported the PR's change into it: k maps shade and the flat-face level through the gloss contour (lut_at), and the closure returns (hi, sh) without the apply_contour pair.
2026-10-22 02:27:06 +00:00
aiongg b43ba11beb Merge PR #8109: Compose: Color Balance works out each channel's curve once, not per pixel 2026-10-22 02:23:07 +00:00
aiongg 2b3af0f815 Merge PR #3193: fix(compose): resolve Dissolve coverage across adjustment, effects, a…
Conflicts:
crates/compose/src/tests.rs: appended tests, kept both
crates/gpu/tests/parity.rs: appended tests, kept both
2026-10-22 02:23:06 +00:00
aiongg ec663f4981 Merge PR #7204: Layer effects in Dissolve mode speckle, as in the reference app (#6995) 2026-10-22 02:22:05 +00:00
aiongg 1bc45862cb Merge PR #6813: A layer that doesn't fit in GPU memory no longer breaks GPU drawing: older pages are freed and the layer is retried 2026-10-21 21:56:21 +00:00
aiongg b9b97f15ff Merge PR #6243: GPU: pin the keep-overlap ordering with a tile-growth regression test 2026-10-21 21:56:20 +00:00
aiongg 94d28535d3 Merge PR #6464: Survive a lost window GPU device: draw nothing, save unsaved work for recovery, close (#3438, #1215) 2026-10-21 18:15:02 +00:00
aiongg 77557d75f1 Merge PR #8229: Integration batch: 8 PRs on main
# Conflicts:
#	crates/engine/src/layer_style.rs
#	crates/io/src/psd_export.rs
#	crates/io/src/smart_map.rs
#	docs/file-format-parity.md
2026-10-21 15:45:07 +00:00
Branch Sync 48d2fb45bd Merge remote-tracking branch 'refs/remotes/upstream/main' into HEAD 2026-10-21 11:18:54 -04:00
Branch Sync d5d937ac3e Merge remote-tracking branch 'refs/remotes/upstream/main' into HEAD 2026-10-21 11:17:09 -04:00
David Mann 86811ddeee Merge PR #6002: Stroke: keep Overprint (overprint)
# Conflicts:
#	crates/engine/src/layer_style.rs
#	crates/ui-egui/src/layer_style.rs
#	docs/file-format-parity.md
2026-10-21 11:36:58 +02:00
David Mann a492716367 Merge PR #5990: Bevel & Emboss: keep the Gloss Contour's Anti-aliased (antialiasGloss) 2026-10-21 11:29:05 +02:00
aiongg fc371711e6 Merge PR #5847: Shapes: Density and Feather on shape layers (#5846)
# Conflicts:
#	docs/scorecard.md
#	docs/target-app-parity.md
#	docs/ui-parity.md
2026-10-21 05:04:03 +00:00
xsvm aab9cdc045 Compose: Color Balance works out each channel's curve once, not per pixel
color_balance_channel recomputed the folded black point, white point and midtone gamma, and four of the five powf calls of the gamma > 1 Hermite join, for every sample. Those depend only on the sliders, so BalanceCurve works them out once per channel and the per-sample step is the clamp and one powf (or the Hermite polynomial). The CPU compositor builds the three curves once per buffer and the GPU plan once per LUT (it sampled color_balance_channel 3 x 4096 times). Results are bit-identical: a new test compares the curve against the per-sample formula it replaced, to the bit, over 123 slider settings, both Preserve Luminosity modes and a 1/1024 ramp.
2026-10-21 10:10:57 +08:00
Higgs32584andClaude Opus 5.5 ac1a7443a9 GPU compositor: pass the timer and batch runs in the OOM retry (main changed submit_batch and run_cell)
main added a GPU timer argument to submit_batch and the batch runs to run_cell,
so the OOM submit-and-retry path no longer compiled after the merge. Also add
the admission-evicted bytes to the flush counter with saturating arithmetic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:09:52 -04:00
Higgs32584 57948f1155 Merge remote-tracking branch 'upstream/main' into fix/6563-layer-texture-oom 2026-10-20 21:09:26 -04:00
aiongg d4576a0c5a Merge PR #5110: Performance: brush dab on a CMYK document within budget (#5108) 2026-10-21 01:06:11 +00:00
Higgs32584andClaude Opus 5.5 7cef41bba2 Merge upstream/main into fix/iris-xe-texel-guard
Conflict in apps/photocraft/src/main.rs: main added
`app.perf.gpu_info.set_available(&rs.available_adapters)` where this branch
installs the texel guard; both are kept (set_available first, then
texel_guard::install).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:55:58 -04:00
Higgs32584andClaude Opus 5.5 1b84acd2e3 GPU compositor: on a layer page OOM, submit and wait before the last retry
Pages dropped to make room can still be held by the work recorded since the
last submit, so the immediate retry may fail although the memory is about to
be freed. render now submits, waits for the GPU and runs the cell once more
before sending the refresh to the CPU (encode, which can't submit, still goes
to the CPU). Bytes dropped to admit pages also count towards the
intermediate-submit threshold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:52:51 -04:00
Higgs32584andClaude Opus 5.5 6a97837bcb zero_block_probe: pick the adapter by name with adapter=<name>
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>
2026-10-20 20:50:26 -04:00
David MannandClaude Opus 5.5 627bb8f1bf Integration: merge main, resolve conflicts (#6002)
docs/file-format-parity.md: both sides added revision-history rows; keep both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 00:11:01 +02:00
0073b395d4 Integration: merge main, resolve conflicts (#5990)
docs/file-format-parity.md: main added three 2026-10-15 revision rows
(DDS save, PNG sTER, PSD guides) where the PR added its own; keep all four.

Co-authored-by: varanus-salvator <262257003+varanus-salvator@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:50:13 +02:00
David Mann 651c32b9eb Merge PR #7566: perf(compose, gpu): many-layer composites, P60/P61 on the GPU 111 -> 28 ms 2026-10-20 22:39:26 +02:00
Akos Arinton 425dd76902 test(gpu): a requested backend fails the parity tests at every setup step
With WGPU_BACKEND set, a device or compositor that could not be created still skipped the
tests (`.ok()?`); like a missing adapter, it now fails them. Without WGPU_BACKEND the tests
skip as before.

Claude-Session: https://claude.ai/code/session_01Aab8aPCQgoN1sYrWPk8Kr6
2026-10-20 22:23:46 +03:00
Akos Arinton e4c2867494 fix(gpu): batch runs no longer depend on the prefix cache
`batch_runs` cut a run at the pass where the render captured its prefix checkpoint, so a cached
render grouped plain layers differently from an uncached one. On D3D12 with FXC a layer's bits
batched and drawn alone differ by about 1 ULP, so reuse stopped being exact there (#5286):
prefix_follows_an_edit_up_the_stack_and_stays_exact and
chunks_keep_their_own_checkpoint_after_a_partial_refresh failed on an RTX 5070 Ti.

The runs now depend on the plan alone and are computed once per render. `run_starts` marks the
passes a run starts at, and the prefix cache captures and resumes only at checkpoints there, so a
resumed render draws the passes above in the same runs as an uncached one. It may resume up to
three layers lower than before, one more batched pass.

The cache tests give their layers a reveal-all mask so each draws alone and keeps a checkpoint;
prefix_follows also runs on batched layers and checks that a capturing render batches like an
uncached one, which fails on the old executor on any backend.

Claude-Session: https://claude.ai/code/session_01Aab8aPCQgoN1sYrWPk8Kr6
2026-10-20 20:05:27 +03:00
Akos Arinton bc1c75d303 test(gpu): parity tests on the backend WGPU_BACKEND asks for
wgpu's Instance::default() ignores WGPU_BACKEND and WGPU_DX12_COMPILER, so the parity tests
always took the default adapter: Vulkan on a Windows machine that has a driver for it, which hid a
D3D12-only difference. They now read both, and a backend asked for that has no adapter or can't
render Rgba32Float fails the tests instead of skipping them.

Claude-Session: https://claude.ai/code/session_01Aab8aPCQgoN1sYrWPk8Kr6
2026-10-20 20:05:00 +03:00
Elvis33LE 40b3bf9b6b test(gpu): reword the tile-growth doc comment for what main does
The comment still described the reverted encoder-batching design; on main
keep_overlap submits its own copy, so the pinned property is that the
kept overlap never overwrites tiles rewritten in the same update,
whatever the submission shape.
2026-10-20 19:02:51 +02:00
David Mann 0b39361076 Merge main into feat/tool-214-12-shape-feather 2026-10-20 17:12:29 +02:00
Nigel Ihza Firdausy f0aae59938 Merge upstream main and retain CPU/GPU Dissolve parity coverage 2026-10-20 19:44:22 +07:00
Hector VillarrealandClaude Opus 5.5 46d48523a7 GPU parity: a bevel gloss contour that starts at 100 % (#7360)
Rolling Slope - Descending, as on the type in the report. Main's shader fails
it (CPU and GPU differ by 131/255); this PR's matches the CPU render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:26:17 -06:00
David Mann c2f3e23a40 Merge PR #5477: perf: chisel bevel size changes, CPU passes on threads and GPU fields share their sharp edges (results unchanged) 2026-10-19 16:51:22 +02:00
David MannandClaude Opus 5.5 0604f39d97 Performance: hand single-part tiles to put without a copy (#5108)
Only CMYK and Lab tiles are split into bands; every other format's one part
went through concat, an extra allocation and copy per tile on the UI thread
(8-bit RGBA, 64 tiles: 2.7 -> 3.3 ms). Now it passes straight through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:43:36 +02:00
Hector VillarrealandClaude Opus 5.5 a42e831698 Map the bevel's shading through its gloss contour, not its light (#7360)
The gloss contour was applied to the highlight and shadow amounts after
the lighting split, so a contour that starts high (Rolling Slope -
Descending) turned every unlit pixel into a full highlight: a bevelled
glyph's flat face painted (255, 40, 42) over its (151, 22, 23) fill. It
now maps the shading before the split, with the flat face's mapped
shading as the neutral, on the CPU and the GPU. Linear is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:01:11 -06:00
Ni-zavandVikram Bhaskaran a221622ad4 fix(compose): cover clipped Dissolve adjustments
Port the clipped-adjustment coverage path from #4711 and verify depth, alpha, masks, and document-coordinate GPU parity.

Co-authored-by: Vikram Bhaskaran <vikram.bhaskaran@outlook.com>
2026-10-19 07:18:54 +07:00
Nigel Ihza FirdausyandNi-zav 2ef53bb673 test(gpu): cover clipped Dissolve opacity and fill parity 2026-10-19 07:18:54 +07:00
Nigel Ihza FirdausyandNi-zav d127cd6afb style(gpu): match rustfmt compact ColorOverlay initialization 2026-10-19 07:18:54 +07:00
Nigel Ihza FirdausyandNi-zav 7e1c21e9a8 test(gpu): split Dissolve parity cases and exercise adjustment masking 2026-10-19 07:18:54 +07:00
Nigel Ihza FirdausyandNi-zav 21871dd40e refactor(gpu): branch explicitly on Dissolve effect-stack coverage 2026-10-19 07:18:54 +07:00
Nigel Ihza FirdausyandNi-zav 28807ed7ab test(gpu): cover Dissolve adjustment, clipping and effect parity 2026-10-19 07:18:53 +07:00
Nigel Ihza FirdausyandNi-zav 61724b49f0 fix(gpu): match CPU Dissolve coverage for adjustment, clipping and effects 2026-10-19 07:18:53 +07:00
00xJ0 4615a8bc26 Survive a lost window GPU device: draw nothing, save unsaved work for recovery, close (#3438, #1215)
When the window's own wgpu device was lost (a driver reset, e.g. after sleep), egui-wgpu could
not draw another frame: a frame with shapes panicked inside it ("Failed to create staging buffer
for index data", #1215, exit 101, unsaved work lost), and a frozen window's close button opened
an unsaved-changes prompt nobody could see (#3438). The #243 fallback only covered the canvas.

- DeviceHealth::device_lost (wgpu reported the loss, not a recorded fault) and ::probe (a
  non-blocking poll that keeps running after a recorded fault).
- RecoveryStore::flush: finish queued autosaves and wait for them.
- gpu_status::check_window, at the end of every frame: once the window's device is lost the
  frame draws nothing; the first time every unsaved document is written to crash recovery
  (Services::flush_autosaves) and the app closes without asking. The next launch reopens them.
- The app watches the window device on the CPU canvas too; ui.gpu.simulateLoss {destroy: true}
  destroys the real device to drive the path.

Verified on an RX 6900 XT, Vulkan and DX12: destroying the device in the running app exited 101
with the egui-wgpu panic before, and now exits 0 with the document recovered on the next launch.
2026-10-18 12:53:48 -07:00
Akos Arinton 33dd5f0103 perf(gpu): blend plain layers several to a pass, P60/P61 111 -> 28 ms
- LayerBlend: a plain layer is blended straight from its texture in one pass
  instead of a Content pass to a scratch chunk and a Blend pass.
- fs_layerbatch: runs of up to four plain layers that only feed each other
  are drawn as one pass, reading and writing the chunk once (the uniform
  record grows to its 256-byte stride to hold the extra layers).
- Drop shadows and outer glows alone: the merge builds the layer from its
  shape (F_LAYER_SHAPE), and nothing is copied back when the effect region
  covers the canvas. Such a layer takes 3 passes instead of 6.
- fs_layerbatch_normal: runs of Normal layers use a shader with Normal's code
  alone. Per-pass timestamps showed the batch spent 80% of its time on the
  register cost of carrying every blend mode, not on the arithmetic; Normal
  goes through the same function in every shader, so a layer gives the same
  bits whichever pass draws it.

P60 / P61 with the GPU compositor (8 GB budget, M4 Pro): 111 / 113 ms ->
28.2 / 28.1 ms. With the app's default Memory Usage these documents still
fall back to the CPU compositor on unified memory, so the budgets stay
unenforced.

Output: over the GPU parity suite (1620 renders) 1038 are bit-identical to
before and the rest differ by at most 1e-4/255, except one pixel one 8-bit
step apart where an adjustment rounds at .5; the mean difference from the CPU
reference is unchanged. New parity tests cover runs of every mode, Normal
runs at 8/16/32-bit and Lab, and exterior effects at any fill.
2026-10-18 22:26:06 +03:00
skylar taylor-barrickandGitHub 5c7c847923 perf(canvas): report how long the GPU spends on a composite (#7275)
`composite_ms` measures encoding and submitting a composite, not running it: on
a 24 MP refresh it read 7.7 ms for work the GPU spent ~171 ms on, so nothing in
the app could see a regression that moved work onto the GPU.

A timestamp is written on the GPU timeline at the start and end of every command
encoder a refresh submits, and the spans are summed and read back
asynchronously. Nothing waits, so a measurement arrives a frame or two later and
carries the pixel count of the refresh it belongs to. It is reported as a new
`Option<f64>` `perf.gpu_ms`; `composite_ms` keeps its meaning, so its consumers
are untouched. At 24 MP it reports 83.9 ms against a waited wall clock of
88.4 ms, where the app previously said 3.1 ms.

What it leaves out, documented in the module: queue-level uploads, which wgpu
runs in its own command buffer ahead of ours, and the end-of-batch writeback
after the last timestamp. That is ~5 % of the reported time here and
proportional to the composite, against the ~22x of the number it replaces.

Unavailable rather than wrong: without TIMESTAMP_QUERY and
TIMESTAMP_QUERY_INSIDE_ENCODERS the timer is never built and `gpu_ms` is None —
on older drivers, the CPU compositor, wasm, and Apple silicon, whose GPUs
support counter sampling only at stage boundaries. `PHOTOCRAFT_NO_GPU_TIMING`
suppresses both the timer and the feature request.

Pass-boundary timestamps would cover Apple silicon and need only
TIMESTAMP_QUERY, but they capture 70-78 % of the encoder-level span here and
the shortfall grows with document size, so they were measured and rejected.

D3D12 is exercised: device creation, a 100 ns/tick period and the full
write/resolve/map round-trip succeed with no validation errors, including
across a flushed three-buffer refresh. Numbers throughout are from an
integrated Intel GPU on Mesa/Vulkan and a software D3D12 adapter; no vendor
Windows driver and no Apple hardware was tested.
2026-10-18 10:41:35 -06:00