React component(s) for inspecting igasset files and igpack bundles.
  • TypeScript 47.8%
  • C++ 46.3%
  • CMake 3.1%
  • WGSL 2.7%
Find a file
Kamaron Peterson 252cb69461
Some checks failed
Test / unit (push) Successful in 17s
Build Demo / build-wasm (push) Successful in 6m22s
Test / build-wasm (push) Successful in 5m48s
Build Demo / build-demo (push) Failing after 26s
Test / browser (push) Successful in 1m7s
Respect alpha channel with texture inspector (#47)
Co-authored-by: Kamaron Peterson <kamaron.peterson@gmail.com>
Reviewed-on: #47
2026-07-19 23:15:09 +00:00
.forgejo/workflows Fix the demo (#46) 2026-07-19 20:30:41 +00:00
demo Fix the demo (#46) 2026-07-19 20:30:41 +00:00
packages/viewer-react kamaron/texture-inspector (#39) 2026-07-11 10:29:28 +00:00
raw_assets kamaron/texture-inspector (#39) 2026-07-11 10:29:28 +00:00
wasm Respect alpha channel with texture inspector (#47) 2026-07-19 23:15:09 +00:00
.clangd More local dev stuff, all AI generated, neat 2026-05-28 00:23:09 -07:00
.gitignore kamaron/texture-inspector (#39) 2026-07-11 10:29:28 +00:00
.prettierignore Add E2E tests for initialization (#1) 2026-06-11 07:34:55 +00:00
.prettierrc.json Add E2E tests for initialization (#1) 2026-06-11 07:34:55 +00:00
CMakeLists.txt Include igasset (#2) 2026-06-27 02:35:00 +00:00
CMakePresets.json Include igasset (#2) 2026-06-27 02:35:00 +00:00
package.json Fix the demo (#46) 2026-07-19 20:30:41 +00:00
pnpm-lock.yaml Fix the demo (#46) 2026-07-19 20:30:41 +00:00
pnpm-workspace.yaml Cursor-generated repo layout + hello world impl 2026-05-27 01:19:07 -07:00
README.md Tighten README: scannable structure for impatient readers (#45) 2026-07-15 09:47:03 +00:00

igasset-viewer-react

React components for inspecting igasset / .igpack files via a WebAssembly + WebGPU runtime. Built to power asset visualization on indigocode.dev.

The library wraps a C++ WASM module (compiled via Emscripten) that owns a WebGPU device and an observable filestore. React components handle canvas lifecycle, pan/zoom/orbit interaction, and filestore state — you just mount a provider and render inspectors.

Installation

npm install @indigocode/igasset-viewer-react react react-dom

The published package includes binary artifacts that must be served at runtime:

  • .wasm files — Emscripten builds (single-threaded + multi-threaded). Modern bundlers handle these automatically via new URL(..., import.meta.url).
  • .igpack files — WGSL shaders and sample assets. You must serve dist/igpack/ at the /igpack/ path on your host. See Bundler config below.

The package is published to the Forgejo NPM registry at git.indigocode.dev. Point the @indigocode scope there in your .npmrc if you haven't already.

Basic usage

Mount <IgviewerProvider>, then render an inspector inside it:

import { IgviewerProvider, TextureInspector } from "@indigocode/igasset-viewer-react";

export function App() {
  return (
    <IgviewerProvider fallback={<p>Loading WebGPU</p>}>
      <TextureInspector />
    </IgviewerProvider>
  );
}

<IgviewerProvider> loads the WASM module, acquires a WebGPU device, creates the runtime + filestore, and auto-loads required shaders. <TextureInspector> is an all-in-one component with a pan/zoom canvas, texture/format/sampler selectors, sprite list, and readouts.

For geometry inspection, swap in <GeometryInspector> (orbit-camera canvas + mesh selector):

import { IgviewerProvider, GeometryInspector } from "@indigocode/igasset-viewer-react";

export function App() {
  return (
    <IgviewerProvider fallback={<p>Loading WebGPU</p>}>
      <GeometryInspector />
    </IgviewerProvider>
  );
}

A bare canvas (no inspector UI) is also available:

import { IgviewerProvider, IgviewerCanvas } from "@indigocode/igasset-viewer-react";

export function App() {
  return (
    <IgviewerProvider>
      <IgviewerCanvas style={{ width: "100%", height: 480 }} />
    </IgviewerProvider>
  );
}

Modest customization

Each inspector accepts custom children to compose your own layout. The provider wrapper is still supplied, so child components can consume context:

import {
  TextureInspector,
  TextureInspectorCanvas,
  TextureInspectorImageSelector,
  TextureInspectorFormatSelector,
  TextureInspectorSizeReadout,
} from "@indigocode/igasset-viewer-react";

<TextureInspector>
  <TextureInspectorCanvas />
  <div style={{ display: "flex", gap: 12, padding: 8 }}>
    <TextureInspectorImageSelector />
    <TextureInspectorFormatSelector />
    <TextureInspectorSizeReadout />
  </div>
</TextureInspector>

Available texture sub-components: TextureInspectorCanvas, TextureInspectorImageSelector, TextureInspectorFormatSelector, TextureInspectorSamplerSelector, TextureInspectorSpriteList, TextureInspectorSizeReadout, TextureInspectorVRAMReadout, TextureInspectorDiskReadout, TextureInspectorPanZoomReadout, TextureInspectorStatusReadout.

Available geometry sub-components: GeometryInspectorCanvas, GeometryInspectorMeshSelector, GeometryInspectorVertexReadout, GeometryInspectorTriangleReadout, GeometryInspectorIndexFormatReadout, GeometryInspectorAttributesReadout, GeometryInspectorOrbitReadout, GeometryInspectorStatusReadout.

Advanced customization

Use the context hooks directly to build fully custom UIs:

import {
  IgviewerProvider,
  TextureInspectorProvider,
  useTextureInspector,
} from "@indigocode/igasset-viewer-react";

function CustomTexturePicker() {
  const { textureSources, selectedTextureIdx, setSelectedTextureIdx } =
    useTextureInspector();

  return (
    <select
      value={selectedTextureIdx}
      onChange={(e) => setSelectedTextureIdx(+e.target.value)}
    >
      <option value={-1}>(none)</option>
      {textureSources.map((src, i) => (
        <option key={i} value={i}>
          {src.sourceType === "igasset" ? src.igassetPath : src.igassetName}
        </option>
      ))}
    </select>
  );
}

export function App() {
  return (
    <IgviewerProvider fallback={<p>Loading</p>}>
      <TextureInspectorProvider>
        <CustomTexturePicker />
      </TextureInspectorProvider>
    </IgviewerProvider>
  );
}

useTextureInspector() exposes: textureSources, selectedTextureIdx/setSelectedTextureIdx, format/setFormat, filterMode/setFilterMode, display (pan/zoom state), sizeText/vramText/diskText, sprites, status, and more. useGeometryInspector() exposes the analogous set for geometry.

For lower-level access, useIgviewer() returns { module, runtime, filestore, threading, capabilities }. Use useFilestoreState() for reactive filestore state and the filter functions (getTextureMetadata, getSpritesheetMetadata, getDracoMetadata, getWgslSourceMetadata) to extract typed metadata.

Loading files

import { useIgviewer } from "@indigocode/igasset-viewer-react";

function AssetLoader() {
  const { filestore } = useIgviewer();

  const handleFile = async (file: File) => {
    const data = new Uint8Array(await file.arrayBuffer());
    if (file.name.endsWith(".igasset")) filestore.load_igasset(file.name, data);
    else if (file.name.endsWith(".igpack")) filestore.load_igpack(file.name, data);
  };

  return <input type="file" onChange={(e) => e.target.files?.[0] && handleFile(e.target.files[0])} />;
}

Provider props

Prop Type Default Description
threading "auto" | "single" | "multi" "auto" Auto picks multi-threaded if crossOriginIsolated, else single.
wasmUrl string Override .wasm URL (bypasses import.meta.url resolution).
device GPUDevice Inject a pre-existing WebGPU device (not destroyed on unmount).
fallback ReactNode null Rendered during WASM + GPU initialization.
errorFallback (error: Error) => ReactNode Rendered if initialization fails.

Threading

The library ships two WASM builds. <IgviewerProvider> auto-selects based on crossOriginIsolated (requires COOP + COEP headers). Force one with threading="single" or threading="multi".

Building + running the demo locally

Prerequisites: Emscripten SDK, CMake 3.24+, Node 20+, pnpm 9+, a WebGPU-capable browser.

pnpm install
pnpm build:wasm   # C++ → WASM via Emscripten (CMakePresets.json, reads $EMSDK)
pnpm dev          # Vite demo at http://localhost:5173

For iterative C++ development:

pnpm dev:debug    # debug WASM (-O0 -g3) + lib build + dev server

Other scripts: pnpm build:lib (library bundle), pnpm build:demo (demo production build), pnpm build (all three), pnpm format / pnpm format:check (Prettier).

CMake presets: emscripten (release, build/) and emscripten-debug (debug, build-debug/). Both read $EMSDK — no emcmake wrapper needed. See CMakePresets.json and .clangd for IDE integration.

Bundler config & static assets

Vite

The demo's vite.config.ts shows the full setup. The key pieces:

import { viteStaticCopy } from "vite-plugin-static-copy";

const coiHeaders = {
  "Cross-Origin-Opener-Policy": "same-origin",
  "Cross-Origin-Embedder-Policy": "require-corp",
};

export default defineConfig({
  plugins: [
    viteStaticCopy({
      targets: [
        { src: "node_modules/@indigocode/igasset-viewer-react/dist/igpack/*", dest: "igpack" },
      ],
    }),
  ],
  server: { headers: coiHeaders },  // required for multi-threaded build
  preview: { headers: coiHeaders },
});

The COOP/COEP headers enable crossOriginIsolated, which unlocks the multi-threaded WASM build. Without them, the provider falls back to single-threaded automatically.

Other bundlers

The .wasm files use new URL("igviewer.wasm", import.meta.url) — supported natively by Webpack 5+, RSPack, and Turbopack. The multi-threaded build also spawns a worker via new Worker(new URL("igviewer-mt.js", import.meta.url), { type: "module" }), which these bundlers also recognize.

For hosts where you can't set COOP/COEP headers (GitHub Pages, plain S3), use coi-serviceworker or force threading="single".

If your bundler can't handle the new URL pattern, copy the .wasm to your static dir and pass wasmUrl to <IgviewerProvider>.