// Diagram 01

Full System Architecture

Every component in a running scheng instrument and how they relate to each other.

INPUTS Syphon In NDI Receive Webcam Video File RTMP / RTSP CONTROL MIDI CC OSC ParamStore smooth() targets → values step_frame() each tick GPU CORE — WgpuRuntime Hot-Reload AssetWatcher naga GLSL compile pipeline cache Shader Graph NodeConfig uniforms u_* input_textures[4] iChannel0–3 Render Pipeline Rgba16Float targets MSAA 1x / 4x / 8x bind groups FrameBlock UBO CustomBlock UBO iChannel0–3 RenderPass/node queue.submit() one batch/frame OutputSinks PreviewSink SyphonSink NdiSink FfmpegSink SpoutSink present() called after submit() non-blocking drop-safe about_to_wait() → tick() → execute_frame() → present() OUTPUTS Syphon Out NDI Out RTMP Stream RTSP Stream Record MP4 Preview Window Video / texture inputs Control signals GPU processing Output / sink
// Diagram 02

Frame Render Loop

What happens every single frame, in order. The render loop never blocks — inputs that fall behind drop frames gracefully.

01 POLL INPUTS Webcam.poll() VideoDecoder .upload_frame() Syphon.poll() 02 PARAMS store.step_frame() MIDI targets → smoothed values 03 BUILD CFG NodeConfig per node frag_shader source uniforms HashMap input_textures[4] 04 GPU RENDER graph.compile() RenderPass per node Rgba16Float targets MSAA if active 05 SUBMIT queue.submit() All GPU commands batched as one CommandEncoder 06 PRESENT sink.present() Preview blit Syphon / NDI FFmpeg pipe — non-blocking — inputs and outputs run on dedicated threads — frame drops preferred over stalling —
// Diagram 03

Graph Node Topology

How nodes connect. Any output can feed any input. The graph compiles to a topologically sorted execution plan each frame.

SINGLE NODE ShaderSource generates content PixelsOut triggers sinks EFFECT CHAIN ShaderSource webcam texture ShaderPass solarize effect ShaderPass color grade PixelsOut → preview FEEDBACK / TEMPORAL ShaderSource current frame ShaderPass mixes prev frame PixelsOut → output PreviousFrame last frame texture A/B MIXER ShaderSource Source A ShaderSource Source B Crossfade u_tbar = MIDI CC1 mix(A, B, tbar) PixelsOut → Syphon / NDI ↑ MIDI CC1 controls mix graph.compile() topologically sorts nodes — execution order is always dependency-correct
// Diagram 04

MIDI → Shader Routing

How a MIDI CC message becomes a shader uniform value — with optional smoothing to remove steppy artifacts.

MIDI Controller CC1 = 64 (any device) midir callback msg[0]=0xB0 ch=1 cc=1 val=64 ParamStore set_by_midi_cc(1, 64) targets["u_tbar"]=0.5 step_frame() each tick smooth → values[0.5] NodeConfig uniforms["u_tbar"] = store.get("u_tbar") CustomBlock GPU binding 6 UBO float u_tbar = 0.5 GLSL Shader uniform float u_tbar; mix(a, b, u_tbar) MIDI THREAD RENDER THREAD (each tick) smooth=0.05 → reaches target in ~20 frames — eliminates steppy CC artifacts
// Diagram 05

Use Case Signal Flows

Three complete real-world signal chains, the colorspace pipeline, and MSAA reference.

LIVE VIDEO MIXER Resolume Syphon Out scheng crossfade Resolume Syphon In ↑ MIDI CC1 T-bar BROADCAST STREAM Webcam FaceTime HD scheng color grade YouTube RTMP ingest ↑ MIDI CC brightness / contrast / hue INSTALLATION Video File loops forever scheng generative NDI Out → projectors COLORSPACE CHAIN Input Rgba8Unorm 8-bit source GPU Render Rgba16Float 16-bit half-float FFmpeg Encode YUV420P bt.709 broadcast color range Player / VLC Rec.709 tagged correct colors ✓ Without bt.709 defaults to bt.601 (SD) washed / wrong colors ✗ scheng sets -colorspace bt709 -color_primaries bt709 -color_trc bt709 automatically on all FFmpeg output MSAA ANTI-ALIASING sample_count = 1 off — aliased edges sample_count = 4 4× MSAA — smooth edges sample_count = 8 8× MSAA — highest quality Pipeline is keyed on (shader_hash, sample_count) — switching recompiles once then caches. Organic shaders rarely benefit from MSAA.
// Diagram 06

Distribution & Packaging

How a compiled scheng instrument is packaged and shipped for each platform. Each platform has different bundling, signing, and dependency requirements.

SOURCE cargo build --release target/release/my-instrument · target/release/my-instrument.exe macOS MyInstrument.app Contents/MacOS/ ← binary Contents/Frameworks/ ← Syphon.framework Contents/Frameworks/ ← libndi.dylib Contents/Resources/assets/shaders/ codesign Developer ID cert --options runtime notarytool Apple malware scan stapler staple Distribute as DMG hdiutil create .dmg Windows MyInstrument/ (folder) my-instrument.exe ← binary Processing.NDI.Lib.x64.dll (if NDI used) Spout.dll (if Spout used) assets/shaders/ ffmpeg must be in PATH on target machine signtool (optional) suppresses SmartScreen warning requires code signing certificate Distribute as ZIP Compress-Archive Linux my-instrument.AppDir/ usr/bin/my-instrument ← binary usr/bin/assets/shaders/ AppRun script ← entry point requires Vulkan driver on target GPU NDI: LD_LIBRARY_PATH=/usr/local/lib appimagetool bundles all into single file runs on any modern distro Distribute as AppImage my-instrument-x86_64.AppImage
// Diagram 07

Asset Resolution at Runtime

How scheng resolves shader files in development vs. a distributed bundle. The hybrid approach gives users hot-reload in development and a self-contained binary in production.

asset_dir() checks for Contents/Resources/ next to binary not found found Development ./assets/shaders/main.frag hot-reload active ✓ Distributed Bundle Contents/Resources/assets/shaders/ self-contained ✓ include_str!() fallback compiled into binary — always works include_str!() fallback same embedded default same code path works in both modes
← Back to scheng home