From d8bb9f8d183fe8cfa2a064276e50feed05689ce4 Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Thu, 3 Dec 2015 13:00:36 -0800 Subject: [PATCH] Fixing screenshot functionality --- interface/src/Application.cpp | 23 +++--- .../src/display-plugins/NullDisplayPlugin.cpp | 6 ++ .../src/display-plugins/NullDisplayPlugin.h | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 77 +++++++++++++++++-- .../src/display-plugins/OpenGLDisplayPlugin.h | 10 ++- libraries/plugins/src/plugins/DisplayPlugin.h | 4 + 6 files changed, 105 insertions(+), 17 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 581fdbec5a..2c58156d2a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -44,6 +44,9 @@ #include <QtNetwork/QNetworkDiskCache> +#include <gl/Config.h> +#include <QtGui/QOpenGLContext> + #include <AccountManager.h> #include <AddressManager.h> #include <ApplicationVersion.h> @@ -150,8 +153,6 @@ #include "InterfaceParentFinder.h" -#include <gpu/GLBackend.h> -#include <QtGui/QOpenGLContext> // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -1091,7 +1092,6 @@ void Application::paintGL() { // update fps once a second if (now - _lastFramesPerSecondUpdate > USECS_PER_SECOND) { _fps = _framesPerSecond.getAverage(); - qDebug() << QString::number(_fps, 'g', 4); _lastFramesPerSecondUpdate = now; } @@ -1344,6 +1344,7 @@ void Application::paintGL() { } // Overlay Composition, needs to occur after screen space effects have completed + // FIXME migrate composition into the display plugins { PROFILE_RANGE(__FUNCTION__ "/compositor"); PerformanceTimer perfTimer("compositor"); @@ -1372,7 +1373,6 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); PerformanceTimer perfTimer("pluginOutput"); - uint64_t displayStart = usecTimestampNow(); auto primaryFramebuffer = framebufferCache->getPrimaryFramebuffer(); auto scratchFramebuffer = framebufferCache->getFramebuffer(); gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { @@ -1386,12 +1386,18 @@ void Application::paintGL() { }); auto finalTexturePointer = scratchFramebuffer->getRenderBuffer(0); GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer); - Q_ASSERT(0 != finalTexture); + Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture)); _lockedFramebufferMap[finalTexture] = scratchFramebuffer; + + uint64_t displayStart = usecTimestampNow(); Q_ASSERT(QOpenGLContext::currentContext() == _offscreenContext->getContext()); - displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size)); + { + PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene"); + PerformanceTimer perfTimer("pluginSubmitScene"); + displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size)); + } Q_ASSERT(QOpenGLContext::currentContext() == _offscreenContext->getContext()); uint64_t displayEnd = usecTimestampNow(); @@ -4499,13 +4505,12 @@ void Application::toggleLogDialog() { } void Application::takeSnapshot() { -#if 0 QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); - QString fileName = Snapshot::saveSnapshot(_glWidget->grabFrameBuffer()); + QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); AccountManager& accountManager = AccountManager::getInstance(); if (!accountManager.isLoggedIn()) { @@ -4516,7 +4521,6 @@ void Application::takeSnapshot() { _snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget); } _snapshotShareDialog->show(); -#endif } float Application::getRenderResolutionScale() const { @@ -4728,6 +4732,7 @@ void Application::updateDisplayMode() { bool first = true; foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); + // This must be a queued connection to avoid a deadlock QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, this, &Application::paintGL, Qt::QueuedConnection); diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index b0f02b1149..f780534bc9 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -9,7 +9,9 @@ // #include "NullDisplayPlugin.h" +#include <QtGui/QImage> #include <plugins/PluginContainer.h> + const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); const QString & NullDisplayPlugin::getName() const { @@ -33,3 +35,7 @@ void NullDisplayPlugin::submitOverlayTexture(uint32_t overlayTexture, const glm: } void NullDisplayPlugin::stop() {} + +QImage NullDisplayPlugin::getScreenshot() const { + return QImage(); +} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index c4052f38dd..23e23e2c4e 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -21,7 +21,7 @@ public: virtual bool hasFocus() const override; virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override; - + virtual QImage getScreenshot() const override; private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e18cc6c82f..22adeb8447 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -7,12 +7,15 @@ // #include "OpenGLDisplayPlugin.h" +#include <condition_variable> + #include <QtCore/QCoreApplication> #include <QtCore/QThread> #include <QtCore/QTimer> #include <QtOpenGL/QGLWidget> #include <QtGui/QOpenGLContext> +#include <QtGui/QImage> #include <NumericalConstants.h> #include <DependencyManager.h> @@ -23,7 +26,9 @@ class PresentThread : public QThread, public Dependency { using Mutex = std::mutex; + using Condition = std::condition_variable; using Lock = std::unique_lock<Mutex>; + friend class OpenGLDisplayPlugin; public: @@ -40,6 +45,25 @@ public: virtual void run() override { Q_ASSERT(_context); while (!_shutdown) { + if (_pendingMainThreadOperation) { + { + Lock lock(_mutex); + // Move the context to the main thread + _context->moveToThread(qApp->thread()); + _widgetContext->moveToThread(qApp->thread()); + _pendingMainThreadOperation = false; + // Release the main thread to do it's action + _condition.notify_one(); + } + + + { + // Main thread does it's thing while we wait on the lock to release + Lock lock(_mutex); + _condition.wait(lock, [&] { return _finishedMainThreadOperation; }); + } + } + // Check before lock if (_newPlugin != nullptr) { Lock lock(_mutex); @@ -69,17 +93,43 @@ public: } _context->doneCurrent(); + _widgetContext->moveToThread(qApp->thread()); _context->moveToThread(qApp->thread()); } + void withMainThreadContext(std::function<void()> f) { + // Signal to the thread that there is work to be done on the main thread + Lock lock(_mutex); + _pendingMainThreadOperation = true; + _finishedMainThreadOperation = false; + _condition.wait(lock, [&] { return !_pendingMainThreadOperation; }); + + _widgetContext->makeCurrent(); + f(); + _widgetContext->doneCurrent(); + + // restore control of the context to the presentation thread and signal + // the end of the operation + _widgetContext->moveToThread(this); + _context->moveToThread(this); + _finishedMainThreadOperation = true; + lock.unlock(); + _condition.notify_one(); + } + private: bool _shutdown { false }; Mutex _mutex; + // Used to allow the main thread to perform context operations + Condition _condition; + bool _pendingMainThreadOperation { false }; + bool _finishedMainThreadOperation { false }; QThread* _mainThread { nullptr }; OpenGLDisplayPlugin* _newPlugin { nullptr }; OpenGLDisplayPlugin* _activePlugin { nullptr }; QOpenGLContext* _context { nullptr }; + QGLContext* _widgetContext { nullptr }; }; OpenGLDisplayPlugin::OpenGLDisplayPlugin() { @@ -114,14 +164,16 @@ void OpenGLDisplayPlugin::activate() { if (!presentThread) { DependencyManager::set<PresentThread>(); presentThread = DependencyManager::get<PresentThread>(); + presentThread->setObjectName("Presentation Thread"); + auto widget = _container->getPrimaryWidget(); - auto glContext = widget->context(); - auto context = glContext->contextHandle(); - glContext->moveToThread(presentThread.data()); - context->moveToThread(presentThread.data()); // Move the OpenGL context to the present thread - presentThread->_context = context; + // Extra code because of the widget 'wrapper' context + presentThread->_widgetContext = widget->context(); + presentThread->_widgetContext->moveToThread(presentThread.data()); + presentThread->_context = presentThread->_widgetContext->contextHandle(); + presentThread->_context->moveToThread(presentThread.data()); // Start execution presentThread->start(); @@ -325,3 +377,18 @@ void OpenGLDisplayPlugin::swapBuffers() { static auto widget = _container->getPrimaryWidget(); widget->swapBuffers(); } + +void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const { + static auto presentThread = DependencyManager::get<PresentThread>(); + presentThread->withMainThreadContext(f); + _container->makeRenderingContextCurrent(); +} + +QImage OpenGLDisplayPlugin::getScreenshot() const { + QImage result; + withMainThreadContext([&] { + static auto widget = _container->getPrimaryWidget(); + result = widget->grabFrameBuffer(); + }); + return result; +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 4426bfd5ef..edbe7db006 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -31,17 +31,20 @@ public: virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override; virtual float presentRate() override; - virtual glm::uvec2 getRecommendedRenderSize() const { + virtual glm::uvec2 getRecommendedRenderSize() const override { return getSurfacePixels(); } - virtual glm::uvec2 getRecommendedUiSize() const { + virtual glm::uvec2 getRecommendedUiSize() const override { return getSurfaceSize(); } + virtual QImage getScreenshot() const override; + protected: friend class PresentThread; + virtual glm::uvec2 getSurfaceSize() const = 0; virtual glm::uvec2 getSurfacePixels() const = 0; @@ -53,6 +56,9 @@ protected: virtual void customizeContext(); virtual void uncustomizeContext(); virtual void cleanupForSceneTexture(uint32_t sceneTexture); + void withMainThreadContext(std::function<void()> f) const; + + void present(); void updateTextures(); void updateFramerate(); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 928b72970b..83afbc9402 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -14,6 +14,7 @@ #include <QtCore/QSize> #include <QtCore/QPoint> +class QImage; #include <GLMHelpers.h> #include <RegisteredMetaTypes.h> @@ -96,6 +97,9 @@ public: return baseProjection; } + // Fetch the most recently displayed image as a QImage + virtual QImage getScreenshot() const = 0; + // HMD specific methods // TODO move these into another class? virtual glm::mat4 getEyeToHeadTransform(Eye eye) const {