From 1e97a69b51d80cf8f440125ddc672e308ce7fedc Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 18 Nov 2016 14:39:09 -0800 Subject: [PATCH] Massive performance improvements. --- interface/src/ui/SnapshotAnimated.cpp | 152 ++++++++++++++++---------- interface/src/ui/SnapshotAnimated.h | 21 ++-- 2 files changed, 106 insertions(+), 67 deletions(-) diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index 70971fd7c9..6850f43f4e 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -14,16 +14,22 @@ #include #include +#include #include "SnapshotAnimated.h" -QTimer SnapshotAnimated::snapshotAnimatedTimer; -GifWriter SnapshotAnimated::snapshotAnimatedGifWriter; +QTimer* SnapshotAnimated::snapshotAnimatedTimer = NULL; qint64 SnapshotAnimated::snapshotAnimatedTimestamp = 0; qint64 SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; -qint64 SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration = 0; bool SnapshotAnimated::snapshotAnimatedTimerRunning = false; QString SnapshotAnimated::snapshotAnimatedPath; QString SnapshotAnimated::snapshotStillPath; +QVector SnapshotAnimated::snapshotAnimatedFrameVector; +QVector SnapshotAnimated::snapshotAnimatedFrameDelayVector; +Application* SnapshotAnimated::app; +float SnapshotAnimated::aspectRatio; +QSharedPointer SnapshotAnimated::snapshotAnimatedDM; +GifWriter SnapshotAnimated::snapshotAnimatedGifWriter; + Setting::Handle SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true); Setting::Handle SnapshotAnimated::snapshotAnimatedDuration("snapshotAnimatedDuration", SNAPSNOT_ANIMATED_DURATION_SECS); @@ -31,77 +37,103 @@ Setting::Handle SnapshotAnimated::snapshotAnimatedDuration("snapshotAnima void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer dm) { // If we're not in the middle of capturing an animated snapshot... if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) { + SnapshotAnimated::snapshotAnimatedTimer = new QTimer(); + SnapshotAnimated::aspectRatio = aspectRatio; + SnapshotAnimated::app = app; + SnapshotAnimated::snapshotAnimatedDM = dm; // Define the output location of the still and animated snapshots. SnapshotAnimated::snapshotStillPath = pathStill; SnapshotAnimated::snapshotAnimatedPath = pathStill; SnapshotAnimated::snapshotAnimatedPath.replace("jpg", "gif"); - // Reset the current animated snapshot last frame duration - SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration = SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC; // Ensure the snapshot timer is Precise (attempted millisecond precision) - SnapshotAnimated::snapshotAnimatedTimer.setTimerType(Qt::PreciseTimer); + SnapshotAnimated::snapshotAnimatedTimer->setTimerType(Qt::PreciseTimer); // Connect the snapshotAnimatedTimer QTimer to the lambda slot function - QObject::connect(&(SnapshotAnimated::snapshotAnimatedTimer), &QTimer::timeout, [=] { - if (SnapshotAnimated::snapshotAnimatedTimerRunning) { - // Get a screenshot from the display, then scale the screenshot down, - // then convert it to the image format the GIF library needs, - // then save all that to the QImage named "frame" - QImage frame(app->getActiveDisplayPlugin()->getScreenshot(aspectRatio)); - frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH).convertToFormat(QImage::Format_RGBA8888); - - // If this is an intermediate or the final frame... - if (SnapshotAnimated::snapshotAnimatedTimestamp > 0) { - // Variable used to determine how long the current frame took to pack - qint64 framePackStartTime = QDateTime::currentMSecsSinceEpoch(); - // Write the frame to the gif - GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter), - (uint8_t*)frame.bits(), - frame.width(), - frame.height(), - round(((float)(framePackStartTime - SnapshotAnimated::snapshotAnimatedTimestamp + SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration)) / 10)); - // Record the current frame timestamp - SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); - // Record how long it took for the current frame to pack - SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration = SnapshotAnimated::snapshotAnimatedTimestamp - framePackStartTime; - // If that was the last frame... - if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) { - // Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE - // that the slot will not be called again in the future. - // See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html - SnapshotAnimated::snapshotAnimatedTimer.stop(); - SnapshotAnimated::snapshotAnimatedTimerRunning = false; - // Reset the current frame timestamp - SnapshotAnimated::snapshotAnimatedTimestamp = 0; - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; - // Write out the end of the GIF - GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter)); - // Let the dependency manager know that the snapshots have been taken. - emit dm->snapshotTaken(SnapshotAnimated::snapshotStillPath, SnapshotAnimated::snapshotAnimatedPath, false); - } - // If that was the first frame... - } else { - // Write out the header and beginning of the GIF file - GifBegin(&(SnapshotAnimated::snapshotAnimatedGifWriter), qPrintable(SnapshotAnimated::snapshotAnimatedPath), frame.width(), frame.height(), SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); - // Write the first to the gif - GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter), - (uint8_t*)frame.bits(), - frame.width(), - frame.height(), - SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); - // Record the current frame timestamp - SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp; - } - } - }); + QObject::connect((SnapshotAnimated::snapshotAnimatedTimer), &QTimer::timeout, captureFrames); // Start the snapshotAnimatedTimer QTimer - argument for this is in milliseconds - SnapshotAnimated::snapshotAnimatedTimer.start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC); SnapshotAnimated::snapshotAnimatedTimerRunning = true; + SnapshotAnimated::snapshotAnimatedTimer->start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC); // If we're already in the middle of capturing an animated snapshot... } else { // Just tell the dependency manager that the capture of the still snapshot has taken place. emit dm->snapshotTaken(pathStill, "", false); } } + +void SnapshotAnimated::captureFrames() { + if (SnapshotAnimated::snapshotAnimatedTimerRunning) { + // Get a screenshot from the display, then scale the screenshot down, + // then convert it to the image format the GIF library needs, + // then save all that to the QImage named "frame" + QImage frame(SnapshotAnimated::app->getActiveDisplayPlugin()->getScreenshot(SnapshotAnimated::aspectRatio)); + frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH); + SnapshotAnimated::snapshotAnimatedFrameVector.append(frame); + + // If that was the first frame... + if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) { + // Record the current frame timestamp + SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); + // Record the first frame timestamp + SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp; + SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); + // If this is an intermediate or the final frame... + } else { + // Push the current frame delay onto the vector + SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10)); + // Record the current frame timestamp + SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); + + // If that was the last frame... + if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) { + // Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE + // that the slot will not be called again in the future. + // See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html + SnapshotAnimated::snapshotAnimatedTimer->stop(); + delete SnapshotAnimated::snapshotAnimatedTimer; + SnapshotAnimated::snapshotAnimatedTimerRunning = false; + // Reset the current frame timestamp + SnapshotAnimated::snapshotAnimatedTimestamp = 0; + SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; + + // Kick off the thread that'll pack the frames into the GIF + QtConcurrent::run(processFrames); + } + } + } +} + +void SnapshotAnimated::processFrames() { + uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width(); + uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height(); + + // Create the GIF from the temporary files + // Write out the header and beginning of the GIF file + GifBegin( + &(SnapshotAnimated::snapshotAnimatedGifWriter), + qPrintable(SnapshotAnimated::snapshotAnimatedPath), + width, + height, + 1); // "1" means "yes there is a delay" with this GifCreator library. + for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) { + // Write each frame to the GIF + GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter), + (uint8_t*)SnapshotAnimated::snapshotAnimatedFrameVector[itr].convertToFormat(QImage::Format_RGBA8888).bits(), + width, + height, + SnapshotAnimated::snapshotAnimatedFrameDelayVector[itr]); + } + // Write out the end of the GIF + GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter)); + + // Clear out the frame and frame delay vectors. + // Also release the memory not required to store the items. + SnapshotAnimated::snapshotAnimatedFrameVector.clear(); + SnapshotAnimated::snapshotAnimatedFrameVector.squeeze(); + SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear(); + SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze(); + + // Let the dependency manager know that the snapshots have been taken. + emit SnapshotAnimated::snapshotAnimatedDM->snapshotTaken(SnapshotAnimated::snapshotStillPath, SnapshotAnimated::snapshotAnimatedPath, false); +} diff --git a/interface/src/ui/SnapshotAnimated.h b/interface/src/ui/SnapshotAnimated.h index 5870eb9e35..78b1529ab4 100644 --- a/interface/src/ui/SnapshotAnimated.h +++ b/interface/src/ui/SnapshotAnimated.h @@ -12,6 +12,7 @@ #ifndef hifi_SnapshotAnimated_h #define hifi_SnapshotAnimated_h +#include #include #include #include @@ -28,20 +29,26 @@ #define SNAPSNOT_ANIMATED_DURATION_MSEC (SNAPSNOT_ANIMATED_DURATION_SECS*1000) #define SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC (1000/SNAPSNOT_ANIMATED_TARGET_FRAMERATE) -// This is the fudge factor that we add to the *first* GIF frame's "delay" value -#define SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC (20) -#define SNAPSNOT_ANIMATED_NUM_FRAMES (SNAPSNOT_ANIMATED_DURATION_SECS * SNAPSNOT_ANIMATED_TARGET_FRAMERATE) class SnapshotAnimated { private: - static QTimer snapshotAnimatedTimer; - static GifWriter snapshotAnimatedGifWriter; + static QTimer* snapshotAnimatedTimer; static qint64 snapshotAnimatedTimestamp; static qint64 snapshotAnimatedFirstFrameTimestamp; - static qint64 snapshotAnimatedLastWriteFrameDuration; static bool snapshotAnimatedTimerRunning; - static QString snapshotAnimatedPath; static QString snapshotStillPath; + + static QString snapshotAnimatedPath; + static QVector snapshotAnimatedFrameVector; + static QVector snapshotAnimatedFrameDelayVector; + static QSharedPointer snapshotAnimatedDM; + static Application* app; + static float aspectRatio; + + static GifWriter snapshotAnimatedGifWriter; + + static void captureFrames(); + static void processFrames(); public: static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer dm); static Setting::Handle alsoTakeAnimatedSnapshot;