diff --git a/toonz/sources/include/toonzqt/flipconsole.h b/toonz/sources/include/toonzqt/flipconsole.h index b9de6cf..91340e5 100644 --- a/toonz/sources/include/toonzqt/flipconsole.h +++ b/toonz/sources/include/toonzqt/flipconsole.h @@ -19,6 +19,7 @@ #include "toonz/imagepainter.h" #include "tstopwatch.h" #include +#include #undef DVAPI #undef DVVAR @@ -52,6 +53,7 @@ class PlaybackExecutor final : public QThread { int m_fps; bool m_abort; + QElapsedTimer m_timer; public: PlaybackExecutor(); @@ -60,12 +62,14 @@ public: void run() override; void abort() { m_abort = true; } - - void emitNextFrame(int fps) { emit nextFrame(fps); } + bool isAborted() { return m_abort; } + void emitNextFrame(int fps) { emit nextFrame(fps, nullptr, 0); } signals: - void nextFrame(int fps); // Must be connect with Qt::BlockingQueuedConnection - // connection type. + void nextFrame( + int fps, QElapsedTimer *timer, + qint64 targetInstant); // Must be connect with + // Qt::BlockingQueuedConnection connection type. }; //----------------------------------------------------------------------------- @@ -298,7 +302,7 @@ public: } bool isLinkable() const { return m_isLinkable; } - void playNextFrame(); + void playNextFrame(QElapsedTimer *timer = nullptr, qint64 targetInstant = 0); void updateCurrentFPS(int val); bool hasButton(std::vector buttonMask, FlipConsole::EGadget buttonId) { @@ -401,9 +405,9 @@ protected slots: } void onButtonPressed(int button); void incrementCurrentFrame(int delta); - void onNextFrame(int fps); + void onNextFrame(int fps, QElapsedTimer *timer, qint64 target); void onCustomizeButtonPressed(QAction *); - bool drawBlanks(int from, int to); + bool drawBlanks(int from, int to, QElapsedTimer *timer, qint64 target); void onSliderRelease(); void onFPSEdited(); diff --git a/toonz/sources/include/toonzqt/flipconsoleowner.h b/toonz/sources/include/toonzqt/flipconsoleowner.h index b3eec23..40a3560 100644 --- a/toonz/sources/include/toonzqt/flipconsoleowner.h +++ b/toonz/sources/include/toonzqt/flipconsoleowner.h @@ -16,7 +16,9 @@ class FlipConsole; class FlipConsoleOwner { public: virtual void onDrawFrame(int frame, - const ImagePainter::VisualSettings &settings) = 0; + const ImagePainter::VisualSettings& settings, + QElapsedTimer* timer = nullptr, + qint64 targetInstant = 0) = 0; virtual void swapBuffers(){}; virtual void changeSwapBehavior(bool enable){}; diff --git a/toonz/sources/stopmotion/stopmotioncontroller.cpp b/toonz/sources/stopmotion/stopmotioncontroller.cpp index 00d7b3c..4befa72 100644 --- a/toonz/sources/stopmotion/stopmotioncontroller.cpp +++ b/toonz/sources/stopmotion/stopmotioncontroller.cpp @@ -1933,8 +1933,8 @@ void StopMotionController::onNewCameraSelected(int index, bool useWebcam) { m_resolutionCombo->hide(); m_resolutionLabel->hide(); m_cameraStatusLabel->hide(); - m_pickZoomButton->setStyleSheet("border:1px solid rgb(0, 0, 0, 0);"); - m_zoomButton->setStyleSheet("border:1px solid rgb(0, 0, 0, 0);"); + m_pickZoomButton->setStyleSheet("border:1px solid rgba(0, 0, 0, 0);"); + m_zoomButton->setStyleSheet("border:1px solid rgba(0, 0, 0, 0);"); m_pickZoomButton->setChecked(false); m_zoomButton->setChecked(false); m_dslrFrame->hide(); @@ -2382,9 +2382,9 @@ void StopMotionController::onPictureStyleChangedSignal(QString text) { void StopMotionController::onFocusCheckToggled(bool on) { #ifdef WITH_CANON if (on) { - m_zoomButton->setStyleSheet("border:1px solid rgb(0, 255, 0, 255);"); + m_zoomButton->setStyleSheet("border:1px solid rgba(0, 255, 0, 255);"); } else { - m_zoomButton->setStyleSheet("border:1px solid rgb(0, 0, 0, 0);"); + m_zoomButton->setStyleSheet("border:1px solid rgba(0, 0, 0, 0);"); } m_zoomButton->blockSignals(true); m_zoomButton->setChecked(on); @@ -2397,10 +2397,10 @@ void StopMotionController::onFocusCheckToggled(bool on) { void StopMotionController::onPickFocusCheckToggled(bool on) { #ifdef WITH_CANON if (on) { - m_pickZoomButton->setStyleSheet("border:1px solid rgb(0, 255, 0, 255);"); + m_pickZoomButton->setStyleSheet("border:1px solid rgba(0, 255, 0, 255);"); } else { - m_pickZoomButton->setStyleSheet("border:1px solid rgb(0, 0, 0, 0);"); + m_pickZoomButton->setStyleSheet("border:1px solid rgba(0, 0, 0, 0);"); } m_pickZoomButton->blockSignals(true); m_pickZoomButton->setChecked(on); diff --git a/toonz/sources/toonz/comboviewerpane.cpp b/toonz/sources/toonz/comboviewerpane.cpp index c342707..9e49841 100644 --- a/toonz/sources/toonz/comboviewerpane.cpp +++ b/toonz/sources/toonz/comboviewerpane.cpp @@ -286,10 +286,13 @@ void ComboViewerPanel::onShowHideActionTriggered(QAction *act) { //----------------------------------------------------------------------------- -void ComboViewerPanel::onDrawFrame( - int frame, const ImagePainter::VisualSettings &settings) { +void ComboViewerPanel::onDrawFrame(int frame, + const ImagePainter::VisualSettings &settings, + QElapsedTimer *timer, qint64 targetInstant) { TApp *app = TApp::instance(); m_sceneViewer->setVisual(settings); + m_sceneViewer->setTimerAndTargetInstant(timer, targetInstant); + TFrameHandle *frameHandle = app->getCurrentFrame(); if (m_sceneViewer->isPreviewEnabled()) { @@ -324,6 +327,12 @@ void ComboViewerPanel::onDrawFrame( else if (settings.m_blankColor != TPixel::Transparent) m_sceneViewer->update(); + + // make sure to redraw the frame here. + // repaint() does NOT immediately redraw the frame for QOpenGLWidget + if (frameHandle->isPlaying()) + qApp->processEvents(QEventLoop::ExcludeUserInputEvents | + QEventLoop::ExcludeSocketNotifiers); } //----------------------------------------------------------------------------- @@ -644,10 +653,10 @@ void ComboViewerPanel::changeWindowTitle() { name = name + tr(" :: Level: ") + imageName; if (!m_sceneViewer->is3DView()) { - TAffine aff = m_sceneViewer->getViewMatrix(); + TAffine aff = m_sceneViewer->getViewMatrix(); if (m_sceneViewer->getIsFlippedX()) aff = aff * TScale(-1, 1); if (m_sceneViewer->getIsFlippedY()) aff = aff * TScale(1, -1); - name = name + " :: Zoom : " + + name = name + " :: Zoom : " + QString::number((int)(100.0 * sqrt(aff.det()) * m_sceneViewer->getDpiFactor())) + "%"; @@ -659,16 +668,15 @@ void ComboViewerPanel::changeWindowTitle() { ->isActualPixelViewOnSceneEditingModeEnabled() && TApp::instance()->getCurrentLevel()->getSimpleLevel() && !CleanupPreviewCheck::instance() - ->isEnabled() // cleanup preview must be OFF - && - !CameraTestCheck::instance() // camera test mode must be OFF - // neither - ->isEnabled() && + ->isEnabled() // cleanup preview must be OFF + && !CameraTestCheck::instance() // camera test mode must be OFF + // neither + ->isEnabled() && !m_sceneViewer->is3DView()) { - TAffine aff = m_sceneViewer->getViewMatrix(); + TAffine aff = m_sceneViewer->getViewMatrix(); if (m_sceneViewer->getIsFlippedX()) aff = aff * TScale(-1, 1); if (m_sceneViewer->getIsFlippedY()) aff = aff * TScale(1, -1); - name = name + " :: Zoom : " + + name = name + " :: Zoom : " + QString::number((int)(100.0 * sqrt(aff.det()) * m_sceneViewer->getDpiFactor())) + "%"; @@ -688,7 +696,7 @@ void ComboViewerPanel::changeWindowTitle() { TAffine aff = m_sceneViewer->getViewMatrix(); if (m_sceneViewer->getIsFlippedX()) aff = aff * TScale(-1, 1); if (m_sceneViewer->getIsFlippedY()) aff = aff * TScale(1, -1); - name = name + " :: Zoom : " + + name = name + " :: Zoom : " + QString::number((int)(100.0 * sqrt(aff.det()) * m_sceneViewer->getDpiFactor())) + "%"; diff --git a/toonz/sources/toonz/comboviewerpane.h b/toonz/sources/toonz/comboviewerpane.h index df778a3..cb6c4f8 100644 --- a/toonz/sources/toonz/comboviewerpane.h +++ b/toonz/sources/toonz/comboviewerpane.h @@ -86,8 +86,8 @@ public: void updateShowHide(); void addShowHideContextMenu(QMenu *); - void onDrawFrame(int frame, - const ImagePainter::VisualSettings &settings) override; + void onDrawFrame(int frame, const ImagePainter::VisualSettings &settings, + QElapsedTimer *timer, qint64 targetInstant) override; void onEnterPanel() { m_sceneViewer->setFocus(Qt::OtherFocusReason); diff --git a/toonz/sources/toonz/flipbook.cpp b/toonz/sources/toonz/flipbook.cpp index ce2bf9c..ac550ac 100644 --- a/toonz/sources/toonz/flipbook.cpp +++ b/toonz/sources/toonz/flipbook.cpp @@ -1668,9 +1668,11 @@ else*/ /*! Set current level frame to image viewer. Add the view image in cache. */ -void FlipBook::onDrawFrame(int frame, const ImagePainter::VisualSettings &vs) { +void FlipBook::onDrawFrame(int frame, const ImagePainter::VisualSettings &vs, + QElapsedTimer *timer, qint64 targetInstant) { try { m_imageViewer->setVisual(vs); + m_imageViewer->setTimerAndTargetInstant(timer, targetInstant); TImageP img = getCurrentImage(frame); diff --git a/toonz/sources/toonz/flipbook.h b/toonz/sources/toonz/flipbook.h index 474550f..5424f69 100644 --- a/toonz/sources/toonz/flipbook.h +++ b/toonz/sources/toonz/flipbook.h @@ -238,7 +238,8 @@ public: void reset(); - void onDrawFrame(int frame, const ImagePainter::VisualSettings &vs) override; + void onDrawFrame(int frame, const ImagePainter::VisualSettings &vs, + QElapsedTimer *timer, qint64 targetInstant) override; void minimize(bool doMinimize); diff --git a/toonz/sources/toonz/imageviewer.cpp b/toonz/sources/toonz/imageviewer.cpp index 768e1c7..96f74f8 100644 --- a/toonz/sources/toonz/imageviewer.cpp +++ b/toonz/sources/toonz/imageviewer.cpp @@ -225,7 +225,8 @@ ImageViewer::ImageViewer(QWidget *parent, FlipBook *flipbook, , m_histogramPopup(0) , m_isRemakingPreviewFx(false) , m_rectRGBPick(false) - , m_firstImage(true) { + , m_firstImage(true) + , m_timer(nullptr) { m_visualSettings.m_sceneProperties = TApp::instance()->getCurrentScene()->getScene()->getProperties(); m_visualSettings.m_drawExternalBG = true; @@ -415,10 +416,13 @@ void ImageViewer::setImage(TImageP image) { if (m_isHistogramEnable && m_histogramPopup->isVisible()) m_histogramPopup->setImage(image); + + // make sure to redraw the frame here. + // repaint() does NOT immediately redraw the frame for QOpenGLWidget + update(); if (!isColorModel()) - repaint(); - else - update(); + qApp->processEvents(QEventLoop::ExcludeUserInputEvents | + QEventLoop::ExcludeSocketNotifiers); } //------------------------------------------------------------------- @@ -563,6 +567,12 @@ void ImageViewer::paintGL() { if (!m_image) { if (m_lutCalibrator && m_lutCalibrator->isValid()) m_lutCalibrator->onEndDraw(m_fbo); + if (m_timer && m_timer->isValid()) { + qint64 currentInstant = m_timer->nsecsElapsed(); + while (currentInstant < m_targetInstant) { + currentInstant = m_timer->nsecsElapsed(); + } + } return; } @@ -642,6 +652,14 @@ void ImageViewer::paintGL() { if (m_lutCalibrator && m_lutCalibrator->isValid()) m_lutCalibrator->onEndDraw(m_fbo); + + // wait to achieve precise fps + if (m_timer && m_timer->isValid()) { + qint64 currentInstant = m_timer->nsecsElapsed(); + while (currentInstant < m_targetInstant) { + currentInstant = m_timer->nsecsElapsed(); + } + } } //------------------------------------------------------------------------------ diff --git a/toonz/sources/toonz/imageviewer.h b/toonz/sources/toonz/imageviewer.h index 42700ad..55b9f94 100644 --- a/toonz/sources/toonz/imageviewer.h +++ b/toonz/sources/toonz/imageviewer.h @@ -17,6 +17,7 @@ class QOpenGLFramebufferObject; class LutCalibrator; class QTouchEvent; class QGestureEvent; +class QElapsedTimer; //----------------------------------------------------------------------------- //==================== @@ -79,6 +80,10 @@ class ImageViewer final : public GLWidgetForHighDpi { bool m_stylusUsed = false; bool m_firstInitialized = true; + // passed from PlaybackExecutor + QElapsedTimer *m_timer; + qint64 m_targetInstant; + int getDragType(const TPoint &pos, const TRect &loadBox); void updateLoadbox(const TPoint &curPos); void updateCursor(const TPoint &curPos); @@ -132,6 +137,11 @@ public: void changeSwapBehavior(bool enable); void invalidateCompHisto(); + void setTimerAndTargetInstant(QElapsedTimer *timer, qint64 target) { + m_timer = timer; + m_targetInstant = target; + } + protected: void contextMenuEvent(QContextMenuEvent *event) override; void initializeGL() override; diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp index 01758ad..98be921 100644 --- a/toonz/sources/toonz/sceneviewer.cpp +++ b/toonz/sources/toonz/sceneviewer.cpp @@ -780,7 +780,8 @@ SceneViewer::SceneViewer(ImageUtils::FullScreenWidget *parent) , m_editPreviewSubCamera(false) , m_locator(NULL) , m_isLocator(false) - , m_isBusyOnTabletMove(false) { + , m_isBusyOnTabletMove(false) + , m_timer(nullptr) { m_visualSettings.m_sceneProperties = TApp::instance()->getCurrentScene()->getScene()->getProperties(); #if defined(x64) @@ -1936,6 +1937,14 @@ void SceneViewer::paintGL() { if (!m_isPicking && m_lutCalibrator && m_lutCalibrator->isValid()) m_lutCalibrator->onEndDraw(m_fbo); + + // wait to achieve precise fps + if (m_timer && m_timer->isValid()) { + qint64 currentInstant = m_timer->nsecsElapsed(); + while (currentInstant < m_targetInstant) { + currentInstant = m_timer->nsecsElapsed(); + } + } } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/sceneviewer.h b/toonz/sources/toonz/sceneviewer.h index f79fdbb..4ad3e4a 100644 --- a/toonz/sources/toonz/sceneviewer.h +++ b/toonz/sources/toonz/sceneviewer.h @@ -39,6 +39,7 @@ class QTouchEvent; class QOpenGLFramebufferObject; class LutCalibrator; class StopMotion; +class QElapsedTimer; namespace ImageUtils { class FullScreenWidget; @@ -189,6 +190,10 @@ class SceneViewer final : public GLWidgetForHighDpi, bool m_firstInitialized = true; + // passed from PlaybackExecutor + QElapsedTimer *m_timer; + qint64 m_targetInstant; + public: enum ReferenceMode { NORMAL_REFERENCE = 1, @@ -290,6 +295,11 @@ public: void bindFBO() override; void releaseFBO() override; + void setTimerAndTargetInstant(QElapsedTimer *timer, qint64 target) { + m_timer = timer; + m_targetInstant = target; + } + public: // SceneViewer's gadget public functions TPointD winToWorld(const QPointF &pos) const; diff --git a/toonz/sources/toonz/viewerpane.cpp b/toonz/sources/toonz/viewerpane.cpp index 68f98fd..6923332 100644 --- a/toonz/sources/toonz/viewerpane.cpp +++ b/toonz/sources/toonz/viewerpane.cpp @@ -261,10 +261,13 @@ void SceneViewerPanel::onShowHideActionTriggered(QAction *act) { updateShowHide(); } -void SceneViewerPanel::onDrawFrame( - int frame, const ImagePainter::VisualSettings &settings) { +void SceneViewerPanel::onDrawFrame(int frame, + const ImagePainter::VisualSettings &settings, + QElapsedTimer *timer, qint64 targetInstant) { TApp *app = TApp::instance(); m_sceneViewer->setVisual(settings); + m_sceneViewer->setTimerAndTargetInstant(timer, targetInstant); + TFrameHandle *frameHandle = app->getCurrentFrame(); if (m_sceneViewer->isPreviewEnabled()) { @@ -298,6 +301,12 @@ void SceneViewerPanel::onDrawFrame( else if (settings.m_blankColor != TPixel::Transparent) m_sceneViewer->update(); + + // make sure to redraw the frame here. + // repaint() does NOT immediately redraw the frame for QOpenGLWidget + if (frameHandle->isPlaying()) + qApp->processEvents(QEventLoop::ExcludeUserInputEvents | + QEventLoop::ExcludeSocketNotifiers); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/viewerpane.h b/toonz/sources/toonz/viewerpane.h index 6197d74..c6a47d5 100644 --- a/toonz/sources/toonz/viewerpane.h +++ b/toonz/sources/toonz/viewerpane.h @@ -42,9 +42,9 @@ class SceneViewerPanel final : public QFrame, TPanelTitleBarButton *m_subcameraPreviewButton; bool m_onionSkinActive = false; UINT m_visiblePartsFlag; - bool m_playSound = true; - bool m_hasSoundtrack = false; - bool m_playing = false; + bool m_playSound = true; + bool m_hasSoundtrack = false; + bool m_playing = false; double m_fps; int m_viewerFps; double m_samplesPerFrame; @@ -62,23 +62,23 @@ public: // toggle show/hide of the widgets according to m_visiblePartsFlag void setVisiblePartsFlag(UINT flag); void updateShowHide(); - void addShowHideContextMenu(QMenu*); + void addShowHideContextMenu(QMenu *); - void onDrawFrame(int frame, - const ImagePainter::VisualSettings &settings) override; + void onDrawFrame(int frame, const ImagePainter::VisualSettings &settings, + QElapsedTimer *timer, qint64 targetInstant) override; void onEnterPanel() { - m_sceneViewer->setFocus(Qt::OtherFocusReason); - // activate shortcut key for this flipconsole - m_flipConsole->makeCurrent(); + m_sceneViewer->setFocus(Qt::OtherFocusReason); + // activate shortcut key for this flipconsole + m_flipConsole->makeCurrent(); } void onLeavePanel() { m_sceneViewer->clearFocus(); } // SaveLoadQSettings - virtual void save(QSettings& settings) const override; - virtual void load(QSettings& settings) override; + virtual void save(QSettings &settings) const override; + virtual void load(QSettings &settings) override; - void initializeTitleBar(TPanelTitleBar* titleBar); + void initializeTitleBar(TPanelTitleBar *titleBar); protected: void showEvent(QShowEvent *) override; @@ -92,7 +92,7 @@ protected: void enableFlipConsoleForCamerastand(bool on); void playAudioFrame(int frame); bool hasSoundtrack(); - void contextMenuEvent(QContextMenuEvent* event) override; + void contextMenuEvent(QContextMenuEvent *event) override; public slots: @@ -112,7 +112,7 @@ protected slots: void onFrameTypeChanged(); void onPlayingStatusChanged(bool playing); // for showing/hiding the parts - void onShowHideActionTriggered(QAction*); + void onShowHideActionTriggered(QAction *); void enableFullPreview(bool enabled); void enableSubCameraPreview(bool enabled); }; diff --git a/toonz/sources/toonzlib/imagepainter.cpp b/toonz/sources/toonzlib/imagepainter.cpp index f76ceb2..f4cc3d4 100644 --- a/toonz/sources/toonzlib/imagepainter.cpp +++ b/toonz/sources/toonzlib/imagepainter.cpp @@ -355,8 +355,9 @@ void Painter::doFlushRasterImages(const TRasterP &rin, int bg, if (m_vSettings.m_useTexture) bbox = m_bbox; else { - double delta = sqrt(fabs(m_finalAff.det())); - bbox = m_bbox.enlarge(delta) * viewRect; + // double delta = sqrt(fabs(m_finalAff.det())); + // bbox = m_bbox.enlarge(delta) * viewRect; + bbox = m_bbox * viewRect; } UCHAR chan = m_vSettings.m_colorMask; @@ -367,13 +368,23 @@ void Painter::doFlushRasterImages(const TRasterP &rin, int bg, // TRaster32P ras; TRasterP _rin = rin; TAffine aff; + bool is16bpc = false; if (m_vSettings.m_useTexture) { ras = _rin; aff = m_aff; // ras->clear(); } else { int lx = rect.getLx(), ly = rect.getLy(); + // when the "30bit display" preference option is enabled, + // image previewed in 16bpc is not dithered & converted to 8bpc, + // but is kept the channel depth as 16bpc. + if (_rin->getPixelSize() == 8) { + ras = TRaster64P(lx, ly); + is16bpc = true; + } else + ras = TRaster32P(lx, ly); + /* // Following lines are used to solve a problem that occurs with some // graphics cards! // It seems that the glReadPixels() function is very slow if the lx length @@ -391,23 +402,22 @@ void Painter::doFlushRasterImages(const TRasterP &rin, int bg, TGL_TYPE, backgroundRas->getRawData()); TRect r = rect - rect.getP00(); ras = backgroundRas->extract(r); - + */ aff = TTranslation(-rect.x0, -rect.y0) * m_finalAff; aff *= TTranslation(TPointD(0.5, 0.5)); // very quick and very dirty fix: // in camerastand the images seems // shifted of an half pixel...it's // a quickput approximation? - } - - // when the "30bit display" preference option is enabled, - // image previewed in 16bpc is not dithered & converted to 8bpc, - // but is kept the channel depth as 16bpc. - bool is16bpc = false; - if (_rin->getPixelSize() == 8) { - TRaster64P rasAux(ras->getLx(), ras->getLy()); - TRop::convert(rasAux, ras); - ras = rasAux; - is16bpc = true; + if (bg == 0x100000) + quickput(ras, buildCheckboard(bg, _rin->getSize()), m_palette, aff, + false); + else { + if (is16bpc) + ((TRaster64P)ras) + ->fill(bg == 0x40000 ? TPixel64::Black : TPixel64::White); + else + ((TRaster32P)ras)->fill(bg == 0x40000 ? TPixel::Black : TPixel::White); + } } ras->lock(); @@ -434,26 +444,6 @@ void Painter::doFlushRasterImages(const TRasterP &rin, int bg, ras->getSize(), true); ras->unlock(); } else { - if (bg == 0x100000) - quickput(ras, buildCheckboard(bg, _rin->getSize()), m_palette, aff, - false); - else { - int lx = (m_imageSize.lx == 0 ? _rin->getLx() : m_imageSize.lx); - int ly = (m_imageSize.ly == 0 ? _rin->getLy() : m_imageSize.ly); - - TRect rect = convert(aff * TRectD(0, 0, lx - 1, ly - 1)); - // Image size is a 0 point. Do nothing - if (rect.x0 == rect.x1 && rect.y0 == rect.y1) return; - - if (is16bpc) { - TRaster64P raux = ras->extract(rect); - raux->fill(bg == 0x40000 ? TPixel64::Black : TPixel64::White); - } else { - TRaster32P raux = ras->extract(rect); - raux->fill(bg == 0x40000 ? TPixel::Black : TPixel::White); - } - } - if (showChannelsOnMatte) quickput(ras, keepChannels(_rin, m_palette, chan), m_palette, m_vSettings.m_useTexture ? TAffine() : aff * TTranslation(offs), diff --git a/toonz/sources/toonzqt/flipconsole.cpp b/toonz/sources/toonzqt/flipconsole.cpp index f4b5bfa..6b15593 100644 --- a/toonz/sources/toonzqt/flipconsole.cpp +++ b/toonz/sources/toonzqt/flipconsole.cpp @@ -22,6 +22,8 @@ #include "../toonz/tapp.h" +#include + // Qt includes #include #include @@ -153,110 +155,98 @@ void PlaybackExecutor::resetFps(int fps) { m_fps = fps; } void PlaybackExecutor::run() { // (Daniele) // We'll build the fps considering an interval of roughly 1 second (the last - // one). - // However, the fps should be sampled at a faster rate. Each sample is taken - // at - // 1/4 second, and the last 4 samples data are stored to keep trace of the - // last - // second of playback. - - TStopWatch timer; - timer.start(); + // one). However, the fps should be sampled at a faster rate. Each sample is + // taken at 1/4 second, and the last 4 samples data are stored to keep trace + // of the last second of playback. + m_timer.start(); - TUINT32 timeResolution = - 250; // Use a sufficient sampling resolution (currently 1/4 sec). - // Fps calculation is made once per sample. + qint64 timeResolution = + 250 * 1000000; // Use a sufficient sampling resolution (currently 1/4 + // sec). Fps calculation is made once per sample. int fps = m_fps, currSample = 0; - TUINT32 playedFramesCount = 0; - TUINT32 loadedInstant, nextSampleInstant = timeResolution; - TUINT32 sampleTotalLoadingTime = 0; - - TUINT32 lastFrameCounts[4] = {0, 0, 0, - 0}; // Keep the last 4 'played frames' counts. - TUINT32 lastSampleInstants[4] = {0, 0, 0, - 0}; // Same for the last sampling instants - TUINT32 lastLoadingTimes[4] = {0, 0, 0, - 0}; // Same for total sample loading times + qint64 playedFramesCount = 0; + qint64 nextSampleInstant = timeResolution; - double targetFrameTime = - 1000.0 / abs(m_fps); // User-required time between frames + qint64 lastFrameCounts[4] = {0, 0, 0, + 0}; // Keep the last 4 'played frames' counts. + qint64 lastSampleInstants[4] = {0, 0, 0, + 0}; // Same for the last sampling instants - TUINT32 emissionInstant = 0; // Effective instant in which loading is invoked - double emissionInstantD = 0.0; // Double precision version of the above + qint64 targetFrameTime = + 1000000000 / (qint64)abs(m_fps); // User-required time between frames - double lastLoadingTime = 0.0; // Mean frame loading time in the last sample + qint64 emissionInstant = 0; // starting instant in which rendering is invoked + qint64 avgSwapTime = 0; // average time for swapping bufers + qint64 shortTermDelayAdjuster = + 0; // accumurate recent errors and adjust in short term while (!m_abort) { - emissionInstant = timer.getTotalTime(); - - // Draw the next frame - if (playedFramesCount) - emit nextFrame(fps); // Show the next frame, telling - // currently measured fps - - if (FlipConsole::m_areLinked) { - // In case there are linked consoles, update them too. - // Their load time must be included in the fps calculation. - int i, consolesCount = FlipConsole::m_visibleConsoles.size(); - for (i = 0; i < consolesCount; ++i) { - FlipConsole *console = FlipConsole::m_visibleConsoles.at(i); - if (console->isLinkable() && console != FlipConsole::m_currentConsole) - console->playbackExecutor().emitNextFrame(m_fps < 0 ? -fps : fps); - } - } - - //-------- Each nextFrame() blocks until the frame has been shown --------- - - ++playedFramesCount; - loadedInstant = timer.getTotalTime(); - sampleTotalLoadingTime += (loadedInstant - emissionInstant); - - // Recalculate data only after the specified time resolution has passed. - if (loadedInstant > nextSampleInstant) { - // Sampling instant. Perform calculations. - - // Store values - TUINT32 framesCount = playedFramesCount - lastFrameCounts[currSample]; - TUINT32 elapsedTime = loadedInstant - lastSampleInstants[currSample]; - double loadingTime = - (sampleTotalLoadingTime - lastLoadingTimes[currSample]) / - (double)framesCount; + emissionInstant = m_timer.nsecsElapsed(); + + if (emissionInstant > nextSampleInstant) { + // Fps calculation + qint64 framesCount = playedFramesCount - lastFrameCounts[currSample]; + qint64 elapsedTime = emissionInstant - lastSampleInstants[currSample]; + fps = troundp((long double)(1000000000 * framesCount) / + (long double)elapsedTime); + + targetFrameTime = + 1000000000 / (qint64)abs(m_fps); // m_fps could have changed... + + // estimate time for swapping buffers + qint64 avgSwapTimeD = (elapsedTime / framesCount) - targetFrameTime; + if (avgSwapTime - avgSwapTimeD > + 20000000) // Reset beyond, say, 20 msecs tolerance. + avgSwapTime = avgSwapTimeD; + else + avgSwapTime += avgSwapTimeD; + avgSwapTime = std::min(targetFrameTime, std::max(avgSwapTime, (qint64)0)); + // preapre for the next sampling lastFrameCounts[currSample] = playedFramesCount; - lastSampleInstants[currSample] = loadedInstant; - lastLoadingTimes[currSample] = sampleTotalLoadingTime; - - currSample = (currSample + 1) % 4; - nextSampleInstant = loadedInstant + timeResolution; - - // Rebuild current fps - fps = troundp((1000 * framesCount) / (double)elapsedTime); - targetFrameTime = 1000.0 / abs(m_fps); // m_fps could have changed... - - // In case the playback is too slow to keep the required pace, reset the - // emission timeline. - // Otherwise, it should be kept as the difference needs to be compensated - // to get the required fps. - if ((int)emissionInstant - (int)emissionInstantD > - 20) // Reset beyond, say, 20 msecs tolerance. - emissionInstantD = (double)loadedInstant - loadingTime; - else - emissionInstantD += - lastLoadingTime - - loadingTime; // Otherwise, just adapt to the new loading time + lastSampleInstants[currSample] = emissionInstant; + currSample = (currSample + 1) % 4; + nextSampleInstant = emissionInstant + timeResolution; + } - lastLoadingTime = loadingTime; + // draw the next frame + if (playedFramesCount) { + qint64 delayAdjust = shortTermDelayAdjuster / 4; + qint64 targetInstant = + emissionInstant + targetFrameTime - avgSwapTime - delayAdjust; + targetInstant = std::max(targetInstant, emissionInstant); + shortTermDelayAdjuster -= delayAdjust; + + // Show the next frame, telling currently measured fps + // The wait time will be inserted at the end of paintGL in order to + // achieve precise playback + emit nextFrame(fps, &m_timer, targetInstant); + + if (FlipConsole::m_areLinked) { + // In case there are linked consoles, update them too. + // Their load time must be included in the fps calculation. + int i, consolesCount = FlipConsole::m_visibleConsoles.size(); + for (i = 0; i < consolesCount; ++i) { + FlipConsole *console = FlipConsole::m_visibleConsoles.at(i); + if (console->isLinkable() && console != FlipConsole::m_currentConsole) + console->playbackExecutor().emitNextFrame(m_fps < 0 ? -fps : fps); + } + } } - // Calculate the new emission instant - emissionInstant = std::max((int)(emissionInstantD += targetFrameTime), 0); + //-------- Each nextFrame() blocks until the frame has been shown --------- + + // accumurate error and slightly adjust waiting time for subsequent frames + qint64 delay = m_timer.nsecsElapsed() - emissionInstant - targetFrameTime; + // just ignore a large error + if (delay < targetFrameTime) shortTermDelayAdjuster += delay; - // Sleep until the next emission instant has been reached - while (timer.getTotalTime() < emissionInstant) msleep(1); + ++playedFramesCount; } m_abort = false; + m_timer.invalidate(); } //========================================================================================== @@ -530,8 +520,10 @@ FlipConsole::FlipConsole(QVBoxLayout *mainLayout, std::vector gadgetsMask, applyCustomizeMask(); - bool ret = connect(&m_playbackExecutor, SIGNAL(nextFrame(int)), this, - SLOT(onNextFrame(int)), Qt::BlockingQueuedConnection); + bool ret = connect(&m_playbackExecutor, + SIGNAL(nextFrame(int, QElapsedTimer *, qint64)), this, + SLOT(onNextFrame(int, QElapsedTimer *, qint64)), + Qt::BlockingQueuedConnection); assert(ret); @@ -765,7 +757,8 @@ void FlipConsole::toggleLinked() { //---------------------------------------------------------------------------- -bool FlipConsole::drawBlanks(int from, int to) { +bool FlipConsole::drawBlanks(int from, int to, QElapsedTimer *timer, + qint64 target) { if (m_blanksCount == 0 || m_isPlay || m_framesCount <= 1) return false; // enable blanks only when the blank button is pressed @@ -781,7 +774,7 @@ bool FlipConsole::drawBlanks(int from, int to) { m_blanksToDraw = (m_blanksToDraw == 0 ? m_blanksCount : m_blanksToDraw - 1); m_settings.m_blankColor = m_blankColor; m_settings.m_drawBlankFrame = true; - m_consoleOwner->onDrawFrame(from, m_settings); + m_consoleOwner->onDrawFrame(from, m_settings, timer, target); m_settings.m_drawBlankFrame = false; return true; } @@ -792,17 +785,19 @@ bool FlipConsole::drawBlanks(int from, int to) { //---------------------------------------------------------------------------- -void FlipConsole::onNextFrame(int fps) { +void FlipConsole::onNextFrame(int fps, QElapsedTimer *timer, + qint64 targetInstant) { + if (playbackExecutor().isAborted()) return; if (fps < 0) // can be negative only if is a linked console; it means that // the master console is playing backward { bool reverse = m_reverse; m_reverse = true; fps = -fps; - playNextFrame(); + playNextFrame(timer, targetInstant); m_reverse = reverse; } else - playNextFrame(); + playNextFrame(timer, targetInstant); if (fps == -1) return; if (m_fpsLabel) @@ -818,7 +813,7 @@ void FlipConsole::onNextFrame(int fps) { //---------------------------------------------------------------------------- -void FlipConsole::playNextFrame() { +void FlipConsole::playNextFrame(QElapsedTimer *timer, qint64 targetInstant) { int from = m_from, to = m_to; if (m_markerFrom <= m_markerTo) from = m_markerFrom, to = m_markerTo; @@ -831,7 +826,7 @@ void FlipConsole::playNextFrame() { m_currentFrame = (m_reverse ? to : from); emit playStateChanged(false); } else { - if (drawBlanks(from, to)) return; + if (drawBlanks(from, to, timer, targetInstant)) return; if (m_reverse) m_currentFrame = @@ -845,7 +840,7 @@ void FlipConsole::playNextFrame() { m_editCurrFrame->setText(QString::number(m_currentFrame)); m_settings.m_blankColor = TPixel::Transparent; m_settings.m_recomputeIfNeeded = true; - m_consoleOwner->onDrawFrame(m_currentFrame, m_settings); + m_consoleOwner->onDrawFrame(m_currentFrame, m_settings, timer, targetInstant); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonzqt/intfield.cpp b/toonz/sources/toonzqt/intfield.cpp index 3ca87f5..16432a4 100644 --- a/toonz/sources/toonzqt/intfield.cpp +++ b/toonz/sources/toonzqt/intfield.cpp @@ -213,7 +213,7 @@ void IntLineEdit::setLineEditBackgroundColor(QColor color) { value = 255; // white QString sheet = - QString("background-color: rgb(") + QString::number(color.red()) + + QString("background-color: rgba(") + QString::number(color.red()) + QString(",") + QString::number(color.green()) + QString(",") + QString::number(color.blue()) + QString(",") + QString::number(color.alpha()) + @@ -298,7 +298,7 @@ IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide) m_slider = new QSlider(Qt::Horizontal, this); ret = ret && connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(onSliderChanged(int))); - ret = ret && connect(m_slider, SIGNAL(sliderReleased()), this, + ret = ret && connect(m_slider, SIGNAL(sliderReleased()), this, SLOT(onSliderReleased())); ret = ret && connect(m_lineEdit, SIGNAL(editingFinished()), this, @@ -406,7 +406,7 @@ int IntField::pos2value(int x) const { else if (posRatio <= 0.9) t = -0.26 + 0.4 * posRatio; else - t = -8.0 + 9.0 * posRatio; + t = -8.0 + 9.0 * posRatio; double sliderVal = (double)m_slider->minimum() + rangeSize * t; return (int)round(sliderVal * pow(0.1, NonLinearSliderPrecision)); }