[Follow up] Improve performance when rendering sample waveforms (#7695)
A follow up to 786088baec to fix rendering issues and crashes.
---------
Co-authored-by: Sotonye Atemie <sakertooth@gmail.com>
This commit is contained in:
@@ -67,6 +67,7 @@ private:
|
||||
SampleClip * m_clip;
|
||||
SampleThumbnail m_sampleThumbnail;
|
||||
QPixmap m_paintPixmap;
|
||||
long m_paintPixmapXPosition;
|
||||
bool splitClip( const TimePos pos ) override;
|
||||
} ;
|
||||
|
||||
|
||||
@@ -54,10 +54,8 @@ public:
|
||||
{
|
||||
QRect sampleRect; //!< A rectangle that covers the entire range of samples.
|
||||
|
||||
QRect drawRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals
|
||||
//!< `sampleRect` when null.
|
||||
|
||||
QRect viewportRect; //!< Clips `drawRect`. Equals `drawRect` when null.
|
||||
QRect viewportRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals
|
||||
//!< `sampleRect` when null.
|
||||
|
||||
float amplification = 1.0f; //!< The amount of amplification to apply to the waveform.
|
||||
|
||||
@@ -95,8 +93,8 @@ private:
|
||||
Peak operator+(const Peak& other) const { return Peak(std::min(min, other.min), std::max(max, other.max)); }
|
||||
Peak operator+(const SampleFrame& frame) const { return *this + Peak{frame}; }
|
||||
|
||||
float min = std::numeric_limits<float>::max();
|
||||
float max = std::numeric_limits<float>::min();
|
||||
float min = std::numeric_limits<float>::infinity();
|
||||
float max = -std::numeric_limits<float>::infinity();
|
||||
};
|
||||
|
||||
Thumbnail() = default;
|
||||
@@ -105,6 +103,7 @@ private:
|
||||
|
||||
Thumbnail zoomOut(float factor) const;
|
||||
|
||||
Peak* data() { return m_peaks.data(); }
|
||||
Peak& operator[](size_t index) { return m_peaks[index]; }
|
||||
const Peak& operator[](size_t index) const { return m_peaks[index]; }
|
||||
|
||||
@@ -134,7 +133,7 @@ private:
|
||||
|
||||
using ThumbnailCache = std::vector<Thumbnail>;
|
||||
std::shared_ptr<ThumbnailCache> m_thumbnailCache = std::make_shared<ThumbnailCache>();
|
||||
|
||||
std::shared_ptr<const SampleBuffer> m_buffer = SampleBuffer::emptyBuffer();
|
||||
inline static std::unordered_map<SampleThumbnailEntry, std::shared_ptr<ThumbnailCache>, Hash> s_sampleThumbnailCacheMap;
|
||||
};
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ SampleThumbnail::Thumbnail SampleThumbnail::Thumbnail::zoomOut(float factor) con
|
||||
}
|
||||
|
||||
SampleThumbnail::SampleThumbnail(const Sample& sample)
|
||||
: m_buffer(sample.buffer())
|
||||
{
|
||||
auto entry = SampleThumbnailEntry{sample.sampleFile(), QFileInfo{sample.sampleFile()}.lastModified()};
|
||||
if (!entry.filePath.isEmpty())
|
||||
@@ -91,11 +92,9 @@ SampleThumbnail::SampleThumbnail(const Sample& sample)
|
||||
s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache;
|
||||
}
|
||||
|
||||
if (!sample.buffer()) { throw std::runtime_error{"Cannot create a sample thumbnail with no buffer"}; }
|
||||
if (sample.sampleSize() == 0) { return; }
|
||||
|
||||
const auto fullResolutionWidth = sample.sampleSize() * DEFAULT_CHANNELS;
|
||||
m_thumbnailCache->emplace_back(&sample.buffer()->data()->left(), fullResolutionWidth, fullResolutionWidth);
|
||||
const auto flatBuffer = m_buffer->data()->data();
|
||||
const auto flatBufferSize = m_buffer->size() * DEFAULT_CHANNELS;
|
||||
m_thumbnailCache->emplace_back(flatBuffer, flatBufferSize, flatBufferSize / AggregationPerZoomStep);
|
||||
|
||||
while (m_thumbnailCache->back().width() >= AggregationPerZoomStep)
|
||||
{
|
||||
@@ -107,48 +106,71 @@ SampleThumbnail::SampleThumbnail(const Sample& sample)
|
||||
void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painter) const
|
||||
{
|
||||
const auto& sampleRect = parameters.sampleRect;
|
||||
const auto& drawRect = parameters.drawRect.isNull() ? sampleRect : parameters.drawRect;
|
||||
const auto& viewportRect = parameters.viewportRect.isNull() ? drawRect : parameters.viewportRect;
|
||||
const auto& viewportRect = parameters.viewportRect.isNull() ? sampleRect : parameters.viewportRect;
|
||||
|
||||
const auto renderRect = sampleRect.intersected(drawRect).intersected(viewportRect);
|
||||
const auto renderRect = sampleRect.intersected(viewportRect);
|
||||
if (renderRect.isNull()) { return; }
|
||||
|
||||
const auto sampleRange = parameters.sampleEnd - parameters.sampleStart;
|
||||
if (sampleRange <= 0 || sampleRange > 1) { return; }
|
||||
if (sampleRange <= 0.0f || sampleRange > 1.0f) { return; }
|
||||
|
||||
const auto targetThumbnailWidth = static_cast<int>(static_cast<double>(sampleRect.width()) / sampleRange);
|
||||
const auto targetThumbnailWidth = static_cast<int>(sampleRect.width() / sampleRange);
|
||||
const auto finerThumbnail = std::find_if(m_thumbnailCache->rbegin(), m_thumbnailCache->rend(),
|
||||
[&](const auto& thumbnail) { return thumbnail.width() >= targetThumbnailWidth; });
|
||||
|
||||
if (finerThumbnail == m_thumbnailCache->rend())
|
||||
{
|
||||
qDebug() << "Could not find closest finer thumbnail for a target width of" << targetThumbnailWidth;
|
||||
return;
|
||||
}
|
||||
const auto useOriginalBuffer = finerThumbnail == m_thumbnailCache->rend();
|
||||
const auto drawOriginalBuffer = static_cast<size_t>(targetThumbnailWidth) == m_buffer->size();
|
||||
|
||||
painter.save();
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const auto thumbnailBeginForward = std::max<int>(renderRect.x() - sampleRect.x(), static_cast<int>(parameters.sampleStart * targetThumbnailWidth));
|
||||
const auto thumbnailEndForward = std::max<int>(renderRect.x() + renderRect.width() - sampleRect.x(), static_cast<int>(parameters.sampleEnd * targetThumbnailWidth));
|
||||
const auto thumbnailBeginForward = std::max<int>(renderRect.x() - sampleRect.x(), parameters.sampleStart * targetThumbnailWidth);
|
||||
const auto thumbnailEndForward = std::max<int>(renderRect.x() + renderRect.width() - sampleRect.x(), parameters.sampleEnd * targetThumbnailWidth);
|
||||
const auto thumbnailBegin = parameters.reversed ? targetThumbnailWidth - thumbnailBeginForward - 1 : thumbnailBeginForward;
|
||||
const auto thumbnailEnd = parameters.reversed ? targetThumbnailWidth - thumbnailEndForward : thumbnailEndForward;
|
||||
const auto advanceThumbnailBy = parameters.reversed ? -1 : 1;
|
||||
|
||||
const auto finerThumbnailScaleFactor = static_cast<double>(finerThumbnail->width()) / targetThumbnailWidth;
|
||||
const auto yScale = drawRect.height() / 2 * parameters.amplification;
|
||||
const auto finerThumbnailWidth = useOriginalBuffer ? m_buffer->size() : finerThumbnail->width();
|
||||
const auto finerThumbnailScaleFactor = static_cast<double>(finerThumbnailWidth) / targetThumbnailWidth;
|
||||
const auto yScale = renderRect.height() / 2 * parameters.amplification;
|
||||
|
||||
for (auto x = renderRect.x(), i = thumbnailBegin; x < renderRect.x() + renderRect.width() && i != thumbnailEnd;
|
||||
++x, i += advanceThumbnailBy)
|
||||
++x, i += advanceThumbnailBy)
|
||||
{
|
||||
const auto beginAggregationAt = &(*finerThumbnail)[static_cast<int>(std::floor(i * finerThumbnailScaleFactor))];
|
||||
const auto endAggregationAt = &(*finerThumbnail)[static_cast<int>(std::ceil((i + 1) * finerThumbnailScaleFactor))];
|
||||
const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{});
|
||||
if (useOriginalBuffer && drawOriginalBuffer)
|
||||
{
|
||||
const auto value = m_buffer->data()->data()[i];
|
||||
painter.drawPoint(x, renderRect.center().y() - value * yScale);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto beginIndex = std::clamp<size_t>(std::floor(i * finerThumbnailScaleFactor), 0, finerThumbnail->width() - 1);
|
||||
const auto endIndex = std::clamp<size_t>(std::ceil((i + 1) * finerThumbnailScaleFactor), 0, finerThumbnail->width() - 1);
|
||||
|
||||
const auto yMin = drawRect.center().y() - peak.min * yScale;
|
||||
const auto yMax = drawRect.center().y() - peak.max * yScale;
|
||||
auto minPeak = 0.f;
|
||||
auto maxPeak = 0.f;
|
||||
|
||||
painter.drawLine(x, yMin, x, yMax);
|
||||
if (useOriginalBuffer)
|
||||
{
|
||||
const auto flatBuffer = m_buffer->data()->data();
|
||||
const auto [min, max] = std::minmax_element(flatBuffer + beginIndex, flatBuffer + endIndex);
|
||||
minPeak = *min;
|
||||
maxPeak = *max;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto beginAggregationAt = finerThumbnail->data() + beginIndex;
|
||||
const auto endAggregationAt = finerThumbnail->data() + endIndex;
|
||||
const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{});
|
||||
minPeak = peak.min;
|
||||
maxPeak = peak.max;
|
||||
}
|
||||
|
||||
const auto yMin = renderRect.center().y() - minPeak * yScale;
|
||||
const auto yMax = renderRect.center().y() - maxPeak * yScale;
|
||||
painter.drawLine(x, yMin, x, yMax);
|
||||
}
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
|
||||
@@ -306,6 +306,8 @@ void ClipView::remove()
|
||||
// as actually deleting the Clip with the deleteLater function. That being said, it shouldn't
|
||||
// be possible to make a Clip without a Track (i.e., Clip::getTrack is never nullptr).
|
||||
m_clip->deleteLater();
|
||||
|
||||
m_trackView->update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "SampleThumbnail.h"
|
||||
#include "Song.h"
|
||||
#include "StringPairDrag.h"
|
||||
#include "TrackView.h"
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
@@ -45,7 +46,8 @@ namespace lmms::gui
|
||||
SampleClipView::SampleClipView( SampleClip * _clip, TrackView * _tv ) :
|
||||
ClipView( _clip, _tv ),
|
||||
m_clip( _clip ),
|
||||
m_paintPixmap()
|
||||
m_paintPixmap(),
|
||||
m_paintPixmapXPosition(0)
|
||||
{
|
||||
// update UI and tooltip
|
||||
updateSample();
|
||||
@@ -210,15 +212,22 @@ void SampleClipView::paintEvent( QPaintEvent * pe )
|
||||
|
||||
if( !needsUpdate() )
|
||||
{
|
||||
painter.drawPixmap( 0, 0, m_paintPixmap );
|
||||
painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
setNeedsUpdate( false );
|
||||
|
||||
if (m_paintPixmap.isNull() || m_paintPixmap.size() != size())
|
||||
const auto trackViewWidth = getTrackView()->rect().width();
|
||||
|
||||
// Use the clip's height to avoid artifacts when rendering while something else is overlaying the clip.
|
||||
const auto viewPortRect = QRect(0, 0, trackViewWidth * 2, rect().height());
|
||||
|
||||
m_paintPixmapXPosition = std::max(0, pe->rect().x() - trackViewWidth);
|
||||
|
||||
if (m_paintPixmap.isNull() || m_paintPixmap.size() != viewPortRect.size())
|
||||
{
|
||||
m_paintPixmap = QPixmap(size());
|
||||
m_paintPixmap = QPixmap(viewPortRect.size());
|
||||
}
|
||||
|
||||
QPainter p( &m_paintPixmap );
|
||||
@@ -274,12 +283,14 @@ void SampleClipView::paintEvent( QPaintEvent * pe )
|
||||
float sampleLength = m_clip->sampleLength() * ppb / ticksPerBar;
|
||||
|
||||
const auto& sample = m_clip->m_sample;
|
||||
|
||||
const auto sampleRextX = static_cast<int>(offsetStart) - m_paintPixmapXPosition;
|
||||
|
||||
if (sample.sampleSize() > 0)
|
||||
{
|
||||
const auto param = SampleThumbnail::VisualizeParameters{
|
||||
.sampleRect = QRect(offsetStart, spacing, sampleLength, height() - spacing),
|
||||
.drawRect = QRect(0, spacing, width(), height() - spacing),
|
||||
.viewportRect = pe->rect(),
|
||||
.sampleRect = QRect(sampleRextX, spacing, sampleLength, height() - spacing),
|
||||
.viewportRect = viewPortRect,
|
||||
.amplification = sample.amplification(),
|
||||
.reversed = sample.reversed()
|
||||
};
|
||||
@@ -295,12 +306,15 @@ void SampleClipView::paintEvent( QPaintEvent * pe )
|
||||
|
||||
// inner border
|
||||
p.setPen( c.lighter( 135 ) );
|
||||
p.drawRect( 1, 1, rect().right() - BORDER_WIDTH,
|
||||
p.drawRect(
|
||||
-m_paintPixmapXPosition + 1,
|
||||
1,
|
||||
rect().right() - BORDER_WIDTH,
|
||||
rect().bottom() - BORDER_WIDTH );
|
||||
|
||||
// outer border
|
||||
p.setPen( c.darker( 200 ) );
|
||||
p.drawRect( 0, 0, rect().right(), rect().bottom() );
|
||||
p.drawRect(-m_paintPixmapXPosition, 0, rect().right(), rect().bottom());
|
||||
|
||||
// draw the 'muted' pixmap only if the clip was manualy muted
|
||||
if( m_clip->isMuted() )
|
||||
@@ -332,7 +346,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe )
|
||||
|
||||
p.end();
|
||||
|
||||
painter.drawPixmap( 0, 0, m_paintPixmap );
|
||||
painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1216,6 +1216,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe )
|
||||
|
||||
const auto param = SampleThumbnail::VisualizeParameters{
|
||||
.sampleRect = QRect(startPos, yOffset, sampleWidth, sampleHeight),
|
||||
.viewportRect = rect(),
|
||||
.amplification = sample.amplification(),
|
||||
.sampleStart = static_cast<float>(sample.startFrame()) / sample.sampleSize(),
|
||||
.sampleEnd = static_cast<float>(sample.endFrame()) / sample.sampleSize(),
|
||||
|
||||
Reference in New Issue
Block a user