From c730e51d1e7d37271a6c7b948f6d62806de6a3a0 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 11 Apr 2019 13:22:51 -0700 Subject: [PATCH 1/4] wip redo stencil work --- cmake/externals/LibOVR/CMakeLists.txt | 4 +- interface/src/Application.cpp | 24 +++++-- interface/src/Application.h | 6 ++ interface/src/SecondaryCamera.cpp | 11 +-- interface/src/graphics/GraphicsEngine.cpp | 4 ++ interface/src/ui/SnapshotAnimated.cpp | 67 ++++++++++--------- .../display-plugins/OpenGLDisplayPlugin.cpp | 9 +++ .../display-plugins/hmd/HmdDisplayPlugin.h | 2 + libraries/gpu/src/gpu/Frame.h | 3 + libraries/plugins/src/plugins/DisplayPlugin.h | 5 ++ .../render-utils/src/StencilMaskPass.cpp | 42 +++++++----- libraries/render-utils/src/StencilMaskPass.h | 13 ++-- libraries/render/src/render/Args.h | 5 ++ libraries/shared/src/RegisteredMetaTypes.cpp | 11 +++ libraries/shared/src/RegisteredMetaTypes.h | 4 ++ libraries/shared/src/StencilMaskMode.h | 18 +++++ plugins/oculus/CMakeLists.txt | 2 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 63 +++++++++++++++++ plugins/oculus/src/OculusBaseDisplayPlugin.h | 8 +++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 45 +++++++++++++ plugins/openvr/src/OpenVrDisplayPlugin.h | 8 +++ 21 files changed, 288 insertions(+), 66 deletions(-) create mode 100644 libraries/shared/src/StencilMaskMode.h diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index ae4cf6320e..481753f7e0 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -17,8 +17,8 @@ if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip - URL_MD5 06804ff9727b910dcd04a37c800053b5 + URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip + URL_MD5 1e3e8b2101387af07ff9c841d0ea285e CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" /CMakeLists.txt LOG_DOWNLOAD 1 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e809120a74..bc0feefb9a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6725,6 +6725,11 @@ void Application::updateRenderArgs(float deltaTime) { } } + appRenderArgs._renderArgs._stencilMaskMode = getActiveDisplayPlugin()->getStencilMaskMode(); + if (appRenderArgs._renderArgs._stencilMaskMode == StencilMaskMode::MESH) { + appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator(); + } + { QMutexLocker viewLocker(&_viewMutex); _myCamera.loadViewFrustum(_displayViewFrustum); @@ -8412,11 +8417,20 @@ void Application::loadAvatarBrowser() const { DependencyManager::get()->openTablet(); } +void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) { + std::lock_guard lock(_snapshotMutex); + _snapshotOperators.push(snapshotOperator); +} + +bool Application::takeSnapshotOperators(std::queue& snapshotOperators) { + std::lock_guard lock(_snapshotMutex); + _snapshotOperators.swap(snapshotOperators); + return !snapshotOperators.empty(); +} + void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { - postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] { - // Get a screenshot and save it - QString path = DependencyManager::get()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename, - TestScriptingInterface::getInstance()->getTestResultsLocation()); + addSnapshotOperator({ [notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) { + QString path = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); // If we're not doing an animated snapshot as well... if (!includeAnimated) { @@ -8428,7 +8442,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa // Get an animated GIF snapshot and save it SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get()); } - }); + }, aspectRatio }); } void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 99e57f1866..b2e59e97d4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -344,6 +344,10 @@ public: void toggleAwayMode(); #endif + using SnapshotOperator = std::pair, float>; + void addSnapshotOperator(const SnapshotOperator& snapshotOperator); + bool takeSnapshotOperators(std::queue& snapshotOperators); + signals: void svoImportRequested(const QString& url); @@ -788,6 +792,8 @@ private: AudioInjectorPointer _snapshotSoundInjector; SharedSoundPointer _snapshotSound; SharedSoundPointer _sampleSound; + std::mutex _snapshotMutex; + std::queue _snapshotOperators; DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin; QString _autoSwitchDisplayModeSupportedHMDPluginName; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 12c9636746..da2874a3f4 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -152,10 +152,12 @@ public: _cachedArgsPointer->_viewport = args->_viewport; _cachedArgsPointer->_displayMode = args->_displayMode; _cachedArgsPointer->_renderMode = args->_renderMode; + _cachedArgsPointer->_stencilMaskMode = args->_stencilMaskMode; args->_blitFramebuffer = destFramebuffer; args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight()); args->_displayMode = RenderArgs::MONO; args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE; + args->_stencilMaskMode = StencilMaskMode::NONE; gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) { batch.disableContextStereo(); @@ -255,10 +257,11 @@ public: void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) { auto args = renderContext->args; if (cachedArgs) { - args->_blitFramebuffer = cachedArgs->_blitFramebuffer; - args->_viewport = cachedArgs->_viewport; - args->_displayMode = cachedArgs->_displayMode; - args->_renderMode = cachedArgs->_renderMode; + args->_blitFramebuffer = cachedArgs->_blitFramebuffer; + args->_viewport = cachedArgs->_viewport; + args->_displayMode = cachedArgs->_displayMode; + args->_renderMode = cachedArgs->_renderMode; + args->_stencilMaskMode = cachedArgs->_stencilMaskMode; } args->popViewFrustum(); diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index c2137d3d97..1842c20e4e 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -244,6 +244,7 @@ void GraphicsEngine::render_performFrame() { finalFramebuffer = framebufferCache->getFramebuffer(); } + std::queue snapshotOperators; if (!_programsCompiled.load()) { gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) { batch.setFramebuffer(finalFramebuffer); @@ -271,11 +272,13 @@ void GraphicsEngine::render_performFrame() { PROFILE_RANGE(render, "/runRenderFrame"); renderArgs._hudOperator = displayPlugin->getHUDOperator(); renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture(); + renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators); renderArgs._blitFramebuffer = finalFramebuffer; render_runRenderFrame(&renderArgs); } } + qDebug() << "boop" << renderArgs._takingSnapshot << snapshotOperators.size(); auto frame = getGPUContext()->endFrame(); frame->frameIndex = _renderFrameCount; frame->framebuffer = finalFramebuffer; @@ -285,6 +288,7 @@ void GraphicsEngine::render_performFrame() { frameBufferCache->releaseFramebuffer(framebuffer); } }; + frame->snapshotOperators = snapshotOperators; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index 9d58d89385..1c5a25b68f 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -62,44 +62,45 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio 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); + SnapshotAnimated::app->addSnapshotOperator({ [](const QImage& snapshot) { + // 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 = snapshot.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 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)) { - SnapshotAnimated::snapshotAnimatedTimerRunning = false; + // If that was the last frame... + if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) { + SnapshotAnimated::snapshotAnimatedTimerRunning = false; - // Notify the user that we're processing the snapshot - // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. - emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); + // Notify the user that we're processing the snapshot + // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. + emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); - // Kick off the thread that'll pack the frames into the GIF - QtConcurrent::run(processFrames); - // 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; + // Kick off the thread that'll pack the frames into the GIF + QtConcurrent::run(processFrames); + // 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::aspectRatio }); } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index c536e6b6e2..1700ca9d54 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -723,6 +723,15 @@ void OpenGLDisplayPlugin::present() { compositeLayers(); } + { // If we have any snapshots this frame, handle them + PROFILE_RANGE_EX(render, "snapshotOperators", 0xffff00ff, frameId) + while (!_currentFrame->snapshotOperators.empty()) { + auto& snapshotOperator = _currentFrame->snapshotOperators.front(); + snapshotOperator.first(getScreenshot(snapshotOperator.second)); + _currentFrame->snapshotOperators.pop(); + } + } + // Take the composite framebuffer and send it to the output device { PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index d8c0ce8e1d..e952b1e8db 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -48,6 +48,8 @@ public: void pluginUpdate() override {}; + virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; } + signals: void hmdMountedChanged(); void hmdVisibleChanged(bool visible); diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 3787ebfacd..94c0919fdb 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -9,6 +9,7 @@ #define hifi_gpu_Frame_h #include +#include #include "Forward.h" #include "Batch.h" @@ -41,6 +42,8 @@ namespace gpu { /// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE FramebufferRecycler framebufferRecycler; + std::queue, float>> snapshotOperators; + protected: friend class Deserializer; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index aa52e57c3f..9db8fc995c 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -27,6 +27,7 @@ #include #include #include "Plugin.h" +#include "StencilMaskMode.h" class QOpenGLFramebufferObject; @@ -221,6 +222,10 @@ public: // for updating plugin-related commands. Mimics the input plugin. virtual void pluginUpdate() = 0; + virtual StencilMaskMode getStencilMaskMode() const { return StencilMaskMode::NONE; } + using StencilMaskMeshOperator = std::function; + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; } + signals: void recommendedFramebufferSizeChanged(const QSize& size); void resetSensorsRequested(); diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 7217a3e5eb..7c89e6b601 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -19,7 +19,6 @@ using namespace render; void PrepareStencil::configure(const Config& config) { _maskMode = config.maskMode; - _forceDraw = config.forceDraw; } graphics::MeshPointer PrepareStencil::getMesh() { @@ -43,6 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { auto state = std::make_shared(); drawMask(*state); state->setColorWriteMask(gpu::State::WRITE_NONE); + state->setCullMode(gpu::State::CullMode::CULL_NONE); _meshStencilPipeline = gpu::Pipeline::create(program, state); } @@ -64,8 +64,28 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { RenderArgs* args = renderContext->args; - // Only draw the stencil mask if in HMD mode or not forced. - if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) { + if (args->_takingSnapshot) { + return; + } + + StencilMaskMode maskMode = _maskMode; + std::function maskOperator = [this](gpu::Batch& batch) { + auto mesh = getMesh(); + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + }; + + if (maskMode == StencilMaskMode::NONE) { + maskMode = args->_stencilMaskMode; + maskOperator = args->_stencilMaskOperator; + } + + if (maskMode == StencilMaskMode::NONE || (maskMode == StencilMaskMode::MESH && !maskOperator)) { return; } @@ -74,20 +94,12 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F batch.setViewportTransform(args->_viewport); - if (_maskMode < 0) { - batch.setPipeline(getMeshStencilPipeline()); - - auto mesh = getMesh(); - batch.setIndexBuffer(mesh->getIndexBuffer()); - batch.setInputFormat((mesh->getVertexFormat())); - batch.setInputStream(0, mesh->getVertexStream()); - - // Draw - auto part = mesh->getPartBuffer().get(0); - batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); - } else { + if (maskMode == StencilMaskMode::PAINT) { batch.setPipeline(getPaintStencilPipeline()); batch.draw(gpu::TRIANGLE_STRIP, 4); + } else if (maskMode == StencilMaskMode::MESH) { + batch.setPipeline(getMeshStencilPipeline()); + maskOperator(batch); } }); } diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index a8e4d1e1f2..bca2ef17a5 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -15,17 +15,19 @@ #include #include #include +#include class PrepareStencilConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty) - Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty) + Q_PROPERTY(StencilMaskMode maskMode MEMBER maskMode NOTIFY dirty) public: PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {} - int maskMode { 0 }; - bool forceDraw { false }; + // -1 -> don't force drawing (fallback to render args mode) + // 0 -> force draw without mesh + // 1 -> force draw with mesh + StencilMaskMode maskMode { StencilMaskMode::NONE }; signals: void dirty(); @@ -66,8 +68,7 @@ private: graphics::MeshPointer _mesh; graphics::MeshPointer getMesh(); - int _maskMode { 0 }; - bool _forceDraw { false }; + StencilMaskMode _maskMode { StencilMaskMode::NONE }; }; diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index b5c98e3428..c4b823256e 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -16,6 +16,7 @@ #include #include +#include #include #include "Forward.h" @@ -133,6 +134,10 @@ namespace render { std::function _hudOperator; gpu::TexturePointer _hudTexture; + + bool _takingSnapshot { false }; + StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE }; + std::function _stencilMaskOperator; }; } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 597e537d8d..9a35796db8 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -40,6 +40,7 @@ int qMapURLStringMetaTypeId = qRegisterMetaType>(); int socketErrorMetaTypeId = qRegisterMetaType(); int voidLambdaType = qRegisterMetaType>(); int variantLambdaType = qRegisterMetaType>(); +int stencilModeMetaTypeId = qRegisterMetaType(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue); @@ -64,6 +65,8 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue); + + qScriptRegisterMetaType(engine, stencilMaskModeToScriptValue, stencilMaskModeFromScriptValue); } QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) { @@ -1283,4 +1286,12 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe } return toReturn; +} + +QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode) { + return engine->newVariant((int)stencilMode); +} + +void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode) { + stencilMode = StencilMaskMode(object.toVariant().toInt()); } \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ea2c5b8354..e663a9d90f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -25,6 +25,7 @@ #include "shared/Bilateral.h" #include "Transform.h" #include "PhysicsCollisionGroups.h" +#include "StencilMaskMode.h" class QColor; class QUrl; @@ -729,5 +730,8 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures); +Q_DECLARE_METATYPE(StencilMaskMode) +QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode); +void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode); #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/StencilMaskMode.h b/libraries/shared/src/StencilMaskMode.h new file mode 100644 index 0000000000..98372890ec --- /dev/null +++ b/libraries/shared/src/StencilMaskMode.h @@ -0,0 +1,18 @@ +// +// Created by Sam Gondelman on 3/26/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_StencilMaskMode_h +#define hifi_StencilMaskMode_h + +enum class StencilMaskMode { + NONE = -1, // for legacy reasons, this is -1 + PAINT = 0, + MESH = 1 +}; + +#endif // hifi_StencilMaskMode_h \ No newline at end of file diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index abce753b4d..6ddc75e1e5 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -18,7 +18,7 @@ if (WIN32 AND (NOT USE_GLES)) link_hifi_libraries( shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml plugins ui-plugins display-plugins input-plugins - audio-client networking render-utils + audio-client networking render-utils graphics ${PLATFORM_GL_BACKEND} ) include_hifi_library_headers(octree) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a67e3127e5..db26537a19 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -227,3 +227,66 @@ QVector OculusBaseDisplayPlugin::getSensorPositions() { return result; } + +DisplayPlugin::StencilMaskMeshOperator OculusBaseDisplayPlugin::getStencilMaskMeshOperator() { + if (_session) { + if (!_stencilMeshesInitialized) { + _stencilMeshesInitialized = true; + ovr::for_each_eye([&](ovrEyeType eye) { + ovrFovStencilDesc stencilDesc = { + ovrFovStencil_HiddenArea, 0, eye, + _eyeRenderDescs[eye].Fov, _eyeRenderDescs[eye].HmdToEyePose.Orientation + }; + // First we get the size of the buffer we need + ovrFovStencilMeshBuffer buffer = { 0, 0, nullptr, 0, 0, nullptr }; + ovrResult result = ovr_GetFovStencil(_session, &stencilDesc, &buffer); + if (!OVR_SUCCESS(result)) { + _stencilMeshesInitialized = false; + return; + } + + std::vector ovrVertices(buffer.UsedVertexCount); + std::vector ovrIndices(buffer.UsedIndexCount); + + // Now we populate the actual buffer + buffer = { (int)ovrVertices.size(), 0, ovrVertices.data(), (int)ovrIndices.size(), 0, ovrIndices.data() }; + result = ovr_GetFovStencil(_session, &stencilDesc, &buffer); + + if (!OVR_SUCCESS(result)) { + _stencilMeshesInitialized = false; + return; + } + + std::vector vertices; + vertices.reserve(ovrVertices.size()); + for (auto& ovrVertex : ovrVertices) { + // We need the vertices in clip space + vertices.emplace_back(ovrVertex.x - (1.0f - (float)eye), 2.0f * ovrVertex.y - 1.0f, 0.0f); + } + + std::vector indices; + indices.reserve(ovrIndices.size()); + for (auto& ovrIndex : ovrIndices) { + indices.push_back(ovrIndex); + } + + _stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data()); + }); + } + + if (_stencilMeshesInitialized) { + return [&](gpu::Batch& batch) { + for (auto& mesh : _stencilMeshes) { + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + } + }; + } + } + return nullptr; +} \ No newline at end of file diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 1abb7cdad7..d442c365ae 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -16,6 +16,8 @@ #define OVRPL_DISABLED #include +#include + class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: @@ -34,6 +36,9 @@ public: QRectF getPlayAreaRect() override; QVector getSensorPositions() override; + virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; } + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override; + protected: void customizeContext() override; void uncustomizeContext() override; @@ -52,4 +57,7 @@ protected: // ovrLayerEyeFovDepth _depthLayer; bool _hmdMounted { false }; bool _visible { true }; + + std::array _stencilMeshes; + bool _stencilMeshesInitialized { false }; }; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 11d941dcd0..cd318dd9b4 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -784,3 +784,48 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() { return QRectF(center.x, center.y, dimensions.x, dimensions.y); } + +DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() { + if (_system) { + if (!_stencilMeshesInitialized) { + _stencilMeshesInitialized = true; + for (auto eye : VR_EYES) { + vr::HiddenAreaMesh_t stencilMesh = _system->GetHiddenAreaMesh(eye); + if (stencilMesh.pVertexData && stencilMesh.unTriangleCount > 0) { + std::vector vertices; + std::vector indices; + + const int NUM_INDICES_PER_TRIANGLE = 3; + int numIndices = stencilMesh.unTriangleCount * NUM_INDICES_PER_TRIANGLE; + vertices.reserve(numIndices); + indices.reserve(numIndices); + for (int i = 0; i < numIndices; i++) { + vr::HmdVector2_t vertex2D = stencilMesh.pVertexData[i]; + // We need the vertices in clip space + vertices.emplace_back(vertex2D.v[0] - (1.0f - (float)eye), 2.0f * vertex2D.v[1] - 1.0f, 0.0f); + indices.push_back(i); + } + + _stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data()); + } else { + _stencilMeshesInitialized = false; + } + } + } + + if (_stencilMeshesInitialized) { + return [&](gpu::Batch& batch) { + for (auto& mesh : _stencilMeshes) { + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + } + }; + } + } + return nullptr; +} \ No newline at end of file diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 265f328920..4b042a700d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -13,6 +13,8 @@ #include +#include + const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only. namespace gl { @@ -67,6 +69,9 @@ public: QRectF getPlayAreaRect() override; + virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; } + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override; + protected: bool internalActivate() override; void internalDeactivate() override; @@ -94,4 +99,7 @@ private: bool _asyncReprojectionActive { false }; bool _hmdMounted { false }; + + std::array _stencilMeshes; + bool _stencilMeshesInitialized { false }; }; From e7d12dc4f88accac9da92bd3bec6fd1cc38fce7d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 17 Apr 2019 15:43:05 -0700 Subject: [PATCH 2/4] fix snapshots and preview --- interface/src/Application.cpp | 22 ++++-- interface/src/Application.h | 4 + interface/src/graphics/GraphicsEngine.cpp | 4 +- interface/src/ui/Snapshot.cpp | 74 ++++++++++--------- interface/src/ui/Snapshot.h | 2 + interface/src/ui/SnapshotAnimated.cpp | 30 ++++---- interface/src/ui/SnapshotAnimated.h | 3 +- .../src/display-plugins/NullDisplayPlugin.cpp | 10 +-- .../src/display-plugins/NullDisplayPlugin.h | 2 - .../display-plugins/OpenGLDisplayPlugin.cpp | 23 +++--- .../src/display-plugins/OpenGLDisplayPlugin.h | 5 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 2 +- libraries/gpu/src/gpu/Frame.h | 1 + libraries/plugins/src/plugins/DisplayPlugin.h | 4 - 14 files changed, 101 insertions(+), 85 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bc0feefb9a..7ad077151e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -8428,6 +8428,17 @@ bool Application::takeSnapshotOperators(std::queue& snapshotOp return !snapshotOperators.empty(); } +void Application::addSecondarySnapshotOperator(const SecondarySnapshotOperator& snapshotOperator) { + std::lock_guard lock(_snapshotMutex); + _secondarySnapshotOperators.push(snapshotOperator); +} + +bool Application::takeSecondarySnapshotOperators(std::queue& snapshotOperators) { + std::lock_guard lock(_snapshotMutex); + _secondarySnapshotOperators.swap(snapshotOperators); + return !snapshotOperators.empty(); +} + void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { addSnapshotOperator({ [notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) { QString path = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); @@ -8439,16 +8450,17 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa emit DependencyManager::get()->stillSnapshotTaken(path, notify); } } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) { - // Get an animated GIF snapshot and save it - SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get()); + qApp->postLambdaEvent([path, aspectRatio] { + // Get an animated GIF snapshot and save it + SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get()); + }); } }, aspectRatio }); } void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { - postLambdaEvent([notify, filename, this] { - QString snapshotPath = DependencyManager::get()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename, - TestScriptingInterface::getInstance()->getTestResultsLocation()); + addSecondarySnapshotOperator([notify, filename](const QImage& snapshot) { + QString snapshotPath = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); }); diff --git a/interface/src/Application.h b/interface/src/Application.h index b2e59e97d4..0004add3b1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -345,8 +345,11 @@ public: #endif using SnapshotOperator = std::pair, float>; + using SecondarySnapshotOperator = std::function; void addSnapshotOperator(const SnapshotOperator& snapshotOperator); bool takeSnapshotOperators(std::queue& snapshotOperators); + void addSecondarySnapshotOperator(const SecondarySnapshotOperator& snapshotOperator); + bool takeSecondarySnapshotOperators(std::queue& snapshotOperators); signals: void svoImportRequested(const QString& url); @@ -794,6 +797,7 @@ private: SharedSoundPointer _sampleSound; std::mutex _snapshotMutex; std::queue _snapshotOperators; + std::queue _secondarySnapshotOperators; DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin; QString _autoSwitchDisplayModeSupportedHMDPluginName; diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index 1842c20e4e..09471a9570 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -245,6 +245,7 @@ void GraphicsEngine::render_performFrame() { } std::queue snapshotOperators; + std::queue secondarySnapshotOperators; if (!_programsCompiled.load()) { gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) { batch.setFramebuffer(finalFramebuffer); @@ -273,12 +274,12 @@ void GraphicsEngine::render_performFrame() { renderArgs._hudOperator = displayPlugin->getHUDOperator(); renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture(); renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators); + qApp->takeSecondarySnapshotOperators(secondarySnapshotOperators); renderArgs._blitFramebuffer = finalFramebuffer; render_runRenderFrame(&renderArgs); } } - qDebug() << "boop" << renderArgs._takingSnapshot << snapshotOperators.size(); auto frame = getGPUContext()->endFrame(); frame->frameIndex = _renderFrameCount; frame->framebuffer = finalFramebuffer; @@ -289,6 +290,7 @@ void GraphicsEngine::render_performFrame() { } }; frame->snapshotOperators = snapshotOperators; + frame->secondarySnapshotOperators = secondarySnapshotOperators; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 60c039ce1f..94523fa6dd 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -159,47 +159,57 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN); _snapshotIndex = 0; + _taking360Snapshot = true; _snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL); } void Snapshot::takeNextSnapshot() { - SecondaryCameraJobConfig* config = - static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); + if (_taking360Snapshot) { + if (!_waitingOnSnapshot) { + _waitingOnSnapshot = true; + qApp->addSecondarySnapshotOperator([this](const QImage& snapshot) { + // Order is: + // 0. Down + // 1. Front + // 2. Left + // 3. Back + // 4. Right + // 5. Up + if (_snapshotIndex < 6) { + _imageArray[_snapshotIndex] = snapshot; + } - // Order is: - // 0. Down - // 1. Front - // 2. Left - // 3. Back - // 4. Right - // 5. Up - if (_snapshotIndex < 6) { - _imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot(); - } + SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); + if (_snapshotIndex == 0) { + // Setup for Front Image capture + config->setOrientation(CAMERA_ORIENTATION_FRONT); + } else if (_snapshotIndex == 1) { + // Setup for Left Image capture + config->setOrientation(CAMERA_ORIENTATION_LEFT); + } else if (_snapshotIndex == 2) { + // Setup for Back Image capture + config->setOrientation(CAMERA_ORIENTATION_BACK); + } else if (_snapshotIndex == 3) { + // Setup for Right Image capture + config->setOrientation(CAMERA_ORIENTATION_RIGHT); + } else if (_snapshotIndex == 4) { + // Setup for Up Image capture + config->setOrientation(CAMERA_ORIENTATION_UP); + } else if (_snapshotIndex == 5) { + _taking360Snapshot = false; + } - if (_snapshotIndex == 0) { - // Setup for Front Image capture - config->setOrientation(CAMERA_ORIENTATION_FRONT); - } else if (_snapshotIndex == 1) { - // Setup for Left Image capture - config->setOrientation(CAMERA_ORIENTATION_LEFT); - } else if (_snapshotIndex == 2) { - // Setup for Back Image capture - config->setOrientation(CAMERA_ORIENTATION_BACK); - } else if (_snapshotIndex == 3) { - // Setup for Right Image capture - config->setOrientation(CAMERA_ORIENTATION_RIGHT); - } else if (_snapshotIndex == 4) { - // Setup for Up Image capture - config->setOrientation(CAMERA_ORIENTATION_UP); - } else if (_snapshotIndex > 5) { + _waitingOnSnapshot = false; + _snapshotIndex++; + }); + } + } else { _snapshotTimer.stop(); // Reset secondary camera render config - static_cast( - qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping")) - ->setCurve(1); + SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1); config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height()); config->setProperty("attachedEntityId", _oldAttachedEntityId); config->setProperty("vFoV", _oldvFoV); @@ -217,8 +227,6 @@ void Snapshot::takeNextSnapshot() { QtConcurrent::run([this]() { convertToEquirectangular(); }); } } - - _snapshotIndex++; } void Snapshot::convertToCubemap() { diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 77bdfd4ac1..f13f4cd587 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -97,6 +97,8 @@ private: bool _cubemapOutputFormat; QTimer _snapshotTimer; qint16 _snapshotIndex; + bool _waitingOnSnapshot { false }; + bool _taking360Snapshot { false }; bool _oldEnabled; QVariant _oldAttachedEntityId; QVariant _oldOrientation; diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index 1c5a25b68f..03ec86e342 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -27,7 +27,6 @@ QString SnapshotAnimated::snapshotAnimatedPath; QString SnapshotAnimated::snapshotStillPath; QVector SnapshotAnimated::snapshotAnimatedFrameVector; QVector SnapshotAnimated::snapshotAnimatedFrameDelayVector; -Application* SnapshotAnimated::app; float SnapshotAnimated::aspectRatio; QSharedPointer SnapshotAnimated::snapshotAnimatedDM; GifWriter SnapshotAnimated::snapshotAnimatedGifWriter; @@ -36,12 +35,11 @@ GifWriter SnapshotAnimated::snapshotAnimatedGifWriter; Setting::Handle SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true); Setting::Handle SnapshotAnimated::snapshotAnimatedDuration("snapshotAnimatedDuration", SNAPSNOT_ANIMATED_DURATION_SECS); -void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer dm) { +void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, 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; @@ -62,7 +60,7 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio void SnapshotAnimated::captureFrames() { if (SnapshotAnimated::snapshotAnimatedTimerRunning) { - SnapshotAnimated::app->addSnapshotOperator({ [](const QImage& snapshot) { + qApp->addSnapshotOperator({ [](const QImage& snapshot) { // 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" @@ -86,21 +84,21 @@ void SnapshotAnimated::captureFrames() { // If that was the last frame... if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) { SnapshotAnimated::snapshotAnimatedTimerRunning = false; - - // Notify the user that we're processing the snapshot - // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. - emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); - - // Kick off the thread that'll pack the frames into the GIF - QtConcurrent::run(processFrames); - // 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::aspectRatio }); + } else { + // Notify the user that we're processing the snapshot + // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. + emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); + + // Kick off the thread that'll pack the frames into the GIF + QtConcurrent::run(processFrames); + // 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; } } diff --git a/interface/src/ui/SnapshotAnimated.h b/interface/src/ui/SnapshotAnimated.h index 87ce533fc3..15734f57c8 100644 --- a/interface/src/ui/SnapshotAnimated.h +++ b/interface/src/ui/SnapshotAnimated.h @@ -42,7 +42,6 @@ private: static QVector snapshotAnimatedFrameVector; static QVector snapshotAnimatedFrameDelayVector; static QSharedPointer snapshotAnimatedDM; - static Application* app; static float aspectRatio; static GifWriter snapshotAnimatedGifWriter; @@ -51,7 +50,7 @@ private: static void processFrames(); static void clearTempVariables(); public: - static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer dm); + static void saveSnapshotAnimated(QString pathStill, float aspectRatio, QSharedPointer dm); static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; }; static Setting::Handle alsoTakeAnimatedSnapshot; static Setting::Handle snapshotAnimatedDuration; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index 47a213cf71..fcd695bed8 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -25,12 +25,4 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) { if (frame) { _gpuContext->consumeFrameUpdates(frame); } -} - -QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const { - return QImage(); -} - -QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const { - return QImage(); -} +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index e4ff1b8b37..7aa763bab9 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -17,8 +17,6 @@ public: glm::uvec2 getRecommendedRenderSize() const override; void submitFrame(const gpu::FramePointer& newFrame) override; - QImage getScreenshot(float aspectRatio = 0.0f) const override; - QImage getSecondaryCameraScreenshot() const override; void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {}; void pluginUpdate() override {}; private: diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 1700ca9d54..f9b6deb846 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -732,6 +732,15 @@ void OpenGLDisplayPlugin::present() { } } + { // If we have any secondary camera snapshots this frame, handle them + PROFILE_RANGE_EX(render, "secondarySnapshotOperators", 0x00ff00ff, frameId) + while (!_currentFrame->secondarySnapshotOperators.empty()) { + auto& snapshotOperator = _currentFrame->secondarySnapshotOperators.front(); + snapshotOperator(getSecondaryCameraScreenshot()); + _currentFrame->secondarySnapshotOperators.pop(); + } + } + // Take the composite framebuffer and send it to the output device { PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId) @@ -796,7 +805,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) { return !!_displayTexture; } -QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { +QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) { auto size = _compositeFramebuffer->getSize(); if (isHmd()) { size.x /= 2; @@ -812,24 +821,18 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { corner.x = round((size.x - bestSize.x) / 2.0f); corner.y = round((size.y - bestSize.y) / 2.0f); } - auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32); - withOtherThreadContext([&] { - glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); - }); + getGLBackend()->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); return screenshot.mirrored(false, true); } -QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const { +QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() { auto textureCache = DependencyManager::get(); auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer(); gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight()); - auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(region.z, region.w, QImage::Format_ARGB32); - withOtherThreadContext([&] { - glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); - }); + getGLBackend()->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); return screenshot.mirrored(false, true); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 49a38ecb4c..e56804c151 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -60,8 +60,6 @@ public: virtual bool setDisplayTexture(const QString& name) override; virtual bool onDisplayTextureReset() { return false; }; - QImage getScreenshot(float aspectRatio = 0.0f) const override; - QImage getSecondaryCameraScreenshot() const override; float presentRate() const override; @@ -185,5 +183,8 @@ protected: // be serialized through this mutex mutable Mutex _presentMutex; float _hudAlpha{ 1.0f }; + + QImage getScreenshot(float aspectRatio); + QImage getSecondaryCameraScreenshot(); }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 321bcc3fd2..874454b391 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -199,7 +199,7 @@ void HmdDisplayPlugin::internalPresent() { float newWidth = sourceSize.x - shiftLeftBy; // Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center... - static float SCALE_WIDTH = 0.9f; + static float SCALE_WIDTH = 0.8f; static float SCALE_OFFSET = 2.0f; newWidth *= SCALE_WIDTH; shiftLeftBy *= SCALE_OFFSET; diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 94c0919fdb..541b9393d3 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -43,6 +43,7 @@ namespace gpu { FramebufferRecycler framebufferRecycler; std::queue, float>> snapshotOperators; + std::queue> secondarySnapshotOperators; protected: friend class Deserializer; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9db8fc995c..c88d76e661 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -173,10 +173,6 @@ public: return QRect(0, 0, recommendedSize.x, recommendedSize.y); } - // Fetch the most recently displayed image as a QImage - virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0; - virtual QImage getSecondaryCameraScreenshot() const = 0; - // will query the underlying hmd api to compute the most recent head pose virtual bool beginFrameRender(uint32_t frameIndex) { return true; } From 4fc97effda095a84131b89059d0a347a78511bfd Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 22 Apr 2019 16:22:18 -0700 Subject: [PATCH 3/4] combine snapshotOperator lists per sam's suggestions --- interface/src/Application.cpp | 22 ++++++------------- interface/src/Application.h | 7 ++---- interface/src/graphics/GraphicsEngine.cpp | 3 --- interface/src/ui/Snapshot.cpp | 4 ++-- interface/src/ui/SnapshotAnimated.cpp | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 15 +++++-------- libraries/gpu/src/gpu/Frame.h | 3 +-- 7 files changed, 18 insertions(+), 38 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 27c6ef2f64..f46d0d3ddd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -8441,23 +8441,15 @@ void Application::loadAvatarBrowser() const { void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) { std::lock_guard lock(_snapshotMutex); _snapshotOperators.push(snapshotOperator); + _hasPrimarySnapshot |= std::get<2>(snapshotOperator); } bool Application::takeSnapshotOperators(std::queue& snapshotOperators) { std::lock_guard lock(_snapshotMutex); + bool hasPrimarySnapshot = _hasPrimarySnapshot; + _hasPrimarySnapshot = false; _snapshotOperators.swap(snapshotOperators); - return !snapshotOperators.empty(); -} - -void Application::addSecondarySnapshotOperator(const SecondarySnapshotOperator& snapshotOperator) { - std::lock_guard lock(_snapshotMutex); - _secondarySnapshotOperators.push(snapshotOperator); -} - -bool Application::takeSecondarySnapshotOperators(std::queue& snapshotOperators) { - std::lock_guard lock(_snapshotMutex); - _secondarySnapshotOperators.swap(snapshotOperators); - return !snapshotOperators.empty(); + return hasPrimarySnapshot; } void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { @@ -8476,15 +8468,15 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get()); }); } - }, aspectRatio }); + }, aspectRatio, true }); } void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { - addSecondarySnapshotOperator([notify, filename](const QImage& snapshot) { + addSnapshotOperator({ [notify, filename](const QImage& snapshot) { QString snapshotPath = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); - }); + }, 0, false }); } void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 967790d034..12523f3108 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -345,12 +345,9 @@ public: void toggleAwayMode(); #endif - using SnapshotOperator = std::pair, float>; - using SecondarySnapshotOperator = std::function; + using SnapshotOperator = std::tuple, float, bool>; void addSnapshotOperator(const SnapshotOperator& snapshotOperator); bool takeSnapshotOperators(std::queue& snapshotOperators); - void addSecondarySnapshotOperator(const SecondarySnapshotOperator& snapshotOperator); - bool takeSecondarySnapshotOperators(std::queue& snapshotOperators); signals: void svoImportRequested(const QString& url); @@ -798,7 +795,7 @@ private: SharedSoundPointer _sampleSound; std::mutex _snapshotMutex; std::queue _snapshotOperators; - std::queue _secondarySnapshotOperators; + bool _hasPrimarySnapshot { false }; DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin; QString _autoSwitchDisplayModeSupportedHMDPluginName; diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index 09471a9570..267822baf2 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -245,7 +245,6 @@ void GraphicsEngine::render_performFrame() { } std::queue snapshotOperators; - std::queue secondarySnapshotOperators; if (!_programsCompiled.load()) { gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) { batch.setFramebuffer(finalFramebuffer); @@ -274,7 +273,6 @@ void GraphicsEngine::render_performFrame() { renderArgs._hudOperator = displayPlugin->getHUDOperator(); renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture(); renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators); - qApp->takeSecondarySnapshotOperators(secondarySnapshotOperators); renderArgs._blitFramebuffer = finalFramebuffer; render_runRenderFrame(&renderArgs); } @@ -290,7 +288,6 @@ void GraphicsEngine::render_performFrame() { } }; frame->snapshotOperators = snapshotOperators; - frame->secondarySnapshotOperators = secondarySnapshotOperators; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 94523fa6dd..93d9188cc8 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -168,7 +168,7 @@ void Snapshot::takeNextSnapshot() { if (_taking360Snapshot) { if (!_waitingOnSnapshot) { _waitingOnSnapshot = true; - qApp->addSecondarySnapshotOperator([this](const QImage& snapshot) { + qApp->addSnapshotOperator({ [this](const QImage& snapshot) { // Order is: // 0. Down // 1. Front @@ -202,7 +202,7 @@ void Snapshot::takeNextSnapshot() { _waitingOnSnapshot = false; _snapshotIndex++; - }); + }, 0, false }); } } else { _snapshotTimer.stop(); diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index 03ec86e342..f8f6a30635 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -86,7 +86,7 @@ void SnapshotAnimated::captureFrames() { SnapshotAnimated::snapshotAnimatedTimerRunning = false; } } - }, SnapshotAnimated::aspectRatio }); + }, SnapshotAnimated::aspectRatio, true }); } else { // Notify the user that we're processing the snapshot // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e80102ec85..4c3de004bd 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -725,20 +725,15 @@ void OpenGLDisplayPlugin::present() { PROFILE_RANGE_EX(render, "snapshotOperators", 0xffff00ff, frameId) while (!_currentFrame->snapshotOperators.empty()) { auto& snapshotOperator = _currentFrame->snapshotOperators.front(); - snapshotOperator.first(getScreenshot(snapshotOperator.second)); + if (std::get<2>(snapshotOperator)) { + std::get<0>(snapshotOperator)(getScreenshot(std::get<1>(snapshotOperator))); + } else { + std::get<0>(snapshotOperator)(getSecondaryCameraScreenshot()); + } _currentFrame->snapshotOperators.pop(); } } - { // If we have any secondary camera snapshots this frame, handle them - PROFILE_RANGE_EX(render, "secondarySnapshotOperators", 0x00ff00ff, frameId) - while (!_currentFrame->secondarySnapshotOperators.empty()) { - auto& snapshotOperator = _currentFrame->secondarySnapshotOperators.front(); - snapshotOperator(getSecondaryCameraScreenshot()); - _currentFrame->secondarySnapshotOperators.pop(); - } - } - // Take the composite framebuffer and send it to the output device { PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId) diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 541b9393d3..9aa99e50f7 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -42,8 +42,7 @@ namespace gpu { /// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE FramebufferRecycler framebufferRecycler; - std::queue, float>> snapshotOperators; - std::queue> secondarySnapshotOperators; + std::queue, float, bool>> snapshotOperators; protected: friend class Deserializer; From 48aef3e6d9ac0e79967ab18be4ef99c59f2c3951 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 23 Apr 2019 08:45:38 -0700 Subject: [PATCH 4/4] build error --- interface/src/Application.cpp | 10 +++++----- interface/src/ui/Snapshot.cpp | 4 ++-- interface/src/ui/SnapshotAnimated.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f46d0d3ddd..77c3d438a4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -8441,7 +8441,7 @@ void Application::loadAvatarBrowser() const { void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) { std::lock_guard lock(_snapshotMutex); _snapshotOperators.push(snapshotOperator); - _hasPrimarySnapshot |= std::get<2>(snapshotOperator); + _hasPrimarySnapshot = _hasPrimarySnapshot || std::get<2>(snapshotOperator); } bool Application::takeSnapshotOperators(std::queue& snapshotOperators) { @@ -8453,7 +8453,7 @@ bool Application::takeSnapshotOperators(std::queue& snapshotOp } void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { - addSnapshotOperator({ [notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) { + addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) { QString path = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); // If we're not doing an animated snapshot as well... @@ -8468,15 +8468,15 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get()); }); } - }, aspectRatio, true }); + }, aspectRatio, true)); } void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { - addSnapshotOperator({ [notify, filename](const QImage& snapshot) { + addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) { QString snapshotPath = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); - }, 0, false }); + }, 0.0f, false)); } void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 93d9188cc8..d97c401351 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -168,7 +168,7 @@ void Snapshot::takeNextSnapshot() { if (_taking360Snapshot) { if (!_waitingOnSnapshot) { _waitingOnSnapshot = true; - qApp->addSnapshotOperator({ [this](const QImage& snapshot) { + qApp->addSnapshotOperator(std::make_tuple([this](const QImage& snapshot) { // Order is: // 0. Down // 1. Front @@ -202,7 +202,7 @@ void Snapshot::takeNextSnapshot() { _waitingOnSnapshot = false; _snapshotIndex++; - }, 0, false }); + }, 0.0f, false)); } } else { _snapshotTimer.stop(); diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index f8f6a30635..b8cffca8ab 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -60,7 +60,7 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio void SnapshotAnimated::captureFrames() { if (SnapshotAnimated::snapshotAnimatedTimerRunning) { - qApp->addSnapshotOperator({ [](const QImage& snapshot) { + qApp->addSnapshotOperator(std::make_tuple([](const QImage& snapshot) { // 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" @@ -86,7 +86,7 @@ void SnapshotAnimated::captureFrames() { SnapshotAnimated::snapshotAnimatedTimerRunning = false; } } - }, SnapshotAnimated::aspectRatio, true }); + }, SnapshotAnimated::aspectRatio, true)); } else { // Notify the user that we're processing the snapshot // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.