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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
Reference in New Issue
Block a user