Improvement of FxMixer multithreading

Use dynamic building of jobqueues with dependency counting:
- At the start, each channel that has no dependencies is added automatically to the queue
- Then, after each channel is processed, it increments the dep.counter of all its recipients
- When a channel's dep.counter hits the amount of its dependencies (senders), it gets automatically added to the queue
- The queue is finished when the master channel has been processed
- Muted channels are automatically processed at the start regardless dependencies, because they don't have to care about senders, being muted

Hopefully this will improve Fx Mixer performance.
This commit is contained in:
Vesa
2014-11-15 18:51:49 +02:00
parent df843e2b32
commit c92774af27
2 changed files with 56 additions and 53 deletions

View File

@@ -57,6 +57,7 @@ class FxChannel : public ThreadableJob
QMutex m_lock;
int m_channelIndex; // what channel index are we
bool m_queued; // are we queued up for rendering yet?
bool m_muted; // are we muted? updated per period so we don't have to call m_muteModel.value() twice
// pointers to other channels that this one sends to
FxRouteVector m_sends;
@@ -65,7 +66,10 @@ class FxChannel : public ThreadableJob
FxRouteVector m_receives;
virtual bool requiresProcessing() const { return true; }
QAtomicInt m_dependenciesMet;
void incrementDeps();
private:
virtual void doProcessing( sampleFrame * _working_buffer );
};
@@ -189,8 +193,6 @@ private:
void allocateChannelsTo(int num);
QMutex m_sendsMutex;
void addChannelLeaf( FxChannel * ch, sampleFrame * buf );
friend class MixerWorkerThread;
friend class FxMixerView;

View File

@@ -69,7 +69,8 @@ FxChannel::FxChannel( int idx, Model * _parent ) :
m_name(),
m_lock(),
m_channelIndex( idx ),
m_queued( false )
m_queued( false ),
m_dependenciesMet( 0 )
{
engine::mixer()->clearAudioBuffer( m_buffer,
engine::mixer()->framesPerPeriod() );
@@ -84,6 +85,16 @@ FxChannel::~FxChannel()
}
void FxChannel::incrementDeps()
{
m_dependenciesMet.ref();
if( m_dependenciesMet >= m_receives.size() )
{
m_queued = true;
MixerWorkerThread::addJob( this );
m_dependenciesMet = 0;
}
}
void FxChannel::doProcessing( sampleFrame * _buf )
@@ -99,25 +110,14 @@ void FxChannel::doProcessing( sampleFrame * _buf )
// <tobydox> this improves cache hit rate
_buf = m_buffer;
if( m_muteModel.value() == false )
if( m_muted == false )
{
// OK, we are not muted, so we go recursively through all the channels
// which send to us (our children)...
foreach( FxRoute * senderRoute, m_receives )
{
FxChannel * sender = senderRoute->sender();
FloatModel * sendModel = senderRoute->amount();
if( ! sendModel ) qFatal( "Error: no send model found from %d to %d", senderRoute->senderIndex(), m_channelIndex );
// wait for the sender job - either it's just been queued yet,
// then ThreadableJob::process() will process it now within this
// thread - otherwise it has been is is being processed by another
// thread and we just have to wait for it to finish
while( sender->state() != ThreadableJob::Done )
{
sender->process();
}
if( sender->m_hasInput || sender->m_stillRunning )
{
// get the send level...
@@ -134,20 +134,34 @@ void FxChannel::doProcessing( sampleFrame * _buf )
m_hasInput = true;
}
}
const float v = m_volumeModel.value();
if( m_hasInput )
{
// only start fxchain when we have input...
m_fxChain.startRunning();
}
m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp, m_hasInput );
m_peakLeft = qMax( m_peakLeft, engine::mixer()->peakValueLeft( _buf, fpp ) * v );
m_peakRight = qMax( m_peakRight, engine::mixer()->peakValueRight( _buf, fpp ) * v );
}
const float v = m_volumeModel.value();
if( m_hasInput )
else
{
// only start fxchain when we have input...
m_fxChain.startRunning();
m_peakLeft = m_peakRight = 0.0f;
}
m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp, m_hasInput );
m_peakLeft = qMax( m_peakLeft, engine::mixer()->peakValueLeft( _buf, fpp ) * v );
m_peakRight = qMax( m_peakRight, engine::mixer()->peakValueRight( _buf, fpp ) * v );
// increment dependency counter of all receivers
foreach( FxRoute * receiverRoute, m_sends )
{
if( receiverRoute->receiver()->m_muteModel.value() == false )
{
receiverRoute->receiver()->incrementDeps();
}
}
}
@@ -462,42 +476,29 @@ void FxMixer::prepareMasterMix()
void FxMixer::addChannelLeaf( FxChannel * ch, sampleFrame * buf )
{
// if we're muted or this channel is seen already, discount it
if( ch->m_queued )
{
return;
}
foreach( FxRoute * senderRoute, ch->m_receives )
{
addChannelLeaf( senderRoute->sender(), buf );
}
// add this channel to job list
ch->m_queued = true;
MixerWorkerThread::addJob( ch );
}
void FxMixer::masterMix( sampleFrame * _buf )
{
const int fpp = engine::mixer()->framesPerPeriod();
// recursively loop through channel dependency chain
// and add all channels to job list that have no dependencies
// when the channel completes it will check its parent to see if it needs
// to be processed.
//m_sendsMutex.lock();
// add the channels that have no dependencies (no incoming senders, ie. no receives)
// to the jobqueue. The channels that have receives get added when their senders get processed, which
// is detected by dependency counting.
// also instantly add all muted channels as they don't need to care about their senders, and can just increment the deps of
// their recipients right away.
MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic );
addChannelLeaf( m_fxChannels[0], _buf );
foreach( FxChannel * ch, m_fxChannels )
{
ch->m_muted = ch->m_muteModel.value();
if( ch->m_receives.size() == 0 || ch->m_muted )
{
ch->m_queued = true;
MixerWorkerThread::addJob( ch );
}
}
while( m_fxChannels[0]->state() != ThreadableJob::Done )
{
MixerWorkerThread::startAndWaitForJobs();
}
//m_sendsMutex.unlock();
const float v = m_fxChannels[0]->m_volumeModel.value();
MixHelpers::addSanitizedMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp );