Motivation
Kromacut has several CPU-heavy operations that may benefit from WebAssembly, particularly large deterministic pixel loops and repeated numeric scoring. WebAssembly is not automatically faster than optimized JavaScript, so this should begin as a measured experiment rather than a broad rewrite.
The goal is to determine whether a WASM compute backend can materially reduce processing time while preserving Kromacut's output, responsiveness, and browser compatibility.
Proposed direction
Add one master toggle under the existing Experimental settings:
- WebAssembly acceleration (experimental)
- When disabled, behavior remains exactly as it is today.
- When enabled, only operations with a supported WASM implementation use it.
- Unsupported operations continue using JavaScript.
- Load the WASM module lazily and cache it after initialization.
- Automatically fall back to JavaScript if WASM cannot load or execute.
The toggle remains available for as long as the WASM backend is experimental. It should only be retired after the accelerated features are demonstrably faster and better overall, reliable across supported environments, maintainable, and accepted as a permanent part of Kromacut. At that point, WASM becomes the default for supported operations while JavaScript remains as an automatic compatibility fallback.
WASM work should run inside a worker. A synchronous WASM call on the main thread can still freeze the interface.
Initial pilot: dedithering
Dedithering is the first experiment. It is a large deterministic pixel loop, has straightforward input and output buffers, and allows exact comparison against the existing JavaScript implementation.
Before porting it, record baselines for representative small and large images and different pass counts. The experiment must measure complete operation time, including WASM initialization and memory-copy costs, rather than timing only the inner kernel.
If the dedithering pilot succeeds, one image quantizer can be evaluated next. Auto-paint is another possible follow-up because it already has a worker boundary and benchmark suite. However, its search and scoring need careful design: crossing the JavaScript/WASM boundary once per candidate could erase the benefit. Candidate scoring would need to be batched or the complete hot search loop moved behind the WASM boundary.
Meshing and export are not recommended for the first experiments because their topology and serialization requirements make correctness substantially riskier.
Implementation direction
Use Rust compiled to WebAssembly, rather than handwritten WebAssembly/WAT.
- Create a small, separate WASM crate instead of coupling the compute module to the Tauri application crate.
- Use
wasm-bindgen to expose the Rust API to the TypeScript worker.
- Keep the Rust dedithering kernel independent of browser APIs so it can be tested natively.
- Keep the existing JavaScript implementation as the correctness reference and permanent fallback.
- Pass complete image buffers or large row blocks across the JavaScript/WASM boundary; never call WASM once per pixel.
- Include module startup, allocation, and buffer-transfer costs in benchmarks.
- Keep handwritten WAT out of scope unless a future profiler identifies a tiny compiler-generated hot spot that demonstrably requires it.
A likely initial layout is:
wasm/
kromacut-wasm/
Cargo.toml
src/
lib.rs
dedither.rs
The TypeScript-facing backend interface should not expose Rust-specific details, allowing the implementation to change without affecting UI code.
Acceptance criteria
Non-goals
- Rewriting every algorithm in WASM.
- Changing generated colors, physical layers, mesh topology, or export contents.
- Removing the JavaScript fallback.
- Adding WASM threads or requiring cross-origin isolation in the initial experiment.
- Accelerating Three.js rendering or JSZip serialization in the first pilot.
Open questions
- What minimum speedup justifies the extra bundle and maintenance cost?
- Should the module use a small dedicated Rust/WASM crate, or another toolchain?
- Should backend selection automatically stay on JavaScript below an input-size threshold?
- Can inputs and outputs use transferable buffers to avoid unnecessary copies?
- Should SIMD be a progressive enhancement after the scalar implementation proves worthwhile?
- After dedithering, which operation dominates real user wait time and should be evaluated next?
Motivation
Kromacut has several CPU-heavy operations that may benefit from WebAssembly, particularly large deterministic pixel loops and repeated numeric scoring. WebAssembly is not automatically faster than optimized JavaScript, so this should begin as a measured experiment rather than a broad rewrite.
The goal is to determine whether a WASM compute backend can materially reduce processing time while preserving Kromacut's output, responsiveness, and browser compatibility.
Proposed direction
Add one master toggle under the existing Experimental settings:
The toggle remains available for as long as the WASM backend is experimental. It should only be retired after the accelerated features are demonstrably faster and better overall, reliable across supported environments, maintainable, and accepted as a permanent part of Kromacut. At that point, WASM becomes the default for supported operations while JavaScript remains as an automatic compatibility fallback.
WASM work should run inside a worker. A synchronous WASM call on the main thread can still freeze the interface.
Initial pilot: dedithering
Dedithering is the first experiment. It is a large deterministic pixel loop, has straightforward input and output buffers, and allows exact comparison against the existing JavaScript implementation.
Before porting it, record baselines for representative small and large images and different pass counts. The experiment must measure complete operation time, including WASM initialization and memory-copy costs, rather than timing only the inner kernel.
If the dedithering pilot succeeds, one image quantizer can be evaluated next. Auto-paint is another possible follow-up because it already has a worker boundary and benchmark suite. However, its search and scoring need careful design: crossing the JavaScript/WASM boundary once per candidate could erase the benefit. Candidate scoring would need to be batched or the complete hot search loop moved behind the WASM boundary.
Meshing and export are not recommended for the first experiments because their topology and serialization requirements make correctness substantially riskier.
Implementation direction
Use Rust compiled to WebAssembly, rather than handwritten WebAssembly/WAT.
wasm-bindgento expose the Rust API to the TypeScript worker.A likely initial layout is:
The TypeScript-facing backend interface should not expose Rust-specific details, allowing the implementation to change without affecting UI code.
Acceptance criteria
Non-goals
Open questions