Skip to content

Audio: FMOD Studio + Vercidium Audio system (phases 1-15) and Windows fixes - #31

Open
sheazywi wants to merge 48 commits into
devfrom
sound-fmod-va
Open

sheazywi wants to merge 48 commits into
devfrom
sound-fmod-va

Conversation

@sheazywi

Copy link
Copy Markdown
Member

What

Replaces the miniaudio backend with FMOD Studio + Vercidium Audio (both required) and builds the full audio system from docs/AUDIO_SYSTEM_PLAN.md in 15 phases: Studio events and banks, listeners, C# API, runtime export of banks, acoustic materials, zones/snapshots, surfaces and physics audio, interactive music, localized dialogue and subtitles, accessibility, dynamic acoustic geometry and portals, voice budgets and validation, and desktop platform profiles.

Also on this branch: Windows fixes found while verifying it, the Beam editor multi-cursor/monospace fixes, deferred-lighting GPU-reset/sync fixes, and agent workflow docs (/plan-le, /profile, /shader-debug, the ImGui correctness rule, the self-contained product principle, and docs/BEAM_EDITOR_PLAN.md).

Why

The old audio path was raw-file playback with no authoring pipeline, no occlusion or reverb, no C# API, and silent exported games. This gives an authored FMOD Studio pipeline with ray-traced acoustics, a complete scripting surface, and working runtime export.

Fixes found during Windows verification (latest commit a51e7012)

  • FMOD update() failed every frame with Live Update on. AudioPerformance enabled metering on a DSP Studio owns; with Live Update enabled, every later Studio::System::update returned FMOD_ERR_BADCOMMAND (logged only once because the engine logs on change). Bisected on Windows; the monitor now meters through its own pass-through fader DSP at index 1 and releases it before bank unload.
  • Leaked ImGui tree nodes (missing TreePop): Audio Debugger "Budgets and Bus Meters" (open by default, leaked every frame) and "Audio Validation"; Project Settings "Surface Sounds", "Dialogue Lines", "Audio Accessibility".

Verification

  • Windows: full solution built in Release and Debug (MSBuild, zero errors; checked by artifact timestamps). fmod.dll, fmodstudio.dll, vaudionative.dll present next to Editor and Lux-Runtime.
  • Editor run (Release): opens LuxSampleProject, FMOD Studio initializes with Live Update, zero [error] lines, clean shutdown (exit code 0). Before the fix, ~14,900 failing updates in 41 s; after, none.
  • Linux (earlier, per phase): headless FMOD/VA regression tests (tests/audio/run.py, run_managed.py) — not re-run for the latest commit (Linux-only harness).
  • Not yet done on Windows: listening pass, driving the fixed ImGui headers interactively, FMOD bank rebuild (FMOD Studio not installed on the test machine), export and launch of a packaged game. VA occlusion is still blocked upstream (repro in docs/vercidium-repro/).

Review

/send-pr rule list applied as automated checks over all added lines (raw new/delete, shared_ptr, Core→Editor includes, untagged logs, ImGui scope balance) plus manual review of UI scope paths and component completeness (all five new components are in AllComponents, scene copy, both serializer directions, the hierarchy UI, and C#). The full ~23k-line diff was not reviewed line by line; each audio phase was reviewed when it landed.

Consider-tier, left as is: static std::string UI buffers in the Dialogue Lines editor (ProjectSettingsWindow.cpp).

Regeneration

Required — many files added/removed (miniaudio removed, new Audio sources). Run scripts\Win-GenProjects.bat --last. FMOD Engine SDK must be in Core/vendor/FMOD/ (or LUX_FMOD_SDK) and Vercidium Audio in Core/vendor/VA_RAY/ (or LUX_VA_SDK); generation fails with the exact missing file otherwise.

Doc updates

.claude/docs/Architecture-LuxEngine.md (§ 2.10 Audio, metering ownership rule), Threading.md (audio budgets), Conventions.md (ImGui correctness), Building.md (audio SDKs), CLAUDE.md / AGENTS.md (product principle, skills), and phase docs under docs/AUDIO_*.md.

🤖 Generated with Claude Code

sheazywi and others added 30 commits September 6, 2026 21:31
…pt-in)

Adds two independent, opt-in audio subsystems and fixes several latent bugs
found while wiring them up.

WHAT

- FMOD Engine as an alternative playback backend (--fmod, LUX_ENABLE_FMOD).
  AudioEngine/AudioSource/AudioListener now compile against either miniaudio
  (default) or FMOD Core, selected by #ifdef within each .cpp, so call sites
  never see the backend and the default build has no FMOD dependency.
  AudioEngine::Update() pumps FMOD::System::update() once per frame from
  Application::Run; it is a no-op under miniaudio.

- RaytracedAudioScene (--raytraced-audio, LUX_ENABLE_RAYTRACED_AUDIO): wraps
  the Vercidium Audio SDK behind a Pimpl. One VAWorld per Scene, created on
  OnRuntimeStart, mirroring static MeshCollider geometry and tracking one
  emitter per AudioSourceComponent. Its per-source occlusion/reverb results
  feed FMOD's Channel::set3DOcclusion / setReverbProperties. Built without
  the SDK every method is a no-op, matching the DiscordSocial pattern.

Both SDKs are proprietary and gitignored; their EULAs forbid redistributing
the SDK itself, so they are fetched manually. Configure.py warns when a
feature is enabled without its SDK present.

BUGS FIXED ALONG THE WAY

- AudioSourceComponent/AudioListenerComponent were never serialized, and a
  legacy-schema guard hard-rejected any scene containing them. Both now
  round-trip through SceneSerializer.
- The same two components were missing from AllComponents/DuplicateComponents,
  so Scene::Copy silently dropped them on Play - audio could never work at
  runtime regardless of backend.
- AttenuationModel was emitted as uint8_t; yaml-cpp writes unsigned char as a
  *character*, so saving a scene with an audio source wrote a raw control byte
  and corrupted the .luxscene. Now uint32_t both directions.
- VA emitters default to zero rays of every type, so sources produced no
  occlusion or reverb at all. Ray counts are now set explicitly.
- GetResult() segfaulted inside the SDK when querying an emitter created in
  the same frame, before its first vaWorldUpdate. Now guarded.
- Stop() destroyed emitters the world still owned pending their reverb tail
  (vaWorldRemoveEmitter answers VA_PENDING_REMOVAL), double-freeing them in
  vaWorldDestroy. Teardown now only destroys what it actually removed.
- Linux-Build.sh regenerated makefiles without the feature flags, silently
  producing a build with the features compiled out. It now forwards
  LUX_PREMAKE_OPTIONS.

VERIFICATION

Core, Editor and Lux-Runtime all build clean with --fmod --raytraced-audio.
FMOD initialises against a real device; scene loads; VA world creation and
static-geometry mirroring verified correct in-editor (mirrored AABB matches
the source transform exactly); per-emitter results reach the playback layer
(Valid=true). Every VA and FMOD call site was additionally compiled against
the real vendored headers in standalone harnesses.

KNOWN ISSUES - --raytraced-audio should stay OFF by default

- Creating a VAWorld inside the engine process corrupts the heap on Play.
  Isolated: FMOD-only is clean, and the identical VA calls are clean in a
  standalone harness, so it is an interaction, root cause not yet found.
- VA occlusion output does not respond to geometry (identical values with no
  wall and with a 100x75m wall), reproducible standalone. Open with Vercidium.
- Windows FMOD paths in Dependencies.lua are unverified placeholders; only the
  Linux package has been fetched.
- Static geometry is mirrored once at Play start and does not follow moving
  colliders; all geometry is hardcoded to VAMaterialConcrete.

Requires project regeneration (new files + premake changes).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Three strands of work on the audio stack.

Ray-traced acoustics (Vercidium)
--------------------------------
Rebuilt to the topology Vercidium's docs describe: the listener is the only
ray caster, casting all five ray types (including the ambient pair we were
leaving at zero), and every source is registered as one of its targets.
Adding a source now costs a target on the listener's existing ray budget
rather than a second budget of its own.

The simulation's full EAX/I3DL2 reverb set is read (vaEmitterGetEAX) and
mapped onto FMOD_REVERB_PROPERTIES - previously one field of ~25 was used.
Every value is clamped to FMOD's documented range because FMOD rejects the
whole struct if one field is out, silently keeping the old reverb.

Occlusion is applied as two bands rather than one scalar: the low-frequency
gain scales the channel's volume (how much gets through) and the relative
high-frequency loss drives the occlusion filter (how much more the highs are
cut). Feeding HF straight into set3DOcclusion double-counts the loss and
makes an occluded source inaudible instead of muffled.

The frame is now explicitly two-phase - WaitForResults, then mutate and read,
then OnUpdate - because the simulation is asynchronous and results were being
read while workers were still writing them. Stop() also waits unconditionally
before draining: vaWorldGetThreadsRunning reads false for work that has been
queued but not started, which skipped the drain entirely.

Measured end to end: reverb decay tracks room size (0.10s open field, 0.32s
small room, 1.39s large hall) and reaches FMOD accepted. Occlusion does not
respond to geometry at all - a Vercidium defect, with a self-contained repro
under docs/vercidium-repro/ that includes reverb from the same emitter as a
control, proving the rays do reach the geometry.

3D visualisation and the Audio Debugger
---------------------------------------
Visualisation rays are delivered on Vercidium's worker threads and are valid
only inside the callback, so they are copied out under a lock and copied
again by the renderer. A ray that hits nothing still occupies its slot,
filled with a far out-of-world sentinel, so bounces are classified by world
containment and each polyline stops at its first miss.

New AudioDebugPanel (View -> Audio Debugger) renders both halves of the
stack, with the reverb the simulation produced and the values FMOD actually
received side by side. Deliberately free of LUX_ENABLE_FMOD /
LUX_ENABLE_RAYTRACED_AUDIO, which are Core-only defines: each backend names
itself through its stats struct instead.

FMOD Studio pipeline
--------------------
Studio is linked alongside Core and owns it - Studio::System::initialize
creates the core system, so Shutdown releases only Studio (releasing both is
a double free) and Update calls Studio then Core. Studio's update does NOT
recompute Core's 3D attenuation; measured with Channel::getAudibility, Studio
alone leaves audibility frozen at the geometry a channel started with, which
is indistinguishable from spatialisation being off.

AudioBankBuilder shells out to fmodstudiocl the way ScriptBuilder shells out
to dotnet, and is not behind LUX_ENABLE_FMOD - building banks needs the
Studio application, not the SDK. Banks rebuild on Play when the .fspro is
newer, and load strings-bank-first (it carries the path table; loading it
late makes every event lookup fail unhelpfully).

.fspro and .bank are asset types so the Content Browser can show and activate
them - activation opens FMOD Studio. Both the browser and the asset registry
treat a Studio project directory as opaque: it is dozens of GUID-named XML
files plus gitignored build output, and importing the banks would put
regenerable files into the tracked registry.

Known gap: the binary runtime project format does not carry the Studio
settings and runtime export does not copy banks, so an exported game ships
without audio. That belongs with the runtime export work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Sources can now reference an authored FMOD Studio event instead of a raw
audio file. When an event is set it supersedes the file: spatialisation,
attenuation, cones, doppler and randomisation are authored in the event, and
the component's config fields for those no longer apply.

Events are referenced by GUID, not by path. The path is stored alongside it
purely as a label and is refreshed from the loaded banks on display - never
used to resolve. Renaming or moving an event in FMOD Studio changes its path
but not its GUID, so a path-referencing scene would go silently mute the
first time a designer reorganises the project, with no error anywhere.

AudioEventInstance wraps FMOD::Studio::EventInstance. Two details worth
knowing: IsPlaying counts SUSTAINING, because an event holding at a sustain
point is audible even though its state is neither STARTING nor PLAYING; and
the destructor stops IMMEDIATE rather than ALLOWFADEOUT, because it runs
during teardown where a fade would outlive the thing being torn down.

Scene owns instances per entity. A null entry in that map is meaningful - it
records an event that could not be resolved, so the "not in any loaded bank"
warning is logged once rather than on every frame. Instances are released
both per entity and with the runtime, since they hold Studio resources.

Ray-traced acoustics reach events as named parameters (Occlusion,
ReverbSend) rather than as a filter applied by the engine. That is the
philosophical difference between the two paths: on the legacy path the
engine decides what occlusion does to a sound, while an event is told what
was measured and its author decides what it means. An event declaring
neither parameter is simply unaffected.

The inspector gains an event picker listing what the loaded banks describe,
with the GUID and 3D/oneshot shown on hover. An assigned event missing from
the loaded banks is flagged rather than reading as "nothing assigned".

The legacy raw-file path is retained and marked as such. The project's
.fspro has no authored sounds yet, so removing it now would leave the engine
playing nothing at all.

Verified end to end: a 3D event authored through fmodstudiocl's scripting
API and built into the master bank is enumerated by the engine on project
open ("Loaded 2 bank(s) describing 1 event(s)"), which exercises bank
enumeration, GUID formatting and the list the picker and serializer share.

Note for anyone adding to this: AudioEventInstance.cpp is a new translation
unit, so the projects need regenerating - and regeneration drops the
--fmod / --raytraced-audio options, which must be passed again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
…path

Adds one authored event to the sample project's FMOD Studio project so the
engine's event path can actually be exercised: without an event in a bank,
the picker is empty, no GUID can be resolved and nothing about the Studio
integration is testable.

The event is 3D (created with a spatializer on its master track) and
assigned to the Master bank, but has no sound on its timeline - what it
exists to prove is bank enumeration, GUID resolution, the inspector picker
and scene serialization, none of which need it to be audible.

Created through fmodstudiocl's scripting API rather than by hand, so it is
reproducible:

    workspace.addEvent("LuxTestEvent", true)
    ev.relationships.banks.add(masterBank)

Separate from the code change so it can be dropped on its own once the
project has real authored audio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
The Studio configuration added with the bank pipeline reached ProjectConfig
and the serializer but never the settings UI, so pointing a project at its
.fspro meant hand-editing the .luxproj YAML. Setting up audio was therefore
undocumentable as an editor workflow, which is how the gap surfaced.

Project Settings -> Audio now exposes the Studio project path, bank output
directory, rebuild-on-play and live update, alongside the existing streaming
threshold.

Below them is live state rather than settings: how many banks and events are
currently loaded, and buttons to build banks or open the project in FMOD
Studio without entering Play. Both report clearly when they cannot work -
a missing .fspro, or fmodstudiocl not being installed - since neither is
recoverable from inside the editor and the fix is an environment variable.

Live update is labelled as taking effect on the next project open, because
the audio engine is initialised from Project::SetActive and the flag is read
there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
…should

Two related passes over AudioSourceComponent.

Picking an event
----------------
Events now record which bank describes them, captured during enumeration -
the only place that relationship is known without asking FMOD again. The
inspector picker becomes two stages, bank then event, with a search inside
the dropdown, because one flat list is fine for five events and unusable for
five hundred. Strings banks are excluded from the bank list: they carry the
path table rather than events, so they would offer a permanently empty list.

The bank is stored on the component as a hint, exactly like the event path -
refreshed from the loaded banks on display and never used to resolve. The
GUID still resolves the event on its own, whichever bank it turns out to live
in. Filter and search state live on the panel rather than the component,
since they are a view preference, not something to serialize into scenes.

Trimming the component
----------------------
AudioSourceConfig carried fifteen fields, most of which an FMOD Studio event
now owns. Keeping them was worse than useless: visible, editable, and
silently ignored the moment an event was assigned. Removed Spatialization,
AttenuationModel, RollOff, Min/MaxGain, Min/MaxDistance, the three cone
angles and DopplerFactor, along with the AttenuationModelType enum and the
setters that fed them. What remains is volume, pitch, play-on-awake, and
looping for the legacy path.

The playlist goes with them - AudioData, the four component helpers, its
runtime storage and about forty-five lines of index juggling in Scene. A
multi-instrument does the same job in Studio, with weighting and no-repeat.
Removing the struct also exposed an OnComponentAdded<AudioData>
specialization that had always been dead, since AudioData was never a
component.

In their place, ParameterOverrides: name/value pairs applied once when the
instance is created, so two entities can share one event and still sound
different. Scripts will drive parameters continuously through the C# API;
these are the authored starting point.

Behaviour changes worth knowing
-------------------------------
Spatialization defaulted to false, which quietly made every raw source 2D
unless someone ticked it. The legacy path is now always 3D with FMOD's
default rolloff - tuning raw-file attenuation is precisely what an event
should be doing instead.

Old scenes still carry the removed keys. The deserializer ignores them
deliberately, with the reason recorded: reading them would resurrect settings
that no longer reach the mixer. Scenes keep playing, with default attenuation
rather than whatever was tuned in the component.

Paused now defaults to true so play-on-awake fires once for events. That
required OnRuntimeStart to clear it when it starts a raw source - otherwise
the per-frame path stays armed and restarts the source every time it
finishes, turning a one-shot into an unintended loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
…lines

Physics colliders previously drew through the selection wireframe pass, which
has depth testing off so selection stays visible through geometry. Colliders
inherited that and floated in front of the walls containing them. They now
have their own pass with depth testing on and depth writes off, reusing the
pre-depth output; the on-top behaviour is still available through the
existing Show Physics Colliders On Top option, which routes back to the
wireframe pass. The render graph declares the collider depth read so the
dependency is real rather than incidental.

Grid and wireframe pipelines gain BackfaceCulling off and DepthWrite off,
which is what those passes actually want - they overlay rather than
contribute occlusion.

Selection outlines sample the jump-flood mask and distance buffers with a
point sampler instead of a linear one. Those textures hold mask classes and
distance vectors, not colour: interpolating them blends values across the
selection boundary, which is meaningless and shows up as a frayed outline.
The composite's alpha ramp is inverted to match, so the edge falls off
outward.

The jump-flood ping-pong reuses one pass across iterations, so its input is
now bound inside the render queue rather than at record time - otherwise the
first draw of an iteration can observe the previous iteration's binding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Transform gizmos operated on the entity's local transform, so dragging a
child moved it in its parent's space rather than the world the handles were
drawn in. They now take the world matrix and convert the edit back through
the parent. They also use the un-reversed projection: ImGuizmo does its own
depth maths and does not expect a reversed-Z matrix.

Scale snap joins translation and rotation snap, bound through the same
editor-preferences path so it persists.

The editor camera built its view matrix with a fixed world-up vector, which
is degenerate when looking straight down or straight up - exactly the
top/bottom orthographic views. It now uses the camera's own up direction,
which stays perpendicular at every orientation.

Grid visibility is restored from settings on startup instead of always
defaulting on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Imports fart-01.wav into the FMOD Studio project and places it on
LuxTestEvent's timeline, with the encoding setting FMOD generated for it.

The event was created empty, which was enough to verify the engine side -
bank enumeration, GUID resolution, the inspector picker and scene
serialization all work without the event being audible. Making a sound is the
one part of the chain that was still unproven.

Also includes SampleProject.fspackage, FMOD's exported project archive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Adds the PostProcess block the serializer now writes for every scene. No
authored change - the scene was saved by the editor and picked up defaults
that did not exist when it was first written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
The complete design for LuxEngine's audio, from the current state to a
general-purpose shipping system. A planning document, not a description of
what exists - Architecture-LuxEngine.md remains the authority on that.

Records the decisions it is built on so the reasoning is inspectable rather
than implicit: general-purpose rather than genre-specific, further investment
in Vercidium, all four subsystems at equal depth, a full runtime C# API, the
legacy raw-file path deleted once events work, a seam left for networking,
comprehensive accessibility, and Windows plus eventual consoles.

Sixteen parts covering principles, architecture and ownership, the core
runtime with function signatures, components, ray-traced acoustics, ambience
and reverb zones, surfaces and physics audio, interactive music, dialogue and
subtitles, accessibility, the C# API, editor tooling, performance budgets,
platforms and shipping, a fifteen-phase roadmap, and six open questions left
explicitly undecided so they are not settled by accident.

Includes the known-broken items rather than only the aspirations: runtime
export ships silent, VA occlusion is blocked upstream, and the premake
feature flags are dropped on every regeneration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsM8FJLGY8SzZqumBdRKh5
Validate bank build inputs and report build failures through the editor.
Preserve event lifetime and playback state across source operations and teardown. Includes FMOD-only implementations and callback support used by phase 4.
Support indexed, weighted listeners and attenuation targets, with prefab reference remapping and a reusable entity picker. Update VA listener integration.
Expose bank loading, components, one-shots, owned instances, parameters and mixer controls. Marshal callbacks on the main thread and invalidate handles safely during reload. Regenerate projects for the new native source.
Keep overlapping phase changes together: bank revisions versus instance generations, runtime source controls, listener synchronization, component persistence, prefab overrides, and editor picking. Documents the resulting FMOD/VA architecture. This integration and the backend build commit complete the preceding phase commits; intermediate commits were not built independently.
Require both SDKs, deploy shared libraries for Editor and Runtime, and read raw-file metadata through FMOD. Raw AudioSource compatibility remains through FMOD Core; this does not complete the planned runtime export or full legacy API removal. Regenerate projects. Combined tree previously verified with Linux Release Core/Editor/Runtime builds and real FMOD/VA headless tests.
Provide a camera/listener, scripted event emitter, mesh collider geometry, bank paths and step-by-step FMOD setup. Includes the authored sample event replacement and asset registry updates. Game assembly build, scene references, managed lifecycle and native Coral loading verified; no visual or audible editor verification.
Persist the current Audio Debugger layout separately from engine and sample changes.
Add a bounded bank manifest to runtime format 17 while preserving reads of older formats. Export validates bank output, copies banks and required FMOD/VA libraries, and preserves asset-relative bank paths. Runtime loads the exact bank manifest before scenes and scripts and rejects failed loads.

Update the export UI, setup guide, architecture reference, and Windows Studio DLL deployment. Regenerate Premake projects for AudioBankManifest.cpp.

Verified Linux Release Core, Editor, and Lux-Runtime builds by artifact timestamps. Headless tests cover relocated FMOD playback, bank failures, library copying, manifest validation, and actual project serializer v17 roundtrip/v16 compatibility. Windows execution and interactive playback remain untested.
sheazywi and others added 9 commits September 16, 2026 09:58
The editor reset the GPU under the sample scene. Four separate defects,
each verified with the Khronos validation layer and sync validation on:

- Tone mapping bound PreDepth as a colour+depth attachment while also
  sampling it. Give SceneComposite a colour-only framebuffer sharing the
  composite image; the depth-bearing framebuffer stays the target for
  world/editor overlays. Drops the suppressed PreDepth layout VUIDs in
  Window.cpp, which were masking this.
- Compute-to-draw barriers took StorageBuffer from main-thread Get(),
  which can select a different frame's buffer than the recorded draw
  uses. Add StorageBufferSet overloads that resolve RT_Get() at record
  time, and map IndirectCommandRead to nvrhi IndirectArgument so the
  indirect-draw barrier transitions to the right state.
- Mesh-culling outputs (VisibleObjectIndexes, IndirectDrawCommands) were
  CPU-visible; NVRHI skips state transitions for those, so the barriers
  above were no-ops. Make them GPUOnly - CPU init already goes through
  writeBuffer.
- PCSS indexed the 64-entry Poisson table dynamically, which RADV expands
  into per-fragment scratch copies and hangs the deferred lighting draw.
  Use constant-index lookups; tests/rendering/run_shadow_shader.py
  compiles the shader and fails if the local tables come back.

Also drops the unused u_SceneColor binding from DeferredLighting, which
caused a further invalid transition of the pass's own colour output.

Verified: Debug editor, sample scene, PCSS soft shadows, grid and entity
icons on - no validation errors, no SYNC-HAZARD, no GPU reset over a
multi-minute run, clean shutdown. Debug and Release build Core, Editor
and Lux-Runtime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019xjdPqbKRShx5nfKeYeZYN
Ctrl+clicking where the cursor already was merged the new cursor back
into the main one, but Cursors::update() used else-if when re-finding
the main and current indices. A cursor that was both left 'current'
pointing past the end of the vector, and the next at(current) threw
std::out_of_range, closing the editor.

The editor body and diff view also drew in the proportional UI font
while TextEditor lays glyphs on a fixed '#'-wide grid, spacing text out.
Push the Mono font around both renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- plan-le: source-grounded phased planning with a verified ledger,
  user decisions, engine-fit analysis and independently verifiable phases.
- profile: measurement-first performance workflow (present ceiling,
  CPU vs GPU, Tracy/RenderDoc, fixed benchmark protocol).
- shader-debug: triage for compile errors, cache fallback, manual reload,
  binding collisions, black output, startup crashes and device loss.
- Conventions.md: ImGui correctness section (every scope closed on every
  path, unique IDs per scope, verify in the editor). Enforced as must-fix
  rule 11 in the shared review list, and referenced from /dev, /cr and
  /plan-le.
- Codex adapters under .agents/skills and CLAUDE.md/AGENTS.md index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…layout

Asset registry write times refreshed by the editor, a spatialiser
distance rolloff change on one FMOD Studio event, and the saved ImGui
layout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- docs/BEAM_EDITOR_PLAN.md: phased plan (0-11) to rework Beam into a
  full in-engine code editor with VS Code / Visual Studio / JetBrains /
  Vim features, in-process shader and C# diagnostics, and no extra
  installs. Includes a verified current-state ledger, decisions, risks
  and a research brief.
- CLAUDE.md / AGENTS.md: Product Principle - self-contained, small,
  refined; no new mandatory installs for game makers.
- send-pr: requiring a separate install is must-fix (rule 10).
- plan-le: web research step with a compacted research brief, a pinned
  goal card, and the product principle as a standing rule.
- Sample FMODDemo scene and editor layout saved by the editor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Gui headers

AudioPerformance enabled input metering on the head DSP of Studio's bus
channel groups. With FMOD Live Update on (the sample project's setting),
changing metering on a Studio-owned DSP makes every later
Studio::System::update return FMOD_ERR_BADCOMMAND. The engine only logs
when the result changes, so it appeared once while failing every frame.
Found by bisecting on Windows; failures stop with the metering call
removed or with Live Update off.

The monitor now meters through a pass-through fader DSP it owns, inserted
at index 1 directly behind the head DSP so it measures the same signal,
and detaches and releases it before banks unload.

Also close PropertyGridHeader tree nodes that were never popped:
Audio Debugger "Budgets and Bus Meters" (open by default, leaked every
frame) and "Audio Validation", and Project Settings "Surface Sounds",
"Dialogue Lines" and "Audio Accessibility".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 14:30
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 4849abe2-5c3a-4fb3-9d28-a72e34296ebd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate correctness issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR replaces miniaudio with FMOD Studio and Vercidium Audio, adding authored audio, acoustics, scripting, runtime export, accessibility, and related platform/editor fixes.

Changes:

  • Adds FMOD/VA runtime integration, components, C# APIs, banks, acoustics, music, dialogue, and accessibility.
  • Adds serialization, asset-pack, SDK, managed, shader, and resource-copy tests.
  • Updates build tooling, sample assets, editor panels, shaders, and workflow documentation.
File summaries
File Reviewed change
tests/runtime/run_resources.py Runtime resource-copy regression test
tests/rendering/run_shadow_shader.py Offline shadow-shader validation
tests/audio/SourceSerializationTests.cpp Audio source serialization tests
tests/audio/run_sdk_layout.py SDK layout validation
tests/audio/run_managed.py Managed audio test runner
tests/audio/README.md Audio test instructions
tests/audio/PlatformTests.cpp Platform and focus tests
tests/audio/MusicSerializationTests.cpp Music serialization tests
tests/audio/GeometrySerializationTests.cpp Geometry serialization tests
tests/audio/FileStreamTests.cpp File-stream tests
tests/audio/AuthorFixture.js FMOD authoring fixture
tests/audio/AssetPackFailureTests.cpp Asset-pack failure tests
scripts/Win-GenProjects.py Windows project generation
scripts/Setup.py SDK setup tooling
scripts/Linux-Build.sh Linux build updates
scripts/Configure.py Build configuration
ScriptCore/Source/Lux/Music.cs Managed music API
ScriptCore/Source/Lux/AudioPortal.cs Managed audio portal API
premake5.lua Audio SDK configuration
Lux-Runtime/src/RuntimeLayer.h Runtime layer declarations
Lux-Runtime/src/RuntimeLayer.cpp Runtime layer integration
Lux-Runtime/src/RuntimeApplication.cpp Runtime application setup
Lux-Runtime/premake5.lua Runtime build configuration
Editor/Source/RuntimeExportUtils.h Runtime export utilities
Editor/Source/Panels/TextEditorPanel.cpp Text editor fixes
Editor/Source/Panels/ProjectSettingsWindow.h Project audio settings declarations
Editor/Source/Panels/ContentBrowserPanel.h Content browser declarations
Editor/Source/Panels/AudioDebugPanel.h Audio debugger declarations
Editor/Source/Panels/ApplicationSettingsPanel.h Application settings declarations
Editor/Source/Panels/ApplicationSettingsPanel.cpp Application settings integration
Editor/Source/EditorLayer.h Editor-layer declarations
Editor/Resources/Shaders/JumpFlood_Pass.glsl Jump-flood pass shader
Editor/Resources/Shaders/JumpFlood_Init.glsl Jump-flood initialization shader
Editor/Resources/Shaders/JumpFlood_Composite.glsl Jump-flood composite shader
Editor/Resources/Shaders/Include/GLSL/ShadowMapping.glslh Shadow-mapping shader helpers
Editor/Resources/Shaders/DeferredLighting.glsl Deferred-lighting shader fixes
Editor/premake5.lua Editor build configuration
Editor/LuxSampleProject/LuxSample.luxproj Sample project configuration
Editor/LuxSampleProject/Assets/Scripts/Source/FmodAudioDemo.cs FMOD sample demo script
Editor/LuxSampleProject/Assets/Scenes/AudioTest.luxscene Audio test scene
Editor/LuxSampleProject/Assets/Audio/SampleProject/SampleProject.fspro FMOD Studio sample project
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Workspace.xml FMOD workspace metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Tags.xml FMOD tag metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/SnapshotGroup/{7fefaf03-60b0-4058-9f39-dc582b445d97}.xml FMOD snapshot metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/SandboxFolder/{6d7ac61e-abd8-4a55-a9f4-6ff6f6021e22}.xml FMOD sandbox metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Return/{905d9352-a23b-4b62-82f8-918d93be45b2}.xml FMOD return metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/ProfilerFolder/{5f35c254-3410-4f97-89af-5c30feb88606}.xml FMOD profiler metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Platform/{c2de980e-8d10-499f-b068-81c40b5763ae}.xml FMOD platform metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/ParameterPresetFolder/{73d54407-c59f-4995-bd0b-b53fe44ed322}.xml FMOD parameter metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Mixer.xml FMOD mixer metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Master.xml FMOD master-bus metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/EventFolder/{19572dc8-1f88-4500-8575-511b176c5352}.xml FMOD event metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/EncodingSetting/{fca1d20d-6b81-4cfe-9ef1-7c4a23ea540e}.xml FMOD encoding metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/EncodingSetting/{2ea4f6be-52d7-47a5-851c-e4f4c0f2d144}.xml FMOD encoding metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/EffectPresetFolder/{7e638cd8-0692-469f-b2aa-d1f7ff41fa82}.xml FMOD effect metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/BankFolder/{f4906ec4-3038-4329-8214-9491f8ee21d1}.xml FMOD bank metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Bank/{fa0b1bba-b14d-4ea4-ad1c-423a63954b99}.xml FMOD bank definition
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/AudioFile/{339ff415-7661-4c23-9d01-5a8c5cc80aed}.xml FMOD audio-file metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Asset/{d0f2bd5e-829b-40b5-9a32-1d715d011040}.xml FMOD asset metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Asset/{c14602ef-22ef-4740-b80c-9c19e7016f58}.xml FMOD asset metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Asset/{a090b716-2b34-48c3-a0f2-ac0a3de23214}.xml FMOD asset metadata
Editor/LuxSampleProject/Assets/Audio/SampleProject/Metadata/Asset/{942e11f9-8e64-4d92-b86b-2b4a9e6e22c6}.xml FMOD asset metadata
docs/vercidium-repro/occlusion_output.txt Vercidium occlusion diagnostics
docs/AUDIO_PERFORMANCE_VALIDATION.md Audio performance validation
Core/Source/Lux/Vendor/TextEditor.cpp Text editor implementation
Core/Source/Lux/Utilities/FileSystem.h Filesystem declarations
Core/Source/Lux/Serialization/FileStream.h File-stream declarations
Core/Source/Lux/Serialization/FileStream.cpp File-stream implementation
Core/Source/Lux/Serialization/AssetPackSerializer.h Asset-pack serializer declarations
Core/Source/Lux/Serialization/AssetPackSerializer.cpp Asset-pack serialization
Core/Source/Lux/Serialization/AssetPack.cpp Asset-pack implementation
Core/Source/Lux/Scripting/ScriptGlue.cpp Script glue integration
Core/Source/Lux/Scripting/ScriptEngine.cpp Script engine integration
Core/Source/Lux/Scripting/AudioScriptBindings.h Managed audio bindings
Core/Source/Lux/Scene/Prefab.cpp Prefab audio support
Core/Source/Lux/Scene/EntityTemplates.h Entity audio templates
Core/Source/Lux/Renderer/SceneRenderer.h Scene renderer declarations
Core/Source/Lux/Renderer/PipelineCompute.h Compute-pipeline declarations
Core/Source/Lux/Renderer/PipelineCompute.cpp Compute-pipeline synchronization
Core/Source/Lux/Renderer/FrameRenderPacket.h Frame render data
Core/Source/Lux/Project/ProjectSerializer.h Project serializer declarations
Core/Source/Lux/Project/ProjectRuntimeFormat.h Runtime project format
Core/Source/Lux/Project/Project.cpp Project integration
Core/Source/Lux/Physics/PhysicsScene.h Physics scene declarations
Core/Source/Lux/Physics/PhysicsScene.cpp Physics scene integration
Core/Source/Lux/Physics/PhysicsContactEvent.h Physics contact events
Core/Source/Lux/Physics/JoltPhysics/JoltContactListener.h Jolt contact listener declarations
Core/Source/Lux/Physics/JoltPhysics/JoltContactListener.cpp Jolt contact event capture
Core/Source/Lux/ImGui/ImGuiEx.h ImGui helpers
Core/Source/Lux/ImGui/AudioWidgets.h Audio widget declarations
Core/Source/Lux/ImGui/AudioWidgets.cpp Audio widgets
Core/Source/Lux/ImGui/AudioAccessibilityWidgets.h Accessibility widget declarations
Core/Source/Lux/Editor/SceneHierarchyPanel.h Scene hierarchy declarations
Core/Source/Lux/Editor/EditorCamera.cpp Editor camera integration
Core/Source/Lux/Core/Window.cpp Window integration
Core/Source/Lux/Core/Log.cpp Logging updates
Core/Source/Lux/Core/Application.cpp Application audio lifecycle
Core/Source/Lux/Audio/PhysicsAudioSystem.h Physics audio system
Core/Source/Lux/Audio/MusicDirector.h Music director
Core/Source/Lux/Audio/DialogueTable.h Dialogue table
Core/Source/Lux/Audio/DialogueDirector.h Dialogue director
Core/Source/Lux/Audio/AudioZoneSystem.h Audio zones
Core/Source/Lux/Audio/AudioZoneSettings.h Audio zone settings
Core/Source/Lux/Audio/AudioValidation.h Audio validation
Core/Source/Lux/Audio/AudioSurfaceTable.h Surface sound table
Core/Source/Lux/Audio/AudioSurfaceTable.cpp Surface sound table implementation
Core/Source/Lux/Audio/AudioSourcePlayback.h Audio playback state
Core/Source/Lux/Audio/AudioSource.h Audio source component
Core/Source/Lux/Audio/AudioPerformanceSettings.h Performance settings
Core/Source/Lux/Audio/AudioPerformance.h Performance monitoring
Core/Source/Lux/Audio/AudioListener.h Audio listener component
Core/Source/Lux/Audio/AudioGeometrySystem.h Acoustic geometry system
Core/Source/Lux/Audio/AudioGeometrySettings.h Geometry settings
Core/Source/Lux/Audio/AudioFocus.cpp Audio focus handling
Core/Source/Lux/Audio/AudioEventRef.h Audio event references
Core/Source/Lux/Audio/AudioBankManifest.h Bank manifest declarations
Core/Source/Lux/Audio/AudioBankManifest.cpp Bank manifest implementation
Core/Source/Lux/Audio/AudioBankBuilder.h Bank builder
Core/Source/Lux/Audio/AudioAccessibilitySettings.h Accessibility settings
Core/Source/Lux/Audio/AudioAccessibilityMixer.h Accessibility mixer
Core/Source/Lux/Audio/AudioAccessibility.h Accessibility system
Core/Source/Lux/Audio/AcousticMaterial.h Acoustic material
Core/Source/Lux/Asset/DialogueTableSerializer.h Dialogue serializer declarations
Core/Source/Lux/Asset/DialogueTableSerializer.cpp Dialogue serialization
Core/Source/Lux/Asset/AudioSurfaceTableSerializer.h Surface serializer declarations
Core/Source/Lux/Asset/AudioSurfaceTableSerializer.cpp Surface serialization
Core/Source/Lux/Asset/AssetTypes.h Audio asset types
Core/Source/Lux/Asset/AssetManager/EditorAssetManager.cpp Editor asset discovery
Core/Source/Lux/Asset/AssetManager.h Asset manager declarations
Core/Source/Lux/Asset/AssetManager.cpp Asset manager integration
Core/Source/Lux/Asset/AssetImporter.cpp Asset importing
Core/Source/Lux/Asset/AssetExtensions.h Asset extensions
Core/premake5.lua Core build configuration
Core/Platform/Windows/WindowsFileSystem.cpp Windows atomic file replacement
Core/Platform/Linux/LinuxFileSystem.cpp Linux atomic file replacement
CLAUDE.md Repository workflow guidance
AGENTS.md Agent workflow guidance
.gitignore SDK and generated-output exclusions
.claude/skills/send-pr/SKILL.md Pull-request workflow
.claude/skills/dev/SKILL.md Development workflow
.claude/skills/cr/SKILL.md Code-review workflow
.claude/docs/Threading.md Threading guidance
.claude/docs/Rendering.md Rendering guidance
.claude/docs/Building.md Build guidance
.agents/skills/shader-debug/SKILL.md Shader-debug workflow
.agents/skills/send-pr/SKILL.md Pull-request workflow
.agents/skills/profile/SKILL.md Profiling workflow
.agents/skills/plan-le/SKILL.md Planning workflow
Review details

Suppressed comments (5)

Core/Source/Lux/Asset/AssetManager/EditorAssetManager.cpp:759

  • This disables recursion at the FMOD project root, so the recursive iterator never visits the .fspro file inside it; the project is therefore absent from a freshly scanned asset registry despite the new AudioProject type and activation callback. Prune only the generated subdirectories, or import the project file before disabling recursion.
    Core/Source/Lux/Audio/AudioGeometrySystem.cpp:113
  • When RemoveGeometry fails, this path leaves entry.Queued false and continues. On the next frame the unchanged input compares equal to entry.Desired, so it is never queued again and the stale VA primitive can remain forever. Keep the key pending when removal fails (and apply the same retry behavior to the other world-operation failures below).
    Core/Source/Lux/Audio/AudioGeometrySystem.cpp:131
  • A failed SetGeometry is treated as a completed queue item: Queued was already cleared, and identical inputs will not be enqueued again. If VA is temporarily unable to accept the update (or mesh data becomes available after the builder attempt), this geometry never appears until an unrelated component change. Requeue the key on this failure; the analogous UpdateGeometry failure at line 136 needs the same treatment.
    Core/Source/Lux/Audio/AudioGeometrySystem.cpp:136
  • UpdateGeometry failures also consume the only queued attempt. Because entry.Queued was cleared before this branch and the desired input is unchanged, a transient VA update failure leaves the old transform/material permanently stale; requeue this key before continuing.
    Editor/LuxSampleProject/Assets/Audio/SampleProject/SampleProject.fspro:2
  • The bundled sample cannot execute this new demo from the committed state: this .fspro contains no authored events, while FmodAudioDemo loads Master.bank/Master.strings.bank and creates event:/Fart; the generated Build/ banks are ignored and tests/audio/AuthorFixture.js is never applied to this project. A fresh sample project therefore logs bank/event-loading failure rather than exercising the advertised audio path. Add the authored fixture/banks to the sample workflow or remove/disable the demo references until they exist.
  • Files reviewed: 161/229 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +67 to +68
std::scoped_lock lock(m_Mutex);
m_Events.push_back(event);
Comment thread Core/Source/Lux/Renderer/PipelineCompute.cpp Outdated
Comment thread tests/audio/run_sdk_layout.py Outdated
path.touch()
environment = dict(os.environ, LUX_FMOD_SDK=str(fmod), LUX_VA_SDK=str(va))
for platform, files in platforms.items():
command = [str(ROOT / "premake5"), "--file=" + str(probe), "--os=" + platform, "check-audio-sdk"]
Comment on lines +24 to +28
std::ofstream file(Project::GetActiveAssetDirectory() / metadata.FilePath);
file << table->ToYAML();
file.flush();
if (!file)
LUX_CORE_ERROR_TAG("Audio", "Failed to save surface table '{}'", metadata.FilePath.string());
sheazywi and others added 9 commits September 17, 2026 11:39
Artifacts on a public repository are downloadable by anyone, and both EULAs
forbid redistributing their runtime libraries outside a game build (FMOD
also forbids shipping them as part of a game engine or tool set).

Windows removes fmod*.dll and vaudionative*.dll; Linux removes the libfmod,
libfmodstudio and libvaudionative shared objects, and its ldd completeness
check now allows only those libraries to be unresolved. Both steps fail if
any remain, and add AUDIO_LIBRARIES_REQUIRED.txt explaining which files to
copy from the user's own SDK downloads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both SDKs are licensed and cannot live in this public repository, so every
CI build failed at project generation. Both jobs now check out the private
repository named by the AUDIO_SDK_REPOSITORY variable into .audio-sdk/ with
the AUDIO_SDK_TOKEN secret (persist-credentials off) and point LUX_FMOD_SDK
and LUX_VA_SDK at it. Missing configuration, including fork pull requests
that cannot read secrets, fails early with an explicit message.

scripts/ci/StageAudioSDKs.py populates that repository from the user's own
SDK downloads with only headers, link/runtime libraries and licence files
(Windows FMOD package, Linux FMOD package or .tar.gz with symlinks copied as
files, and Vercidium Audio for every platform it contains). It refuses to
write inside the public repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Core's post-build copies Coral.Managed from Build/Release only (pinned on
purpose so one Coral build is deployed). On Windows the solution still built
Coral.Managed in its own configuration, so a clean Debug or Dist build never
produced Build/Release and the copy failed (MSB3073). It only worked locally
because an earlier Release build had left the files behind; CI exposed it.

Reopen Coral.Managed in premake5.lua (the vendored submodule stays untouched)
with a configmap to Release for Debug, Debug-AS and Dist. Verified by moving
Build/Release aside and building Debug: Coral.Managed built in Release first
and Core's post-build copied all four files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…irement

libvaudionative.so from Vercidium Audio 1.8.0 references sqrtf, log10f and
acosf at GLIBC_2.43, so linking the editor failed on ubuntu-24.04 (glibc
2.39). Ubuntu 26.04 runners (generally available 2026-09-17) ship glibc 2.43.
Documented in Building.md, including that the same floor applies to Linux
machines building or running LuxEngine and exported games.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- FMODDemo scene: house test scene for ray-traced acoustics.
- FMOD Studio metadata: edited event {0e6cd051} and new event {b1db803c}.
- CubeScene import material and its asset-registry entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phased plan (0-14) for a material editor with live preview, thumbnails,
a full standard material, Unreal-style instances and a node graph built on
imgui-node-editor, grounded in the current code (no material editing is
reachable in the editor today) and research on Godot, Unreal, OpenPBR and
Blender.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…terial thumbnails

Material editor plan phases 0-2.

- MaterialEditorPanel (View > Material Editor): one tab per material, explicit
  Save/Revert, unsaved-tab close prompt, and one undo step per finished edit.
  Opens from a Content Browser double-click and from the Inspector's Edit buttons.
- MaterialPreview: a private Scene + Viewport (own SceneRenderer, no editor
  targets) showing the material on the project's default meshes; orbit and zoom.
- MaterialThumbnailer: renders stale material thumbnails one at a time, reads
  the pixels back on the render thread and hands CPU pixels to ThumbnailCache.
  Thumbnails are drawn padded with rounded corners.
- Inspector: Material Slots section for multi-submesh meshes.
- Remove the unregistered MaterialEditorPanel / MaterialsPanel.

Fix: MaterialAsset kept its values only in the shader push-constant block,
which the opaque shader lacks Transparency for and the transparent shader
lacks entirely, so reading them was an out-of-bounds read (crash on opening a
material) and transparent materials lost colour and opacity. The asset now
owns its values; MaterialScene reads them from the asset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- MaterialSurfaceParameters on MaterialAsset: emissive colour + map, occlusion
  map + strength, packed-map (ORM) channel selection for metalness /
  roughness / occlusion, specular, normal strength, height map as bump, and UV
  tiling / offset / rotation. Defaults reproduce existing shading (verified
  byte-identical on a material thumbnail).
- GPUMaterialData / GPUMaterial grow in step (144 bytes, std430).
- Emission is written by the G-buffer pass straight into scene color (loaded,
  not cleared); deferred lighting blends additively on top, so emissive maps
  keep their colour without a new G-buffer target.
- Renderer::BeginRenderPass honours per-attachment AttachmentLoadOp;
  FramebufferBlendMode::Additive is implemented (it hit VERIFY(false)).
- MaterialSerializer: new keys written only when non-default; files from before
  emissive colour migrate as data; transparent materials keep roughness and
  normal map.
- Assimp import maps emissive colour/strength/texture and occlusion textures.
- Material Editor: Emission, packed-channel, occlusion, bump and UV controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fer barrier, SDK test

- AudioSurfaceTableSerializer: enforce the 1 MiB limit on save, as the loader
  and asset pack already do, so an oversized table is refused instead of
  saved unloadable.
- JoltContactListener: queue a Begin/Persist only if the contact is still live
  under the second lock, so a removal between the locks cannot be followed by
  a stale event that resurrects the contact.
- PipelineCompute: a barrier on an unallocated storage buffer is a no-op again
  (regressed to a shipped VERIFY in 71eb9b3; a StorageBufferSet can resolve a
  null buffer mid-resize or before first use).
- tests/audio/run_sdk_layout.py: find premake the way Linux-Build.sh does
  (root, then vendor/bin, .exe on Windows) and compare missing-file paths in
  both slash forms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants