Skip to content

Runtime material compilation & hot-reload (Phases 0-1) - #252

Open
local-sandbox-agent[bot] wants to merge 6 commits into
developfrom
asb/runtime-material-compile
Open

Runtime material compilation & hot-reload (Phases 0-1)#252
local-sandbox-agent[bot] wants to merge 6 commits into
developfrom
asb/runtime-material-compile

Conversation

@local-sandbox-agent

@local-sandbox-agent local-sandbox-agent Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Phase 0 — reload materials at runtime

FilamentApp.reloadMaterialFromBytes(oldMaterial, bytes) swaps a new compiled material into a running scene.

  • Material instance setters record every value in a shadow state. Filament instances are write-only and cannot be re-parented, so this recording is how a replacement instance gets its values.
  • findRenderablesUsingMaterial finds every primitive that uses a material. It scans the scene natively, plus a registry for renderables not yet attached.
  • Reload builds one replacement instance per old instance, replays the recorded values, re-points the primitives, then destroys the old instances before the old material.
  • Works everywhere the engine runs, including web (bytes only, no compiler).

Phase 1 — compile materials at runtime

FilamentApp.compileMaterial(matSource, ...) compiles .mat source inside the running engine and returns the .filamat bytes that createMaterial and reloadMaterialFromBytes accept.

  • Native Engine_compileMaterial (desktop only) runs the same matc pipeline: matp parse then filamat::MaterialBuilder::build. Other platforms get a stub that explains it is not supported; web throws from Dart.
  • #include flattening is done in Dart. Circular or too-deep includes are errors.
  • Link config: matp on linux/macOS, plus glslang/SPIRV/spirv-cross libs on Windows.
  • ffigen bindings regenerated.

Example — live material editor

examples/flutter/material_editor is a reference app for both phases: a sphere renders in a ViewerWidget, and a text field below holds .mat source.

  • Debounced edits (or Apply) call compileMaterial, then reloadMaterialFromBytes hot-swaps the package onto the sphere — instance parameters (baseTint/roughness/metallic) survive every swap.
  • MaterialCompileException messages render inline under the editor; the sphere keeps the last good material.
  • Ships materials/starter.mat (lit, three parameters) so the app opens with something editable that renders immediately. The starter and the README's suggested edits were verified to compile through the runtime API.
  • Non-desktop platforms still run, with the default material and an explanatory status, matching where the compiler is linked.

Tests

  • material_reload_tests (4): scan, reload and replay, instance sharing, destroyOld.
  • material_compile_tests (8): every shipped .mat runtime-compiles and loads with parameters intact; parse errors surface the message; defines and includes work; end-to-end compile, render, recompile, hot-swap. (materials/vdtm.mat is excluded — broken source, not shipped.)
  • Existing material suites still pass (35 + 4).

Verified

  • All suites pass under xvfb-run dart test -j1 in the container.
  • flutter analyze: no new issues (package at the 48-issue pre-existing baseline; example analyzes clean).
  • Example: flutter build linux --debug succeeds; the container has no GPU (/dev/dri absent), so the on-screen edit/swap loop still needs a real machine.

CI must confirm

  • macOS/Windows link config compiles.
  • Web build still succeeds with the stub.
  • Android/iOS builds unaffected.
  • The material_editor example runs on a desktop machine with a GPU (the container cannot start the render context).

nmfisher and others added 3 commits August 23, 2026 08:42
Maps the current matc/resgen build-time pipeline and runtime
createMaterial path, verifies Filament v1.75.0's runtime compile
stack (matp + filamat::MaterialBuilder) against thermion's own R2
artifacts (end-to-end .mat -> filamat proof on Linux), and proposes
a phased plan: reload-only MVP, desktop runtime compile, then
Android/iOS artifact work. Research only; no code changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Add reloadMaterialFromBytes + findRenderablesUsingMaterial to
FilamentApp, per docs/research/runtime-material-compile.md section 4.1.

Filament cannot re-parent a MaterialInstance to a different Material,
and instances are write-only (no parameter getters). So:

- FFIMaterialInstance now records every setParameter*/raster-state call
  in a MaterialInstanceShadowState, and applyTo() replays the recording
  onto a fresh instance.
- Scene_scanForMaterial (new C API) walks each scene's renderables and
  reports the primitives whose instance belongs to a material.
  MaterialInstance_getMaterial (new C API) exposes instance->material.
- FFIFilamentApp.reloadMaterialFromBytes builds the new material,
  creates one replacement instance per distinct old instance, replays
  recorded state, re-points primitives, then destroys the old instances
  and material on the render thread.
- findRenderablesUsingMaterial unions the native scene scan with an
  app-level attachment registry (records from setMaterialInstanceAt) so
  renderables not yet in a scene are still found; stale records are
  pruned by re-validating against the renderable's current instance.
- MaterialInstance wrappers are cached per native pointer so repeated
  getMaterialInstanceAt calls return the same wrapper and keep the
  shadow recording intact; destroy() is idempotent and unregisters.

Tests: test/material_reload_tests.dart (scan, replay + pixel check,
instance-sharing preservation, destroyOld=false ownership).

Co-Authored-By: Claude <noreply@anthropic.com>
Implements Phase 1 of docs/research/runtime-material-compile.md: compile
.mat sources to .filamat packages inside the running engine, so the
Phase 0 reload path can be driven by source edits without matc.

Native (desktop builds only, guarded by THERMION_RUNTIME_MATERIAL_COMPILE):
- Engine_compileMaterial: matp::MaterialParser::parse feeding
  filamat::MaterialBuilder::build(engine->getJobSystem()).
  MaterialBuilder::init() runs once behind a mutex (glslang init is not
  thread-safe). Platform/targetApi/optimization are validated and mapped
  to the builder enums; targetApi can be derived from the engine backend
  via filamat::targetApiFromBackend; defines arrive as a flat JSON object
  parsed in C++. Every other platform gets a stub that fails with an
  explanatory message.
- Engine_compileMaterialRenderThread follows the readPixels idiom: the
  task fills outData/outSize then notifies the Dart callback.
- utils/JobSystem.h is not shipped in the artifact headers; a forward
  declaration replaces the include.

Link config: matp added to the link libraries on linux/macOS (before
utils, which it references); on Windows via #pragma comment(lib) in
ThermionWin32.h, including the glslang/SPIRV/spirv-cross libs that the
Windows filamat/matp do not bundle.

Dart:
- FilamentApp.compileMaterial(matSource, {platform, targetApi,
  optimization, defines, includePaths, embedSource}) plus the
  MaterialCompilePlatform/MaterialTargetApi/MaterialOptimization enums
  and MaterialCompileException. Web throws UnsupportedError.
- #include directives are flattened in Dart before compiling (matp has
  no include callback): resolved against includePaths then the bare
  path via the resource loader, with circular/over-deep chain detection.
- FFIMaterial.destroy now retires its created instances first — a
  material destroyed with live instances strands the render thread.
- ffigen bindings regenerated (both flavors).

Tests (material_compile_tests): golden compile of every material
build.sh ships (11 workspace + 3 example .mat) accepted by
Material::Builder with parameters intact; parse errors surface the
compiler message; explicit target API/optimization selection; defines
reach the preprocessor; nested/missing/circular include handling; and
an end-to-end compile -> attach -> render -> recompile -> hot-swap
loop. materials/vdtm.mat is excluded: its source is broken ('z'
declared inside an if block) and it is not in the shipped set.

Verified in container (linux): new suites + reload/material smoke
suites pass; flutter analyze reports no new issues; libthermion_dart.so
exports the compile API and links the filamat/matp/glslang stack
(94 exported filamat/matp symbols, 2695 in symtab). macOS/Windows link
config and the web stub are untested here — CI must confirm those
platforms build.
@local-sandbox-agent
local-sandbox-agent Bot force-pushed the asb/runtime-material-compile branch from 3bc1f42 to fb09f4a Compare August 23, 2026 08:52
nmfisher and others added 3 commits August 23, 2026 08:53
The research plan served its purpose as the implementation blueprint;
keeping it out of the tree keeps the PR scoped to the runtime material
compile feature itself. (Local copy retained outside git.)
…-reload)

examples/flutter/material_editor: a reference app for the Phase 0/1 APIs.
A sphere renders in a ViewerWidget; below it, a TextField holds Filament
.mat source. Debounced edits (or the Apply button) run
FilamentApp.compileMaterial in the running engine and hot-swap the
package onto the sphere via reloadMaterialFromBytes, which replays
instance parameters (baseTint/roughness/metallic survive every swap).
MaterialCompileException messages are shown inline under the editor.

Ships materials/starter.mat (lit, three parameters) so the app opens
with an editable material that renders immediately; the starter and the
README's suggested edits were verified to compile through the runtime
API. Non-desktop platforms keep running with the default material and
an explanatory status, matching where the compiler is linked.

flutter analyze: clean. flutter build linux --debug: builds; the
container has no GPU (/dev/dri absent) so the render context cannot
start there — verified on-screen behavior needs a real machine.

Co-Authored-By: Claude <noreply@anthropic.com>
The PR's auto-merge branch carries a CI commit that ran dart format
over the new material files and regenerated the ffigen bindings (same
symbol set, different declaration order). Take those exact versions so
the pushed head matches what CI already produced and the next CI run
has nothing to reformat.

Verified after adopting: flutter analyze at the 48-issue baseline,
material_compile_tests + material_reload_tests 12/12.

Co-Authored-By: Claude <noreply@anthropic.com>
@local-sandbox-agent
local-sandbox-agent Bot force-pushed the asb/runtime-material-compile branch from 31c78c7 to 4645e8e Compare August 23, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant