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 {