Skip to content

fix(cluster): drop bogus AkAlloca and reset per-frame state#5

Open
Rakdos8 wants to merge 1 commit into
carbonengine:mainfrom
Rakdos8:fix/rt-stack-and-unbounded-growth
Open

fix(cluster): drop bogus AkAlloca and reset per-frame state#5
Rakdos8 wants to merge 1 commit into
carbonengine:mainfrom
Rakdos8:fix/rt-stack-and-unbounded-growth

Conversation

@Rakdos8
Copy link
Copy Markdown

@Rakdos8 Rakdos8 commented May 15, 2026

Summary

Fixes a stack-corruption risk and an unbounded per-frame memory growth in
the real-time clustering path.

Problem

  1. CreateOutputObject did AkAlloca(sizeof(AkAudioObject*)) and treated
    it as an AkAudioObject. CreateOutputObjects only needs a single
    pointer slot it fills itself, so the alloca was wrong and a
    stack-corruption hazard.
  2. performClustering never cleared sse_values, which is push_back'd
    every iteration of every audio frame — unbounded memory growth plus a
    convergence test indexed by iter against stale data.

Fix

Use a null pointer slot (matching the existing pattern in
ObjectClusterFX.cpp). Clear sse_values and assign labels at the start
of performClustering so each run starts clean.

Type

Stability (stack corruption) + real-time correctness/memory (Critical).

Testing

Manual review; output-object creation now follows the Wwise SDK contract;
clustering state is per-call.

Copy link
Copy Markdown
Collaborator

@phevosccp phevosccp left a comment

Choose a reason for hiding this comment

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

Hi and thanks for taking the time to contribute.
Reading through your description, the testing section is unclear. Could you elaborate on how did you test the plugin?
Have you actually build the plugin and tested this in runtime a environment?

CreateOutputObject passed (AkAudioObject*)AkAlloca(sizeof(AkAudioObject*))
as its output slot: a miscopy of the SDK Particle Generator example. The
alloca'd buffer is never used (the real slot is &newObject) and the value
is overwritten by CreateOutputObjects before being read -- dead code, not
active stack corruption. Use a nullptr slot, matching the SDK example and
the ownership model GetOutputObjects already uses in ObjectClusterFX.

performClustering never cleared sse_values (push_back'd every iteration
of every audio frame): unbounded growth plus a stale-indexed convergence
test. Clear it and assign labels so each run starts from clean state.
@Rakdos8 Rakdos8 force-pushed the fix/rt-stack-and-unbounded-growth branch from 9c32e3b to 0bcdf98 Compare May 18, 2026 11:26
@Rakdos8
Copy link
Copy Markdown
Author

Rakdos8 commented May 18, 2026

Hi and thanks for taking the time to contribute. Reading through your description, the testing section is unclear. Could you elaborate on how did you test the plugin? Have you actually build the plugin and tested this in runtime a environment?

Thanks again for the review and the doc links - I dug into the "Creating Sound Engine Object Processor Plug-ins" page you sent, and I think it actually settles this line in the patch's favour. Let me lay it out so you can sanity-check me.

The SDK doc states for CreateOutputObjects:

AkAudioObjects::ppObjects, Returned array of pointers to the objects newly created, allocated by the caller. Pass nullptr if they're not needed.

So the caller allocates the array of pointers; the objects themselves are created by CreateOutputObjects. The canonical example for exactly this pattern is the Particle Generator on that same page (single object per input, copy positioning, store key - i.e. what this plugin does):

AkAudioObject ** arNewObjects = (AkAudioObject**)AkAlloca(numObjsOut * sizeof(AkAudioObject*));
AkAudioObjects outputObjects;
outputObjects.uNumObjects = numObjsOut;
outputObjects.ppObjectBuffers = nullptr;
outputObjects.ppObjects = arNewObjects;
if (m_pContext->CreateOutputObjects(in_objects.ppObjectBuffers[i]->GetChannelConfig(), outputObjects) == AK_Success)
{
  AkAudioObject * pObject = arNewObjects[iObj]; // engine filled the slot

The official AkAlloca allocates numObjsOut * sizeof(AkAudioObject*) - an array of N pointers (8 bytes for N=1), and the slot's initial contents are never read; CreateOutputObjects overwrites them with the created object's pointer.

In Utilities.cpp#L60-L66, &newObject is that 1-element pointer array (the arNewObjects of the example, with numObjsOut == 1). CreateOutputObjects writes the created object into newObject, so its initial value - garbage or nullptr - is irrelevant because it's overwritten before it's read. nullptr is just the safe initialiser.

The original line:

AkAudioObject* newObject = (AkAudioObject*)AkAlloca(sizeof(AkAudioObject*));

looks like a miscopy of that example: the AkAlloca buffer is never used (the actual slot is &newObject, not the alloca'd memory), and the cast is AkAudioObject* instead of AkAudioObject**. To be precise and fair: it doesn't crash today - the alloca'd value is silently discarded - so this is a dead-code / clarity cleanup that aligns the call with the SDK's documented pattern, not a runtime crash fix. Happy to revert if you read the ppObjects ownership differently, but the Particle Generator example seems unambiguous that the engine allocates the objects and the caller only provides the pointer slots.

For what it's worth, I didn't rely on the SDK docs alone - I also cross-checked against an existing convention in this codebase. ObjectClusterFX::GetCurrentOutputObjects() (ObjectClusterFX.cpp#L548-L571) already uses the sibling SDK call GetOutputObjects, which has the same caller-provides-the-pointer-array / engine-fills-the-slots ownership contract: the caller passes a std::vector<AkAudioObject*> (m_tempObjects, ObjectClusterFX.h#L235) and the sound engine populates the entries.
Notably, outputObjects.ppObjects = nullptr; is already used there as a valid value (L553, the count-only first call). To be precise: that's GetOutputObjects, not a second CreateOutputObjects call site - CreateOutputObjects is invoked only in Utilities.cpp - so I'm citing it as the same ownership model already trusted in this code, not as duplicated logic. The patch just makes the CreateOutputObjects call consistent with both that in-repo convention and the SDK's Particle Generator example.

I've also amended the commit message to reflect reality more accurately: the original called the old AkAlloca a "stack-corruption risk" and said the fix "matches the pattern already used in ObjectClusterFX" - both overstated. It doesn't corrupt anything (the alloca'd value is silently discarded before it's ever read), and ObjectClusterFX uses the sibling GetOutputObjects, not a second CreateOutputObjects. The amended message now describes it as removing a dead/miscopied allocation and aligning the call with the SDK's Particle Generator example and the ownership model GetOutputObjects already uses in this codebase.

On testing - honest disclosure unchanged: I don't have a Wwise authoring + game build env available right now, so I validated this against the SDK contract and the documented examples rather than at runtime, and was hoping to lean on a CI build if one is wired into the pipeline. I'm a bit rusty on modern C++ too, so please push back if I've misread anything. If there's no CI, I'm glad to write up the exact frames/scenarios that exercise CreateOutputObject and the sse_values growth for someone with a build env to confirm, or to follow any test-harness pointer you use for plugin PRs.

@phevosccp
Copy link
Copy Markdown
Collaborator

phevosccp commented May 19, 2026

Hi,
so fyi currently, we are working on finalizing the contribution guidelines and PR templates, so we apologize for the lack of concrete instructions for external pull requests.

While i don't want to presume anything, i think its reasonable to expect contributors to build and test their code before making a submission.
Please understand that while we test changes anyway for anything that is part of our production pipeline, it shows genuine interest and ensures code quality if you do that your self too.

Regarding Wwise you can get a free personal license and use their C++ SDK along with their CLI tools for building any wwise plugin.
I suggest starting here: Creating Audio Plug-ins
Once you've build then can youuse it to make a test scene with a game engine (UE5 and Unity are recommended because are supporting their SDK integration ).

If you don't want to use one of these engines you can either use Wwise's:

  • Integration Demo Sample A simple lightweight executable with various demos that you can build and modify yourself.
  • Use any of the sample games that Wwise has provides Wwise Samples

Please bear with us as we are still currently working on open sourcing fully the engine and several parts are not out yet and some processes not finalized.

On a different note, I realize that what i have listed here today should be added to the README and instructions for how to create sample test scene for spatial audio (both of which are absent at the moment, primarily because we were not focusing on making this process optimal - but we will add these as soon as we can) to improve the process and make it easy for external contributors. Besides that i would take a closer look at your changes when time allows.

If you need any help with building/testing the plugin reply to this thread and we will figure it out together.

@Rakdos8
Copy link
Copy Markdown
Author

Rakdos8 commented May 20, 2026

Thanks a lot for the detailed pointers and for being upfront about the in-flight contribution guidelines - no worries at all on that front.

That all sounds completely reasonable to me. I'd much rather build and validate this at runtime myself than lean on your pipeline, so I'm happy to wait. It actually gives me the time to set up a proper local build/test environment: I'll grab the free Wwise personal license + C++ SDK and CLI tools, work through the "Creating Audio Plug-ins" guide, and then exercise the change with the Integration Demo Sample (or one of the sample games) so I can confirm both the CreateOutputObject path and the sse_values growth at runtime rather than against the SDK contract alone.

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