Commit b540689a authored by Simon Eugster's avatar Simon Eugster
Browse files

AbstractScopeWidget changes:

* Support for realtime updates
* Semaphores explained, comments added

svn path=/trunk/kdenlive/; revision=4609
parent 08a49ce4
......@@ -20,6 +20,8 @@
#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);
......@@ -32,6 +34,9 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
m_accelFactorHUD(1),
m_accelFactorScope(1),
m_accelFactorBackground(1),
initialDimensionUpdateDone(false)
{
......@@ -49,7 +54,6 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
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);
......@@ -64,14 +68,17 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
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()
......@@ -84,12 +91,15 @@ void AbstractScopeWidget::prodHUDThread() {}
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 {
......@@ -153,29 +163,43 @@ void AbstractScopeWidget::customContextMenuRequested(const QPoint &pos)
///// 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)
{
......@@ -214,3 +238,12 @@ void AbstractScopeWidget::slotRenderZoneUpdated()
}
}
}
void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
{
if (!realtimeChecked) {
m_accelFactorHUD = 1;
m_accelFactorScope = 1;
m_accelFactorBackground = 1;
}
}
......@@ -77,15 +77,28 @@ protected:
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;
......@@ -96,18 +109,18 @@ protected:
/** 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;
......@@ -121,9 +134,11 @@ protected:
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:
......@@ -139,6 +154,9 @@ 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;
......@@ -147,11 +165,16 @@ private:
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*
......@@ -159,9 +182,12 @@ private slots:
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);
};
......
......@@ -25,8 +25,10 @@ WaveformGenerator::~WaveformGenerator()
{
}
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();
......@@ -60,7 +62,7 @@ QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QIm
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) {
......@@ -80,7 +82,7 @@ QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QIm
bits += stepsize;
x += stepsize;
x %= iw;
x %= iw; // Modulo image width, to represent the current x position in the image
}
if (drawAxis) {
......@@ -102,7 +104,7 @@ QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QIm
}
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;
......
......@@ -23,7 +23,7 @@ public:
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);
......
......@@ -32,21 +32,21 @@ TestWidget::~TestWidget()
///// 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();
}
......
......@@ -27,9 +27,9 @@ public:
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;
......
......@@ -52,26 +52,26 @@ bool Waveform::isHUDDependingOnInput() const { return false; }
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();
}
......@@ -38,9 +38,9 @@ private:
/// 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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment