aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--al/source.cpp64
-rw-r--r--alc/alc.cpp26
-rw-r--r--alc/alcontext.h29
-rw-r--r--alc/alu.cpp47
4 files changed, 154 insertions, 12 deletions
diff --git a/al/source.cpp b/al/source.cpp
index 54705e6a..60a9a782 100644
--- a/al/source.cpp
+++ b/al/source.cpp
@@ -486,6 +486,50 @@ inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context)
}
+VoiceChange *GetVoiceChangers(ALCcontext *ctx, size_t count)
+{
+ VoiceChange *tail{ctx->mVoiceChangeTail};
+ if UNLIKELY(!count) return tail;
+
+ if(tail == ctx->mCurrentVoiceChange.load(std::memory_order_acquire))
+ {
+ ctx->allocVoiceChanges(count);
+ tail = ctx->mVoiceChangeTail;
+ }
+ else if(count > 1)
+ {
+ VoiceChange *head{tail->mNext.load(std::memory_order_acquire)};
+ size_t avail{1};
+ for(;avail < count;++avail)
+ {
+ if(head == ctx->mCurrentVoiceChange.load(std::memory_order_acquire))
+ break;
+ head = head->mNext.load(std::memory_order_acquire);
+ }
+ if(avail < count)
+ {
+ ctx->allocVoiceChanges(count - avail);
+ tail = ctx->mVoiceChangeTail;
+ }
+ }
+
+ VoiceChange *head{tail};
+ for(size_t avail{1};avail < count;++avail)
+ head = head->mNext.load(std::memory_order_relaxed);
+ ctx->mVoiceChangeTail = head->mNext.exchange(nullptr, std::memory_order_relaxed);
+
+ return tail;
+}
+
+void SendVoiceChangers(ALCcontext *ctx, VoiceChange *tail)
+{
+ VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
+ while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)})
+ oldhead = next;
+ oldhead->mNext.store(tail, std::memory_order_release);
+}
+
+
bool EnsureSources(ALCcontext *context, size_t needed)
{
size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(),
@@ -541,20 +585,16 @@ void FreeSource(ALCcontext *context, ALsource *source)
if(IsPlayingOrPaused(source))
{
- ALCdevice *device{context->mDevice.get()};
- BackendLockGuard _{*device->Backend};
if(ALvoice *voice{GetSourceVoice(source, context)})
{
- voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
- voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
- voice->mSourceID.store(0u, std::memory_order_relaxed);
- std::atomic_thread_fence(std::memory_order_release);
- /* Don't set the voice to stopping if it was already stopped or
- * stopping.
- */
- ALvoice::State oldvstate{ALvoice::Playing};
- voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
- std::memory_order_acq_rel, std::memory_order_acquire);
+ VoiceChange *vchg{GetVoiceChangers(context, 1)};
+
+ voice->mPendingStop.store(true, std::memory_order_relaxed);
+ vchg->mVoice = voice;
+ vchg->mSourceID = id;
+ vchg->mState = AL_STOPPED;
+
+ SendVoiceChangers(context, vchg);
}
}
diff --git a/alc/alc.cpp b/alc/alc.cpp
index 5752fe17..46e930ad 100644
--- a/alc/alc.cpp
+++ b/alc/alc.cpp
@@ -1642,6 +1642,24 @@ void ALCcontext::processUpdates()
}
+void ALCcontext::allocVoiceChanges(size_t addcount)
+{
+ constexpr size_t clustersize{128};
+ /* Convert element count to cluster count. */
+ addcount = (addcount+(clustersize-1)) / clustersize;
+ while(addcount)
+ {
+ VoiceChangeCluster cluster{new VoiceChange[clustersize]};
+ for(size_t i{1};i < clustersize;++i)
+ cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed);
+ cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed);
+ mVoiceChangeCluster.emplace_back(std::move(cluster));
+ mVoiceChangeTail = mVoiceChangeCluster.back().get();
+ --addcount;
+ }
+}
+
+
/* alcSetError
*
* Stores the latest ALC device error
@@ -2568,6 +2586,14 @@ void ALCcontext::init()
}
mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
+ allocVoiceChanges(1);
+ {
+ VoiceChange *cur{mVoiceChangeTail};
+ while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)})
+ cur = next;
+ mCurrentVoiceChange.store(cur, std::memory_order_relaxed);
+ }
+
mExtensionList = alExtList;
diff --git a/alc/alcontext.h b/alc/alcontext.h
index ba3942f5..59fda1a7 100644
--- a/alc/alcontext.h
+++ b/alc/alcontext.h
@@ -56,6 +56,17 @@ struct ALcontextProps {
};
+struct VoiceChange {
+ ALvoice *mVoice{nullptr};
+ ALuint mSourceID{0};
+ ALenum mState{0};
+
+ std::atomic<VoiceChange*> mNext{nullptr};
+
+ DEF_NEWDEL(VoiceChange)
+};
+
+
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
@@ -128,6 +139,24 @@ struct ALCcontext : public al::intrusive_ref<ALCcontext> {
std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr};
std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr};
+ /* Asynchronous voice change actions are processed as a linked list of
+ * VoiceChange objects by the mixer, which is atomically appended to.
+ * However, to avoid allocating each object individually, they're allocated
+ * in clusters that are stored in a vector for easy automatic cleanup.
+ */
+ using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
+ al::vector<VoiceChangeCluster> mVoiceChangeCluster;
+
+ /* The voice change tail is the beginning of the "free" elements, up to and
+ * *excluding* the current. If tail==current, there's no free elements and
+ * new ones need to be allocated. The current voice change is the element
+ * last processed, and any after are pending.
+ */
+ VoiceChange *mVoiceChangeTail{};
+ std::atomic<VoiceChange*> mCurrentVoiceChange{};
+
+ void allocVoiceChanges(size_t addcount);
+
al::vector<ALvoice> mVoices;
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
diff --git a/alc/alu.cpp b/alc/alu.cpp
index 36d4174a..748338ab 100644
--- a/alc/alu.cpp
+++ b/alc/alu.cpp
@@ -1605,9 +1605,56 @@ void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
}
+void SendSourceStateEvent(ALCcontext *context, ALuint id, ALenum state)
+{
+ RingBuffer *ring{context->mAsyncEvents.get()};
+ auto evt_vec = ring->getWriteVector();
+ if(evt_vec.first.len < 1) return;
+
+ AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
+ evt->u.srcstate.id = id;
+ evt->u.srcstate.state = state;
+
+ ring->writeAdvance(1);
+}
+
+void ProcessVoiceChanges(ALCcontext *ctx)
+{
+ VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)};
+ VoiceChange *next{cur->mNext.load(std::memory_order_acquire)};
+ if(!next) return;
+
+ const ALbitfieldSOFT enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)};
+ do {
+ cur = next;
+
+ bool success{false};
+ ALvoice *voice{cur->mVoice};
+ if(cur->mState == AL_INITIAL || cur->mState == AL_STOPPED)
+ {
+ if(voice)
+ {
+ voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mSourceID.exchange(0u, std::memory_order_relaxed);
+ ALvoice::State oldvstate{ALvoice::Playing};
+ success = voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
+ std::memory_order_relaxed, std::memory_order_acquire);
+ voice->mPendingStop.store(false, std::memory_order_release);
+ }
+ success |= (cur->mState == AL_INITIAL);
+ }
+ if(success && (enabledevt&EventType_SourceStateChange))
+ SendSourceStateEvent(ctx, cur->mSourceID, cur->mState);
+ } while((next=cur->mNext.load(std::memory_order_acquire)));
+ ctx->mCurrentVoiceChange.store(cur, std::memory_order_release);
+}
+
void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
const al::span<ALvoice> voices)
{
+ ProcessVoiceChanges(ctx);
+
IncrementRef(ctx->mUpdateCount);
if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
{