Port behavior and regression coverage into current modules; preserve current upstream behavior.
Bound aggregate parallel plugin memory and preserve serial/parallel equality and total-budget regressions.
wasmi 2.0's strict limits divide the code section's size by its function
count before validating it, so a module whose code section declares 0
functions but more than 1000 bytes (crafted, truncated inside the section,
or a duplicate code section) panicked in Plugin::load. Check every code
section for that one case before handing the bytes to wasmi and return
Error::Module; everything else is still left to wasmi.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adjustment layers (compose blend_back), destructive edits
(mix_through_selection) and plug-in filters mixed their result back as
o + (r - o) * k. In f32 the difference had already rounded a small
result away against a huge backdrop, so a Color Lookup's 0.1 over 2^20
came out 0.125, and 0.5 over 1e8 came out 0. They now compute
o * (1 - k) + r * k, which is exactly r at full coverage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plug-ins: refuse a manifest that declares a parameter twice (#9441)
`ordered_params` kept every entry of the `params` object, so a repeated
key gave one value two dialog controls and the last spec won in
`resolve_params`. Refuse the repeated key with an error naming it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plug-ins: quote the repeated parameter name in the error (#9441)
The duplicate check runs before validate() checks the name, so a key
with control or bidi characters must not reach the message unescaped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address CodeRabbit review: a repeated parameter is named before its spec is read
The key was checked after next_entry had parsed the value and after the
64-parameter limit, so a repeated key with a broken second spec, or one
past the limit, reported that error instead of naming the key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A filter run gives each band a fresh wasmi instance capped at 512 MiB of
linear memory, and ran one band per rayon worker. The cap was therefore
per instance times the thread count: one run on a 16-thread machine could
hold about 8 GiB, and a plug-in smart filter repeated that on every
re-render.
Limits gains max_total_memory_bytes (default 2 GiB), the budget for all
of one run's instances together, and Limits::parallel_bands(threads),
which allows only as many concurrent bands as max_memory_bytes fits into
that budget (at least one). Plugin::apply uses it to size its groups of
parallel bands, so with the defaults a run holds at most four instances
at the cap however many cores there are. Plug-ins configured with a
smaller per-instance cap keep every worker busy. The limit is listed in
docs/plugins.md.
Tests (crates/plugins/tests/abi.rs):
- parallel_bands_fit_the_total_memory_budget: 16 workers with the
default limits run 4 bands at once, not 16 (failed before: 16 != 4).
- one_band_at_a_time_matches_parallel_bands: a budget that holds one
instance still covers every band on an 8-thread pool, with the same
pixels as parallel bands.
Fixes#8603
2026-10-23 11:43:32 -04:00
Michael Higginsandcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Unknown manifest fields were always ignored, so a plug-in may already
carry any "unit" value; failing its install would break it. Anything but
"px" (an unknown string, a number, null...) now reads as "none", as when
the field is absent. The plug-in loader has no warning channel, so this
stays silent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filter plug-in's live preview runs on a reduced copy of a large
document, but its params only went through the built-in name list, so a
pixel length with another name (Chromatic Aberration's Amount) previewed
2-4 times too large, and a unitless `size` or `radius` was divided.
number and int params now take an optional "unit" ("px" or "none", the
default; anything else is a manifest error). The preview divides px
params by the reduction (defaults included, ints rounded, kept in range)
and limits the factor so a px param with a positive minimum never drops
below it. Unitless params are never scaled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strict workspace Clippy stopped on collapsible_match in affinity, raw, plugins, engine, ui-egui and xtask. Pure conditions become guarded match arms (each falling through to an existing empty arm), and the ruler drag state and the measurement command result are bound to locals, preserving evaluation order, error messages and fallbacks.
The same lint fixes also appear as a side commit in #5157 (David2766), which can drop it on rebase.
When one band failed, its siblings stopped with a generic "another band failed"
error, which could win the collection order and hide the real cause. The abort
flag now carries the first error, so every band reports it; tests cover traps,
error codes and the instruction budget across parallel bands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* photocraft-plugins: sandboxed WebAssembly filter plug-ins (wasmi host, ABI v1)
A new L4 crate runs plug-ins in wasmi (pure Rust, MIT/Apache) with no host
imports, a fuel budget metered in slices with a wall-clock deadline, a memory
cap, a recursion cap and wasmi's strict module limits. Every band gets a fresh
instance; traps, budgets, bad manifests and bad pointers are all Errors.
Pixels go over as interleaved normalised f32 in tile-aligned bands, in
parallel on native, blended by the selection. Includes the example Invert
plug-in (no_std Rust, examples/plugins/invert-rs) built into a test fixture,
hostile-module tests, and docs/plugins.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Engine: plugin.* commands (install, list, run, remove, reload)
plugin.run filters the active pixel layer, a targeted channel or a smart
object (recorded as a smart filter) as one undo step; Preferences >
Plug-ins gains Use Additional Plug-ins Folder, loaded when prefs apply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI: Filter > Plug-ins lists installed plug-ins with generated dialogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plug-ins: bands never outgrow the memory cap; regenerate parity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Bench: example plug-in Invert vs built-in Invert on 24 MP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>