#include <QMenu>
#include <QPainter>
+const int REALTIME_FPS = 30;
+
const QColor light(250, 238, 226, 255);
const QColor dark ( 40, 40, 39, 255);
const QColor dark2( 25, 25, 23, 255);
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
+ m_accelFactorHUD(1),
+ m_accelFactorScope(1),
+ m_accelFactorBackground(1),
initialDimensionUpdateDone(false)
{
m_aAutoRefresh->setCheckable(true);
m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
m_aRealtime->setCheckable(true);
- m_aRealtime->setEnabled(false);
m_menu = new QMenu(this);
m_menu->setPalette(m_scopePalette);
m_activeRender = m_clipMonitor->render;
}
- connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint)));
+ bool b = true;
- connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
- connect(this, SIGNAL(signalHUDRenderingFinished(uint)), this, SLOT(slotHUDRenderingFinished(uint)));
- connect(this, SIGNAL(signalScopeRenderingFinished(uint)), this, SLOT(slotScopeRenderingFinished(uint)));
- connect(this, SIGNAL(signalBackgroundRenderingFinished(uint)), this, SLOT(slotBackgroundRenderingFinished(uint)));
+ b &= connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint)));
+ b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
+ b &= connect(this, SIGNAL(signalHUDRenderingFinished(uint,uint)), this, SLOT(slotHUDRenderingFinished(uint,uint)));
+ b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint,uint)), this, SLOT(slotScopeRenderingFinished(uint,uint)));
+ b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint,uint)), this, SLOT(slotBackgroundRenderingFinished(uint,uint)));
+ b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool)));
+ Q_ASSERT(b);
}
AbstractScopeWidget::~AbstractScopeWidget()
void AbstractScopeWidget::prodScopeThread()
{
+ // Try to acquire the semaphore. This must only succeed if m_threadScope is not running
+ // anymore. Therefore the semaphore must NOT be released before m_threadScope ends.
+ // If acquiring the semaphore fails, the thread is still running.
if (m_semaphoreScope.tryAcquire(1)) {
Q_ASSERT(!m_threadScope.isRunning());
m_newScopeFrames.fetchAndStoreRelaxed(0);
m_newScopeUpdates.fetchAndStoreRelaxed(0);
- m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope);
+ m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope);
qDebug() << "Scope thread started in " << widgetName();
} else {
///// Slots /////
-void AbstractScopeWidget::slotHUDRenderingFinished(uint)
-{
-
-}
+void AbstractScopeWidget::slotHUDRenderingFinished(uint, uint) {}
-void AbstractScopeWidget::slotScopeRenderingFinished(uint)
+void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
{
+ // The signal can be received before the thread has really finished. So we
+ // need to wait until it has really finished before starting a new thread.
qDebug() << "Scope rendering has finished, waiting for termination in " << widgetName();
m_threadScope.waitForFinished();
m_imgScope = m_threadScope.result();
+
+ // The scope thread has finished. Now we can release the semaphore, allowing a new thread.
+ // See prodScopeThread where the semaphore is acquired again.
m_semaphoreScope.release(1);
this->update();
+ // Calculate the acceleration factor hint to get «realtime» updates.
+ int accel;
+ if (m_aRealtime->isChecked()) {
+ accel = ceil((float)mseconds*REALTIME_FPS/1000 );
+ if (m_accelFactorScope < 1) {
+ // If mseconds is 0.
+ accel = 1;
+ }
+ // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
+ // If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen
+ // then :) Therefore use a local variable.
+ m_accelFactorScope = accel;
+ }
+
if ( (m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
qDebug() << "Trying to start a new thread for " << widgetName()
<< ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates;
+ prodScopeThread();
}
}
-void AbstractScopeWidget::slotBackgroundRenderingFinished(uint)
-{
-
-}
+void AbstractScopeWidget::slotBackgroundRenderingFinished(uint, uint) {}
void AbstractScopeWidget::slotActiveMonitorChanged(bool isClipMonitor)
{
}
}
}
+
+void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
+{
+ if (!realtimeChecked) {
+ m_accelFactorHUD = 1;
+ m_accelFactorScope = 1;
+ m_accelFactorBackground = 1;
+ }
+}
Monitor *m_clipMonitor;
Render *m_activeRender;
+
/** The context menu. Feel free to add new entries in your implementation. */
QMenu *m_menu;
+
+ /** Enables auto refreshing of the scope.
+ This is when a new frame is shown on the active monitor.
+ Resize events always force a recalculation. */
QAction *m_aAutoRefresh;
+
+ /** Realtime rendering. Should be disabled if it is not supported.
+ Use the accelerationFactor variable passed to the render functions as a hint of
+ how many times faster the scope should be calculated. */
QAction *m_aRealtime;
+
/** Offset from the widget's borders */
const uchar offset;
+ /** The rect on the widget we're painting in. */
QRect m_scopeRect;
+
+ /** Images storing the calculated layers. Will be used on repaint events. */
QImage m_imgHUD;
QImage m_imgScope;
QImage m_imgBackground;
/** Where on the widget we can paint in */
virtual QRect scopeRect() = 0;
- /** @brief HUD renderer.
- Must emit signalHUDRenderingFinished().
- Should */
- virtual QImage renderHUD() = 0;
- /** @brief Scope renderer. Must emit signalScopeRenderingFinished(). */
- virtual QImage renderScope() = 0;
- /** @brief Background renderer. Must emit signalBackgroundRenderingFinished(). */
- virtual QImage renderBackground() = 0;
+ /** @brief HUD renderer. Must emit signalHUDRenderingFinished(). @see renderScope */
+ virtual QImage renderHUD(uint accelerationFactor) = 0;
+ /** @brief Scope renderer. Must emit signalScopeRenderingFinished()
+ when calculation has finished, to allow multi-threading.
+ accelerationFactor hints how much faster than usual the calculation should be accomplished, if possible. */
+ virtual QImage renderScope(uint accelerationFactor) = 0;
+ /** @brief Background renderer. Must emit signalBackgroundRenderingFinished(). @see renderScope */
+ virtual QImage renderBackground(uint accelerationFactor) = 0;
/** Must return true if the HUD layer depends on the input monitor.
- If it does not, then it does not need to be re-calculated when
- a new frame from the monitor is incoming. */
+ If it does not, then it does not need to be re-calculated when
+ a new frame from the monitor is incoming. */
virtual bool isHUDDependingOnInput() const = 0;
/** @see isHUDDependingOnInput() */
virtual bool isScopeDependingOnInput() const = 0;
void resizeEvent(QResizeEvent *);
signals:
- void signalHUDRenderingFinished(uint mseconds);
- void signalScopeRenderingFinished(uint mseconds);
- void signalBackgroundRenderingFinished(uint mseconds);
+ /** mseconds represent the time taken for the calculation,
+ accelerationFactor the acceleration factor that has been used. */
+ void signalHUDRenderingFinished(uint mseconds, uint accelerationFactor);
+ void signalScopeRenderingFinished(uint mseconds, uint accelerationFactor);
+ void signalBackgroundRenderingFinished(uint mseconds, uint accelerationFactor);
private:
QAtomicInt m_newScopeUpdates;
QAtomicInt m_newBackgroundUpdates;
+ /** The semaphores ensure that the QFutures for the HUD/Scope/Background threads cannot
+ be assigned a new thread while it is still running. (Could cause deadlocks and other
+ nasty things known from parallelism. */
QSemaphore m_semaphoreHUD;
QSemaphore m_semaphoreScope;
QSemaphore m_semaphoreBackground;
QFuture<QImage> m_threadScope;
QFuture<QImage> m_threadBackground;
+ int m_accelFactorHUD;
+ int m_accelFactorScope;
+ int m_accelFactorBackground;
+
bool initialDimensionUpdateDone;
void prodHUDThread();
void prodScopeThread();
void prodBackgroundThread();
+
private slots:
/** @brief Must be called when the active monitor has shown a new frame.
This slot must be connected in the implementing class, it is *not*
void slotActiveMonitorChanged(bool isClipMonitor);
void customContextMenuRequested(const QPoint &pos);
void slotRenderZoneUpdated();
- void slotHUDRenderingFinished(uint mseconds);
- void slotScopeRenderingFinished(uint mseconds);
- void slotBackgroundRenderingFinished(uint mseconds);
+ void slotHUDRenderingFinished(uint mseconds, uint accelerationFactor);
+ void slotScopeRenderingFinished(uint mseconds, uint accelerationFactor);
+ void slotBackgroundRenderingFinished(uint mseconds, uint accelerationFactor);
+
+ /** Resets the acceleration factors to 1 when realtime rendering is disabled. */
+ void slotResetRealtimeFactor(bool realtimeChecked);
};
{
}
-QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QImage &image, const bool &drawAxis)
+QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QImage &image, const bool &drawAxis, const uint &accelFactor)
{
+ Q_ASSERT(accelFactor >= 1);
+
QTime time;
time.start();
const float wPrediv = (float)(ww-1)/(iw-1);
const uchar *bits = image.bits();
- const uint stepsize = 4;
+ const uint stepsize = 4*accelFactor;
for (uint i = 0, x = 0; i < byteCount; i += stepsize) {
bits += stepsize;
x += stepsize;
- x %= iw;
+ x %= iw; // Modulo image width, to represent the current x position in the image
}
if (drawAxis) {
}
uint diff = time.elapsed();
- qDebug() << "Waveform calculation ended. Time taken: " << diff << " ms. Sending signal now.";
+// qDebug() << "Waveform calculation ended. Time taken: " << diff << " ms. Sending signal now.";
emit signalCalculationFinished(wave, diff);
return wave;
WaveformGenerator();
~WaveformGenerator();
- QImage calculateWaveform(const QSize &waveformSize, const QImage &image, const bool &drawAxis);
+ QImage calculateWaveform(const QSize &waveformSize, const QImage &image, const bool &drawAxis, const uint &accelFactor = 1);
signals:
void signalCalculationFinished(QImage image, const uint &ms);
///// Implemented Methods /////
-QImage TestWidget::renderHUD()
+QImage TestWidget::renderHUD(uint)
{
- emit signalHUDRenderingFinished(0);
+ emit signalHUDRenderingFinished(0, 1);
return QImage();
}
-QImage TestWidget::renderScope()
+QImage TestWidget::renderScope(uint)
{
- emit signalScopeRenderingFinished(0);
+ emit signalScopeRenderingFinished(0, 1);
return QImage();
}
-QImage TestWidget::renderBackground()
+QImage TestWidget::renderBackground(uint)
{
- emit signalBackgroundRenderingFinished(0);
+ emit signalBackgroundRenderingFinished(0, 1);
return QImage();
}
QString widgetName() const;
/// Implemented methods ///
- QImage renderHUD();
- QImage renderScope();
- QImage renderBackground();
+ QImage renderHUD(uint accelerationFactor);
+ QImage renderScope(uint accelerationFactor);
+ QImage renderBackground(uint accelerationFactor);
bool isHUDDependingOnInput() const;
bool isScopeDependingOnInput() const;
bool isBackgroundDependingOnInput() const;
bool Waveform::isScopeDependingOnInput() const { return true; }
bool Waveform::isBackgroundDependingOnInput() const { return false; }
-QImage Waveform::renderHUD()
+QImage Waveform::renderHUD(uint)
{
- emit signalHUDRenderingFinished(0);
+ emit signalHUDRenderingFinished(0, 1);
return QImage();
}
-QImage Waveform::renderScope()
+QImage Waveform::renderScope(uint accelFactor)
{
QTime start = QTime::currentTime();
start.start();
QImage wave = m_waveformGenerator->calculateWaveform(scopeRect().size(),
- m_activeRender->extractFrame(m_activeRender->seekFramePosition()), true);
+ m_activeRender->extractFrame(m_activeRender->seekFramePosition()), true, accelFactor);
- emit signalScopeRenderingFinished(start.elapsed());
+ emit signalScopeRenderingFinished(start.elapsed(), 1);
return wave;
}
-QImage Waveform::renderBackground()
+QImage Waveform::renderBackground(uint)
{
- emit signalBackgroundRenderingFinished(0);
+ emit signalBackgroundRenderingFinished(0, 1);
return QImage();
}
/// Implemented methods ///
QRect scopeRect();
- QImage renderHUD();
- QImage renderScope();
- QImage renderBackground();
+ QImage renderHUD(uint);
+ QImage renderScope(uint);
+ QImage renderBackground(uint);
bool isHUDDependingOnInput() const;
bool isScopeDependingOnInput() const;
bool isBackgroundDependingOnInput() const;