Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

chroma_scope_wgpu

chroma_scope_wgpu is a decoded-frame video-scope library implemented with Rust, wgpu, and WebAssembly. It renders waveform, RGB/YRGB parade, vectorscope, CIE 1931 xy, and histogram views while keeping accepted scale, graticule, reference-line, resize, and snapshot behavior.

The library boundary starts after decoding: the host supplies decoded RGBA/BGRA or NV12 planes; Rust validates the frame, owns analysis and GPU resources, and renders the requested scopes. File decoding, WebRTC transport, and application layout remain host responsibilities.

License: this repository uses the PolyForm Noncommercial License 1.0.0. Commercial use is not permitted. Because this restriction is not OSI-compatible, the project is accurately described as source-available, not OSI open source.

Architecture

The browser package has two independent entry points:

Entry Purpose Owns
chroma-scope-wgpu-web UI-free renderer Decoded-frame validation, persistent WASM input memory, Rust/wgpu rendering, static geometry and optional label canvases
chroma-scope-wgpu-web/workbench Optional official UI English/Chinese module panels, automatic 4K/mobile density, analysis-rate controls, responsive layout, settings popovers, reset actions and reference-line gestures

The workbench uses the public core adapter. It does not contain a second analysis or renderer implementation. The runnable examples are:

  • /canvas-only.html — application-owned canvases with no module-window UI.
  • / — the official responsive workbench and local video test harness.

Requirements

  • Rust 1.96.1 or newer
  • wasm32-unknown-unknown target
  • wasm-pack 0.15 or newer
  • Node.js 24 and npm 11, or compatible releases
  • A browser and GPU that support WebGPU

Install the Rust target once:

rustup target add wasm32-unknown-unknown

WebGPU requires a secure context

Browser WebGPU is exposed only in a secure context. Loopback addresses such as http://127.0.0.1 and http://localhost receive a development exception, but an ordinary LAN URL such as http://192.168.x.x does not.

For phones, tablets, development boards, and other LAN clients, serve the page over HTTPS. The certificate must contain the host name or IP address in its Subject Alternative Name and must be trusted by the client. A public deployment should use a normal trusted certificate. This library intentionally has no WebGL or CPU-rendering fallback; when navigator.gpu or a suitable adapter is unavailable, initialization fails explicitly.

Build

Build the Rust crate and WASM package directly:

cargo build --release
wasm-pack build --target web --release --out-dir web/pkg

Build the release WASM package, install locked web dependencies, type-check, and create web/dist/:

./scripts/build-web.sh

Generated artifacts:

  • web/pkg/ — browser-targeted WASM bindings
  • web/dist/ — deployable Vite output
  • target/ — native and Rust build output

All three are generated locally and excluded from version control.

Run the Demo

Loopback HTTP is sufficient for development on the same computer:

npm --prefix web run dev

Open http://127.0.0.1:5173/.

For HTTPS and LAN access, provide your own certificate and private key:

VITE_HTTPS_CERT=/absolute/path/dev.crt \
VITE_HTTPS_KEY=/absolute/path/dev.key \
npm --prefix web run dev:https -- --port 5174

Open https://<server-host>:5174/. Certificate files belong outside the repository; web/.certs/ and the local override config are ignored.

Browser API Quick Start

Supply one application-owned canvas per enabled scope. The core caches static graticules and redraws them only when size or display options invalidate the cache.

import { createWgpuScopeRenderer } from "chroma-scope-wgpu-web";
import type {
  AnalysisConfig,
  ScopeName,
  ScopeRenderTarget,
  VideoScopeFrame
} from "chroma-scope-wgpu-web";

const targetScopes: ScopeName[] = ["waveform", "parade", "vectorscope", "cie", "histogram"];
const targets = Object.fromEntries(targetScopes.map((scope) => {
  const canvas = document.querySelector<HTMLCanvasElement>(`canvas[data-scope="${scope}"]`)!;
  return [scope, {
    canvas,
    width: canvas.clientWidth,
    height: canvas.clientHeight,
    devicePixelRatio: window.devicePixelRatio
  }];
})) as Record<ScopeName, ScopeRenderTarget>;

const renderer = await createWgpuScopeRenderer({ targets });

const frame: VideoScopeFrame = {
  width: 1920,
  height: 1080,
  timestampUs: 0,
  format: "nv12",
  range: "limited",
  color: { primaries: "rec709", transfer: "bt709", matrix: "bt709" },
  planes: [
    { data: lumaBytes, strideBytes: 1920 },
    { data: interleavedChromaBytes, strideBytes: 1920 }
  ]
};

const analysis: AnalysisConfig = {
  scopes: ["waveform", "parade", "vectorscope"],
  signalDensityWidth: frame.width,
  signalDensityHeight: 192,
  vectorscopeDensityWidth: 768,
  vectorscopeDensityHeight: 768,
  cieDensityWidth: 512,
  cieDensityHeight: 512,
  histogramBins: 256,
  waveformChannels: ["y"],
  paradeChannels: ["y", "r", "g", "b"]
};

await renderer.submitFrame(frame, analysis, "half");

Display and lifecycle operations are independent from frame submission:

renderer.updateDisplayOptions({
  waveform: { scaleStyle: "10bit", signalRange: "data", mode: "rgb" },
  vectorscope: { targetMode: "75+100", zoom2x: false }
});

renderer.resize("waveform", 960, 540, window.devicePixelRatio);
const snapshot = await renderer.requestSnapshot();
const statistics = renderer.stats();
await renderer.waitForIdle();
renderer.dispose();

submitFrame() accepts decoded rgba8, bgra8, and nv12 frames in the realtime WebGPU path. Unsupported formats and invalid strides, offsets, or metadata fail explicitly. See docs/API.md for the complete frame, analysis, display, timing, and error contracts.

Direct WASM Input Memory

The typed adapter manages a persistent three-slot WASM input ring. Schedulers that integrate directly with the generated binding may reserve and fill a slot themselves:

import init, { WasmRenderer } from "./web/pkg/chroma_scope_wgpu.js";

await init();
const wasm = await WasmRenderer.create(dynamicCanvases, staticCanvases);
wasm.configure(config);
wasm.reserveInput(0, lumaBytes.length + chromaBytes.length);
const input = wasm.inputView(0); // reacquire after every reserveInput call
input.set(lumaBytes, 0);
input.set(chromaBytes, lumaBytes.length);
wasm.submitNv12(
  input.subarray(0, lumaBytes.length),
  input.subarray(lumaBytes.length),
  1920, 1080, 1920, 1920, 0, 0, 0,
  "half", "limited", "bt709"
);

Do not retain inputView() across a later reserveInput() that may grow WASM memory. Keep no more than three submitted leases in flight and call waitForIdle() before destroying externally owned storage.

Repository Layout

.
├── src/                 Rust analysis, geometry, GPU resources and WASM binding
│   ├── geometry/        Static scope geometry and label layouts
│   └── gpu/             wgpu pipelines, resources, submissions and timing
├── shaders/             WGSL compute, render, line and composite shaders
├── web/
│   ├── src/             Typed core adapter, optional workbench and examples
│   └── tests/           Browser-adapter unit tests
├── tests/               Rust contracts, fixtures, golden data and browser tests
├── scripts/             Build, verification and optional A/B tooling
├── reports/             Machine-readable accepted verification evidence
└── docs/                API and verification protocols

Source code lives in src/, shaders/, and web/src/. Generated output, certificates, Playwright artifacts, and the local CodeGraph database are ignored. .codegraph/.gitignore is retained so contributors can create a local index without committing machine-specific data.

Verification

Run all self-contained checks available from a clean clone:

./scripts/verify-core.sh

This runs Rust formatting, Clippy, native tests, WASM target checks, the release web build, TypeScript checks, unit tests, browser contracts, and script tests.

The historical TypeScript/WebGPU implementation and large video/NV12 fixtures are intentionally not duplicated in this repository. To reproduce cross-backend visual, decoded-input, performance, and stability gates, point the harness at a reference checkout:

export CHROMA_SCOPE_REFERENCE_ROOT=/absolute/path/to/chroma_scope
./scripts/verify.sh

RK3588 A/B verification additionally requires ADB_SERIAL, BOARD_HOST, VITE_HTTPS_CERT, and VITE_HTTPS_KEY. Exact protocols and the accepted reports are documented in docs/verification.md.

License

Licensed under the PolyForm Noncommercial License 1.0.0. You may use, study, modify, and distribute the software only for permitted noncommercial purposes under those terms. Contact the licensor separately for commercial licensing.

About

Rust + wgpu/WebGPU video scopes for decoded RGBA and NV12 frames, with WebAssembly support and an optional bilingual workbench. Source-available for noncommercial use.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages