diff --git a/CMakeLists.txt b/CMakeLists.txt index a216a0ada73..0c6a8686cf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ option(WANT_DEBUG_TSAN "Enable ThreadSanitizer" OFF) option(WANT_DEBUG_MSAN "Enable MemorySanitizer" OFF) option(WANT_DEBUG_UBSAN "Enable UndefinedBehaviorSanitizer" OFF) option(WANT_DEBUG_GPROF "Enable gprof profiler" OFF) +option(WANT_DEBUG_TRACY "Enable Tracy profiler" OFF) OPTION(BUNDLE_QT_TRANSLATIONS "Install Qt translation files for LMMS" OFF) option(WANT_DEBUG_CPACK "Show detailed logs for packaging commands" OFF) option(WANT_CPACK_TARBALL "Request CPack to create a tarball instead of an installer" OFF) @@ -709,7 +710,30 @@ if(WANT_DEBUG_GPROF) add_link_options(-pg) set(STATUS_GPROF "OK") else() - set(STATUS_GPROF "Disabled ${STATUS_GPROF}") + set(STATUS_GPROF "Disabled${STATUS_GPROF}") +endif() + +# Tracy profiler +if(WANT_DEBUG_TRACY) + include(FetchContent) + + FetchContent_Declare( + tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG 05cceee0df3b8d7c6fa87e9638af311dbabc63cb # v0.13.1 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(tracy) + + if(TARGET TracyClient) + set(LMMS_DEBUG_TRACY TRUE) + set(STATUS_TRACY "Enabled") + else() + set(STATUS_TRACY "Error fetching dependency") + endif() +else() + set(STATUS_TRACY "Disabled") endif() # add enabled sanitizers @@ -880,6 +904,7 @@ MESSAGE( "* Debug using UBSanitizer : ${STATUS_DEBUG_UBSAN}\n" "* Debug packaging commands : ${STATUS_DEBUG_CPACK}\n" "* Profile using GNU profiler : ${STATUS_GPROF}\n" +"* Profile using Tracy profiler : ${STATUS_TRACY}\n" "* Debug assertions : ${STATUS_ASSERTIONS}\n" "* Experimental Qt6 support : ${STATUS_QT6}\n" ) diff --git a/include/AudioEngineProfiler.h b/include/AudioEngineProfiler.h index 7cf7e0857ac..b99c8827bbc 100644 --- a/include/AudioEngineProfiler.h +++ b/include/AudioEngineProfiler.h @@ -29,6 +29,7 @@ #include #include +#include "lmmsconfig.h" #include "LmmsTypes.h" #include "MicroTimer.h" @@ -38,13 +39,10 @@ namespace lmms class AudioEngineProfiler { public: - AudioEngineProfiler(); + AudioEngineProfiler() = default; ~AudioEngineProfiler() = default; - void startPeriod() - { - m_periodTimer.reset(); - } + void startPeriod(); void finishPeriod( sample_rate_t sampleRate, f_cnt_t framesPerPeriod ); @@ -73,20 +71,22 @@ class AudioEngineProfiler class Probe { public: - Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type) - : m_profiler(profiler) - , m_type(type) - { - profiler.startDetail(type); - } - ~Probe() { m_profiler.finishDetail(m_type); } - Probe& operator=(const Probe&) = delete; + Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type); + ~Probe(); + Probe(const Probe&) = delete; Probe(Probe&&) = delete; + Probe& operator=(const Probe&) = delete; + Probe& operator=(Probe&&) = delete; private: - AudioEngineProfiler &m_profiler; + AudioEngineProfiler& m_profiler; const AudioEngineProfiler::DetailType m_type; + +#ifdef LMMS_DEBUG_TRACY + struct TracyZoneContext { std::uint32_t id; std::int32_t active; }; + [[maybe_unused]] TracyZoneContext m_context{}; +#endif }; private: @@ -97,7 +97,7 @@ class AudioEngineProfiler } MicroTimer m_periodTimer; - std::atomic m_cpuLoad; + std::atomic m_cpuLoad = 0; QFile m_outputFile; // Use arrays to avoid dynamic allocations in realtime code diff --git a/include/AudioEngineWorkerThread.h b/include/AudioEngineWorkerThread.h index 60a5ef8104d..cff29e7184e 100644 --- a/include/AudioEngineWorkerThread.h +++ b/include/AudioEngineWorkerThread.h @@ -28,8 +28,10 @@ #include #include +#include +#include -class QWaitCondition; +#include "lmmsconfig.h" namespace lmms { @@ -113,7 +115,14 @@ class AudioEngineWorkerThread : public QThread void run() override; static JobQueue globalJobQueue; - static QWaitCondition * queueReadyWaitCond; + +#ifdef LMMS_DEBUG_TRACY + // Tracy uses a std::mutex wrapper, so it requires std::condition_variable_any + static inline std::optional queueReadyWaitCond{}; +#else + static inline std::optional queueReadyWaitCond{}; +#endif + static QList workerThreads; volatile bool m_quit; diff --git a/include/InstrumentSoundShaping.h b/include/InstrumentSoundShaping.h index f1ae9ecf2cf..f4abef26f7c 100644 --- a/include/InstrumentSoundShaping.h +++ b/include/InstrumentSoundShaping.h @@ -69,6 +69,7 @@ class InstrumentSoundShaping : public Model, public JournallingObject f_cnt_t envFrames( const bool _only_vol = false ) const; f_cnt_t releaseFrames() const; + // TODO: Remove? (dead code) float volumeLevel( NotePlayHandle * _n, const f_cnt_t _frame ); diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 80b44c3a948..c748feaa45d 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -172,7 +172,7 @@ class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note return m_totalFramesPlayed; } - /*! Returns volume level at given frame (envelope/LFO) */ + /*! Returns volume level at given frame (envelope/LFO) TODO: Remove? (dead code) */ float volumeLevel( const f_cnt_t frame ); /*! Returns instrument track which is being played by this handle (const version) */ diff --git a/include/Plugin.h b/include/Plugin.h index 100e9f658eb..dcbac46c99a 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -27,6 +27,7 @@ #include #include +#include #include "JournallingObject.h" #include "Model.h" @@ -244,6 +245,9 @@ class LMMS_EXPORT Plugin : public Model, public JournallingObject //! Return display-name out of sub plugin or descriptor QString displayName() const override; + //! Same as @ref displayName, but as a cached UTF-8 string + const std::string& displayNameUtf8() const; + //! Return logo out of sub plugin or descriptor const PixmapLoader *logo() const; @@ -303,6 +307,9 @@ class LMMS_EXPORT Plugin : public Model, public JournallingObject Descriptor::SubPluginFeatures::Key m_key; + //! Cached UTF-8 display name + mutable std::string m_displayName; + // pointer to instantiation-function in plugin using InstantiationHook = Plugin* (*)(Model*, void*); } ; diff --git a/include/Track.h b/include/Track.h index 92cb38f26e6..6c00c086d1d 100644 --- a/include/Track.h +++ b/include/Track.h @@ -153,7 +153,7 @@ class LMMS_EXPORT Track : public Model, public JournallingObject // name-stuff virtual const QString & name() const { - return m_name; + return m_nameQString; } QString displayName() const override @@ -161,6 +161,11 @@ class LMMS_EXPORT Track : public Model, public JournallingObject return name(); } + const std::string& displayNameUtf8() const + { + return m_name; + } + using Model::dataChanged; inline int getHeight() @@ -214,7 +219,8 @@ public slots: private: TrackContainer* m_trackContainer; Type m_type; - QString m_name; + std::string m_name; + QString m_nameQString; int m_height; protected: diff --git a/include/TracyProfiling.h b/include/TracyProfiling.h new file mode 100644 index 00000000000..511c4d7140c --- /dev/null +++ b/include/TracyProfiling.h @@ -0,0 +1,240 @@ +/* + * TracyProfiling.h - Tracy profiling for LMMS + * + * Copyright (c) 2026 Dalton Messmer + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LMMS_TRACY_PROFILING_H +#define LMMS_TRACY_PROFILING_H + +#include "lmmsconfig.h" + +#ifdef LMMS_DEBUG_TRACY + +#ifndef TRACY_ENABLE +#define TRACY_ENABLE +#endif +#include +#include + +#else + +// Define no-op Tracy macros (copied from tracy/Tracy.hpp) + +#define TracyNoop + +#define ZoneNamed(x,y) +#define ZoneNamedN(x,y,z) +#define ZoneNamedC(x,y,z) +#define ZoneNamedNC(x,y,z,w) + +#define ZoneTransient(x,y) +#define ZoneTransientN(x,y,z) + +#define ZoneScoped +#define ZoneScopedN(x) +#define ZoneScopedC(x) +#define ZoneScopedNC(x,y) + +#define ZoneText(x,y) +#define ZoneTextV(x,y,z) +#define ZoneTextF(x,...) +#define ZoneTextVF(x,y,...) +#define ZoneName(x,y) +#define ZoneNameV(x,y,z) +#define ZoneNameF(x,...) +#define ZoneNameVF(x,y,...) +#define ZoneColor(x) +#define ZoneColorV(x,y) +#define ZoneValue(x) +#define ZoneValueV(x,y) +#define ZoneIsActive false +#define ZoneIsActiveV(x) false + +#define FrameMark +#define FrameMarkNamed(x) +#define FrameMarkStart(x) +#define FrameMarkEnd(x) + +#define FrameImage(x,y,z,w,a) + +#define TracyLockable( type, varname ) type varname +#define TracyLockableN( type, varname, desc ) type varname +#define TracySharedLockable( type, varname ) type varname +#define TracySharedLockableN( type, varname, desc ) type varname +#define LockableBase( type ) type +#define SharedLockableBase( type ) type +#define LockMark(x) (void)x +#define LockableName(x,y,z) + +#define TracyPlot(x,y) +#define TracyPlotConfig(x,y,z,w,a) + +#define TracyMessage(x,y) +#define TracyMessageL(x) +#define TracyMessageC(x,y,z) +#define TracyMessageLC(x,y) +#define TracyAppInfo(x,y) + +#define TracyAlloc(x,y) +#define TracyFree(x) +#define TracyMemoryDiscard(x) +#define TracySecureAlloc(x,y) +#define TracySecureFree(x) +#define TracySecureMemoryDiscard(x) + +#define TracyAllocN(x,y,z) +#define TracyFreeN(x,y) +#define TracySecureAllocN(x,y,z) +#define TracySecureFreeN(x,y) + +#define ZoneNamedS(x,y,z) +#define ZoneNamedNS(x,y,z,w) +#define ZoneNamedCS(x,y,z,w) +#define ZoneNamedNCS(x,y,z,w,a) + +#define ZoneTransientS(x,y,z) +#define ZoneTransientNS(x,y,z,w) + +#define ZoneScopedS(x) +#define ZoneScopedNS(x,y) +#define ZoneScopedCS(x,y) +#define ZoneScopedNCS(x,y,z) + +#define TracyAllocS(x,y,z) +#define TracyFreeS(x,y) +#define TracyMemoryDiscardS(x,y) +#define TracySecureAllocS(x,y,z) +#define TracySecureFreeS(x,y) +#define TracySecureMemoryDiscardS(x,y) + +#define TracyAllocNS(x,y,z,w) +#define TracyFreeNS(x,y,z) +#define TracySecureAllocNS(x,y,z,w) +#define TracySecureFreeNS(x,y,z) + +#define TracyMessageS(x,y,z) +#define TracyMessageLS(x,y) +#define TracyMessageCS(x,y,z,w) +#define TracyMessageLCS(x,y,z) + +#define TracySourceCallbackRegister(x,y) +#define TracyParameterRegister(x,y) +#define TracyParameterSetup(x,y,z,w) +#define TracyIsConnected false +#define TracyIsStarted false +#define TracySetProgramName(x) + +#define TracyFiberEnter(x) +#define TracyFiberEnterHint(x,y) +#define TracyFiberLeave + +// Define no-op Tracy C API macros (copied from tracy/TracyC.h) + +typedef const void* TracyCZoneCtx; + +typedef const void* TracyCLockCtx; + +#define TracyCZone(c,x) +#define TracyCZoneN(c,x,y) +#define TracyCZoneC(c,x,y) +#define TracyCZoneNC(c,x,y,z) +#define TracyCZoneEnd(c) +#define TracyCZoneText(c,x,y) +#define TracyCZoneName(c,x,y) +#define TracyCZoneColor(c,x) +#define TracyCZoneValue(c,x) + +#define TracyCAlloc(x,y) +#define TracyCFree(x) +#define TracyCMemoryDiscard(x) +#define TracyCSecureAlloc(x,y) +#define TracyCSecureFree(x) +#define TracyCSecureMemoryDiscard(x) + +#define TracyCAllocN(x,y,z) +#define TracyCFreeN(x,y) +#define TracyCSecureAllocN(x,y,z) +#define TracyCSecureFreeN(x,y) + +#define TracyCFrameMark +#define TracyCFrameMarkNamed(x) +#define TracyCFrameMarkStart(x) +#define TracyCFrameMarkEnd(x) +#define TracyCFrameImage(x,y,z,w,a) + +#define TracyCPlot(x,y) +#define TracyCPlotF(x,y) +#define TracyCPlotI(x,y) +#define TracyCPlotConfig(x,y,z,w,a) + +#define TracyCMessage(x,y) +#define TracyCMessageL(x) +#define TracyCMessageC(x,y,z) +#define TracyCMessageLC(x,y) +#define TracyCAppInfo(x,y) + +#define TracyCZoneS(x,y,z) +#define TracyCZoneNS(x,y,z,w) +#define TracyCZoneCS(x,y,z,w) +#define TracyCZoneNCS(x,y,z,w,a) + +#define TracyCAllocS(x,y,z) +#define TracyCFreeS(x,y) +#define TracyCMemoryDiscardS(x,y) +#define TracyCSecureAllocS(x,y,z) +#define TracyCSecureFreeS(x,y) +#define TracyCSecureMemoryDiscardS(x,y) + +#define TracyCAllocNS(x,y,z,w) +#define TracyCFreeNS(x,y,z) +#define TracyCSecureAllocNS(x,y,z,w) +#define TracyCSecureFreeNS(x,y,z) + +#define TracyCMessageS(x,y,z) +#define TracyCMessageLS(x,y) +#define TracyCMessageCS(x,y,z,w) +#define TracyCMessageLCS(x,y,z) + +#define TracyCLockCtx(l) +#define TracyCLockAnnounce(l) +#define TracyCLockTerminate(l) +#define TracyCLockBeforeLock(l) +#define TracyCLockAfterLock(l) +#define TracyCLockAfterUnlock(l) +#define TracyCLockAfterTryLock(l,x) +#define TracyCLockMark(l) +#define TracyCLockCustomName(l,x,y) + +#define TracyCIsConnected 0 +#define TracyCIsStarted 0 + +#define TracyCBeginSamplingProfiling() 0 +#define TracyCEndSamplingProfiling() + +#ifdef TRACY_FIBERS +# define TracyCFiberEnter(fiber) +# define TracyCFiberLeave +#endif + +#endif + +#endif // LMMS_TRACY_PROFILING_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7378af9ce3..b47dd756626 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -177,6 +177,10 @@ if(LMMS_HAVE_LV2) list(APPEND EXTRA_LIBRARIES Lilv::lilv) endif() +if(LMMS_DEBUG_TRACY) + list(APPEND EXTRA_LIBRARIES TracyClient) +endif() + SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} diff --git a/src/core/AudioBuffer.cpp b/src/core/AudioBuffer.cpp index 553a0b559e2..388bf8902b6 100644 --- a/src/core/AudioBuffer.cpp +++ b/src/core/AudioBuffer.cpp @@ -27,6 +27,7 @@ #include "ConfigManager.h" #include "MixHelpers.h" #include "SharedMemory.h" +#include "TracyProfiling.h" namespace lmms { @@ -203,6 +204,7 @@ auto AudioBuffer::hasAnySignal() const -> bool void AudioBuffer::sanitize(const ChannelFlags& channels, ch_cnt_t upperBound) { + ZoneScoped; if (!MixHelpers::useNaNHandler()) { return; } bool changesMade = false; @@ -225,12 +227,14 @@ void AudioBuffer::sanitize(const ChannelFlags& channels, ch_cnt_t upperBound) if (changesMade && hasInterleavedBuffer() && (channels[0] || channels[1])) { // Keep the temporary interleaved buffer in sync + ZoneScopedN("Copy to interleaved buffer"); toInterleaved(groupBuffers(0), interleavedBuffer()); } } void AudioBuffer::sanitizeAll() { + ZoneScoped; if (!MixHelpers::useNaNHandler()) { return; } bool changesMade = false; @@ -247,12 +251,15 @@ void AudioBuffer::sanitizeAll() if (changesMade && hasInterleavedBuffer()) { // Keep the temporary interleaved buffer in sync + ZoneScopedN("Copy to interleaved buffer"); toInterleaved(groupBuffers(0), interleavedBuffer()); } } auto AudioBuffer::updateSilenceFlags(const ChannelFlags& channels, ch_cnt_t upperBound) -> bool { + ZoneScoped; + assert(upperBound <= MaxChannelsPerAudioBuffer); // Invariant: Any channel bits at or above `totalChannels()` must be marked silent @@ -290,6 +297,8 @@ auto AudioBuffer::updateSilenceFlags(const ChannelFlags& channels, ch_cnt_t uppe auto AudioBuffer::updateAllSilenceFlags() -> bool { + ZoneScoped; + // Invariant: Any channel bits at or above `totalChannels()` must be marked silent assert((~m_silenceFlags & createMask(totalChannels())).none()); @@ -317,6 +326,8 @@ auto AudioBuffer::updateAllSilenceFlags() -> bool void AudioBuffer::silenceChannels(const ChannelFlags& channels, ch_cnt_t upperBound) { + ZoneScoped; + auto needSilenced = ~m_silenceFlags; needSilenced &= channels; @@ -332,6 +343,7 @@ void AudioBuffer::silenceChannels(const ChannelFlags& channels, ch_cnt_t upperBo if (hasInterleavedBuffer() && (needSilenced[0] || needSilenced[1])) { // Keep the temporary interleaved buffer in sync + ZoneScopedN("Copy to interleaved buffer"); toInterleaved(groupBuffers(0), interleavedBuffer()); } @@ -340,9 +352,10 @@ void AudioBuffer::silenceChannels(const ChannelFlags& channels, ch_cnt_t upperBo void AudioBuffer::silenceAllChannels() { - std::ranges::fill(m_sourceBuffer, 0); - std::ranges::fill(m_interleavedBuffer, 0); + ZoneScoped; + std::ranges::fill(m_sourceBuffer, 0.f); + std::ranges::fill(m_interleavedBuffer, 0.f); m_silenceFlags.set(); } diff --git a/src/core/AudioBusHandle.cpp b/src/core/AudioBusHandle.cpp index 11343249c34..1798cf2c12e 100644 --- a/src/core/AudioBusHandle.cpp +++ b/src/core/AudioBusHandle.cpp @@ -33,6 +33,7 @@ #include "Mixer.h" #include "Engine.h" #include "MixHelpers.h" +#include "TracyProfiling.h" namespace lmms { @@ -109,6 +110,7 @@ bool AudioBusHandle::processEffects() void AudioBusHandle::doProcessing() { + ZoneScopedN("AudioBusHandle::doProcessing"); if (m_mutedModel && m_mutedModel->value()) { return; @@ -122,6 +124,7 @@ void AudioBusHandle::doProcessing() //qDebug( "Playhandles: %d", m_playHandles.size() ); for (PlayHandle* ph : m_playHandles) // now we mix all playhandle buffers into our internal buffer { + ZoneScopedN("PlayHandle loop"); if (ph->buffer()) { if (ph->usesBuffer() @@ -140,6 +143,8 @@ void AudioBusHandle::doProcessing() if (m_bufferUsage) { + ZoneScopedN("Processing"); + // PlayHandle buffers were written to the temporary interleaved buffer auto buffer = m_buffer.interleavedBuffer(); @@ -147,6 +152,7 @@ void AudioBusHandle::doProcessing() // has both vol and pan models if (m_volumeModel && m_panningModel) { + ZoneScopedN("Vol & Pan"); ValueBuffer* volBuf = m_volumeModel->valueBuffer(); ValueBuffer* panBuf = m_panningModel->valueBuffer(); @@ -204,6 +210,7 @@ void AudioBusHandle::doProcessing() // has vol model only else if (m_volumeModel) { + ZoneScopedN("Vol"); ValueBuffer* volBuf = m_volumeModel->valueBuffer(); if (volBuf) diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index f243243ba16..c4f8a4dcc36 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -24,18 +24,19 @@ #include "AudioEngine.h" -#include "MixHelpers.h" - #include "lmmsconfig.h" -#include "AudioEngineWorkerThread.h" #include "AudioBusHandle.h" +#include "AudioEngineWorkerThread.h" +#include "BufferManager.h" +#include "ConfigManager.h" +#include "EnvelopeAndLfoParameters.h" #include "Hardware.h" #include "Mixer.h" -#include "Song.h" -#include "EnvelopeAndLfoParameters.h" +#include "MixHelpers.h" #include "NotePlayHandle.h" -#include "ConfigManager.h" +#include "Song.h" +#include "TracyProfiling.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" @@ -58,8 +59,6 @@ #include "MidiApple.h" #include "MidiDummy.h" -#include "BufferManager.h" - namespace lmms { @@ -207,26 +206,30 @@ void AudioEngine::renderStageNoteSetup() clearInternal(); } - // remove all play-handles that have to be deleted and delete - // them if they still exist... - // maybe this algorithm could be optimized... - ConstPlayHandleList::Iterator it_rem = m_playHandlesToRemove.begin(); - while( it_rem != m_playHandlesToRemove.end() ) { - PlayHandleList::Iterator it = std::find( m_playHandles.begin(), m_playHandles.end(), *it_rem ); + ZoneScopedN("Remove PlayHandles"); - if( it != m_playHandles.end() ) + // remove all play-handles that have to be deleted and delete + // them if they still exist... + // maybe this algorithm could be optimized... + ConstPlayHandleList::Iterator it_rem = m_playHandlesToRemove.begin(); + while( it_rem != m_playHandlesToRemove.end() ) { - (*it)->audioBusHandle()->removePlayHandle(*it); - if((*it)->type() == PlayHandle::Type::NotePlayHandle) + PlayHandleList::Iterator it = std::find( m_playHandles.begin(), m_playHandles.end(), *it_rem ); + + if( it != m_playHandles.end() ) { - NotePlayHandleManager::release((NotePlayHandle*)*it); + (*it)->audioBusHandle()->removePlayHandle(*it); + if((*it)->type() == PlayHandle::Type::NotePlayHandle) + { + NotePlayHandleManager::release((NotePlayHandle*)*it); + } + else delete *it; + m_playHandles.erase(it); } - else delete *it; - m_playHandles.erase(it); - } - it_rem = m_playHandlesToRemove.erase( it_rem ); + it_rem = m_playHandlesToRemove.erase( it_rem ); + } } swapBuffers(); @@ -254,7 +257,10 @@ void AudioEngine::renderStageInstruments() { AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments); - AudioEngineWorkerThread::fillJobQueue(m_playHandles); + { + ZoneScopedN("Fill job queue"); + AudioEngineWorkerThread::fillJobQueue(m_playHandles); + } AudioEngineWorkerThread::startAndWaitForJobs(); } @@ -265,32 +271,39 @@ void AudioEngine::renderStageEffects() AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects); // STAGE 2: process effects of all instrument- and sampletracks - AudioEngineWorkerThread::fillJobQueue(m_audioBusHandles); + { + ZoneScopedN("Fill job queue"); + AudioEngineWorkerThread::fillJobQueue(m_audioBusHandles); + } AudioEngineWorkerThread::startAndWaitForJobs(); - // removed all play handles which are done - for( PlayHandleList::Iterator it = m_playHandles.begin(); - it != m_playHandles.end(); ) { - if( ( *it )->affinityMatters() && - ( *it )->affinity() != QThread::currentThread() ) - { - ++it; - continue; - } - if( ( *it )->isFinished() ) + ZoneScopedN("Remove finished PlayHandles"); + + // removed all play handles which are done + for (PlayHandleList::Iterator it = m_playHandles.begin(); + it != m_playHandles.end(); ) { - (*it)->audioBusHandle()->removePlayHandle(*it); - if((*it)->type() == PlayHandle::Type::NotePlayHandle) + if( ( *it )->affinityMatters() && + ( *it )->affinity() != QThread::currentThread() ) { - NotePlayHandleManager::release((NotePlayHandle*)*it); + ++it; + continue; + } + if( ( *it )->isFinished() ) + { + (*it)->audioBusHandle()->removePlayHandle(*it); + if((*it)->type() == PlayHandle::Type::NotePlayHandle) + { + NotePlayHandleManager::release((NotePlayHandle*)*it); + } + else delete *it; + it = m_playHandles.erase(it); + } + else + { + ++it; } - else delete *it; - it = m_playHandles.erase(it); - } - else - { - ++it; } } } @@ -337,6 +350,8 @@ std::span AudioEngine::renderNextPeriod() void AudioEngine::swapBuffers() { + ZoneScoped; + m_inputBufferWrite = (m_inputBufferWrite + 1) % 2; m_inputBufferRead = (m_inputBufferRead + 1) % 2; m_inputBufferFrames[m_inputBufferWrite] = 0; @@ -371,6 +386,8 @@ void AudioEngine::clearNewPlayHandles() // all remaining notes etc. would be played until their end void AudioEngine::clearInternal() { + ZoneScoped; + // TODO: m_midiClient->noteOffAll(); for (auto ph : m_playHandles) { @@ -460,6 +477,8 @@ void AudioEngine::removeAudioBusHandle(AudioBusHandle* busHandle) bool AudioEngine::addPlayHandle( PlayHandle* handle ) { + ZoneScoped; + // Only add play handles if we have the CPU capacity to process them. // Instrument play handles are not added during playback, but when the // associated instrument is created, so add those unconditionally. @@ -482,6 +501,8 @@ bool AudioEngine::addPlayHandle( PlayHandle* handle ) void AudioEngine::removePlayHandle(PlayHandle * ph) { + ZoneScoped; + requestChangeInModel(); // check thread affinity as we must not delete play-handles // which were created in a thread different than the audio engine thread @@ -540,6 +561,8 @@ void AudioEngine::removePlayHandle(PlayHandle * ph) void AudioEngine::removePlayHandlesOfTypes(Track * track, PlayHandle::Types types) { + ZoneScoped; + requestChangeInModel(); PlayHandleList::Iterator it = m_playHandles.begin(); while( it != m_playHandles.end() ) diff --git a/src/core/AudioEngineProfiler.cpp b/src/core/AudioEngineProfiler.cpp index 735ed1f29b9..5b259009994 100644 --- a/src/core/AudioEngineProfiler.cpp +++ b/src/core/AudioEngineProfiler.cpp @@ -26,20 +26,25 @@ #include -namespace lmms -{ +#include "TracyProfiling.h" -AudioEngineProfiler::AudioEngineProfiler() : - m_periodTimer(), - m_cpuLoad( 0 ), - m_outputFile() -{ -} +namespace lmms { +namespace { +[[maybe_unused]] constexpr const char* PeriodName = "AudioEngine period"; +} // namespace + +void AudioEngineProfiler::startPeriod() +{ + m_periodTimer.reset(); + FrameMarkStart(PeriodName); +} void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, f_cnt_t framesPerPeriod ) { + FrameMarkEnd(PeriodName); + // Time taken to process all data and fill the audio buffer. const unsigned int periodElapsed = m_periodTimer.elapsed(); // Maximum time the processing can take before causing buffer underflow. Convert to us. @@ -65,8 +70,6 @@ void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, f_cnt_t frames } } - - void AudioEngineProfiler::setOutputFile( const QString& outputFile ) { m_outputFile.close(); @@ -74,4 +77,55 @@ void AudioEngineProfiler::setOutputFile( const QString& outputFile ) m_outputFile.open( QFile::WriteOnly | QFile::Truncate ); } +AudioEngineProfiler::Probe::Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type) + : m_profiler(profiler) + , m_type(type) +{ + profiler.startDetail(type); + +#ifdef LMMS_DEBUG_TRACY + TracyCZone(context, true); + + switch (type) + { + case DetailType::NoteSetup: + TracyCZoneName(context, "Note Setup", sizeof("Note Setup")); + TracyCZoneColor(context, tracy::Color::SpringGreen); // #00ff7f + break; + case DetailType::Instruments: + TracyCZoneName(context, "Instruments", sizeof("Instruments")); + TracyCZoneColor(context, tracy::Color::Tomato); // #ff6347 + break; + case DetailType::Effects: + TracyCZoneName(context, "Effects", sizeof("Effects")); + TracyCZoneColor(context, tracy::Color::DarkTurquoise); // #00ced1 + break; + case DetailType::Mixing: + TracyCZoneName(context, "Mixing", sizeof("Mixing")); + TracyCZoneColor(context, tracy::Color::Maroon); // #b03060 + break; + default: break; + } + + // Store the Tracy context + m_context.id = context.id; + m_context.active = context.active; +#endif +} + +AudioEngineProfiler::Probe::~Probe() +{ +#ifdef LMMS_DEBUG_TRACY + // Restore the Tracy context + auto context = TracyCZoneCtx { + .id = m_context.id, + .active = m_context.active + }; + + TracyCZoneEnd(context); +#endif + + m_profiler.finishDetail(m_type); +} + } // namespace lmms diff --git a/src/core/AudioEngineWorkerThread.cpp b/src/core/AudioEngineWorkerThread.cpp index c58b6bdd768..6b64d59fd97 100644 --- a/src/core/AudioEngineWorkerThread.cpp +++ b/src/core/AudioEngineWorkerThread.cpp @@ -25,19 +25,18 @@ #include "AudioEngineWorkerThread.h" #include -#include -#include +#include // IWYU pragma: keep +#include #include "AudioEngine.h" #include "Hardware.h" #include "ThreadableJob.h" - +#include "TracyProfiling.h" namespace lmms { AudioEngineWorkerThread::JobQueue AudioEngineWorkerThread::globalJobQueue; -QWaitCondition * AudioEngineWorkerThread::queueReadyWaitCond = nullptr; QList AudioEngineWorkerThread::workerThreads; // implementation of internal JobQueue @@ -110,9 +109,9 @@ AudioEngineWorkerThread::AudioEngineWorkerThread( AudioEngine* audioEngine ) : m_quit( false ) { // initialize global static data - if( queueReadyWaitCond == nullptr ) + if (!queueReadyWaitCond) { - queueReadyWaitCond = new QWaitCondition; + queueReadyWaitCond.emplace(); } // keep track of all instantiated worker threads - this is used for @@ -145,12 +144,22 @@ void AudioEngineWorkerThread::quit() void AudioEngineWorkerThread::startAndWaitForJobs() { - queueReadyWaitCond->wakeAll(); - // The last worker-thread is never started. Instead it's processed "inline" - // i.e. within the global AudioEngine thread. This way we can reduce latencies - // that otherwise would be caused by synchronizing with another thread. - globalJobQueue.run(); - globalJobQueue.wait(); + ZoneScoped; + { + ZoneScopedN("Notify all"); + queueReadyWaitCond->notify_all(); + } + { + // The last worker-thread is never started. Instead it's processed "inline" + // i.e. within the global AudioEngine thread. This way we can reduce latencies + // that otherwise would be caused by synchronizing with another thread. + ZoneScopedN("Run job queue"); + globalJobQueue.run(); + } + { + ZoneScopedN("Wait for job queue"); + globalJobQueue.wait(); + } } @@ -160,13 +169,23 @@ void AudioEngineWorkerThread::run() { disableDenormals(); - QMutex m; - while( m_quit == false ) +#ifdef LMMS_DEBUG_TRACY + static auto id = std::atomic{0}; + + // NOTE: The following is a memory leak, but it's recommended by Tracy's + // documentation for dynamic names + char* name = new char[16]; // NOLINT + std::snprintf(name, 16, "Audio %i", id.fetch_add(1)); + tracy::SetThreadNameWithHint(name, 1); +#endif + + TracyLockable(std::mutex, m); + while (m_quit == false) { - m.lock(); - queueReadyWaitCond->wait( &m ); + std::unique_lock lock{m}; + queueReadyWaitCond->wait(lock); + globalJobQueue.run(); - m.unlock(); } } diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 368a0ad89a4..9e6235c8aa1 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -33,6 +33,7 @@ #include "EffectControls.h" #include "EffectView.h" #include "SampleFrame.h" +#include "TracyProfiling.h" namespace lmms { @@ -94,8 +95,12 @@ void Effect::loadSettings( const QDomElement & _this ) bool Effect::processAudioBuffer(AudioBuffer& inOut) { + ZoneScopedC(0xafeeee); // #afeeee (tracy::Color::PaleTurquoise) + ZoneNameF("Effect::processAudioBuffer [%s]", displayNameUtf8().c_str()); + if (!isAwake()) { + ZoneScopedN("Sleeping"); if (!inOut.hasSignal(0b11)) { // Sleeping plugins need to zero any track channels their output is routed to in order to @@ -113,11 +118,16 @@ bool Effect::processAudioBuffer(AudioBuffer& inOut) if (!isProcessingAudio()) { // Plugin is awake but not processing audio + ZoneScopedN("Bypassed"); processBypassedImpl(); return false; } - const auto status = processImpl(inOut.interleavedBuffer().asSampleFrames().data(), inOut.frames()); + ProcessStatus status; + { + ZoneScopedN("Process"); + status = processImpl(inOut.interleavedBuffer().asSampleFrames().data(), inOut.frames()); + } // Copy interleaved plugin output to planar toPlanar(inOut.interleavedBuffer(), inOut.groupBuffers(0)); diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index 264b61781f2..1e1c6d9abbb 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -32,6 +32,7 @@ #include "Effect.h" #include "DummyEffect.h" #include "MixHelpers.h" +#include "TracyProfiling.h" namespace lmms { @@ -187,6 +188,8 @@ void EffectChain::moveUp( Effect * _effect ) bool EffectChain::processAudioBuffer(AudioBuffer& buffer) { + ZoneScopedN("EffectChain::processAudioBuffer"); + if( m_enabledModel.value() == false ) { return false; diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 4d6e0f04c68..77cfccf95f8 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -32,6 +32,7 @@ #include "Oscillator.h" #include "PathUtil.h" #include "Song.h" +#include "TracyProfiling.h" namespace lmms { @@ -257,10 +258,11 @@ void EnvelopeAndLfoParameters::updateLfoShapeData() -inline void EnvelopeAndLfoParameters::fillLfoLevel( float * _buf, +void EnvelopeAndLfoParameters::fillLfoLevel(float* _buf, f_cnt_t _frame, const f_cnt_t _frames ) { + ZoneScoped; if( m_lfoAmountIsZero || _frame <= m_lfoPredelayFrames ) { for( f_cnt_t offset = 0; offset < _frames; ++offset ) @@ -296,10 +298,12 @@ void EnvelopeAndLfoParameters::fillLevel( float * _buf, f_cnt_t _frame, const f_cnt_t _release_begin, const f_cnt_t _frames ) { - QMutexLocker m(&m_paramMutex); + ZoneScoped; + QMutexLocker m(&m_paramMutex); // TODO: Use hazard pointer? fillLfoLevel( _buf, _frame, _frames ); + const bool controlEnvAmount = m_controlEnvAmountModel.value(); for( f_cnt_t offset = 0; offset < _frames; ++offset, ++_buf, ++_frame ) { float env_level; @@ -326,9 +330,9 @@ void EnvelopeAndLfoParameters::fillLevel( float * _buf, f_cnt_t _frame, } // at this point, *_buf is LFO level - *_buf = m_controlEnvAmountModel.value() ? - env_level * ( 0.5f + *_buf ) : - env_level + *_buf; + *_buf = controlEnvAmount + ? env_level * (0.5f + *_buf) + : env_level + *_buf; } } diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index de9c6c1d046..a8491553f85 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -30,6 +30,7 @@ #include "InstrumentTrack.h" #include "lmms_math.h" #include "PresetPreviewPlayHandle.h" +#include "TracyProfiling.h" #include #include @@ -230,6 +231,8 @@ InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) { + ZoneScopedN("Process note stacking"); + const int base_note_key = _n->key(); const ChordTable & chord_table = ChordTable::getInstance(); // we add chord-subnotes to note if either note is a base-note and @@ -338,6 +341,8 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) : void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) { + ZoneScopedN("Process arpeggio"); + const int base_note_key = _n->key(); if( _n->origin() == NotePlayHandle::Origin::Arpeggio || _n->origin() == NotePlayHandle::Origin::NoteStacking || diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp index d6f5b4b29cf..767e0414369 100644 --- a/src/core/InstrumentPlayHandle.cpp +++ b/src/core/InstrumentPlayHandle.cpp @@ -22,12 +22,12 @@ * */ - #include "InstrumentPlayHandle.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "Engine.h" #include "AudioEngine.h" +#include "TracyProfiling.h" namespace lmms { @@ -42,7 +42,10 @@ InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTr void InstrumentPlayHandle::play(SampleFrame* working_buffer) { - InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack(); + InstrumentTrack* instrumentTrack = m_instrument->instrumentTrack(); + + ZoneScopedC(0xff7f50); // #ff7f50 (tracy::Color::Coral) + ZoneNameF("InstrumentPlayHandle::play [%s]", instrumentTrack->displayNameUtf8().c_str()); // ensure that all our nph's have been processed first auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true); @@ -50,6 +53,7 @@ void InstrumentPlayHandle::play(SampleFrame* working_buffer) bool nphsLeft; do { + ZoneScopedN("Process PlayHandles"); nphsLeft = false; for (const auto& handle : nphv) { @@ -62,7 +66,10 @@ void InstrumentPlayHandle::play(SampleFrame* working_buffer) } while (nphsLeft); - m_instrument->play(working_buffer); + { + ZoneScopedN("Instrument::play"); + m_instrument->play(working_buffer); + } // Process the audio buffer that the instrument has just worked on... const f_cnt_t frames = Engine::audioEngine()->framesPerPeriod(); diff --git a/src/core/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index b2cf98ae25e..5c28da40bcb 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -32,6 +32,7 @@ #include "Engine.h" #include "Instrument.h" #include "InstrumentTrack.h" +#include "TracyProfiling.h" namespace lmms { @@ -109,6 +110,8 @@ void InstrumentSoundShaping::processAudioBuffer( SampleFrame* buffer, const f_cnt_t frames, NotePlayHandle* n ) { + ZoneScopedN("Process sound shaping"); + const f_cnt_t envTotalFrames = n->totalFramesPlayed(); f_cnt_t envReleaseBegin = envTotalFrames - n->releaseFramesDone() + n->framesBeforeRelease(); @@ -131,6 +134,8 @@ void InstrumentSoundShaping::processAudioBuffer( SampleFrame* buffer, if( m_filterEnabledModel.value() ) { + ZoneScopedN("Process filter"); + QVarLengthArray cutBuffer(frames); QVarLengthArray resBuffer(frames); @@ -226,6 +231,8 @@ void InstrumentSoundShaping::processAudioBuffer( SampleFrame* buffer, if (volumeParameters.isUsed()) { + ZoneScopedN("Process volume envelope"); + QVarLengthArray volBuffer(frames); volumeParameters.fillLevel(volBuffer.data(), envTotalFrames, envReleaseBegin, frames); diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index f4f1d9cbbe1..e5ed507bfee 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -33,7 +33,7 @@ #include "ValueBuffer.h" #include "SampleFrame.h" - +#include "TracyProfiling.h" static bool s_NaNHandler; @@ -44,6 +44,8 @@ namespace lmms::MixHelpers namespace { +[[maybe_unused]] constexpr std::uint32_t TracyColor = 0xeedd82; // #eedd82 (tracy::Color::LightGoldenrod) + constexpr auto SilenceThreshold = 0.000001f; // -120 dBFS /*! \brief Function for applying MIXOP on all sample frames */ @@ -71,6 +73,7 @@ inline void run(SampleFrame* dst, const sample_t* srcLeft, const sample_t* srcRi bool isSilent( const SampleFrame* src, int frames ) { + ZoneScopedC(TracyColor); for( int i = 0; i < frames; ++i ) { if (std::abs(src[i][0]) >= SilenceThreshold || std::abs(src[i][1]) >= SilenceThreshold) @@ -84,7 +87,8 @@ bool isSilent( const SampleFrame* src, int frames ) bool isSilent(std::span buffer) { - return std::ranges::all_of(buffer, [&](const sample_t s) { return std::abs(s) < SilenceThreshold; }); + ZoneScopedC(TracyColor); + return std::ranges::all_of(buffer, [](const sample_t s) { return std::abs(s) < SilenceThreshold; }); } bool useNaNHandler() @@ -99,6 +103,7 @@ void setNaNHandler( bool use ) bool sanitize(std::span buffer) { + ZoneScopedC(TracyColor); if (!useNaNHandler()) { return false; } for (std::size_t f = 0; f < buffer.size(); ++f) @@ -134,12 +139,14 @@ struct AddOp void add( SampleFrame* dst, const SampleFrame* src, int frames ) { + ZoneScopedC(TracyColor); run<>( dst, src, frames, AddOp() ); } void add(PlanarBufferView dst, PlanarBufferView src) { + ZoneScopedC(TracyColor); assert(dst.channels() == src.channels()); assert(dst.frames() == src.frames()); @@ -172,6 +179,7 @@ struct AddMultipliedOp void addMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames ) { + ZoneScopedC(TracyColor); run<>( dst, src, frames, AddMultipliedOp(coeffSrc) ); } @@ -191,6 +199,7 @@ struct AddSwappedMultipliedOp void multiply(SampleFrame* dst, float coeff, int frames) { + ZoneScopedC(TracyColor); for (int i = 0; i < frames; ++i) { dst[i] *= coeff; @@ -199,12 +208,14 @@ void multiply(SampleFrame* dst, float coeff, int frames) void addSwappedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames ) { + ZoneScopedC(TracyColor); run<>( dst, src, frames, AddSwappedMultipliedOp(coeffSrc) ); } void addMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ) { + ZoneScopedC(TracyColor); for( int f = 0; f < frames; ++f ) { dst[f][0] += src[f][0] * coeffSrc * coeffSrcBuf->values()[f]; @@ -214,6 +225,7 @@ void addMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coef void addMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ) { + ZoneScopedC(TracyColor); for( int f = 0; f < frames; ++f ) { dst[f][0] += src[f][0] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f]; @@ -224,6 +236,7 @@ void addMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuff void addSanitizedMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ) { + ZoneScopedC(TracyColor); if ( !useNaNHandler() ) { addMultipliedByBuffer( dst, src, coeffSrc, coeffSrcBuf, @@ -240,6 +253,7 @@ void addSanitizedMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, f void addSanitizedMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ) { + ZoneScopedC(TracyColor); if ( !useNaNHandler() ) { addMultipliedByBuffers( dst, src, coeffSrcBuf1, coeffSrcBuf2, @@ -275,6 +289,7 @@ struct AddSanitizedMultipliedOp void addSanitizedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames ) { + ZoneScopedC(TracyColor); if ( !useNaNHandler() ) { addMultiplied( dst, src, coeffSrc, frames ); @@ -306,7 +321,7 @@ struct AddMultipliedStereoOp void addMultipliedStereo( SampleFrame* dst, const SampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames ) { - + ZoneScopedC(TracyColor); run<>( dst, src, frames, AddMultipliedStereoOp(coeffSrcLeft, coeffSrcRight) ); } @@ -334,6 +349,7 @@ struct MultiplyAndAddMultipliedOp void multiplyAndAddMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffDst, float coeffSrc, int frames ) { + ZoneScopedC(TracyColor); run<>( dst, src, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) ); } @@ -344,6 +360,7 @@ void multiplyAndAddMultipliedJoined( SampleFrame* dst, const sample_t* srcRight, float coeffDst, float coeffSrc, int frames ) { + ZoneScopedC(TracyColor); run<>( dst, srcLeft, srcRight, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) ); } diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 27109f4eedf..7dd75522f0e 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -34,6 +34,7 @@ #include "PatternStore.h" #include "SampleTrack.h" #include "TrackContainer.h" // For TrackContainer::TrackList typedef +#include "TracyProfiling.h" namespace lmms { @@ -83,8 +84,9 @@ MixerChannel::~MixerChannel() } -inline void MixerChannel::processed() +void MixerChannel::processed() { + ZoneScoped; for( const MixerRoute * receiverRoute : m_sends ) { if( receiverRoute->receiver()->m_muted == false ) @@ -96,6 +98,7 @@ inline void MixerChannel::processed() void MixerChannel::incrementDeps() { + ZoneScoped; const auto i = m_dependenciesMet++ + 1; if( i >= m_receives.size() && ! m_queued ) { @@ -161,18 +164,23 @@ void MixerChannel::unmuteReceiverForSolo() void MixerChannel::doProcessing() { + ZoneScopedN("MixerChannel::doProcessing"); + const f_cnt_t fpp = Engine::audioEngine()->framesPerPeriod(); if( m_muted == false ) { + ZoneScopedN("Processing"); for( MixerRoute * senderRoute : m_receives ) { + ZoneScoped; MixerChannel * sender = senderRoute->sender(); FloatModel * sendModel = senderRoute->amount(); if( ! sendModel ) qFatal( "Error: no send model found from %d to %d", senderRoute->senderIndex(), m_channelIndex ); if (sender->m_buffer.hasAnySignal() || sender->m_stillRunning) { + ZoneScopedN("Process sender route"); auto buffer = m_buffer.interleavedBuffer().asSampleFrames(); // figure out if we're getting sample-exact input @@ -639,6 +647,8 @@ FloatModel * Mixer::channelSendModel( mix_ch_t fromChannel, mix_ch_t toChannel ) void Mixer::mixToChannel(const AudioBuffer& buffer, mix_ch_t dest) { + ZoneScoped; + const auto channel = m_mixerChannels[dest]; if (!channel->m_muteModel.value()) { @@ -658,6 +668,7 @@ void Mixer::mixToChannel(const AudioBuffer& buffer, mix_ch_t dest) void Mixer::prepareMasterMix() { + ZoneScoped; m_mixerChannels[0]->m_buffer.silenceAllChannels(); } @@ -665,77 +676,91 @@ void Mixer::prepareMasterMix() void Mixer::masterMix( SampleFrame* _buf ) { + ZoneScoped; const int fpp = Engine::audioEngine()->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. - AudioEngineWorkerThread::resetJobQueue( AudioEngineWorkerThread::JobQueue::OperationMode::Dynamic ); - for( MixerChannel * ch : m_mixerChannels ) - { - ch->m_muted = ch->m_muteModel.value(); - if( ch->m_muted ) // instantly "process" muted channels - { - ch->processed(); - ch->done(); - } - else if( ch->m_receives.size() == 0 ) + { + ZoneScopedN("Add independent channels to job queue"); + + // 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. + AudioEngineWorkerThread::resetJobQueue( AudioEngineWorkerThread::JobQueue::OperationMode::Dynamic ); + for( MixerChannel * ch : m_mixerChannels ) { - ch->m_queued = true; - AudioEngineWorkerThread::addJob( ch ); + ZoneScoped; + ch->m_muted = ch->m_muteModel.value(); + if( ch->m_muted ) // instantly "process" muted channels + { + ch->processed(); + ch->done(); + } + else if( ch->m_receives.size() == 0 ) + { + ch->m_queued = true; + AudioEngineWorkerThread::addJob( ch ); + } } } - while (m_mixerChannels[0]->state() != ThreadableJob::ProcessingState::Done) + { - bool found = false; - for( MixerChannel * ch : m_mixerChannels ) + ZoneScopedN("Process channels"); + + while (m_mixerChannels[0]->state() != ThreadableJob::ProcessingState::Done) { - const auto s = ch->state(); - if (s == ThreadableJob::ProcessingState::Queued - || s == ThreadableJob::ProcessingState::InProgress) + bool found = false; + for( MixerChannel * ch : m_mixerChannels ) + { + const auto s = ch->state(); + if (s == ThreadableJob::ProcessingState::Queued + || s == ThreadableJob::ProcessingState::InProgress) + { + found = true; + break; + } + } + if( !found ) { - found = true; break; } + AudioEngineWorkerThread::startAndWaitForJobs(); } - if( !found ) - { - break; - } - AudioEngineWorkerThread::startAndWaitForJobs(); } - auto buffer = m_mixerChannels[0]->m_buffer.interleavedBuffer().asSampleFrames(); + { + ZoneScopedN("Post-processing"); + auto buffer = m_mixerChannels[0]->m_buffer.interleavedBuffer().asSampleFrames(); - // handle sample-exact data in master volume fader - ValueBuffer * volBuf = m_mixerChannels[0]->m_volumeModel.valueBuffer(); + // handle sample-exact data in master volume fader + ValueBuffer * volBuf = m_mixerChannels[0]->m_volumeModel.valueBuffer(); - if( volBuf ) - { - for( int f = 0; f < fpp; f++ ) + if( volBuf ) { - buffer[f][0] *= volBuf->values()[f]; - buffer[f][1] *= volBuf->values()[f]; + for( int f = 0; f < fpp; f++ ) + { + buffer[f][0] *= volBuf->values()[f]; + buffer[f][1] *= volBuf->values()[f]; + } } - } - const float v = volBuf - ? 1.0f - : m_mixerChannels[0]->m_volumeModel.value(); - MixHelpers::addSanitizedMultiplied(_buf, buffer.data(), v, fpp); + const float v = volBuf + ? 1.0f + : m_mixerChannels[0]->m_volumeModel.value(); + MixHelpers::addSanitizedMultiplied(_buf, buffer.data(), v, fpp); - // clear all channel buffers and - // reset channel process state - for( int i = 0; i < numChannels(); ++i) - { - m_mixerChannels[i]->m_buffer.silenceAllChannels(); - m_mixerChannels[i]->reset(); - m_mixerChannels[i]->m_queued = false; - m_mixerChannels[i]->m_dependenciesMet = 0; + // clear all channel buffers and + // reset channel process state + for( int i = 0; i < numChannels(); ++i) + { + m_mixerChannels[i]->m_buffer.silenceAllChannels(); + m_mixerChannels[i]->reset(); + m_mixerChannels[i]->m_queued = false; + m_mixerChannels[i]->m_dependenciesMet = 0; + } } } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 9c153636153..2fa9d3306db 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -31,6 +31,7 @@ #include "InstrumentTrack.h" #include "Instrument.h" #include "Song.h" +#include "TracyProfiling.h" #include "lmms_math.h" namespace lmms @@ -185,6 +186,9 @@ int NotePlayHandle::midiKey() const void NotePlayHandle::play( SampleFrame* _working_buffer ) { + ZoneScopedC(0xff7f50); // #ff7f50 (tracy::Color::Coral) + ZoneNameF("NotePlayHandle::play [%s]", m_instrumentTrack->displayNameUtf8().c_str()); + if (m_muted) { return; @@ -223,6 +227,7 @@ void NotePlayHandle::play( SampleFrame* _working_buffer ) && m_totalFramesPlayed == 0 && !m_hasMidiNote && ( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) ) { + ZoneScopedN("Process NoteOn event"); m_hasMidiNote = true; const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); @@ -368,6 +373,7 @@ bool NotePlayHandle::isFromTrack( const Track * _track ) const void NotePlayHandle::noteOff( const f_cnt_t _s ) { + ZoneScoped; if( m_released ) { return; @@ -478,6 +484,7 @@ int NotePlayHandle::index() const ConstNotePlayHandleList NotePlayHandle::nphsOfInstrumentTrack( const InstrumentTrack * _it, bool _all_ph ) { + ZoneScoped; const PlayHandleList & playHandles = Engine::audioEngine()->playHandles(); ConstNotePlayHandleList cnphv; @@ -519,6 +526,7 @@ bool NotePlayHandle::operator==( const NotePlayHandle & _nph ) const void NotePlayHandle::updateFrequency() { + ZoneScoped; int masterPitch = m_instrumentTrack->m_useMasterPitchModel.value() ? Engine::getSong()->masterPitch() : 0; int baseNote = m_instrumentTrack->baseNoteModel()->value(); float detune = m_baseDetuning->value(); @@ -632,11 +640,23 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac int midiEventChannel, NotePlayHandle::Origin origin ) { - // TODO: use some lockless data structures - s_mutex.lockForWrite(); - if (s_availableIndex < 0) { extend(NPH_CACHE_INCREMENT); } - NotePlayHandle * nph = s_available[s_availableIndex--]; - s_mutex.unlock(); + ZoneScoped; + + struct MutexWrapper + { + void lock() { s_mutex.lockForWrite(); } + void unlock() { s_mutex.unlock(); } + }; + + NotePlayHandle* nph; + { + // TODO: use some lockless data structures + TracyLockable(MutexWrapper, mutex); + std::lock_guard lock{mutex}; + + if (s_availableIndex < 0) { extend(NPH_CACHE_INCREMENT); } + nph = s_available[s_availableIndex--]; + } new( (void*)nph ) NotePlayHandle( instrumentTrack, offset, frames, noteToPlay, parent, midiEventChannel, origin ); return nph; @@ -645,10 +665,22 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac void NotePlayHandleManager::release( NotePlayHandle * nph ) { + ZoneScoped; + + struct MutexWrapper + { + void lock() { s_mutex.lockForRead(); } + void unlock() { s_mutex.unlock(); } + }; + nph->NotePlayHandle::~NotePlayHandle(); - s_mutex.lockForRead(); - s_available[++s_availableIndex] = nph; - s_mutex.unlock(); + + { + TracyLockable(MutexWrapper, mutex); + std::lock_guard lock{mutex}; + + s_available[++s_availableIndex] = nph; + } } diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp index 134fcd31100..65e807d5410 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -26,6 +26,7 @@ #include "AudioEngine.h" #include "BufferManager.h" #include "Engine.h" +#include "TracyProfiling.h" #include @@ -52,6 +53,8 @@ PlayHandle::~PlayHandle() void PlayHandle::doProcessing() { + ZoneScopedN("PlayHandle::doProcessing"); + if( m_usesBuffer ) { m_bufferReleased = false; diff --git a/src/core/Plugin.cpp b/src/core/Plugin.cpp index fe34c5c311e..5ab71b67838 100644 --- a/src/core/Plugin.cpp +++ b/src/core/Plugin.cpp @@ -104,6 +104,15 @@ QString Plugin::displayName() const +const std::string& Plugin::displayNameUtf8() const +{ + m_displayName = displayName().toStdString(); + return m_displayName; +} + + + + const PixmapLoader* Plugin::logo() const { return (m_descriptor->subPluginFeatures && m_key.isValid()) diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index a5aa5000913..0b2445f2105 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -28,6 +28,7 @@ #include "ProjectRenderer.h" #include "Song.h" #include "PerfLog.h" +#include "TracyProfiling.h" #include "AudioFileWave.h" #include "AudioFileOgg.h" @@ -153,6 +154,7 @@ void ProjectRenderer::startProcessing() void ProjectRenderer::run() { + ZoneScoped; PerfLogTimer perfLog("Project Render"); Engine::getSong()->startExport(); diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 12417d0c4ba..0414f49d49f 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -29,6 +29,7 @@ #include "PatternTrack.h" #include "SampleClip.h" #include "SampleTrack.h" +#include "TracyProfiling.h" namespace lmms { @@ -79,6 +80,9 @@ SamplePlayHandle::~SamplePlayHandle() void SamplePlayHandle::play( SampleFrame* buffer ) { + ZoneScopedC(0xff7f50); // #ff7f50 (tracy::Color::Coral) + ZoneNameF("SamplePlayHandle::play [%s]", m_track ? m_track->displayNameUtf8().c_str() : "no track"); + const f_cnt_t fpp = Engine::audioEngine()->framesPerPeriod(); //play( 0, _try_parallelizing ); if( framesDone() >= totalFrames() ) @@ -101,6 +105,7 @@ void SamplePlayHandle::play( SampleFrame* buffer ) if( !( m_track && m_track->isMuted() ) && !(m_patternTrack && m_patternTrack->isMuted())) { + ZoneScoped; /* StereoVolumeVector v = { { m_volumeModel->value() / DefaultVolume, m_volumeModel->value() / DefaultVolume } };*/ diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index f62de7885e5..3cc30eece7d 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -29,7 +29,7 @@ #include "PatternTrack.h" #include "SampleBuffer.h" #include "SampleClip.h" - +#include "TracyProfiling.h" namespace lmms { @@ -65,6 +65,9 @@ SampleRecordHandle::~SampleRecordHandle() void SampleRecordHandle::play( SampleFrame* /*_working_buffer*/ ) { + ZoneScopedC(0xff7f50); // #ff7f50 (tracy::Color::Coral) + ZoneNameF("SampleRecordHandle::play [%s]", m_track ? m_track->displayNameUtf8().c_str() : "no track"); + const SampleFrame* recbuf = Engine::audioEngine()->inputBuffer(); const f_cnt_t frames = Engine::audioEngine()->inputBufferFrames(); writeBuffer( recbuf, frames ); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 1d3a7bc291e..3ebcf279352 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -55,7 +55,7 @@ #include "Scale.h" #include "SongEditor.h" #include "PeakController.h" - +#include "TracyProfiling.h" namespace lmms { @@ -206,6 +206,8 @@ void Song::savePlayStartPosition() void Song::processNextBuffer() { + ZoneScoped; + // If nothing is playing, there is nothing to do if (!m_playing) { return; } @@ -354,8 +356,9 @@ void Song::processNextBuffer() void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, f_cnt_t) { - AutomatedValueMap values; + ZoneScoped; + AutomatedValueMap values; QSet recordedModels; TrackContainer* container = this; diff --git a/src/core/Track.cpp b/src/core/Track.cpp index e44475d9374..72568287e6f 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -654,9 +654,10 @@ BoolModel *Track::getMutedModel() void Track::setName(const QString& newName) { - if (m_name != newName) + if (m_nameQString != newName) { - m_name = newName; + m_nameQString = newName; + m_name = newName.toStdString(); if (auto song = Engine::getSong()) { diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index 54da10b2a6a..0389e861083 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -26,6 +26,7 @@ #include "AudioDevice.h" #include "AudioEngine.h" +#include "TracyProfiling.h" namespace lmms { @@ -47,12 +48,14 @@ AudioDevice::~AudioDevice() void AudioDevice::startProcessing() { + ZoneScoped; m_running.test_and_set(std::memory_order_acquire); startProcessingImpl(); } void AudioDevice::stopProcessing() { + ZoneScoped; m_running.clear(std::memory_order_release); stopProcessingImpl(); } @@ -95,6 +98,7 @@ int AudioDevice::convertToS16(const SampleFrame* _ab, int_sample_t * _output_buffer, const bool _convert_endian ) { + ZoneScoped; if( _convert_endian ) { for( f_cnt_t frame = 0; frame < _frames; ++frame ) diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 705762c2b20..2ae43cb49bc 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -33,6 +33,7 @@ #include "AudioEngine.h" #include "ConfigManager.h" +#include "TracyProfiling.h" namespace lmms { @@ -164,6 +165,7 @@ void AudioSdl::sdlAudioCallback( void * _udata, Uint8 * _buf, int _len ) void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) { + ZoneScoped; if (!isRunning()) { memset( _buf, 0, _len ); @@ -180,7 +182,9 @@ void AudioSdl::sdlInputAudioCallback(void *_udata, Uint8 *_buf, int _len) { _this->sdlInputAudioCallback( _buf, _len ); } -void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) { +void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) +{ + ZoneScoped; auto samples_buffer = (SampleFrame*)_buf; f_cnt_t frames = _len / sizeof ( SampleFrame ); diff --git a/src/gui/tracks/TrackView.cpp b/src/gui/tracks/TrackView.cpp index 1d282338f6f..0a4616af81a 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -359,7 +359,7 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) if( height() < DEFAULT_TRACK_HEIGHT ) { - setToolTip(m_track->m_name); + setToolTip(m_track->m_nameQString); } } diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index e63ce1fa4d8..df0dafcc823 100644 --- a/src/lmmsconfig.h.in +++ b/src/lmmsconfig.h.in @@ -38,6 +38,7 @@ #cmakedefine LMMS_HAVE_SF_COMPLEVEL #cmakedefine LMMS_DEBUG_FPE +#cmakedefine LMMS_DEBUG_TRACY #cmakedefine LMMS_HAVE_PTHREAD_H #cmakedefine LMMS_HAVE_UNISTD_H diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 148351822f7..b53bb329e57 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -41,6 +41,7 @@ #include "PianoRoll.h" #include "Pitch.h" #include "Song.h" +#include "TracyProfiling.h" namespace lmms { @@ -223,6 +224,8 @@ InstrumentTrack::~InstrumentTrack() void InstrumentTrack::processAudioBuffer( SampleFrame* buf, const f_cnt_t frames, NotePlayHandle* n ) { + ZoneScopedN("InstrumentTrack::processAudioBuffer"); + // we must not play the sound if this InstrumentTrack is muted... if( isMuted() || ( Engine::getSong()->playMode() != Song::PlayMode::MidiClip && n && n->isPatternTrackMuted() ) || ! m_instrument ) @@ -566,6 +569,8 @@ f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const void InstrumentTrack::playNote( NotePlayHandle* n, SampleFrame* workingBuffer ) { + ZoneScopedN("InstrumentTrack::playNote"); + // Note: under certain circumstances the working buffer is a nullptr. // These cases are triggered in PlayHandle::doProcessing when the play method is called with a nullptr. // TODO: Find out if we can skip processing at a higher level if the buffer is nullptr. @@ -577,8 +582,11 @@ void InstrumentTrack::playNote( NotePlayHandle* n, SampleFrame* workingBuffer ) if( n->isMasterNote() == false && m_instrument != nullptr ) { - // all is done, so now lets play the note! - m_instrument->playNote( n, workingBuffer ); + { + // all is done, so now lets play the note! + ZoneScopedN("Instrument::playNote"); + m_instrument->playNote( n, workingBuffer ); + } // This is effectively the same as checking if workingBuffer is not a nullptr. // Calling processAudioBuffer with a nullptr leads to crashes. Hence the check. diff --git a/tests/scripted/check-namespace b/tests/scripted/check-namespace index a816f0d7bd5..fcf645114fc 100755 --- a/tests/scripted/check-namespace +++ b/tests/scripted/check-namespace @@ -63,6 +63,7 @@ known_no_namespace_lmms = { 'src/core/main.cpp', # nothing to set under a namespace 'include/debug.h', + 'include/TracyProfiling.h', 'include/versioninfo.h', 'plugins/CarlaBase/CarlaConfig/config.h', 'plugins/CarlaBase/DummyCarla.cpp',