Skip to content

About

Standalone Rust CPU renderer for Noisemaker's effect catalog

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Noisemaker for CPU (Rust)

Open source from Noise Factor · more projects

Noisemaker for Rust

This package supports the "Export Shader Pipeline" feature in Noisedeck.app. The feature runs shader compositions on other platforms. Noise Factor derives this package from the upstream Noisemaker Engine project and tests it for pixel-level parity.

Noisemaker for Rust is a standalone CPU renderer for Noisemaker's generated effect catalog. The Cargo package and command are named noisemaker-for-rust and noisemaker-rs. Rust code imports the library as noisemaker_cpu.

The renderer contains the catalog and typed shader IR at compile time. Normal library and CLI rendering does not invoke JavaScript, Node.js, Python, a browser, a GPU, the network, FFI, or a runtime shader compiler.

Build and install

Rust 1.85 or newer is required.

cargo build --release
cargo install --path .

Library API

Render one catalog effect with its defaults:

use std::collections::BTreeMap;
use noisemaker_cpu::{ParamValue, RenderOptions, Surface, render_effect};

let options = RenderOptions { width: 256, height: 256, ..Default::default() };
let image = render_effect(
    "synth/gradient",
    &BTreeMap::<String, ParamValue>::new(),
    &BTreeMap::<String, Surface>::new(),
    &options,
)?;
# Ok::<(), Box<dyn std::error::Error>>(())

Or render a full DSL program:

use noisemaker_cpu::{CpuRenderer, RenderOptions};

let mut renderer = CpuRenderer::new()?;
let result = renderer.render(
    "search synth,classicNoisedeck,filter; gradient().kaleido(sides: 8).vignette().write(o0)",
    &RenderOptions { width: 256, height: 256, ..Default::default() },
)?;
# Ok::<(), Box<dyn std::error::Error>>(())

Runnable versions are in examples/render_effect.rs and examples/render_dsl.rs. They write to NOISEMAKER_OUTPUT when set and otherwise choose an untracked temporary path.

Command line

The CLI has seven commands:

noisemaker-rs generate EFFECT [OPTIONS]
noisemaker-rs apply EFFECT INPUT.png [OPTIONS]
noisemaker-rs animate EFFECT [OPTIONS]
noisemaker-rs run [PROGRAM|-] [OPTIONS]
noisemaker-rs render PROGRAM|- [OPTIONS]
noisemaker-rs effect EFFECT [OPTIONS]
noisemaker-rs effects

generate renders an image generator. It also accepts random as the effect. This deterministically selects from the sorted eligible generator pool using --seed and prints the resolved ID first. apply renders an image filter or mixer at the input image dimensions. run reads DSL from a file or standard input, and render is its compatibility alias. effect constructs a bounded, meaningful program for any catalog domain. effects prints all 210 sorted ID<TAB>KIND records.

Common options are:

--width N           output width (default 512)
--height N          output height (default 512)
--time N            finite render time (default 0)
--seed N            signed deterministic seed (default 1)
--output PATH       output file; --filename is an alias
--input PATH        bind a PNG to imageTex and textTex
--texture NAME=PATH bind a named external PNG; repeatable
--param NAME=VALUE  override a catalog parameter; repeatable, last value wins
--external-input FIX bind a deterministic external-input fixture (repeatable):
                    `midi`, `audio`, or `mesh`. Required by the reactive
                    (`synth/roll`, `synth/scope`, `synth/spectrum`) and mesh
                    (`render/meshLoader`, `render/meshRender`) effects; the
                    fixtures byte-match the CPU port's parity fixtures.

animate additionally accepts --frame-count (50), --fps (30), --speed (1), and --save-frames DIR. It calls ffmpeg directly with literal arguments. If ffmpeg is absent, saved frames remain usable. Without --save-frames, the command fails with installation guidance.

PNG and MP4 destinations are created through exclusive, unpredictable sibling temporary files and renamed only after successful encoding. A failed command preserves any pre-existing destination and removes only its own temporary file. Width times height is limited to 16,777,216 pixels.

Catalog and execution model

The catalog contains all 210 CPU effects across image, volume, particle, renderer, and loop domains, including the five upstream external-input effects that were formerly excluded: the reactive synth/roll, synth/scope, and synth/spectrum (MIDI/audio state bound as uniforms through --external-input midi or audio) and the mesh render/meshLoader and render/meshRender (a parsed OBJ packed into data textures and rasterized by a native CPU triangle-mesh draw adapter matching the upstream drawMode: 'triangles' semantics: depth test LESS, CCW back-face culling, Blinn-Phong shading, gamma 1/2.2). Their fixtures are the deterministic constants shared with the CPU port's parity harness. The complete generated inventory and the 460 non-null compile-time choices are listed in docs/EFFECTS.md.

External-texture effects require --input or a matching --texture binding. Volume work scales cubically with volumeSize. Iterated effects default to 60 steps, so large canvases can be expensive on a scalar CPU renderer. Particle filters are meaningful inside a pointsEmit/state/pointsRender chain. The effect command constructs that owner path automatically. Loop markers must be paired around the repeated chain.

Coordinates are normalized with the origin at the lower left inside shader execution while Surface and PNG rows are stored top-down. Colors are linear floating-point RGBA internally and clamp/round to RGBA8 during PNG encoding.

Overlay texture generation is cached by a bounded least-recently-used CPU cache. RenderOptions::one_shot selects Initial or Ready one-shot semantics. Ready is the public default.

Four programs use complete native fragment replacements for semantics that the generic typed IR cannot represent directly. Snow uses an exact-key float32 semantic adapter to preserve the JavaScript CPU port's operation boundaries. CRT remains on typed IR but selects one exact-key builtin-semantic adapter for its reduced-turn float32 sine. This matches the JavaScript CPU port without changing sin for any other shader. Temporal Aberration preserves the JavaScript factory's current true-branch assignment quirk behind its exact program key. Ordinary typed-IR conditional assignments retain their standard behavior. The canonical hash_uint(uint) helper follows the JavaScript CPU compiler's body routing at its exact mangled function target: a murmur-style finalizer body keeps the source-compatibility hash, while the LCG-seeded mix body (the points-pipeline agent kernels) executes the authority's LCG sequence. Differently named user functions continue to execute their typed-IR bodies.

Compact CSL syntax is deliberately not implemented. Use the documented chained DSL, whose parser provides source locations and catalog-aware validation.

Regeneration and parity

The checked-in bundle is generated from locked Noisemaker metadata and shader sources. Verify canonical generated files and the generated effect reference:

PYTHONDONTWRITEBYTECODE=1 python3 scripts/generate_bundle.py --check

Supplying --source PATH rebuilds from a source tree containing metadata.json, bundle-lock.json, and effects.json or effects/. Existing shader hashes are immutable. There is no update-lock mode. The packaged maintainer generator includes its complete scripts/transpiler dependency.

To move to a new upstream release, fetch every eligible effect with NM_SHADER_VERSION=<release> through scripts/transpiler/cdn.py, which caches effects/<id>.json under scripts/transpiler/.cdn-cache/<release>/. Build the source tree from that cache (keeping only the programs the catalog's passes name), a metadata.json that is src/generated/catalog.json without paramNames and paramAliases plus any changed definitions, and a copy of src/generated/bundle-lock.json whose hashes are updated for the programs that intentionally changed. Take scripts/upstream-param-contract.json from the CPU port's upstream-snapshot.js at the same upstream revision.

Cross-language maintenance parity compares one shared DSL program through the built Rust and JavaScript CLIs, at 8×8 pixels unless --size says otherwise. It writes exactly one sorted record for every catalog effect and requires exact RGBA8 bytes (zero tolerance). It reports every unsupported interface with a stable reason. It treats timeouts or render errors as failures:

python3 scripts/parity.py \
  --rust target/release/noisemaker-rs \
  --js ../noisemaker-for-cpu/bin/noisemaker-cpu.js

Use --only ID, --size N, --timeout SECONDS, and --json PATH for focused or machine-readable runs. --sweep extends the run with a bounded non-default parameter sweep and stateful multi-frame sequences: every effect's default render is joined by at most two one-parameter variants (deterministically derived from the catalog metadata) and, for iterated, particle, loop, reactive, and mesh effects, three frames of the default program at the 0.1/0.4/0.7 time sequence. Build the sequence example with the same toolchain, profile, and target directory as the Rust CLI, then pass both binary paths explicitly:

cargo build --locked --release --bin noisemaker-rs --example parity_sequence
python3 scripts/parity.py \
  --rust target/release/noisemaker-rs \
  --rust-sequence target/release/examples/parity_sequence \
  --js ../noisemaker-for-cpu/bin/noisemaker-cpu.js \
  --sweep

Frame cases reuse one renderer per side and feed each frame's floating-point output through imageTex/textTex into the next frame's media/blendMode chain. This measures explicit prior-output feedback; each renderer call recreates its internal particle and feedback resources. Each case is one sorted record (ID, ID#param:NAME, ID#frame:N) and the sweep report states expected, executed, byte-exact, and failed counts at tolerance 0. JavaScript is an offline test oracle only. It is never a production runtime dependency.

On every push, CI checks formatting, lints, the 1.85 minimum, the generator, and the test suite without the catalog smoke tests. A push that changes src/**, the Cargo manifests, or scripts/parity.py also runs the Linux cross-port parity against the pinned CPU port revision. The catalog smoke tests (every default program and every compile-time choice) and the Windows cross-port parity run weekly and on manual dispatch. Only a weekly or manually dispatched run releases the export kit, after its Linux parity passes.

Current support and limits

The live compatibility report is the current measured support: the tested source and oracle revisions, the platforms, and the parity counts. The issues labelled gap are the current qualification limits. Both are GitHub issues in this repository and are updated as results change.

Contributing and security

See CONTRIBUTING.md for the contribution policy and the complete local verification commands, and the Code of Conduct for participation expectations. Report suspected vulnerabilities privately as described in SECURITY.md, not in a public issue.

License and trademark

Code is available under the MIT License. See TRADEMARK.md for trademark use.

About

Standalone Rust CPU renderer for Noisemaker's effect catalog

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages