Don't allocate full buffer lines in each voice

There's now effectively a 16-channel limit for buffers (as determined by the
number of elements in DeviceBase::mSampleData). Any more than that are
ignored when mixing.
This commit is contained in:
Chris Robinson 2021-06-21 09:04:33 -07:00
parent 7584458ecd
commit 8f3148ba53
4 changed files with 55 additions and 30 deletions

View File

@ -509,17 +509,23 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL
/* Even if storing really high order ambisonics, we only mix channels for
* orders up to MaxAmbiOrder. The rest are simply dropped.
*/
const ALuint num_channels{(buffer->mChannels == FmtUHJ2) ? 3 :
ALuint num_channels{(buffer->mChannels == FmtUHJ2) ? 3 :
ChannelsFromFmt(buffer->mChannels, minu(buffer->mAmbiOrder, MaxAmbiOrder))};
if UNLIKELY(num_channels > device->mSampleData.size())
{
ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels,
device->mSampleData.size(), buffer->mChannels, buffer->mAmbiOrder);
num_channels = static_cast<ALuint>(device->mSampleData.size());
}
if(voice->mChans.capacity() > 2 && num_channels < voice->mChans.capacity())
{
decltype(voice->mChans){}.swap(voice->mChans);
decltype(voice->mVoiceSamples){}.swap(voice->mVoiceSamples);
decltype(voice->mPrevSamples){}.swap(voice->mPrevSamples);
}
voice->mChans.reserve(maxu(2, num_channels));
voice->mChans.resize(num_channels);
voice->mVoiceSamples.reserve(maxu(2, num_channels));
voice->mVoiceSamples.resize(num_channels);
voice->mPrevSamples.reserve(maxu(2, num_channels));
voice->mPrevSamples.resize(num_channels);
voice->prepare(device);

View File

@ -20,6 +20,8 @@
#include "intrusive_ptr.h"
#include "mixer/hrtfdefs.h"
#include "opthelpers.h"
#include "resampler_limits.h"
#include "uhjfilter.h"
#include "vector.h"
struct BackendBase;
@ -166,6 +168,11 @@ struct DeviceBase {
std::chrono::nanoseconds FixedLatency{0};
/* Temp storage used for mixer processing. */
static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding +
UhjDecoder::sFilterDelay};
using MixerBufferLine = std::array<float,MixerLineSize>;
alignas(16) std::array<MixerBufferLine,16> mSampleData;
alignas(16) float ResampledData[BufferLineSize];
alignas(16) float FilteredData[BufferLineSize];
union {

View File

@ -51,7 +51,8 @@ struct NEONTag;
struct CopyTag;
static_assert(!(sizeof(Voice::BufferLine)&15), "Voice::BufferLine must be a multiple of 16 bytes");
static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15),
"DeviceBase::MixerBufferLine must be a multiple of 16 bytes");
Resampler ResamplerDefault{Resampler::Linear};
@ -198,7 +199,7 @@ const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *ds
}
void LoadSamples(const al::span<Voice::BufferLine> dstSamples, const size_t dstOffset,
void LoadSamples(const al::span<DeviceBase::MixerBufferLine> dstSamples, const size_t dstOffset,
const al::byte *src, const size_t srcOffset, const FmtType srctype, const FmtChannels srcchans,
const size_t srcstep, const size_t samples) noexcept
{
@ -242,7 +243,7 @@ void LoadSamples(const al::span<Voice::BufferLine> dstSamples, const size_t dstO
void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
const size_t srcStep, const size_t samplesToLoad,
const al::span<Voice::BufferLine> voiceSamples)
const al::span<DeviceBase::MixerBufferLine> voiceSamples)
{
const uint loopStart{buffer->mLoopStart};
const uint loopEnd{buffer->mLoopEnd};
@ -286,7 +287,7 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples,
const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep,
const size_t samplesToLoad, const al::span<Voice::BufferLine> voiceSamples)
const size_t samplesToLoad, const al::span<DeviceBase::MixerBufferLine> voiceSamples)
{
/* Load what's left to play from the buffer */
const size_t remaining{minz(samplesToLoad, numCallbackSamples)};
@ -306,7 +307,7 @@ void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples
void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
const size_t srcStep, const size_t samplesToLoad,
const al::span<Voice::BufferLine> voiceSamples)
const al::span<DeviceBase::MixerBufferLine> voiceSamples)
{
/* Crawl the buffer queue to fill in the temp buffer */
size_t samplesLoaded{0};
@ -504,6 +505,9 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
else if UNLIKELY(!BufferListItem)
Counter = std::min(Counter, 64u);
al::span<DeviceBase::MixerBufferLine> MixingSamples{
Device->mSampleData.data() + Device->mSampleData.size() - mChans.size(),
mChans.size()};
const uint PostPadding{MaxResamplerEdge +
((mFmtChannels==FmtUHJ2 || mFmtChannels==FmtUHJ3 || mFmtChannels==FmtUHJ4)
? uint{UhjDecoder::sFilterDelay} : 0u)};
@ -535,14 +539,14 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
DataSize64 += PostPadding;
if(DataSize64 <= LineSize - MaxResamplerEdge)
if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge)
SrcBufferSize = static_cast<uint>(DataSize64);
else
{
/* If the source size got saturated, we can't fill the desired
* dst size. Figure out how many samples we can actually mix.
*/
SrcBufferSize = LineSize - MaxResamplerEdge;
SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge;
DataSize64 = SrcBufferSize - PostPadding;
DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
@ -559,11 +563,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
if UNLIKELY(!BufferListItem)
{
auto prevSamples = mPrevSamples.data();
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerPadding;
for(auto &chanbuffer : mVoiceSamples)
for(auto &chanbuffer : MixingSamples)
{
auto srciter = chanbuffer.data() + MaxResamplerEdge;
auto srcend = chanbuffer.data() + MaxResamplerPadding;
auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding,
chanbuffer.data());
++prevSamples;
/* When loading from a voice that ended prematurely, only take
* the samples that get closest to 0 amplitude. This helps
@ -571,16 +577,22 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
*/
auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
{ return std::abs(lhs) < std::abs(rhs); };
srciter = std::min_element(srciter, srcend, abs_lt);
auto srciter = std::min_element(srcend - MaxResamplerEdge, srcend, abs_lt);
std::fill(srciter+1, chanbuffer.data() + SrcBufferSize, *srciter);
}
}
else
{
auto prevSamples = mPrevSamples.data();
for(auto &chanbuffer : MixingSamples)
{
std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer.data());
++prevSamples;
}
if((mFlags&VoiceIsStatic))
LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels,
mNumChannels, SrcBufferSize, mVoiceSamples);
mNumChannels, SrcBufferSize, MixingSamples);
else if((mFlags&VoiceIsCallback))
{
if(!(mFlags&VoiceCallbackStopped))
@ -605,27 +617,34 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
}
}
LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels,
mNumChannels, SrcBufferSize, mVoiceSamples);
mNumChannels, SrcBufferSize, MixingSamples);
}
else
LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels,
mNumChannels, SrcBufferSize, mVoiceSamples);
mNumChannels, SrcBufferSize, MixingSamples);
if(mDecoder)
{
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
mDecoder->decode(mVoiceSamples, MaxResamplerEdge, SrcBufferSize, srcOffset);
mDecoder->decode(MixingSamples, MaxResamplerEdge, SrcBufferSize, srcOffset);
}
}
auto voiceSamples = mVoiceSamples.begin();
auto prevSamples = mPrevSamples.data();
auto voiceSamples = MixingSamples.begin();
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
for(auto &chandata : mChans)
{
/* Store the last source samples used for next time. */
std::copy_n(voiceSamples->data()+srcOffset, MaxResamplerPadding, prevSamples->data());
++prevSamples;
/* Resample, then apply ambisonic upsampling as needed. */
float *ResampledData{Resample(&mResampleState,
voiceSamples->data() + MaxResamplerEdge, DataPosFrac, increment,
{Device->ResampledData, DstBufferSize})};
++voiceSamples;
if((mFlags&VoiceIsAmbisonic))
chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize},
chandata.mAmbiScale);
@ -673,11 +692,6 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo
MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
parms.Gains.Current.data(), TargetGains, Counter, OutPos);
}
/* Store the last source samples used for next time. */
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
std::copy_n(voiceSamples->data()+srcOffset, MaxResamplerPadding, voiceSamples->data());
++voiceSamples;
}
/* Update positions */
DataPosFrac += increment*DstBufferSize;
@ -809,7 +823,7 @@ void Voice::prepare(DeviceBase *device)
mStep = 0;
/* Make sure the sample history is cleared. */
std::fill(mVoiceSamples.begin(), mVoiceSamples.end(), BufferLine{});
std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{});
/* Don't need to set the VoiceIsAmbisonic flag if the device is not higher
* order than the voice. No HF scaling is necessary to mix it.

View File

@ -237,10 +237,8 @@ struct Voice {
* now current (which may be overwritten if the buffer data is still
* available).
*/
static constexpr size_t LineSize{BufferLineSize + MaxResamplerPadding +
UhjDecoder::sFilterDelay};
using BufferLine = std::array<float,LineSize>;
al::vector<BufferLine,16> mVoiceSamples{2};
using HistoryLine = std::array<float,MaxResamplerPadding>;
al::vector<HistoryLine,16> mPrevSamples{2};
struct ChannelData {
float mAmbiScale;