From c92774af27948a7065a68d1be5e5e3624dc87d00 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 15 Nov 2014 18:51:49 +0200 Subject: [PATCH 1/3] 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. --- include/FxMixer.h | 8 ++-- src/core/FxMixer.cpp | 101 ++++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index 5c33e6fa8..c473e07d9 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -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; diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index df05b1e6a..feb0f20fd 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -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 ) // 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 ); From ca06a10a4292f95c8922f4b089dbf7756078078c Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 15 Nov 2014 21:11:40 +0200 Subject: [PATCH 2/3] Fix segfault when enabling/disabling sends while playing --- src/core/FxMixer.cpp | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index feb0f20fd..2edcc4565 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -394,11 +394,9 @@ void FxMixer::deleteChannelSend( FxRoute * route ) bool FxMixer::isInfiniteLoop( fx_ch_t sendFrom, fx_ch_t sendTo ) { if( sendFrom == sendTo ) return true; - //m_sendsMutex.lock(); FxChannel * from = m_fxChannels[sendFrom]; FxChannel * to = m_fxChannels[sendTo]; bool b = checkInfiniteLoop( from, to ); - //m_sendsMutex.unlock(); return b; } @@ -480,26 +478,30 @@ void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::mixer()->framesPerPeriod(); - // 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 ); - foreach( FxChannel * ch, m_fxChannels ) + if( m_sendsMutex.tryLock() ) { - ch->m_muted = ch->m_muteModel.value(); - if( ch->m_receives.size() == 0 || ch->m_muted ) + // 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 ); + foreach( FxChannel * ch, m_fxChannels ) { - ch->m_queued = true; - MixerWorkerThread::addJob( ch ); + 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(); } - while( m_fxChannels[0]->state() != ThreadableJob::Done ) - { - MixerWorkerThread::startAndWaitForJobs(); - } - + const float v = m_fxChannels[0]->m_volumeModel.value(); MixHelpers::addSanitizedMultiplied( _buf, m_fxChannels[0]->m_buffer, v, fpp ); From 8ef10f4f8198c92b8e518821477dd3ffe063212c Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 16 Nov 2014 13:46:54 +0200 Subject: [PATCH 3/3] More updates to FxMixer - better handling of muted channels --- include/FxMixer.h | 1 + include/ThreadableJob.h | 5 +++++ src/core/FxMixer.cpp | 26 ++++++++++++++++++-------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/include/FxMixer.h b/include/FxMixer.h index c473e07d9..88b5a0997 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -69,6 +69,7 @@ class FxChannel : public ThreadableJob QAtomicInt m_dependenciesMet; void incrementDeps(); + void processed(); private: virtual void doProcessing( sampleFrame * _working_buffer ); diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h index 0a7655c51..c3da4c547 100644 --- a/include/ThreadableJob.h +++ b/include/ThreadableJob.h @@ -61,6 +61,11 @@ public: { m_state = Queued; } + + inline void done() + { + m_state = Done; + } void process( sampleFrame* workingBuffer = NULL ) { diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 2edcc4565..a83e84da8 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -85,6 +85,17 @@ FxChannel::~FxChannel() } +inline void FxChannel::processed() +{ + foreach( FxRoute * receiverRoute, m_sends ) + { + if( receiverRoute->receiver()->m_muted == false ) + { + receiverRoute->receiver()->incrementDeps(); + } + } +} + void FxChannel::incrementDeps() { m_dependenciesMet.ref(); @@ -155,13 +166,7 @@ void FxChannel::doProcessing( sampleFrame * _buf ) } // increment dependency counter of all receivers - foreach( FxRoute * receiverRoute, m_sends ) - { - if( receiverRoute->receiver()->m_muteModel.value() == false ) - { - receiverRoute->receiver()->incrementDeps(); - } - } + processed(); } @@ -489,7 +494,12 @@ void FxMixer::masterMix( sampleFrame * _buf ) foreach( FxChannel * ch, m_fxChannels ) { ch->m_muted = ch->m_muteModel.value(); - if( ch->m_receives.size() == 0 || ch->m_muted ) + if( ch->m_muted ) // instantly "process" muted channels + { + ch->processed(); + ch->done(); + } + else if( ch->m_receives.size() == 0 ) { ch->m_queued = true; MixerWorkerThread::addJob( ch );