mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-24 14:47:15 -04:00
Show detailed CPU load information in a tool-tip (#5970)
* Profiler rework * Workaround for GCC bug * Rollback QFile removal * Use enum instead of a plain index to describe detailed stats * Use the GCC workaround code for all compilers to avoid redundancy * Update and fix comments * Implement suggestions from review * Split AudioEngine::renderNextBuffer() into separate functions, fix old formatting * Remove QFile include * Revert formatting changes * Apply suggestion from review (remove unnecessary template parameter) * Revert more formatting changes * Convert DetailType to enum class * DetailType enum class improvements suggested in review * Use std::atomic for m_detailLoad * RAII-style profiler probes * Apply suggestions from code review Co-authored-by: Dominic Clark <mrdomclark@gmail.com> * Fix namespace comment * Improve CPU load widget precision and use floats for load computations * Atomic m_cpuLoad * Add custom step size support for CPULoadWidget * Apply suggestions from review (convert the profiler probe into a nested class, other small changes) * Do not limit stored load averages to 100% --------- Co-authored-by: sakertooth <sakertooth@gmail.com> Co-authored-by: Dominic Clark <mrdomclark@gmail.com>
This commit is contained in:
@@ -209,6 +209,7 @@ lmms--gui--Oscilloscope {
|
||||
lmms--gui--CPULoadWidget {
|
||||
border: none;
|
||||
background: url(resources:cpuload_bg.png);
|
||||
qproperty-stepSize: 4;
|
||||
}
|
||||
|
||||
/* scrollbar: trough */
|
||||
|
||||
@@ -241,6 +241,7 @@ lmms--gui--Oscilloscope {
|
||||
lmms--gui--CPULoadWidget {
|
||||
border: none;
|
||||
background: url(resources:cpuload_bg.png);
|
||||
qproperty-stepSize: 1;
|
||||
}
|
||||
|
||||
/* scrollbar: trough */
|
||||
|
||||
@@ -275,6 +275,11 @@ public:
|
||||
return m_profiler.cpuLoad();
|
||||
}
|
||||
|
||||
int detailLoad(const AudioEngineProfiler::DetailType type) const
|
||||
{
|
||||
return m_profiler.detailLoad(type);
|
||||
}
|
||||
|
||||
const qualitySettings & currentQualitySettings() const
|
||||
{
|
||||
return m_qualitySettings;
|
||||
@@ -401,6 +406,10 @@ private:
|
||||
AudioDevice * tryAudioDevices();
|
||||
MidiClient * tryMidiClients();
|
||||
|
||||
void renderStageNoteSetup();
|
||||
void renderStageInstruments();
|
||||
void renderStageEffects();
|
||||
void renderStageMix();
|
||||
|
||||
const surroundSampleFrame * renderNextBuffer();
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#ifndef LMMS_AUDIO_ENGINE_PROFILER_H
|
||||
#define LMMS_AUDIO_ENGINE_PROFILER_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <QFile>
|
||||
|
||||
#include "lmms_basics.h"
|
||||
@@ -53,11 +55,55 @@ public:
|
||||
|
||||
void setOutputFile( const QString& outputFile );
|
||||
|
||||
enum class DetailType {
|
||||
NoteSetup,
|
||||
Instruments,
|
||||
Effects,
|
||||
Mixing,
|
||||
Count
|
||||
};
|
||||
|
||||
constexpr static auto DetailCount = static_cast<std::size_t>(DetailType::Count);
|
||||
|
||||
int detailLoad(const DetailType type) const
|
||||
{
|
||||
return m_detailLoad[static_cast<std::size_t>(type)].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
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(const Probe&) = delete;
|
||||
Probe(Probe&&) = delete;
|
||||
|
||||
private:
|
||||
AudioEngineProfiler &m_profiler;
|
||||
const AudioEngineProfiler::DetailType m_type;
|
||||
};
|
||||
|
||||
private:
|
||||
void startDetail(const DetailType type) { m_detailTimer[static_cast<std::size_t>(type)].reset(); }
|
||||
void finishDetail(const DetailType type)
|
||||
{
|
||||
m_detailTime[static_cast<std::size_t>(type)] = m_detailTimer[static_cast<std::size_t>(type)].elapsed();
|
||||
}
|
||||
|
||||
MicroTimer m_periodTimer;
|
||||
int m_cpuLoad;
|
||||
std::atomic<float> m_cpuLoad;
|
||||
QFile m_outputFile;
|
||||
|
||||
// Use arrays to avoid dynamic allocations in realtime code
|
||||
std::array<MicroTimer, DetailCount> m_detailTimer;
|
||||
std::array<int, DetailCount> m_detailTime{0};
|
||||
std::array<std::atomic<float>, DetailCount> m_detailLoad{0};
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#ifndef LMMS_GUI_CPU_LOAD_WIDGET_H
|
||||
#define LMMS_GUI_CPU_LOAD_WIDGET_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <QTimer>
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
@@ -40,6 +41,7 @@ namespace lmms::gui
|
||||
class CPULoadWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int stepSize MEMBER m_stepSize)
|
||||
public:
|
||||
CPULoadWidget( QWidget * _parent );
|
||||
~CPULoadWidget() override = default;
|
||||
@@ -54,6 +56,8 @@ protected slots:
|
||||
|
||||
|
||||
private:
|
||||
int stepSize() const { return std::max(1, m_stepSize); }
|
||||
|
||||
int m_currentLoad;
|
||||
|
||||
QPixmap m_temp;
|
||||
@@ -64,6 +68,8 @@ private:
|
||||
|
||||
QTimer m_updateTimer;
|
||||
|
||||
int m_stepSize;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
@@ -333,12 +333,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames )
|
||||
|
||||
|
||||
|
||||
|
||||
const surroundSampleFrame * AudioEngine::renderNextBuffer()
|
||||
void AudioEngine::renderStageNoteSetup()
|
||||
{
|
||||
m_profiler.startPeriod();
|
||||
|
||||
s_renderingThread = true;
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup);
|
||||
|
||||
if( m_clearSignal )
|
||||
{
|
||||
@@ -387,9 +384,15 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
|
||||
m_newPlayHandles.free( e );
|
||||
e = next;
|
||||
}
|
||||
}
|
||||
|
||||
// STAGE 1: run and render all play handles
|
||||
AudioEngineWorkerThread::fillJobQueue<PlayHandleList>( m_playHandles );
|
||||
|
||||
|
||||
void AudioEngine::renderStageInstruments()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments);
|
||||
|
||||
AudioEngineWorkerThread::fillJobQueue(m_playHandles);
|
||||
AudioEngineWorkerThread::startAndWaitForJobs();
|
||||
|
||||
// removed all play handles which are done
|
||||
@@ -417,16 +420,28 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AudioEngine::renderStageEffects()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects);
|
||||
|
||||
// STAGE 2: process effects of all instrument- and sampletracks
|
||||
AudioEngineWorkerThread::fillJobQueue(m_audioPorts);
|
||||
AudioEngineWorkerThread::startAndWaitForJobs();
|
||||
}
|
||||
|
||||
|
||||
// STAGE 3: do master mix in mixer
|
||||
|
||||
void AudioEngine::renderStageMix()
|
||||
{
|
||||
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing);
|
||||
|
||||
Mixer *mixer = Engine::mixer();
|
||||
mixer->masterMix(m_outputBufferWrite);
|
||||
|
||||
|
||||
emit nextAudioBuffer(m_outputBufferRead);
|
||||
|
||||
runChangesInModel();
|
||||
@@ -435,10 +450,22 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
|
||||
EnvelopeAndLfoParameters::instances()->trigger();
|
||||
Controller::triggerFrameCounter();
|
||||
AutomatableModel::incrementPeriodCounter();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const surroundSampleFrame *AudioEngine::renderNextBuffer()
|
||||
{
|
||||
m_profiler.startPeriod();
|
||||
s_renderingThread = true;
|
||||
|
||||
renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles
|
||||
renderStageInstruments(); // STAGE 1: run and render all play handles
|
||||
renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks
|
||||
renderStageMix(); // STAGE 3: do master mix in mixer
|
||||
|
||||
s_renderingThread = false;
|
||||
|
||||
m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod );
|
||||
m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod);
|
||||
|
||||
return m_outputBufferRead;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "AudioEngineProfiler.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
@@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() :
|
||||
|
||||
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
|
||||
{
|
||||
int periodElapsed = m_periodTimer.elapsed();
|
||||
// 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.
|
||||
const uint64_t timeLimit = static_cast<uint64_t>(1000000) * framesPerPeriod / sampleRate;
|
||||
|
||||
const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod;
|
||||
m_cpuLoad = std::clamp<int>((newCpuLoad * 0.1f + m_cpuLoad * 0.9f), 0, 100);
|
||||
// Compute new overall CPU load and apply exponential averaging.
|
||||
// The result is used for overload detection in AudioEngine::criticalXRuns()
|
||||
// → the weight of a new sample must be high enough to allow relatively fast changes!
|
||||
const auto newCpuLoad = 100.f * periodElapsed / timeLimit;
|
||||
m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f;
|
||||
|
||||
// Compute detailed load analysis. Can use stronger averaging to get more stable readout.
|
||||
for (std::size_t i = 0; i < DetailCount; i++)
|
||||
{
|
||||
const auto newLoad = 100.f * m_detailTime[i] / timeLimit;
|
||||
const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed);
|
||||
m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if( m_outputFile.isOpen() )
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <QPainter>
|
||||
|
||||
#include "AudioEngine.h"
|
||||
@@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
|
||||
QPainter p( &m_temp );
|
||||
p.drawPixmap( 0, 0, m_background );
|
||||
|
||||
// as load-indicator consists of small 2-pixel wide leds with
|
||||
// 1 pixel spacing, we have to make sure, only whole leds are
|
||||
// shown which we achieve by the following formula
|
||||
int w = ( m_leds.width() * m_currentLoad / 300 ) * 3;
|
||||
// Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to
|
||||
// draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size.
|
||||
int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize();
|
||||
if( w > 0 )
|
||||
{
|
||||
p.drawPixmap( 23, 3, m_leds, 0, 0, w,
|
||||
@@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
|
||||
|
||||
void CPULoadWidget::updateCpuLoad()
|
||||
{
|
||||
// smooth load-values a bit
|
||||
int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2;
|
||||
if( new_load != m_currentLoad )
|
||||
// Additional display smoothing for the main load-value. Stronger averaging
|
||||
// cannot be used directly in the profiler: cpuLoad() must react fast enough
|
||||
// to be useful as overload indicator in AudioEngine::criticalXRuns().
|
||||
const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2;
|
||||
|
||||
if (new_load != m_currentLoad)
|
||||
{
|
||||
auto engine = Engine::audioEngine();
|
||||
setToolTip(
|
||||
tr("DSP total: %1%").arg(new_load) + "\n"
|
||||
+ tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n"
|
||||
+ tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n"
|
||||
+ tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n"
|
||||
+ tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing))
|
||||
);
|
||||
m_currentLoad = new_load;
|
||||
m_changed = true;
|
||||
update();
|
||||
@@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad()
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms::gui
|
||||
} // namespace lmms::gui
|
||||
|
||||
Reference in New Issue
Block a user