diff options
-rw-r--r-- | al/source.cpp | 199 | ||||
-rw-r--r-- | alc/alu.cpp | 26 |
2 files changed, 164 insertions, 61 deletions
diff --git a/al/source.cpp b/al/source.cpp index 3c1758d1..8085b429 100644 --- a/al/source.cpp +++ b/al/source.cpp @@ -554,35 +554,6 @@ void InitVoice(ALvoice *voice, ALsource *source, ALbufferlistitem *BufferList, A } -/** - * Returns if the last known state for the source was playing or paused. Does - * not sync with the mixer voice. - */ -inline bool IsPlayingOrPaused(ALsource *source) -{ return source->state == AL_PLAYING || source->state == AL_PAUSED; } - -/** - * Returns an updated source state using the matching voice's status (or lack - * thereof). - */ -inline ALenum GetSourceState(ALsource *source, ALvoice *voice) -{ - if(!voice && source->state == AL_PLAYING) - source->state = AL_STOPPED; - return source->state; -} - -/** - * Returns if the source should specify an update, given the context's - * deferring state and the source's last known state. - */ -inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context) -{ - return !context->mDeferUpdates.load(std::memory_order_acquire) && - IsPlayingOrPaused(source); -} - - VoiceChange *GetVoiceChanger(ALCcontext *ctx) { VoiceChange *vchg{ctx->mVoiceChangeTail}; @@ -625,6 +596,128 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) } +bool SetVoiceOffset(ALvoice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, + ALCdevice *device) +{ + /* First, get a free voice to start at the new offset. */ + auto voicelist = context->getVoicesSpan(); + bool free_voices{false}; + for(const ALvoice *voice : voicelist) + { + free_voices |= (voice->mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false); + if(free_voices) break; + } + if UNLIKELY(!free_voices) + { + auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); + if(allvoices.size() == voicelist.size()) + context->allocVoices(1); + context->mActiveVoiceCount.fetch_add(1, std::memory_order_release); + voicelist = context->getVoicesSpan(); + } + + ALvoice *newvoice{}; + ALuint vidx{0}; + for(ALvoice *voice : voicelist) + { + if(voice->mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped + && voice->mSourceID.load(std::memory_order_relaxed) == 0u + && voice->mPendingChange.load(std::memory_order_relaxed) == false) + { + newvoice = voice; + break; + } + ++vidx; + } + + /* Initialize the new voice and set its starting offset. + * TODO: It might be better to have the VoiceChange processing copy the old + * voice's mixing parameters (and pending update) insead of initializing it + * all here. This would just need to set the minimum properties to link the + * voice to the source and its position-dependent properties (including the + * fading flag). + */ + InitVoice(newvoice, source, source->queue, context, device); + if(vpos.pos > 0 || vpos.frac > 0 || vpos.bufferitem != source->queue) + newvoice->mFlags |= VOICE_IS_FADING; + newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed); + newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed); + newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed); + source->VoiceIdx = vidx; + + /* Set the old and new voices as having a pending change, and send them off + * with a new offset voice change. + */ + oldvoice->mPendingChange.store(true, std::memory_order_relaxed); + newvoice->mPendingChange.store(true, std::memory_order_relaxed); + + VoiceChange *vchg{GetVoiceChanger(context)}; + vchg->mOldVoice = oldvoice; + vchg->mVoice = newvoice; + vchg->mSourceID = source->id; + vchg->mState = AL_SAMPLE_OFFSET; + SendVoiceChanges(context, vchg); + + /* If the old voice still has a sourceID, it's still active and the change- + * over will work on the next update. + */ + if LIKELY(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) + return true; + + /* Otherwise, if the new voice's pending change cleared, the change-over + * already happened. + */ + if(!newvoice->mPendingChange.load(std::memory_order_acquire)) + return true; + + /* Otherwise, wait for any current mix to finish and check one last time. */ + ALuint refcount; + while((refcount=device->MixCount.load(std::memory_order_acquire))&1) + std::this_thread::yield(); + if(!newvoice->mPendingChange.exchange(false, std::memory_order_acq_rel)) + return true; + /* The change-over failed because the old voice stopped before the new + * voice could start at the new offset. Let go of the new voice and have + * the caller store the source offset since it's stopped. + */ + newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + newvoice->mSourceID.store(0u, std::memory_order_relaxed); + return false; +} + + +/** + * Returns if the last known state for the source was playing or paused. Does + * not sync with the mixer voice. + */ +inline bool IsPlayingOrPaused(ALsource *source) +{ return source->state == AL_PLAYING || source->state == AL_PAUSED; } + +/** + * Returns an updated source state using the matching voice's status (or lack + * thereof). + */ +inline ALenum GetSourceState(ALsource *source, ALvoice *voice) +{ + if(!voice && source->state == AL_PLAYING) + source->state = AL_STOPPED; + return source->state; +} + +/** + * Returns if the source should specify an update, given the context's + * deferring state and the source's last known state. + */ +inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context) +{ + return !context->mDeferUpdates.load(std::memory_order_acquire) && + IsPlayingOrPaused(source); +} + + bool EnsureSources(ALCcontext *context, size_t needed) { size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), @@ -1091,26 +1184,16 @@ bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a CHECKSIZE(values, 1); CHECKVAL(values[0] >= 0.0f); - if(IsPlayingOrPaused(Source)) + if(ALvoice *voice{GetSourceVoice(Source, Context)}) { - ALCdevice *device{Context->mDevice.get()}; - std::lock_guard<BackendBase> _{*device->Backend}; - /* Double-check that the source is still playing while we have the - * lock. - */ - if(ALvoice *voice{GetSourceVoice(Source, Context)}) - { - if((voice->mFlags&VOICE_IS_CALLBACK)) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, - "Source offset for callback is invalid"); - auto vpos = GetSampleOffset(Source->queue, prop, values[0]); - if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid offset"); - - voice->mPosition.store(vpos->pos, std::memory_order_relaxed); - voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_release); + if((voice->mFlags&VOICE_IS_CALLBACK)) + SETERR_RETURN(Context, AL_INVALID_VALUE, false, + "Source offset for callback is invalid"); + auto vpos = GetSampleOffset(Source->queue, prop, values[0]); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid offset"); + + if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mDevice.get())) return true; - } } Source->OffsetType = prop; Source->Offset = values[0]; @@ -1319,22 +1402,16 @@ bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const a CHECKSIZE(values, 1); CHECKVAL(values[0] >= 0); - if(IsPlayingOrPaused(Source)) + if(ALvoice *voice{GetSourceVoice(Source, Context)}) { - std::lock_guard<BackendBase> _{*device->Backend}; - if(ALvoice *voice{GetSourceVoice(Source, Context)}) - { - if((voice->mFlags&VOICE_IS_CALLBACK)) - SETERR_RETURN(Context, AL_INVALID_VALUE, false, - "Source offset for callback is invalid"); - auto vpos = GetSampleOffset(Source->queue, prop, values[0]); - if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid source offset"); - - voice->mPosition.store(vpos->pos, std::memory_order_relaxed); - voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); - voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_release); + if((voice->mFlags&VOICE_IS_CALLBACK)) + SETERR_RETURN(Context, AL_INVALID_VALUE, false, + "Source offset for callback is invalid"); + auto vpos = GetSampleOffset(Source->queue, prop, values[0]); + if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid source offset"); + + if(SetVoiceOffset(voice, *vpos, Source, Context, device)) return true; - } } Source->OffsetType = prop; Source->Offset = values[0]; diff --git a/alc/alu.cpp b/alc/alu.cpp index 4d9c6f57..42642e75 100644 --- a/alc/alu.cpp +++ b/alc/alu.cpp @@ -1677,6 +1677,32 @@ void ProcessVoiceChanges(ALCcontext *ctx) ALvoice *voice{cur->mVoice}; voice->mPlayState.store(ALvoice::Playing, std::memory_order_release); } + else if(cur->mState == AL_SAMPLE_OFFSET) + { + /* Changing a voice offset never sends a source change event. */ + ALvoice *oldvoice{cur->mOldVoice}; + oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + /* If there's no sourceID, the old voice finished so don't start + * the new one at its new offset. + */ + if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u) + { + /* Otherwise, set the voice to stopping if it's not already (it + * would already be if paused), and play the new voice as + * appropriate. + */ + ALvoice::State oldvstate{ALvoice::Playing}; + oldvoice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping, + std::memory_order_relaxed, std::memory_order_acquire); + + ALvoice *voice{cur->mVoice}; + if(oldvstate == ALvoice::Playing) + voice->mPlayState.store(ALvoice::Playing, std::memory_order_release); + voice->mPendingChange.store(false, std::memory_order_release); + } + oldvoice->mPendingChange.store(false, std::memory_order_release); + } if(sendevt && (enabledevt&EventType_SourceStateChange)) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); } while((next=cur->mNext.load(std::memory_order_acquire))); |