diff options
-rw-r--r-- | al/source.cpp | 64 | ||||
-rw-r--r-- | alc/alc.cpp | 26 | ||||
-rw-r--r-- | alc/alcontext.h | 29 | ||||
-rw-r--r-- | alc/alu.cpp | 47 |
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)) { |