aboutsummaryrefslogtreecommitdiffstats
path: root/alc/context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'alc/context.cpp')
-rw-r--r--alc/context.cpp387
1 files changed, 387 insertions, 0 deletions
diff --git a/alc/context.cpp b/alc/context.cpp
new file mode 100644
index 00000000..9f0b6272
--- /dev/null
+++ b/alc/context.cpp
@@ -0,0 +1,387 @@
+
+#include "config.h"
+
+#include "context.h"
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <numeric>
+#include <stddef.h>
+#include <stdexcept>
+
+#include "AL/efx.h"
+
+#include "al/auxeffectslot.h"
+#include "al/source.h"
+#include "al/effect.h"
+#include "al/event.h"
+#include "al/listener.h"
+#include "albit.h"
+#include "alc/alu.h"
+#include "core/async_event.h"
+#include "core/device.h"
+#include "core/logging.h"
+#include "core/voice.h"
+#include "core/voice_change.h"
+#include "device.h"
+#include "effectslot.h"
+#include "ringbuffer.h"
+#include "vecmat.h"
+
+
+namespace {
+
+using namespace std::placeholders;
+
+using voidp = void*;
+
+/* Default context extensions */
+constexpr ALchar alExtList[] =
+ "AL_EXT_ALAW "
+ "AL_EXT_BFORMAT "
+ "AL_EXT_DOUBLE "
+ "AL_EXT_EXPONENT_DISTANCE "
+ "AL_EXT_FLOAT32 "
+ "AL_EXT_IMA4 "
+ "AL_EXT_LINEAR_DISTANCE "
+ "AL_EXT_MCFORMATS "
+ "AL_EXT_MULAW "
+ "AL_EXT_MULAW_BFORMAT "
+ "AL_EXT_MULAW_MCFORMATS "
+ "AL_EXT_OFFSET "
+ "AL_EXT_source_distance_model "
+ "AL_EXT_SOURCE_RADIUS "
+ "AL_EXT_STEREO_ANGLES "
+ "AL_LOKI_quadriphonic "
+ "AL_SOFT_bformat_ex "
+ "AL_SOFTX_bformat_hoa "
+ "AL_SOFT_block_alignment "
+ "AL_SOFTX_callback_buffer "
+ "AL_SOFTX_convolution_reverb "
+ "AL_SOFT_deferred_updates "
+ "AL_SOFT_direct_channels "
+ "AL_SOFT_direct_channels_remix "
+ "AL_SOFT_effect_target "
+ "AL_SOFT_events "
+ "AL_SOFTX_filter_gain_ex "
+ "AL_SOFT_gain_clamp_ex "
+ "AL_SOFTX_hold_on_disconnect "
+ "AL_SOFT_loop_points "
+ "AL_SOFTX_map_buffer "
+ "AL_SOFT_MSADPCM "
+ "AL_SOFT_source_latency "
+ "AL_SOFT_source_length "
+ "AL_SOFT_source_resampler "
+ "AL_SOFT_source_spatialize "
+ "AL_SOFTX_UHJ";
+
+} // namespace
+
+
+std::atomic<ALCcontext*> ALCcontext::sGlobalContext{nullptr};
+
+thread_local ALCcontext *ALCcontext::sLocalContext{nullptr};
+ALCcontext::ThreadCtx::~ThreadCtx()
+{
+ if(ALCcontext *ctx{ALCcontext::sLocalContext})
+ {
+ const bool result{ctx->releaseIfNoDelete()};
+ ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx},
+ result ? "" : ", leak detected");
+ }
+}
+thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext;
+
+ALeffect ALCcontext::sDefaultEffect;
+
+
+ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
+{ }
+
+ContextBase::~ContextBase()
+{
+ size_t count{0};
+ ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)};
+ if(cprops)
+ {
+ ++count;
+ delete cprops;
+ }
+ cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire);
+ while(cprops)
+ {
+ std::unique_ptr<ContextProps> old{cprops};
+ cprops = old->next.load(std::memory_order_relaxed);
+ ++count;
+ }
+ TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s");
+
+ count = 0;
+ EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)};
+ while(eprops)
+ {
+ std::unique_ptr<EffectSlotProps> old{eprops};
+ eprops = old->next.load(std::memory_order_relaxed);
+ ++count;
+ }
+ TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
+
+ if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)})
+ {
+ al::destroy_n(curarray->end(), curarray->size());
+ delete curarray;
+ }
+
+ count = 0;
+ VoicePropsItem *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)};
+ while(vprops)
+ {
+ std::unique_ptr<VoicePropsItem> old{vprops};
+ vprops = old->next.load(std::memory_order_relaxed);
+ ++count;
+ }
+ TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s");
+
+ delete mVoices.exchange(nullptr, std::memory_order_relaxed);
+
+ count = 0;
+ ListenerProps *lprops{mParams.ListenerUpdate.exchange(nullptr, std::memory_order_relaxed)};
+ if(lprops)
+ {
+ ++count;
+ delete lprops;
+ }
+ lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire);
+ while(lprops)
+ {
+ std::unique_ptr<ListenerProps> old{lprops};
+ lprops = old->next.load(std::memory_order_relaxed);
+ ++count;
+ }
+ TRACE("Freed %zu listener property object%s\n", count, (count==1)?"":"s");
+
+ if(mAsyncEvents)
+ {
+ count = 0;
+ auto evt_vec = mAsyncEvents->getReadVector();
+ if(evt_vec.first.len > 0)
+ {
+ al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.first.buf), evt_vec.first.len);
+ count += evt_vec.first.len;
+ }
+ if(evt_vec.second.len > 0)
+ {
+ al::destroy_n(reinterpret_cast<AsyncEvent*>(evt_vec.second.buf), evt_vec.second.len);
+ count += evt_vec.second.len;
+ }
+ if(count > 0)
+ TRACE("Destructed %zu orphaned event%s\n", count, (count==1)?"":"s");
+ mAsyncEvents->readAdvance(count);
+ }
+}
+
+void ContextBase::allocVoiceChanges(size_t addcount)
+{
+ constexpr size_t clustersize{128};
+ /* Convert element count to cluster count. */
+ addcount = (addcount+(clustersize-1)) / clustersize;
+ while(addcount)
+ {
+ VoiceChangeCluster cluster{std::make_unique<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);
+ mVoiceChangeClusters.emplace_back(std::move(cluster));
+ mVoiceChangeTail = mVoiceChangeClusters.back().get();
+ --addcount;
+ }
+}
+
+void ContextBase::allocVoices(size_t addcount)
+{
+ constexpr size_t clustersize{32};
+ /* Convert element count to cluster count. */
+ addcount = (addcount+(clustersize-1)) / clustersize;
+
+ if(addcount >= std::numeric_limits<int>::max()/clustersize - mVoiceClusters.size())
+ throw std::runtime_error{"Allocating too many voices"};
+ const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize};
+ TRACE("Increasing allocated voices to %zu\n", totalcount);
+
+ auto newarray = VoiceArray::Create(totalcount);
+ while(addcount)
+ {
+ mVoiceClusters.emplace_back(std::make_unique<Voice[]>(clustersize));
+ --addcount;
+ }
+
+ auto voice_iter = newarray->begin();
+ for(VoiceCluster &cluster : mVoiceClusters)
+ {
+ for(size_t i{0};i < clustersize;++i)
+ *(voice_iter++) = &cluster[i];
+ }
+
+ if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel))
+ {
+ mDevice->waitForMix();
+ delete oldvoices;
+ }
+}
+
+
+ALCcontext::ALCcontext(al::intrusive_ptr<ALCdevice> device)
+ : ContextBase{device.get()}, mALDevice{std::move(device)}
+{
+ mPropsDirty.test_and_clear(std::memory_order_relaxed);
+}
+
+ALCcontext::~ALCcontext()
+{
+ TRACE("Freeing context %p\n", voidp{this});
+
+ size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u},
+ [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); })};
+ if(count > 0)
+ WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
+ mSourceList.clear();
+ mNumSources = 0;
+
+ mDefaultSlot = nullptr;
+ count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u},
+ [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<uint>(al::popcount(~sublist.FreeMask)); });
+ if(count > 0)
+ WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
+ mEffectSlotList.clear();
+ mNumEffectSlots = 0;
+}
+
+void ALCcontext::init()
+{
+ if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback)
+ {
+ mDefaultSlot = std::make_unique<ALeffectslot>();
+ aluInitEffectPanning(&mDefaultSlot->mSlot, this);
+ }
+
+ EffectSlotArray *auxslots;
+ if(!mDefaultSlot)
+ auxslots = EffectSlot::CreatePtrArray(0);
+ else
+ {
+ auxslots = EffectSlot::CreatePtrArray(1);
+ (*auxslots)[0] = &mDefaultSlot->mSlot;
+ mDefaultSlot->mState = SlotState::Playing;
+ }
+ 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;
+
+
+ mParams.Matrix = alu::Matrix::Identity();
+ mParams.Velocity = alu::Vector{};
+ mParams.Gain = mListener.Gain;
+ mParams.MetersPerUnit = mListener.mMetersPerUnit;
+ mParams.DopplerFactor = mDopplerFactor;
+ mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
+ mParams.SourceDistanceModel = mSourceDistanceModel;
+ mParams.mDistanceModel = mDistanceModel;
+
+
+ mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false);
+ StartEventThrd(this);
+
+
+ allocVoices(256);
+ mActiveVoiceCount.store(64, std::memory_order_relaxed);
+}
+
+bool ALCcontext::deinit()
+{
+ if(sLocalContext == this)
+ {
+ WARN("%p released while current on thread\n", voidp{this});
+ sThreadContext.set(nullptr);
+ release();
+ }
+
+ ALCcontext *origctx{this};
+ if(sGlobalContext.compare_exchange_strong(origctx, nullptr))
+ release();
+
+ bool ret{};
+ /* First make sure this context exists in the device's list. */
+ auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire);
+ if(auto toremove = static_cast<size_t>(std::count(oldarray->begin(), oldarray->end(), this)))
+ {
+ using ContextArray = al::FlexArray<ContextBase*>;
+ auto alloc_ctx_array = [](const size_t count) -> ContextArray*
+ {
+ if(count == 0) return &DeviceBase::sEmptyContextArray;
+ return ContextArray::Create(count).release();
+ };
+ auto *newarray = alloc_ctx_array(oldarray->size() - toremove);
+
+ /* Copy the current/old context handles to the new array, excluding the
+ * given context.
+ */
+ std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(),
+ std::bind(std::not_equal_to<>{}, _1, this));
+
+ /* Store the new context array in the device. Wait for any current mix
+ * to finish before deleting the old array.
+ */
+ mDevice->mContexts.store(newarray);
+ if(oldarray != &DeviceBase::sEmptyContextArray)
+ {
+ mDevice->waitForMix();
+ delete oldarray;
+ }
+
+ ret = !newarray->empty();
+ }
+ else
+ ret = !oldarray->empty();
+
+ StopEventThrd(this);
+
+ return ret;
+}
+
+void ALCcontext::processUpdates()
+{
+ std::lock_guard<std::mutex> _{mPropLock};
+ if(mDeferUpdates.exchange(false, std::memory_order_acq_rel))
+ {
+ /* Tell the mixer to stop applying updates, then wait for any active
+ * updating to finish, before providing updates.
+ */
+ mHoldUpdates.store(true, std::memory_order_release);
+ while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) {
+ /* busy-wait */
+ }
+
+ if(mPropsDirty.test_and_clear(std::memory_order_acq_rel))
+ UpdateContextProps(this);
+ if(mListener.mPropsDirty.test_and_clear(std::memory_order_acq_rel))
+ UpdateListenerProps(this);
+ UpdateAllEffectSlotProps(this);
+ UpdateAllSourceProps(this);
+
+ /* Now with all updates declared, let the mixer continue applying them
+ * so they all happen at once.
+ */
+ mHoldUpdates.store(false, std::memory_order_release);
+ }
+}