From 712048ce51de5233cd484214e52d10a8b11a0a8f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 24 Nov 2015 19:35:10 +0100 Subject: [PATCH 01/42] ignore other than left press events in edit.js, this makes the rightclick camera more confortable while in edit.js/edit mode --- examples/edit.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/edit.js b/examples/edit.js index 74551384c9..9b83079198 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -738,6 +738,10 @@ var mousePressStartPosition = { var mouseDown = false; function mousePressEvent(event) { + if (!event.isLeftButton) { + // If another mouse button than left is pressed ignore it + return false; + } mouseDown = true; mousePressStartPosition = { x: event.x, From a600df5ea646c0e41639e5aed07ac550aad672b4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 3 Dec 2015 16:04:39 -0800 Subject: [PATCH 02/42] try a different method of exagerating hand motion onto distanly held object --- examples/controllers/handControllerGrab.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index ebfa248f58..5763115bfc 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -34,7 +34,7 @@ var BUMPER_ON_VALUE = 0.5; // distant manipulation // -var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did @@ -659,6 +659,12 @@ function MyController(hand) { this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); this.handPreviousRotation = handRotation; + // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + this.actionID = NULL_ACTION_ID; this.actionID = Entities.addAction("spring", this.grabbedEntity, { targetPosition: this.currentObjectPosition, @@ -689,8 +695,6 @@ function MyController(hand) { this.currentAvatarOrientation = MyAvatar.orientation; this.overlayLineOff(); - - }; this.continueDistanceHolding = function() { @@ -719,8 +723,12 @@ function MyController(hand) { this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); // the action was set up on a previous call. update the targets. - var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * - DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); + var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + // how far did avatar move this timestep? var currentPosition = MyAvatar.position; var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); @@ -751,11 +759,11 @@ function MyController(hand) { var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); this.handRelativePreviousPosition = handToAvatar; - // magnify the hand movement but not the change from avatar movement & rotation + // magnify the hand movement but not the change from avatar movement & rotation handMoved = Vec3.subtract(handMoved, handMovementFromTurning); var superHandMoved = Vec3.multiply(handMoved, radius); - // Move the object by the magnified amount and then by amount from avatar movement & rotation + // Move the object by the magnified amount and then by amount from avatar movement & rotation var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); From 1b42c5a172267efe7e4b7ba92ff586e675e49ded Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 3 Dec 2015 16:16:01 -0800 Subject: [PATCH 03/42] Make MyAvatar.get/setAnimGraphUrl take a url(!), and make setting it actually do something. --- interface/src/avatar/MyAvatar.cpp | 18 +++++++++++++++--- interface/src/avatar/MyAvatar.h | 6 +++--- interface/src/ui/PreferencesDialog.cpp | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index eca39a0a44..1b1dc563c9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1283,6 +1283,15 @@ void MyAvatar::initHeadBones() { } } +void MyAvatar::setAnimGraphUrl(const QUrl& url) { + if (_animGraphUrl == url) { + return; + } + destroyAnimGraph(); + _animGraphUrl = url; + initAnimGraph(); +} + void MyAvatar::initAnimGraph() { // avatar.json // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 @@ -1299,16 +1308,19 @@ void MyAvatar::initAnimGraph() { // or run a local web-server // python -m SimpleHTTPServer& //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - auto graphUrl = QUrl(_animGraphUrl.isEmpty() ? - QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") : - _animGraphUrl); + auto graphUrl =_animGraphUrl.isEmpty() ? + QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") : + QUrl(_animGraphUrl); + qCDebug(interfaceapp) << "*** FIXME initAnimGraph" << graphUrl.toString(); _rig->initAnimGraph(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + qCDebug(interfaceapp) << "*** FIXME initAnimGraph ready" << graphUrl.toString(); } void MyAvatar::destroyAnimGraph() { + qCDebug(interfaceapp) << "*** FIXME destroyAnimGraph" << _animGraphUrl.toString(); _rig->destroyAnimGraph(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 13575388e3..3698ac02dc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -253,13 +253,13 @@ public slots: virtual void rebuildSkeletonBody() override; - const QString& getAnimGraphUrl() const { return _animGraphUrl; } + Q_INVOKABLE QUrl getAnimGraphUrl() const { return _animGraphUrl; } void setEnableDebugDrawDefaultPose(bool isEnabled); void setEnableDebugDrawAnimPose(bool isEnabled); void setEnableDebugDrawPosition(bool isEnabled); void setEnableMeshVisible(bool isEnabled); - void setAnimGraphUrl(const QString& url) { _animGraphUrl = url; } + Q_INVOKABLE void setAnimGraphUrl(const QUrl& url); glm::vec3 getPositionForAudio(); glm::quat getOrientationForAudio(); @@ -360,7 +360,7 @@ private: // Avatar Preferences QUrl _fullAvatarURLFromPreferences; QString _fullAvatarModelName; - QString _animGraphUrl {""}; + QUrl _animGraphUrl {""}; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c37755b823..a82fb39be7 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -190,7 +190,7 @@ void PreferencesDialog::loadPreferences() { ui.leanScaleSpin->setValue(myAvatar->getLeanScale()); ui.avatarScaleSpin->setValue(myAvatar->getScale()); - ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl()); + ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl().toString()); ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond()); From 187c213f416bb2a29d9e5f40ea916b3dc863c281 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 3 Dec 2015 16:42:57 -0800 Subject: [PATCH 04/42] reset skeleton --- interface/src/avatar/MyAvatar.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1b1dc563c9..2ccaf2d1c8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1288,6 +1288,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { return; } destroyAnimGraph(); + _skeletonModel.reset(); // Why is this necessary? Without this, we crash in the next render. _animGraphUrl = url; initAnimGraph(); } @@ -1311,16 +1312,13 @@ void MyAvatar::initAnimGraph() { auto graphUrl =_animGraphUrl.isEmpty() ? QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") : QUrl(_animGraphUrl); - qCDebug(interfaceapp) << "*** FIXME initAnimGraph" << graphUrl.toString(); _rig->initAnimGraph(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes - qCDebug(interfaceapp) << "*** FIXME initAnimGraph ready" << graphUrl.toString(); } void MyAvatar::destroyAnimGraph() { - qCDebug(interfaceapp) << "*** FIXME destroyAnimGraph" << _animGraphUrl.toString(); _rig->destroyAnimGraph(); } From 17b5ade9c039b7d76a4bc4f8a42c1f810cc8f22f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 3 Dec 2015 16:45:46 -0800 Subject: [PATCH 05/42] allow moving distantly grabbed objects with head --- examples/controllers/handControllerGrab.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 5763115bfc..bcb76716d9 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -37,6 +37,7 @@ var BUMPER_ON_VALUE = 0.5; var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did +var MOVE_WITH_HEAD = false; // experimental head-controll of distantly held objects var NO_INTERSECT_COLOR = { red: 10, @@ -658,6 +659,7 @@ function MyController(hand) { this.currentObjectTime = now; this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); this.handPreviousRotation = handRotation; + this.currentCameraOrientation = Camera.orientation; // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0); @@ -785,6 +787,16 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { x: 0.0, y: 0.0, z: objDistance }); + var after = Vec3.multiplyQbyV(Camera.orientation, { x: 0.0, y: 0.0, z: objDistance }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, From 786e241a29bbd0e86f34060e6c4fc3bb4d05c092 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Fri, 4 Dec 2015 10:14:40 -0800 Subject: [PATCH 06/42] blocks dont collide when held --- unpublishedScripts/hiddenEntityReset.js | 4 ++++ unpublishedScripts/masterReset.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 48745715f9..259cdd066c 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -1366,7 +1366,11 @@ userData: JSON.stringify({ resetMe: { resetMe: true + }, + grabbableKey; { + invertSolidWhileHeld: true } + }) }); diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 6a870f988f..2034bd9601 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -1346,6 +1346,9 @@ MasterReset = function() { userData: JSON.stringify({ resetMe: { resetMe: true + }, + grabbableKey: { + invertSolidWhileHeld: true } }) }); From b9ace94ff1fa8e0b86603c10afe3f20eb050d7ae Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 30 Nov 2015 09:35:18 -0800 Subject: [PATCH 07/42] Prototyping threaded present --- interface/resources/qml/Stats.qml | 7 +- interface/src/Application.cpp | 83 +++--- interface/src/Application.h | 6 + interface/src/GLCanvas.cpp | 21 -- interface/src/GLCanvas.h | 2 - interface/src/PluginContainerProxy.cpp | 87 ++++-- interface/src/PluginContainerProxy.h | 7 +- interface/src/avatar/AvatarUpdate.cpp | 3 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/ApplicationCompositor.cpp | 2 +- interface/src/ui/Stats.cpp | 8 +- interface/src/ui/Stats.h | 6 +- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 80 +++--- .../Basic2DWindowOpenGLDisplayPlugin.h | 9 +- .../src/display-plugins/DisplayPlugin.cpp | 30 +- .../src/display-plugins/NullDisplayPlugin.cpp | 13 +- .../src/display-plugins/NullDisplayPlugin.h | 6 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 258 +++++++++++++++--- .../src/display-plugins/OpenGLDisplayPlugin.h | 66 +++-- .../WindowOpenGLDisplayPlugin.cpp | 28 +- .../WindowOpenGLDisplayPlugin.h | 12 +- .../openvr/OpenVrDisplayPlugin.cpp | 40 +-- .../openvr/OpenVrDisplayPlugin.h | 6 +- .../stereo/InterleavedStereoDisplayPlugin.cpp | 8 +- .../stereo/InterleavedStereoDisplayPlugin.h | 3 +- libraries/gl/src/gl/GLEscrow.h | 24 +- libraries/plugins/src/plugins/DisplayPlugin.h | 30 +- .../plugins/src/plugins/PluginContainer.h | 11 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 28 +- plugins/oculus/src/OculusBaseDisplayPlugin.h | 8 +- .../oculus/src/OculusDebugDisplayPlugin.cpp | 10 - plugins/oculus/src/OculusDebugDisplayPlugin.h | 3 - plugins/oculus/src/OculusDisplayPlugin.cpp | 60 ++-- plugins/oculus/src/OculusDisplayPlugin.h | 11 +- plugins/oculus/src/OculusHelpers.cpp | 1 - .../src/OculusLegacyDisplayPlugin.h | 1 - tests/controllers/src/main.cpp | 7 +- 37 files changed, 600 insertions(+), 387 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 84381cc754..56d4f9c14b 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -45,7 +45,12 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Framerate: " + root.framerate + text: "Render Rate: " + root.renderrate + } + Text { + color: root.fontColor; + font.pixelSize: root.fontSize + text: "Present Rate: " + root.presentrate } Text { color: root.fontColor; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c552feb51b..581fdbec5a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -150,6 +150,8 @@ #include "InterfaceParentFinder.h" +#include +#include // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -1089,6 +1091,7 @@ 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; } @@ -1136,7 +1139,7 @@ void Application::paintGL() { _lastInstantaneousFps = instantaneousFps; auto displayPlugin = getActiveDisplayPlugin(); - displayPlugin->preRender(); + // FIXME not needed anymore? _offscreenContext->makeCurrent(); // update the avatar with a fresh HMD pose @@ -1304,6 +1307,13 @@ void Application::paintGL() { auto baseProjection = renderArgs._viewFrustum->getProjection(); auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); + + // Tell the plugin what pose we're using to render. In this case we're just using the + // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it + // for rotational timewarp. If we move to support positonal timewarp, we need to + // ensure this contains the full pose composed with the eye offsets. + mat4 headPose = displayPlugin->getHeadPose(_frameCount); + // FIXME we probably don't need to set the projection matrix every frame, // only when the display plugin changes (or in non-HMD modes when the user // changes the FOV manually, which right now I don't think they can. @@ -1319,12 +1329,7 @@ void Application::paintGL() { mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale); eyeOffsets[eye] = eyeOffsetTransform; - // Tell the plugin what pose we're using to render. In this case we're just using the - // unmodified head pose because the only plugin that cares (the Oculus plugin) uses it - // for rotational timewarp. If we move to support positonal timewarp, we need to - // ensure this contains the full pose composed with the eye offsets. - mat4 headPose = displayPlugin->getHeadPose(); - displayPlugin->setEyeRenderPose(eye, headPose); + displayPlugin->setEyeRenderPose(_frameCount, eye, headPose); eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); @@ -1367,44 +1372,35 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); PerformanceTimer perfTimer("pluginOutput"); - auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); - GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); - // Ensure the rendering context commands are completed when rendering - GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - // Ensure the sync object is flushed to the driver thread before releasing the context - // CRITICAL for the mac driver apparently. - glFlush(); - _offscreenContext->doneCurrent(); - - // Switches to the display plugin context - displayPlugin->preDisplay(); - // Ensure all operations from the previous context are complete before we try to read the fbo - glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(sync); uint64_t displayStart = usecTimestampNow(); + auto primaryFramebuffer = framebufferCache->getPrimaryFramebuffer(); + auto scratchFramebuffer = framebufferCache->getFramebuffer(); + gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { + gpu::Vec4i rect; + rect.z = size.width(); + rect.w = size.height(); + batch.setFramebuffer(scratchFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); + batch.blit(primaryFramebuffer, rect, scratchFramebuffer, rect); + batch.setFramebuffer(nullptr); + }); + auto finalTexturePointer = scratchFramebuffer->getRenderBuffer(0); + GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer); - { - PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); - PerformanceTimer perfTimer("pluginDisplay"); - displayPlugin->display(finalTexture, toGlm(size)); - } + Q_ASSERT(0 != finalTexture); + Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture)); + _lockedFramebufferMap[finalTexture] = scratchFramebuffer; + Q_ASSERT(QOpenGLContext::currentContext() == _offscreenContext->getContext()); + displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size)); + Q_ASSERT(QOpenGLContext::currentContext() == _offscreenContext->getContext()); - { - PROFILE_RANGE(__FUNCTION__ "/bufferSwap"); - PerformanceTimer perfTimer("bufferSwap"); - displayPlugin->finishFrame(); - } uint64_t displayEnd = usecTimestampNow(); const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs _lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND; - } { - PerformanceTimer perfTimer("makeCurrent"); - _offscreenContext->makeCurrent(); Stats::getInstance()->setRenderDetails(renderArgs._details); - // Reset the gpu::Context Stages // Back to the default framebuffer; gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) { @@ -2612,7 +2608,7 @@ void Application::updateMyAvatarLookAtPosition() { lookAtPosition.x = -lookAtPosition.x; } if (isHMD) { - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); + glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(_frameCount); glm::quat hmdRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); } else { @@ -4503,6 +4499,7 @@ 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())); @@ -4519,7 +4516,7 @@ void Application::takeSnapshot() { _snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget); } _snapshotShareDialog->show(); - +#endif } float Application::getRenderResolutionScale() const { @@ -4702,10 +4699,6 @@ const DisplayPlugin* Application::getActiveDisplayPlugin() const { return ((Application*)this)->getActiveDisplayPlugin(); } -bool _activatingDisplayPlugin{ false }; -QVector> _currentDisplayPluginActions; -QVector> _currentInputPluginActions; - static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); QString name = displayPlugin->getName(); @@ -4735,9 +4728,9 @@ void Application::updateDisplayMode() { bool first = true; foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); - QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, [this] { - paintGL(); - }); + QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, + this, &Application::paintGL, Qt::QueuedConnection); + QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { resizeGL(); }); @@ -4917,7 +4910,7 @@ mat4 Application::getEyeOffset(int eye) const { mat4 Application::getHMDSensorPose() const { if (isHMDMode()) { - return getActiveDisplayPlugin()->getHeadPose(); + return getActiveDisplayPlugin()->getHeadPose(_frameCount); } return mat4(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index e2445f7f22..1af252de95 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -158,6 +158,7 @@ public: bool isForeground() const { return _isForeground; } + uint32_t getFrameCount() { return _frameCount; } float getFps() const { return _fps; } float const HMD_TARGET_FRAME_RATE = 75.0f; float const DESKTOP_TARGET_FRAME_RATE = 60.0f; @@ -425,6 +426,11 @@ private: DisplayPluginPointer _displayPlugin; InputPluginList _activeInputPlugins; + bool _activatingDisplayPlugin { false }; + QVector> _currentDisplayPluginActions; + QVector> _currentInputPluginActions; + QMap _lockedFramebufferMap; + MainWindow* _window; ToolWindow* _toolWindow; diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 0b4c6dde3d..ec96f7c5d4 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -13,27 +13,6 @@ #include "Application.h" #include "GLCanvas.h" -#include - -#include "MainWindow.h" -#include "Menu.h" - -void GLCanvas::paintGL() { - PROFILE_RANGE(__FUNCTION__); - - // FIXME - I'm not sure why this still remains, it appears as if this GLCanvas gets a single paintGL call near - // the beginning of the application starting up. I'm not sure if we really need to call Application::paintGL() - // in this case, since the display plugins eventually handle all the painting - bool isThrottleFPSEnabled = Menu::getInstance()->isOptionChecked(MenuOption::ThrottleFPSIfNotFocus); - if (!qApp->getWindow()->isMinimized() || !isThrottleFPSEnabled) { - qApp->paintGL(); - } -} - -void GLCanvas::resizeGL(int width, int height) { - qApp->resizeGL(); -} - bool GLCanvas::event(QEvent* event) { if (QEvent::Paint == event->type() && qApp->isAboutToQuit()) { return true; diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 0442159eeb..f707046c7c 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -18,8 +18,6 @@ class GLCanvas : public GLWidget { Q_OBJECT protected: - virtual void paintGL() override; - virtual void resizeGL(int width, int height) override; virtual bool event(QEvent* event) override; }; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index 2e5c883897..8774eecd77 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -1,17 +1,23 @@ #include "PluginContainerProxy.h" -#include -#include +#include +#include #include #include #include +#include +#include +#include #include "Application.h" #include "MainWindow.h" #include "GLCanvas.h" #include "ui/DialogsManager.h" +#include +#include + PluginContainerProxy::PluginContainerProxy() { } @@ -36,30 +42,31 @@ extern QVector> _currentInputPluginActions; std::map _exclusiveGroups; QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { - auto menu = Menu::getInstance(); - MenuWrapper* parentItem = menu->getMenu(path); - QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); - if (!groupName.isEmpty()) { - QActionGroup* group{ nullptr }; - if (!_exclusiveGroups.count(groupName)) { - group = _exclusiveGroups[groupName] = new QActionGroup(menu); - group->setExclusive(true); - } else { - group = _exclusiveGroups[groupName]; - } - group->addAction(action); - } - connect(action, &QAction::triggered, [=] { - onClicked(action->isChecked()); - }); - action->setCheckable(checkable); - action->setChecked(checked); - if (_activatingDisplayPlugin) { - _currentDisplayPluginActions.push_back({ path, name }); - } else { - _currentInputPluginActions.push_back({ path, name }); - } - return action; + //auto menu = Menu::getInstance(); + //MenuWrapper* parentItem = menu->getMenu(path); + //QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); + //if (!groupName.isEmpty()) { + // QActionGroup* group{ nullptr }; + // if (!_exclusiveGroups.count(groupName)) { + // group = _exclusiveGroups[groupName] = new QActionGroup(menu); + // group->setExclusive(true); + // } else { + // group = _exclusiveGroups[groupName]; + // } + // group->addAction(action); + //} + //connect(action, &QAction::triggered, [=] { + // onClicked(action->isChecked()); + //}); + //action->setCheckable(checkable); + //action->setChecked(checked); + //if (_activatingDisplayPlugin) { + // _currentDisplayPluginActions.push_back({ path, name }); + //} else { + // _currentInputPluginActions.push_back({ path, name }); + //} + //return action; + return nullptr; } void PluginContainerProxy::removeMenuItem(const QString& menuName, const QString& menuItem) { @@ -150,10 +157,36 @@ void PluginContainerProxy::showDisplayPluginsTools() { DependencyManager::get()->hmdTools(true); } -QGLWidget* PluginContainerProxy::getPrimarySurface() { +QGLWidget* PluginContainerProxy::getPrimaryWidget() { return qApp->_glWidget; } +QWindow* PluginContainerProxy::getPrimaryWindow() { + return qApp->_glWidget->windowHandle(); +} + +QOpenGLContext* PluginContainerProxy::getPrimaryContext() { + return qApp->_glWidget->context()->contextHandle(); +} + const DisplayPlugin* PluginContainerProxy::getActiveDisplayPlugin() const { return qApp->getActiveDisplayPlugin(); } + +bool PluginContainerProxy::makeRenderingContextCurrent() { + return qApp->_offscreenContext->makeCurrent(); +} + +void PluginContainerProxy::releaseSceneTexture(uint32_t texture) { + Q_ASSERT(QThread::currentThread() == qApp->thread()); + auto& framebufferMap = qApp->_lockedFramebufferMap; + Q_ASSERT(framebufferMap.contains(texture)); + auto framebufferPointer = framebufferMap[texture]; + framebufferMap.remove(texture); + auto framebufferCache = DependencyManager::get(); + framebufferCache->releaseFramebuffer(framebufferPointer); +} + +void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) { + +} diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index 79f8287b66..5cc1cc8583 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -22,7 +22,12 @@ class PluginContainerProxy : public QObject, PluginContainer { virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override; virtual void showDisplayPluginsTools() override; virtual void requestReset() override; - virtual QGLWidget* getPrimarySurface() override; + virtual bool makeRenderingContextCurrent() override; + virtual void releaseSceneTexture(uint32_t texture) override; + virtual void releaseOverlayTexture(uint32_t texture) override; + virtual QGLWidget* getPrimaryWidget() override; + virtual QWindow* getPrimaryWindow() override; + virtual QOpenGLContext* getPrimaryContext() override; virtual bool isForeground() override; virtual const DisplayPlugin* getActiveDisplayPlugin() const override; diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 52fa568879..99e5d2acaa 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -30,7 +30,8 @@ void AvatarUpdate::synchronousProcess() { // Keep our own updated value, so that our asynchronous code can consult it. _isHMDMode = qApp->isHMDMode(); - _headPose = qApp->getActiveDisplayPlugin()->getHeadPose(); + auto frameCount = qApp->getFrameCount(); + _headPose = qApp->getActiveDisplayPlugin()->getHeadPose(frameCount); if (_updateBillboard) { DependencyManager::get()->getMyAvatar()->doUpdateBillboard(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5f43b834ec..1a098ec5f6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1200,7 +1200,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl if (qApp->isHMDMode()) { glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); - glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose(); + glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose(qApp->getFrameCount()); glm::mat4 leftEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Left); leftEyePose = leftEyePose * headPose; glm::vec3 leftEyePosition = extractTranslation(leftEyePose); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index eb43b9d864..4cee190a47 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -287,7 +287,7 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int mat4 camMat; _cameraBaseTransform.getMatrix(camMat); auto displayPlugin = qApp->getActiveDisplayPlugin(); - auto headPose = displayPlugin->getHeadPose(); + auto headPose = displayPlugin->getHeadPose(qApp->getFrameCount()); auto eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye); camMat = (headPose * eyeToHead) * camMat; batch.setViewportTransform(renderArgs->_viewport); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 12692698e7..358dc49bdb 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "BandwidthRecorder.h" #include "Menu.h" @@ -118,7 +119,12 @@ void Stats::updateStats(bool force) { STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange()); STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, nodeList->size()); - STAT_UPDATE(framerate, (int)qApp->getFps()); + STAT_UPDATE(renderrate, (int)qApp->getFps()); + if (qApp->getActiveDisplayPlugin()) { + STAT_UPDATE(presentrate, (int)qApp->getActiveDisplayPlugin()->presentRate()); + } else { + STAT_UPDATE(presentrate, -1); + } STAT_UPDATE(simrate, (int)qApp->getAverageSimsPerSecond()); STAT_UPDATE(avatarSimrate, (int)qApp->getAvatarSimrate()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index d1c0dd19d7..eb28883001 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -32,7 +32,8 @@ class Stats : public QQuickItem { Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) STATS_PROPERTY(int, serverCount, 0) - STATS_PROPERTY(int, framerate, 0) + STATS_PROPERTY(int, renderrate, 0) + STATS_PROPERTY(int, presentrate, 0) STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) @@ -115,7 +116,8 @@ signals: void expandedChanged(); void timingExpandedChanged(); void serverCountChanged(); - void framerateChanged(); + void renderrateChanged(); + void presentrateChanged(); void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 9366ec4403..36216f8912 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -30,44 +30,41 @@ const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { return NAME; } -std::vector _framerateActions; -QAction* _vsyncAction{ nullptr }; - void Basic2DWindowOpenGLDisplayPlugin::activate() { - _framerateActions.clear(); - _container->addMenuItem(MENU_PATH(), FULLSCREEN, - [this](bool clicked) { - if (clicked) { - _container->setFullscreen(getFullscreenTarget()); - } else { - _container->unsetFullscreen(); - } - }, true, false); - _container->addMenu(FRAMERATE); - _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_UNLIMITED, - [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); - _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_60, - [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_50, - [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_40, - [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - _framerateActions.push_back( - _container->addMenuItem(FRAMERATE, FRAMERATE_30, - [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + //_framerateActions.clear(); + //_container->addMenuItem(MENU_PATH(), FULLSCREEN, + // [this](bool clicked) { + // if (clicked) { + // _container->setFullscreen(getFullscreenTarget()); + // } else { + // _container->unsetFullscreen(); + // } + // }, true, false); + //_container->addMenu(FRAMERATE); + //_framerateActions.push_back( + // _container->addMenuItem(FRAMERATE, FRAMERATE_UNLIMITED, + // [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); + //_framerateActions.push_back( + // _container->addMenuItem(FRAMERATE, FRAMERATE_60, + // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + //_framerateActions.push_back( + // _container->addMenuItem(FRAMERATE, FRAMERATE_50, + // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + //_framerateActions.push_back( + // _container->addMenuItem(FRAMERATE, FRAMERATE_40, + // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + //_framerateActions.push_back( + // _container->addMenuItem(FRAMERATE, FRAMERATE_30, + // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); WindowOpenGLDisplayPlugin::activate(); - // Vsync detection happens in the parent class activate, so we need to check after that - if (_vsyncSupported) { - _vsyncAction = _container->addMenuItem(MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); - } else { - _vsyncAction = nullptr; - } + //// Vsync detection happens in the parent class activate, so we need to check after that + //if (_vsyncSupported) { + // _vsyncAction = _container->addMenuItem(MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); + //} else { + // _vsyncAction = nullptr; + //} updateFramerate(); } @@ -76,19 +73,18 @@ void Basic2DWindowOpenGLDisplayPlugin::deactivate() { WindowOpenGLDisplayPlugin::deactivate(); } -void Basic2DWindowOpenGLDisplayPlugin::display(GLuint sceneTexture, const glm::uvec2& sceneSize) { +void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { if (_vsyncAction) { - bool wantVsync = _vsyncAction->isChecked(); - bool vsyncEnabed = isVsyncEnabled(); - if (vsyncEnabed ^ wantVsync) { - enableVsync(wantVsync); - } + _wantVsync = _vsyncAction->isChecked(); + //bool vsyncEnabed = isVsyncEnabled(); + //if (vsyncEnabed ^ wantVsync) { + // enableVsync(wantVsync); + //} } - WindowOpenGLDisplayPlugin::display(sceneTexture, sceneSize); + WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture, sceneSize); } - int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15; static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index f4655ab79f..80aebf9efc 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -10,6 +10,8 @@ #include "WindowOpenGLDisplayPlugin.h" class QScreen; +class QAction; + class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT @@ -19,7 +21,7 @@ public: virtual void activate() override; virtual void deactivate() override; - virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; virtual bool isThrottled() const override; @@ -31,6 +33,9 @@ private: void updateFramerate(); static const QString NAME; QScreen* getFullscreenTarget(); - uint32_t _framerateTarget{ 0 }; + std::vector _framerateActions; + QAction* _vsyncAction { nullptr }; + uint32_t _framerateTarget { 0 }; int _fullscreenTarget{ -1 }; + bool _wantVsync { true }; }; diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 8155d69826..85e832abdd 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -25,22 +25,22 @@ const QString& DisplayPlugin::MENU_PATH() { DisplayPluginList getDisplayPlugins() { DisplayPlugin* PLUGIN_POOL[] = { new Basic2DWindowOpenGLDisplayPlugin(), -#ifdef DEBUG new NullDisplayPlugin(), -#endif - - // Stereo modes - - // SBS left/right - new SideBySideStereoDisplayPlugin(), - // Interleaved left/right - new InterleavedStereoDisplayPlugin(), - - // HMDs -#ifdef Q_OS_WIN - // SteamVR SDK - new OpenVrDisplayPlugin(), -#endif +//#ifdef DEBUG +//#endif +// +// // Stereo modes +// +// // SBS left/right +// new SideBySideStereoDisplayPlugin(), +// // Interleaved left/right +// new InterleavedStereoDisplayPlugin(), +// +// // HMDs +//#ifdef Q_OS_WIN +// // SteamVR SDK +// new OpenVrDisplayPlugin(), +//#endif nullptr }; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index ce512962ff..b0f02b1149 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -9,6 +9,7 @@ // #include "NullDisplayPlugin.h" +#include const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); const QString & NullDisplayPlugin::getName() const { @@ -23,8 +24,12 @@ bool NullDisplayPlugin::hasFocus() const { return false; } -void NullDisplayPlugin::preRender() {} -void NullDisplayPlugin::preDisplay() {} -void NullDisplayPlugin::display(uint32_t sceneTexture, const glm::uvec2& sceneSize) {} -void NullDisplayPlugin::finishFrame() {} +void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { + _container->releaseSceneTexture(sceneTexture); +} + +void NullDisplayPlugin::submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) { + _container->releaseOverlayTexture(overlayTexture); +} + void NullDisplayPlugin::stop() {} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 8cd5c2bc37..c4052f38dd 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -19,10 +19,8 @@ public: virtual glm::uvec2 getRecommendedRenderSize() const override; virtual bool hasFocus() const override; - virtual void preRender() override; - virtual void preDisplay() override; - virtual void display(uint32_t sceneTexture, const glm::uvec2& sceneSize) override; - virtual void finishFrame() 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; 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 09f4ba9897..e18cc6c82f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -6,71 +6,167 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OpenGLDisplayPlugin.h" -#include +#include +#include +#include + +#include +#include + +#include +#include +#include #include +#include #include +class PresentThread : public QThread, public Dependency { + using Mutex = std::mutex; + using Lock = std::unique_lock; + friend class OpenGLDisplayPlugin; +public: + + ~PresentThread() { + _shutdown = true; + wait(); + } + + void setNewDisplayPlugin(OpenGLDisplayPlugin* plugin) { + Lock lock(_mutex); + _newPlugin = plugin; + } + + virtual void run() override { + Q_ASSERT(_context); + while (!_shutdown) { + // Check before lock + if (_newPlugin != nullptr) { + Lock lock(_mutex); + // Check if we have a new plugin to activate + if (_newPlugin != nullptr) { + // Deactivate the old plugin + if (_activePlugin != nullptr) { + _activePlugin->uncustomizeContext(); + } + + _newPlugin->customizeContext(); + _activePlugin = _newPlugin; + _newPlugin = nullptr; + _context->doneCurrent(); + } + lock.unlock(); + } + + // If there's no active plugin, just sleep + if (_activePlugin == nullptr) { + QThread::usleep(100); + continue; + } + + // take the latest texture and present it + _activePlugin->present(); + + } + _context->doneCurrent(); + _context->moveToThread(qApp->thread()); + } + + +private: + bool _shutdown { false }; + Mutex _mutex; + QThread* _mainThread { nullptr }; + OpenGLDisplayPlugin* _newPlugin { nullptr }; + OpenGLDisplayPlugin* _activePlugin { nullptr }; + QOpenGLContext* _context { nullptr }; +}; OpenGLDisplayPlugin::OpenGLDisplayPlugin() { + _sceneTextureEscrow.setRecycler([this](GLuint texture){ + cleanupForSceneTexture(texture); + _container->releaseSceneTexture(texture); + }); + + _overlayTextureEscrow.setRecycler([this](GLuint texture) { + _container->releaseOverlayTexture(texture); + }); + connect(&_timer, &QTimer::timeout, this, [&] { - if (_active) { + if (_active && _sceneTextureEscrow.depth() < 1) { emit requestRender(); } }); } -OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { +void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) { + Lock lock(_mutex); + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); + _sceneTextureToFrameIndexMap.remove(sceneTexture); } -void OpenGLDisplayPlugin::preDisplay() { - makeCurrent(); -}; - -void OpenGLDisplayPlugin::preRender() { - // NOOP -} - -void OpenGLDisplayPlugin::finishFrame() { - swapBuffers(); - doneCurrent(); -}; - -void OpenGLDisplayPlugin::customizeContext() { - using namespace oglplus; - // TODO: write the poper code for linux -#if defined(Q_OS_WIN) - _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); -#endif - - Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); - Context::Disable(Capability::Blend); - Context::Disable(Capability::DepthTest); - Context::Disable(Capability::CullFace); - - - _program = loadDefaultShader(); - _plane = loadPlane(_program); - - enableVsync(); -} void OpenGLDisplayPlugin::activate() { + _timer.start(2); + + // Start the present thread if necessary + auto presentThread = DependencyManager::get(); + if (!presentThread) { + DependencyManager::set(); + presentThread = DependencyManager::get(); + 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; + + // Start execution + presentThread->start(); + } + presentThread->setNewDisplayPlugin(this); DisplayPlugin::activate(); - _timer.start(1); + emit requestRender(); } void OpenGLDisplayPlugin::stop() { - DisplayPlugin::activate(); _timer.stop(); } void OpenGLDisplayPlugin::deactivate() { - _active = false; _timer.stop(); + DisplayPlugin::deactivate(); +} +void OpenGLDisplayPlugin::customizeContext() { + auto presentThread = DependencyManager::get(); + Q_ASSERT(thread() == presentThread->thread()); + + bool makeCurrentResult = makeCurrent(); + Q_ASSERT(makeCurrentResult); + + // TODO: write the proper code for linux +#if defined(Q_OS_WIN) + _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); +#endif + enableVsync(); + + using namespace oglplus; + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); + Context::Disable(Capability::Blend); + Context::Disable(Capability::DepthTest); + Context::Disable(Capability::CullFace); + + _program = loadDefaultShader(); + _plane = loadPlane(_program); + + doneCurrent(); +} + +void OpenGLDisplayPlugin::uncustomizeContext() { makeCurrent(); - Q_ASSERT(0 == glGetError()); _program.reset(); _plane.reset(); doneCurrent(); @@ -118,13 +214,74 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { return false; } -void OpenGLDisplayPlugin::display( - GLuint finalTexture, const glm::uvec2& sceneSize) { +void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { + { + Lock lock(_mutex); + _sceneTextureToFrameIndexMap[sceneTexture] = frameIndex; + } + + // Submit it to the presentation thread via escrow + _sceneTextureEscrow.submit(sceneTexture); +} + +void OpenGLDisplayPlugin::submitOverlayTexture(GLuint sceneTexture, const glm::uvec2& sceneSize) { + // Submit it to the presentation thread via escrow + _overlayTextureEscrow.submit(sceneTexture); +} + +void OpenGLDisplayPlugin::updateTextures() { + _currentSceneTexture = _sceneTextureEscrow.fetchAndRelease(_currentSceneTexture); + _currentOverlayTexture = _overlayTextureEscrow.fetchAndRelease(_currentOverlayTexture); +} + +void OpenGLDisplayPlugin::updateFramerate() { + uint64_t now = usecTimestampNow(); + static uint64_t lastSwapEnd { now }; + uint64_t diff = now - lastSwapEnd; + lastSwapEnd = now; + if (diff != 0) { + Lock lock(_mutex); + _usecsPerFrame.updateAverage(diff); + } +} + + +void OpenGLDisplayPlugin::internalPresent() { using namespace oglplus; - uvec2 size = getSurfaceSize(); + uvec2 size = getSurfacePixels(); Context::Viewport(size.x, size.y); - glBindTexture(GL_TEXTURE_2D, finalTexture); + Context::Clear().DepthBuffer(); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); drawUnitQuad(); + swapBuffers(); + updateFramerate(); +} + +void OpenGLDisplayPlugin::present() { + auto makeCurrentResult = makeCurrent(); + Q_ASSERT(makeCurrentResult); + if (!makeCurrentResult) { + qDebug() << "Failed to make current"; + return; + } + + updateTextures(); + if (_currentSceneTexture) { + internalPresent(); + updateFramerate(); + } + doneCurrent(); +} + +float OpenGLDisplayPlugin::presentRate() { + float result { -1.0f }; + { + Lock lock(_mutex); + result = _usecsPerFrame.getAverage(); + result = 1.0f / result; + result *= USECS_PER_SECOND; + } + return result; } void OpenGLDisplayPlugin::drawUnitQuad() { @@ -151,3 +308,20 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { return true; #endif } +bool OpenGLDisplayPlugin::makeCurrent() { + static auto widget = _container->getPrimaryWidget(); + widget->makeCurrent(); + auto result = widget->context()->contextHandle() == QOpenGLContext::currentContext(); + Q_ASSERT(result); + return result; +} + +void OpenGLDisplayPlugin::doneCurrent() { + static auto widget = _container->getPrimaryWidget(); + widget->doneCurrent(); +} + +void OpenGLDisplayPlugin::swapBuffers() { + static auto widget = _container->getPrimaryWidget(); + widget->swapBuffers(); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 43d8e5af6b..4426bfd5ef 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -9,42 +9,74 @@ #include "DisplayPlugin.h" -#include -#include +#include -class GlWindow; -class QOpenGLContext; +#include +#include +#include +#include class OpenGLDisplayPlugin : public DisplayPlugin { +protected: + using Mutex = std::recursive_mutex; + using Lock = std::unique_lock; public: OpenGLDisplayPlugin(); - virtual ~OpenGLDisplayPlugin(); - virtual void preRender() override; - virtual void preDisplay() override; - virtual void finishFrame() override; - virtual void activate() override; virtual void deactivate() override; virtual void stop() override; virtual bool eventFilter(QObject* receiver, QEvent* event) override; - virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) 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 float presentRate() override; + + virtual glm::uvec2 getRecommendedRenderSize() const { + return getSurfacePixels(); + } + + virtual glm::uvec2 getRecommendedUiSize() const { + return getSurfaceSize(); + } protected: - virtual void customizeContext(); - virtual void drawUnitQuad(); - virtual glm::uvec2 getSurfaceSize() const = 0; - virtual void makeCurrent() = 0; - virtual void doneCurrent() = 0; - virtual void swapBuffers() = 0; + friend class PresentThread; + virtual glm::uvec2 getSurfaceSize() const = 0; + virtual glm::uvec2 getSurfacePixels() const = 0; + + // FIXME make thread safe? virtual bool isVsyncEnabled(); virtual void enableVsync(bool enable = true); + // These functions must only be called on the presentation thread + virtual void customizeContext(); + virtual void uncustomizeContext(); + virtual void cleanupForSceneTexture(uint32_t sceneTexture); + void present(); + void updateTextures(); + void updateFramerate(); + void drawUnitQuad(); + bool makeCurrent(); + void doneCurrent(); + void swapBuffers(); + // Plugin specific functionality to composite the scene and overlay and present the result + virtual void internalPresent(); + mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; - bool _vsyncSupported{ false }; + bool _vsyncSupported { false }; + + Mutex _mutex; + SimpleMovingAverage _usecsPerFrame { 10 }; + QMap _sceneTextureToFrameIndexMap; + + GLuint _currentSceneTexture { 0 }; + GLuint _currentOverlayTexture { 0 }; + + GLTextureEscrow _overlayTextureEscrow; + GLTextureEscrow _sceneTextureEscrow; }; diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp index e6fb2be2f5..b215b19b15 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -11,14 +11,7 @@ #include "plugins/PluginContainer.h" -WindowOpenGLDisplayPlugin::WindowOpenGLDisplayPlugin() { -} - -glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { - return getSurfaceSize(); -} - -glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfacePixels() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size() * _window->devicePixelRatio()); @@ -26,8 +19,7 @@ glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { return result; } - -glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedUiSize() const { +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size()); @@ -40,11 +32,8 @@ bool WindowOpenGLDisplayPlugin::hasFocus() const { } void WindowOpenGLDisplayPlugin::activate() { + _window = _container->getPrimaryWidget(); OpenGLDisplayPlugin::activate(); - _window = _container->getPrimarySurface(); - _window->makeCurrent(); - customizeContext(); - _window->doneCurrent(); } void WindowOpenGLDisplayPlugin::deactivate() { @@ -52,14 +41,3 @@ void WindowOpenGLDisplayPlugin::deactivate() { _window = nullptr; } -void WindowOpenGLDisplayPlugin::makeCurrent() { - _window->makeCurrent(); -} - -void WindowOpenGLDisplayPlugin::doneCurrent() { - _window->doneCurrent(); -} - -void WindowOpenGLDisplayPlugin::swapBuffers() { - _window->swapBuffers(); -} diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h index fc7691fc56..51e5d32503 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h @@ -9,21 +9,17 @@ #include "OpenGLDisplayPlugin.h" -class QGLWidget; +class QWidget; class WindowOpenGLDisplayPlugin : public OpenGLDisplayPlugin { public: - WindowOpenGLDisplayPlugin(); - virtual glm::uvec2 getRecommendedRenderSize() const override; - virtual glm::uvec2 getRecommendedUiSize() const override; virtual bool hasFocus() const override; virtual void activate() override; virtual void deactivate() override; protected: virtual glm::uvec2 getSurfaceSize() const override final; - virtual void makeCurrent() override; - virtual void doneCurrent() override; - virtual void swapBuffers() override; - QGLWidget* _window{ nullptr }; + virtual glm::uvec2 getSurfacePixels() const override final; + + QWidget* _window { nullptr }; }; diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 4278165e25..68a711a847 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -152,7 +152,7 @@ glm::mat4 OpenVrDisplayPlugin::getEyeToHeadTransform(Eye eye) const { return _eyesData[eye]._eyeOffset; } -glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { +glm::mat4 OpenVrDisplayPlugin::getHeadPose(uint32_t frameIndex) const { return _trackedDevicePoseMat4[0]; } @@ -160,26 +160,26 @@ void OpenVrDisplayPlugin::customizeContext() { WindowOpenGLDisplayPlugin::customizeContext(); } -void OpenVrDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - // Flip y-axis since GL UV coords are backwards. - static vr::Compositor_TextureBounds leftBounds{ 0, 1, 0.5f, 0 }; - static vr::Compositor_TextureBounds rightBounds{ 0.5f, 1, 1, 0 }; - _compositor->Submit(vr::Eye_Left, (void*)finalTexture, &leftBounds); - _compositor->Submit(vr::Eye_Right, (void*)finalTexture, &rightBounds); - glFinish(); -} +//void OpenVrDisplayPlugin::display(uint32_t frameIndex, uint32_t finalTexture, const glm::uvec2& sceneSize) { +// // Flip y-axis since GL UV coords are backwards. +// static vr::Compositor_TextureBounds leftBounds{ 0, 1, 0.5f, 0 }; +// static vr::Compositor_TextureBounds rightBounds{ 0.5f, 1, 1, 0 }; +// _compositor->Submit(vr::Eye_Left, (void*)finalTexture, &leftBounds); +// _compositor->Submit(vr::Eye_Right, (void*)finalTexture, &rightBounds); +// glFinish(); +//} -void OpenVrDisplayPlugin::finishFrame() { -// swapBuffers(); - doneCurrent(); - _compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount); - for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { - _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); - } - openvr_for_each_eye([&](vr::Hmd_Eye eye) { - _eyesData[eye]._pose = _trackedDevicePoseMat4[0]; - }); -}; +//void OpenVrDisplayPlugin::finishFrame() { +//// swapBuffers(); +// doneCurrent(); +// _compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount); +// for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { +// _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); +// } +// openvr_for_each_eye([&](vr::Hmd_Eye eye) { +// _eyesData[eye]._pose = _trackedDevicePoseMat4[0]; +// }); +//}; #endif diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h index 15d37d9de8..c8887276b7 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -31,13 +31,11 @@ public: virtual void resetSensors() override; virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; - virtual glm::mat4 getHeadPose() const override; + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const override; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; +// virtual void display(uint32_t frameIndex, uint32_t finalTexture, const glm::uvec2& sceneSize) override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; private: vr::IVRSystem* _hmd { nullptr }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 72921b4f90..4332c53d81 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -66,10 +66,6 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { return result; } -void InterleavedStereoDisplayPlugin::display( - GLuint finalTexture, const glm::uvec2& sceneSize) { - using namespace oglplus; - _program->Bind(); - Uniform(*_program, "textureSize").SetValue(sceneSize); - WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); +void InterleavedStereoDisplayPlugin::internalPresent() { + // FIXME } diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 3044d91247..b9b3566349 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -19,7 +19,8 @@ public: virtual void customizeContext() override; virtual glm::uvec2 getRecommendedRenderSize() const override; - void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + + void internalPresent() override; private: static const QString NAME; diff --git a/libraries/gl/src/gl/GLEscrow.h b/libraries/gl/src/gl/GLEscrow.h index db9f033de8..d860f1239c 100644 --- a/libraries/gl/src/gl/GLEscrow.h +++ b/libraries/gl/src/gl/GLEscrow.h @@ -76,7 +76,7 @@ public: } }; - using Mutex = std::mutex; + using Mutex = std::recursive_mutex; using Lock = std::unique_lock; using Recycler = std::function; // deque gives us random access, double ended push & pop and size, all in constant time @@ -130,6 +130,21 @@ public: return result; } + // Returns the next available resource provided by the submitter, + // or if none is available (which could mean either the submission + // list is empty or that the first item on the list isn't yet signaled + // Also releases any previous texture held by the caller + T fetchAndRelease(T oldValue) { + T result = fetch(); + if (!result) { + return oldValue; + } + if (oldValue) { + release(oldValue); + } + return result; + } + // If fetch returns a non-zero value, it's the responsibility of the // client to release it at some point void release(T t, GLsync readSync = 0) { @@ -162,7 +177,12 @@ private: pop(_releases); } - trash.swap(_trash); + { + // FIXME I don't think this lock should be necessary, only the submitting thread + // touches the trash + Lock lock(_mutex); + trash.swap(_trash); + } } // FIXME maybe doing a timing on the deleters and warn if it's taking excessive time? diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5b00391f09..928b72970b 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -65,29 +65,15 @@ public: // processing messages in the middle of submitFrame virtual void stop() = 0; - /** - * Called by the application before the frame rendering. Can be used for - * render timing related calls (for instance, the Oculus begin frame timing - * call) - */ - virtual void preRender() = 0; - /** - * Called by the application immediately before calling the display function. - * For OpenGL based plugins, this is the best place to put activate the output - * OpenGL context - */ - virtual void preDisplay() = 0; - /** * Sends the scene texture to the display plugin. */ - virtual void display(uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0; + virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0; /** - * Called by the application immeidately after display. For OpenGL based - * displays, this is the best place to put the buffer swap - */ - virtual void finishFrame() = 0; + * Sends the scene texture to the display plugin. + */ + virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) = 0; // Does the rendering surface have current focus? virtual bool hasFocus() const = 0; @@ -116,12 +102,12 @@ public: static const glm::mat4 transform; return transform; } - virtual glm::mat4 getHeadPose() const { + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const { static const glm::mat4 pose; return pose; } // Needed for timewarp style features - virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) { + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { // NOOP } @@ -129,8 +115,8 @@ public: virtual void abandonCalibration() {} virtual void resetSensors() {} - virtual float devicePixelRatio() { return 1.0; } - + virtual float devicePixelRatio() { return 1.0f; } + virtual float presentRate() { return -1.0f; } static const QString& MENU_PATH(); signals: diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index f013bfe3bf..25c2bcb11f 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -8,11 +8,15 @@ #pragma once #include +#include #include class QAction; class QGLWidget; class QScreen; +class QOpenGLContext; +class QWindow; + class DisplayPlugin; class PluginContainer { @@ -30,7 +34,12 @@ public: virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; virtual void showDisplayPluginsTools() = 0; virtual void requestReset() = 0; - virtual QGLWidget* getPrimarySurface() = 0; + virtual bool makeRenderingContextCurrent() = 0; + virtual void releaseSceneTexture(uint32_t texture) = 0; + virtual void releaseOverlayTexture(uint32_t texture) = 0; + virtual QGLWidget* getPrimaryWidget() = 0; + virtual QWindow* getPrimaryWindow() = 0; + virtual QOpenGLContext* getPrimaryContext() = 0; virtual bool isForeground() = 0; virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0; }; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 7fd956a08f..805ad75e95 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -15,13 +15,6 @@ uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const { return _desiredFramebufferSize; } -void OculusBaseDisplayPlugin::preRender() { -#if (OVR_MAJOR_VERSION >= 6) - ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex); - _trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds); -#endif -} - glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { return _eyeProjections[eye]; } @@ -29,7 +22,6 @@ glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseP void OculusBaseDisplayPlugin::resetSensors() { #if (OVR_MAJOR_VERSION >= 6) ovr_RecenterPose(_hmd); - preRender(); #endif } @@ -37,15 +29,14 @@ glm::mat4 OculusBaseDisplayPlugin::getEyeToHeadTransform(Eye eye) const { return glm::translate(mat4(), toGlm(_eyeOffsets[eye])); } -glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const { - return toGlm(_trackingState.HeadPose.ThePose); +glm::mat4 OculusBaseDisplayPlugin::getHeadPose(uint32_t frameIndex) const { +#if (OVR_MAJOR_VERSION >= 6) + auto frameTiming = ovr_GetFrameTiming(_hmd, frameIndex); + auto trackingState = ovr_GetTrackingState(_hmd, frameTiming.DisplayMidpointSeconds); + return toGlm(trackingState.HeadPose.ThePose); +#endif } -void OculusBaseDisplayPlugin::setEyeRenderPose(Eye eye, const glm::mat4& pose) { - _eyePoses[eye] = ovrPoseFromGlm(pose); -} - - bool OculusBaseDisplayPlugin::isSupported() const { #if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { @@ -64,9 +55,11 @@ bool OculusBaseDisplayPlugin::isSupported() const { // DLL based display plugins MUST initialize GLEW inside the DLL code. void OculusBaseDisplayPlugin::customizeContext() { + makeCurrent(); glewExperimental = true; GLenum err = glewInit(); glGetError(); + doneCurrent(); WindowOpenGLDisplayPlugin::customizeContext(); } @@ -123,8 +116,6 @@ void OculusBaseDisplayPlugin::activate() { eyeSizes[0].x + eyeSizes[1].x, std::max(eyeSizes[0].y, eyeSizes[1].y)); - _frameIndex = 0; - if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { qFatal("Could not attach to sensor device"); @@ -159,9 +150,6 @@ void OculusBaseDisplayPlugin::deactivate() { #endif } -void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - ++_frameIndex; -} float OculusBaseDisplayPlugin::getIPD() const { float result = OVR_DEFAULT_IPD; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index ba1924bfff..711be6aa5e 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -30,24 +30,18 @@ public: virtual glm::uvec2 getRecommendedUiSize() const override final { return uvec2(1920, 1080); } virtual void resetSensors() override final; virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override final; - virtual glm::mat4 getHeadPose() const override final; - virtual void setEyeRenderPose(Eye eye, const glm::mat4& pose) override final; virtual float getIPD() const override final; + virtual glm::mat4 getHeadPose(uint32_t frameIndex) const override; protected: virtual void customizeContext() override; - virtual void preRender() override final; - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; protected: - ovrPosef _eyePoses[2]; ovrVector3f _eyeOffsets[2]; mat4 _eyeProjections[3]; mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; - ovrTrackingState _trackingState; - unsigned int _frameIndex{ 0 }; #if (OVR_MAJOR_VERSION >= 6) ovrHmd _hmd; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp index 7a8b355ddd..26bb3cf9b2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp @@ -28,13 +28,3 @@ void OculusDebugDisplayPlugin::customizeContext() { OculusBaseDisplayPlugin::customizeContext(); enableVsync(false); } - -void OculusDebugDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); - OculusBaseDisplayPlugin::display(finalTexture, sceneSize); -} - -void OculusDebugDisplayPlugin::finishFrame() { - swapBuffers(); - doneCurrent(); -}; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index d23c6ba567..04b68704cc 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -15,10 +15,7 @@ public: virtual bool isSupported() const override; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; private: static const QString NAME; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 923b8bde6e..bcb39f5100 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -144,7 +144,6 @@ static const QString MONO_PREVIEW = "Mono Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; void OculusDisplayPlugin::activate() { - _container->addMenuItem(MENU_PATH(), MONO_PREVIEW, [this](bool clicked) { _monoPreview = clicked; @@ -155,6 +154,8 @@ void OculusDisplayPlugin::activate() { void OculusDisplayPlugin::customizeContext() { OculusBaseDisplayPlugin::customizeContext(); + bool makeCurrentResult = makeCurrent(); + Q_ASSERT(makeCurrentResult); #if (OVR_MAJOR_VERSION >= 6) _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); @@ -168,20 +169,24 @@ void OculusDisplayPlugin::customizeContext() { enableVsync(false); // Only enable mirroring if we know vsync is disabled _enablePreview = !isVsyncEnabled(); + doneCurrent(); } -void OculusDisplayPlugin::deactivate() { +void OculusDisplayPlugin::uncustomizeContext() { #if (OVR_MAJOR_VERSION >= 6) makeCurrent(); _sceneFbo.reset(); doneCurrent(); #endif - - OculusBaseDisplayPlugin::deactivate(); + OculusBaseDisplayPlugin::uncustomizeContext(); } -void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { +void OculusDisplayPlugin::internalPresent() { #if (OVR_MAJOR_VERSION >= 6) + if (!_currentSceneTexture) { + return; + } + using namespace oglplus; // Need to make sure only the display plugin is responsible for // controlling vsync @@ -196,7 +201,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi } else { Context::Viewport(windowSize.x, windowSize.y); } - glBindTexture(GL_TEXTURE_2D, finalTexture); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); Q_ASSERT(0 == err); drawUnitQuad(); @@ -205,16 +210,24 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi _sceneFbo->Bound([&] { auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); - glBindTexture(GL_TEXTURE_2D, finalTexture); + glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); drawUnitQuad(); }); - ovr_for_each_eye([&](ovrEyeType eye) { - _sceneLayer.RenderPose[eye] = _eyePoses[eye]; - }); + uint32_t frameIndex { 0 }; + EyePoses eyePoses; + { + Lock lock(_mutex); + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(_currentSceneTexture)); + frameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; + Q_ASSERT(_frameEyePoses.contains(frameIndex)); + eyePoses = _frameEyePoses[frameIndex]; + } + + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = eyePoses.first; + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = eyePoses.second; - auto windowSize = toGlm(_window->size()); { ovrViewScaleDesc viewScaleDesc; viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; @@ -228,19 +241,26 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi } } _sceneFbo->Increment(); - - ++_frameIndex; #endif -} -/* + /* The swapbuffer call here is only required if we want to mirror the content to the screen. - However, it should only be done if we can reliably disable v-sync on the mirror surface, + However, it should only be done if we can reliably disable v-sync on the mirror surface, otherwise the swapbuffer delay will interefere with the framerate of the headset -*/ -void OculusDisplayPlugin::finishFrame() { + */ if (_enablePreview) { swapBuffers(); } - doneCurrent(); -}; +} + +void OculusDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { + auto ovrPose = ovrPoseFromGlm(pose); + { + Lock lock(_mutex); + if (eye == Eye::Left) { + _frameEyePoses[frameIndex].first = ovrPose; + } else { + _frameEyePoses[frameIndex].second = ovrPose; + } + } +} diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index c1224ecf3a..5509715b9f 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -15,22 +15,21 @@ using SwapFboPtr = QSharedPointer; class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: virtual void activate() override; - virtual void deactivate() override; virtual const QString & getName() const override; + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final; protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void internalPresent() override; virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; + virtual void uncustomizeContext() override; private: + using EyePoses = std::pair; static const QString NAME; bool _enablePreview { false }; bool _monoPreview { true }; + QMap _frameEyePoses; -#if (OVR_MAJOR_VERSION >= 6) SwapFboPtr _sceneFbo; -#endif }; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index f93580e5a3..37c560e9ad 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -11,7 +11,6 @@ namespace Oculus { ovrHmd _hmd; - unsigned int _frameIndex{ 0 }; ovrEyeRenderDesc _eyeRenderDescs[2]; ovrPosef _eyePoses[2]; ovrVector3f _eyeOffsets[2]; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 6e3f864aee..ccf1ffdb57 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -46,7 +46,6 @@ private: static const QString NAME; ovrHmd _hmd; - unsigned int _frameIndex; ovrTrackingState _trackingState; ovrEyeRenderDesc _eyeRenderDescs[2]; ovrPosef _eyePoses[2]; diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 664a894e44..c5c038fde3 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -91,7 +91,12 @@ public: virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override {} virtual void showDisplayPluginsTools() override {} virtual void requestReset() override {} - virtual QGLWidget* getPrimarySurface() override { return nullptr; } + virtual bool makeRenderingContextCurrent() override { return true; } + virtual void releaseSceneTexture(uint32_t texture) override {} + virtual void releaseOverlayTexture(uint32_t texture) override {} + virtual QGLWidget* getPrimaryWidget() override { return nullptr; } + virtual QWindow* getPrimaryWindow() override { return nullptr; } + virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual bool isForeground() override { return true; } virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; } }; From d8bb9f8d183fe8cfa2a064276e50feed05689ce4 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Dec 2015 13:00:36 -0800 Subject: [PATCH 08/42] 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 +#include +#include + #include #include #include @@ -150,8 +153,6 @@ #include "InterfaceParentFinder.h" -#include -#include // 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 #include + 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 + #include #include #include #include #include +#include #include #include @@ -23,7 +26,9 @@ class PresentThread : public QThread, public Dependency { using Mutex = std::mutex; + using Condition = std::condition_variable; using Lock = std::unique_lock; + 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 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 = DependencyManager::get(); + 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 f) const { + static auto presentThread = DependencyManager::get(); + 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 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 #include +class QImage; #include #include @@ -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 { From 7262a10e62cf181e4d36d14d95349545b19c0d4b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Dec 2015 15:39:09 -0800 Subject: [PATCH 09/42] Refactoring present thread / GL base plugin for saner context management --- .../src/display-plugins/DisplayPlugin.cpp | 22 +++--- .../display-plugins/OpenGLDisplayPlugin.cpp | 74 +++++++------------ .../src/display-plugins/OpenGLDisplayPlugin.h | 2 - .../stereo/InterleavedStereoDisplayPlugin.cpp | 6 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 2 - plugins/oculus/src/OculusDisplayPlugin.cpp | 5 -- 6 files changed, 42 insertions(+), 69 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 85e832abdd..6c34612e8c 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -26,17 +26,17 @@ DisplayPluginList getDisplayPlugins() { DisplayPlugin* PLUGIN_POOL[] = { new Basic2DWindowOpenGLDisplayPlugin(), new NullDisplayPlugin(), -//#ifdef DEBUG -//#endif -// -// // Stereo modes -// -// // SBS left/right -// new SideBySideStereoDisplayPlugin(), -// // Interleaved left/right -// new InterleavedStereoDisplayPlugin(), -// -// // HMDs +#ifdef DEBUG +#endif + + // Stereo modes + + // SBS left/right + new SideBySideStereoDisplayPlugin(), + // Interleaved left/right + new InterleavedStereoDisplayPlugin(), + + // HMDs //#ifdef Q_OS_WIN // // SteamVR SDK // new OpenVrDisplayPlugin(), diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 22adeb8447..45e6daef31 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -28,8 +28,6 @@ class PresentThread : public QThread, public Dependency { using Mutex = std::mutex; using Condition = std::condition_variable; using Lock = std::unique_lock; - - friend class OpenGLDisplayPlugin; public: ~PresentThread() { @@ -42,6 +40,14 @@ public: _newPlugin = plugin; } + void setContext(QGLContext * context) { + // Move the OpenGL context to the present thread + // Extra code because of the widget 'wrapper' context + _context = context; + _context->moveToThread(this); + _context->contextHandle()->moveToThread(this); + } + virtual void run() override { Q_ASSERT(_context); while (!_shutdown) { @@ -50,7 +56,7 @@ public: Lock lock(_mutex); // Move the context to the main thread _context->moveToThread(qApp->thread()); - _widgetContext->moveToThread(qApp->thread()); + _context->contextHandle()->moveToThread(qApp->thread()); _pendingMainThreadOperation = false; // Release the main thread to do it's action _condition.notify_one(); @@ -67,6 +73,7 @@ public: // Check before lock if (_newPlugin != nullptr) { Lock lock(_mutex); + _context->makeCurrent(); // Check if we have a new plugin to activate if (_newPlugin != nullptr) { // Deactivate the old plugin @@ -77,8 +84,8 @@ public: _newPlugin->customizeContext(); _activePlugin = _newPlugin; _newPlugin = nullptr; - _context->doneCurrent(); } + _context->doneCurrent(); lock.unlock(); } @@ -89,12 +96,14 @@ public: } // take the latest texture and present it + _context->makeCurrent(); _activePlugin->present(); - + _context->doneCurrent(); } + _context->doneCurrent(); - _widgetContext->moveToThread(qApp->thread()); _context->moveToThread(qApp->thread()); + _context->contextHandle()->moveToThread(qApp->thread()); } void withMainThreadContext(std::function f) { @@ -104,14 +113,16 @@ public: _finishedMainThreadOperation = false; _condition.wait(lock, [&] { return !_pendingMainThreadOperation; }); - _widgetContext->makeCurrent(); + _context->makeCurrent(); f(); - _widgetContext->doneCurrent(); + _context->doneCurrent(); + + // Move the context back to the presentation thread + _context->moveToThread(this); + _context->contextHandle()->moveToThread(this); // 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(); @@ -119,6 +130,9 @@ public: private: + void makeCurrent(); + void doneCurrent(); + bool _shutdown { false }; Mutex _mutex; // Used to allow the main thread to perform context operations @@ -128,8 +142,7 @@ private: QThread* _mainThread { nullptr }; OpenGLDisplayPlugin* _newPlugin { nullptr }; OpenGLDisplayPlugin* _activePlugin { nullptr }; - QOpenGLContext* _context { nullptr }; - QGLContext* _widgetContext { nullptr }; + QGLContext* _context { nullptr }; }; OpenGLDisplayPlugin::OpenGLDisplayPlugin() { @@ -165,16 +178,8 @@ void OpenGLDisplayPlugin::activate() { DependencyManager::set(); presentThread = DependencyManager::get(); presentThread->setObjectName("Presentation Thread"); - auto widget = _container->getPrimaryWidget(); - - // Move the OpenGL context to the present thread - // 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()); - + presentThread->setContext(widget->context()); // Start execution presentThread->start(); } @@ -196,9 +201,6 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - bool makeCurrentResult = makeCurrent(); - Q_ASSERT(makeCurrentResult); - // TODO: write the proper code for linux #if defined(Q_OS_WIN) _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); @@ -213,15 +215,11 @@ void OpenGLDisplayPlugin::customizeContext() { _program = loadDefaultShader(); _plane = loadPlane(_program); - - doneCurrent(); } void OpenGLDisplayPlugin::uncustomizeContext() { - makeCurrent(); _program.reset(); _plane.reset(); - doneCurrent(); } // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the @@ -310,19 +308,11 @@ void OpenGLDisplayPlugin::internalPresent() { } void OpenGLDisplayPlugin::present() { - auto makeCurrentResult = makeCurrent(); - Q_ASSERT(makeCurrentResult); - if (!makeCurrentResult) { - qDebug() << "Failed to make current"; - return; - } - updateTextures(); if (_currentSceneTexture) { internalPresent(); updateFramerate(); } - doneCurrent(); } float OpenGLDisplayPlugin::presentRate() { @@ -360,18 +350,6 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { return true; #endif } -bool OpenGLDisplayPlugin::makeCurrent() { - static auto widget = _container->getPrimaryWidget(); - widget->makeCurrent(); - auto result = widget->context()->contextHandle() == QOpenGLContext::currentContext(); - Q_ASSERT(result); - return result; -} - -void OpenGLDisplayPlugin::doneCurrent() { - static auto widget = _container->getPrimaryWidget(); - widget->doneCurrent(); -} void OpenGLDisplayPlugin::swapBuffers() { static auto widget = _container->getPrimaryWidget(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index edbe7db006..809a52ef7f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -63,8 +63,6 @@ protected: void updateTextures(); void updateFramerate(); void drawUnitQuad(); - bool makeCurrent(); - void doneCurrent(); void swapBuffers(); // Plugin specific functionality to composite the scene and overlay and present the result virtual void internalPresent(); diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 4332c53d81..ffaf005533 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -67,5 +67,9 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { } void InterleavedStereoDisplayPlugin::internalPresent() { - // FIXME + using namespace oglplus; + _program->Bind(); + auto sceneSize = getRecommendedRenderSize(); + Uniform(*_program, "textureSize").SetValue(sceneSize); + WindowOpenGLDisplayPlugin::internalPresent(); } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 805ad75e95..b6690ea76e 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -55,11 +55,9 @@ bool OculusBaseDisplayPlugin::isSupported() const { // DLL based display plugins MUST initialize GLEW inside the DLL code. void OculusBaseDisplayPlugin::customizeContext() { - makeCurrent(); glewExperimental = true; GLenum err = glewInit(); glGetError(); - doneCurrent(); WindowOpenGLDisplayPlugin::customizeContext(); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index bcb39f5100..21c318677e 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -154,8 +154,6 @@ void OculusDisplayPlugin::activate() { void OculusDisplayPlugin::customizeContext() { OculusBaseDisplayPlugin::customizeContext(); - bool makeCurrentResult = makeCurrent(); - Q_ASSERT(makeCurrentResult); #if (OVR_MAJOR_VERSION >= 6) _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); @@ -169,14 +167,11 @@ void OculusDisplayPlugin::customizeContext() { enableVsync(false); // Only enable mirroring if we know vsync is disabled _enablePreview = !isVsyncEnabled(); - doneCurrent(); } void OculusDisplayPlugin::uncustomizeContext() { #if (OVR_MAJOR_VERSION >= 6) - makeCurrent(); _sceneFbo.reset(); - doneCurrent(); #endif OculusBaseDisplayPlugin::uncustomizeContext(); } From 60aa93c38c6633b3d604f0e800cad33cd75f4048 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Dec 2015 16:42:17 -0800 Subject: [PATCH 10/42] Fixing menu items in display plugins --- interface/src/Application.cpp | 9 +-- interface/src/Application.h | 2 - interface/src/PluginContainerProxy.cpp | 66 ++++++++-------- interface/src/PluginContainerProxy.h | 14 +++- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 79 +++++++++---------- .../Basic2DWindowOpenGLDisplayPlugin.h | 3 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 16 ++-- .../src/display-plugins/OpenGLDisplayPlugin.h | 3 +- .../stereo/InterleavedStereoDisplayPlugin.h | 1 - .../stereo/StereoDisplayPlugin.cpp | 2 +- .../src/input-plugins/SixenseManager.cpp | 2 +- .../input-plugins/ViveControllerManager.cpp | 2 +- libraries/plugins/src/plugins/Forward.h | 5 ++ .../plugins/src/plugins/PluginContainer.h | 8 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 3 +- plugins/oculus/src/OculusDisplayPlugin.cpp | 2 +- 16 files changed, 120 insertions(+), 97 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c58156d2a..bbc36e8623 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4777,19 +4777,18 @@ void Application::updateDisplayMode() { return; } - if (!_currentDisplayPluginActions.isEmpty()) { + + if (!_pluginContainer->currentDisplayActions().isEmpty()) { auto menu = Menu::getInstance(); - foreach(auto itemInfo, _currentDisplayPluginActions) { + foreach(auto itemInfo, _pluginContainer->currentDisplayActions()) { menu->removeMenuItem(itemInfo.first, itemInfo.second); } - _currentDisplayPluginActions.clear(); + _pluginContainer->currentDisplayActions().clear(); } if (newDisplayPlugin) { _offscreenContext->makeCurrent(); - _activatingDisplayPlugin = true; newDisplayPlugin->activate(); - _activatingDisplayPlugin = false; _offscreenContext->makeCurrent(); offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); _offscreenContext->makeCurrent(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 1af252de95..d12d29cde8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -427,8 +427,6 @@ private: InputPluginList _activeInputPlugins; bool _activatingDisplayPlugin { false }; - QVector> _currentDisplayPluginActions; - QVector> _currentInputPluginActions; QMap _lockedFramebufferMap; MainWindow* _window; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index 8774eecd77..abb52380d0 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -36,37 +36,31 @@ void PluginContainerProxy::removeMenu(const QString& menuName) { Menu::getInstance()->removeMenu(menuName); } -extern bool _activatingDisplayPlugin; -extern QVector> _currentDisplayPluginActions; -extern QVector> _currentInputPluginActions; -std::map _exclusiveGroups; - -QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { - //auto menu = Menu::getInstance(); - //MenuWrapper* parentItem = menu->getMenu(path); - //QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); - //if (!groupName.isEmpty()) { - // QActionGroup* group{ nullptr }; - // if (!_exclusiveGroups.count(groupName)) { - // group = _exclusiveGroups[groupName] = new QActionGroup(menu); - // group->setExclusive(true); - // } else { - // group = _exclusiveGroups[groupName]; - // } - // group->addAction(action); - //} - //connect(action, &QAction::triggered, [=] { - // onClicked(action->isChecked()); - //}); - //action->setCheckable(checkable); - //action->setChecked(checked); - //if (_activatingDisplayPlugin) { - // _currentDisplayPluginActions.push_back({ path, name }); - //} else { - // _currentInputPluginActions.push_back({ path, name }); - //} - //return action; - return nullptr; +QAction* PluginContainerProxy::addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + auto menu = Menu::getInstance(); + MenuWrapper* parentItem = menu->getMenu(path); + QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); + if (!groupName.isEmpty()) { + QActionGroup* group{ nullptr }; + if (!_exclusiveGroups.count(groupName)) { + group = _exclusiveGroups[groupName] = new QActionGroup(menu); + group->setExclusive(true); + } else { + group = _exclusiveGroups[groupName]; + } + group->addAction(action); + } + connect(action, &QAction::triggered, [=] { + onClicked(action->isChecked()); + }); + action->setCheckable(checkable); + action->setChecked(checked); + if (type == PluginType::DISPLAY_PLUGIN) { + _currentDisplayPluginActions.push_back({ path, name }); + } else { + _currentInputPluginActions.push_back({ path, name }); + } + return action; } void PluginContainerProxy::removeMenuItem(const QString& menuName, const QString& menuItem) { @@ -188,5 +182,13 @@ void PluginContainerProxy::releaseSceneTexture(uint32_t texture) { } void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) { - + // FIXME implement present thread compositing +} + +QVector>& PluginContainerProxy::currentDisplayActions() { + return _currentDisplayPluginActions; +} + +QVector>& PluginContainerProxy::currentInputActions() { + return _currentInputPluginActions; } diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index 5cc1cc8583..cd15510885 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -2,19 +2,23 @@ #ifndef hifi_PluginContainerProxy_h #define hifi_PluginContainerProxy_h -#include -#include +#include +#include #include #include +class QActionGroup; + class PluginContainerProxy : public QObject, PluginContainer { Q_OBJECT PluginContainerProxy(); virtual ~PluginContainerProxy(); + virtual QVector>& currentDisplayActions() override; + virtual QVector>& currentInputActions() override; virtual void addMenu(const QString& menuName) override; virtual void removeMenu(const QString& menuName) override; - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; + virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override; virtual bool isOptionChecked(const QString& name) override; virtual void setIsOptionChecked(const QString& path, bool checked) override; @@ -32,8 +36,12 @@ class PluginContainerProxy : public QObject, PluginContainer { virtual const DisplayPlugin* getActiveDisplayPlugin() const override; QRect _savedGeometry{ 10, 120, 800, 600 }; + std::map _exclusiveGroups; + QVector> _currentDisplayPluginActions; + QVector> _currentInputPluginActions; friend class Application; + }; #endif \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 36216f8912..6c450e0735 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -31,60 +31,59 @@ const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { } void Basic2DWindowOpenGLDisplayPlugin::activate() { - //_framerateActions.clear(); - //_container->addMenuItem(MENU_PATH(), FULLSCREEN, - // [this](bool clicked) { - // if (clicked) { - // _container->setFullscreen(getFullscreenTarget()); - // } else { - // _container->unsetFullscreen(); - // } - // }, true, false); - //_container->addMenu(FRAMERATE); - //_framerateActions.push_back( - // _container->addMenuItem(FRAMERATE, FRAMERATE_UNLIMITED, - // [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); - //_framerateActions.push_back( - // _container->addMenuItem(FRAMERATE, FRAMERATE_60, - // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - //_framerateActions.push_back( - // _container->addMenuItem(FRAMERATE, FRAMERATE_50, - // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - //_framerateActions.push_back( - // _container->addMenuItem(FRAMERATE, FRAMERATE_40, - // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - //_framerateActions.push_back( - // _container->addMenuItem(FRAMERATE, FRAMERATE_30, - // [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); - WindowOpenGLDisplayPlugin::activate(); - //// Vsync detection happens in the parent class activate, so we need to check after that - //if (_vsyncSupported) { - // _vsyncAction = _container->addMenuItem(MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); - //} else { - // _vsyncAction = nullptr; - //} + _framerateActions.clear(); + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), FULLSCREEN, + [this](bool clicked) { + if (clicked) { + _container->setFullscreen(getFullscreenTarget()); + } else { + _container->unsetFullscreen(); + } + }, true, false); + _container->addMenu(FRAMERATE); + _framerateActions.push_back( + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_UNLIMITED, + [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); + _framerateActions.push_back( + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_60, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_50, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_40, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, FRAMERATE, FRAMERATE_30, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + + // Vsync detection happens in the parent class activate, so we need to check after that + if (_vsyncSupported) { + _vsyncAction = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); + } else { + _vsyncAction = nullptr; + } updateFramerate(); } -void Basic2DWindowOpenGLDisplayPlugin::deactivate() { - WindowOpenGLDisplayPlugin::deactivate(); -} - void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) { if (_vsyncAction) { _wantVsync = _vsyncAction->isChecked(); - //bool vsyncEnabed = isVsyncEnabled(); - //if (vsyncEnabed ^ wantVsync) { - // enableVsync(wantVsync); - //} } WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture, sceneSize); } +void Basic2DWindowOpenGLDisplayPlugin::internalPresent() { + if (_wantVsync != isVsyncEnabled()) { + enableVsync(_wantVsync); + } + WindowOpenGLDisplayPlugin::internalPresent(); +} + int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15; static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 80aebf9efc..36a1a73b94 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -19,10 +19,11 @@ public: virtual const QString & getName() const override; virtual void activate() override; - virtual void deactivate() override; virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; + virtual void internalPresent() override; + virtual bool isThrottled() const override; protected: diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 45e6daef31..12aa37cd56 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -145,6 +145,8 @@ private: QGLContext* _context { nullptr }; }; +bool OpenGLDisplayPlugin::_vsyncSupported = false; + OpenGLDisplayPlugin::OpenGLDisplayPlugin() { _sceneTextureEscrow.setRecycler([this](GLuint texture){ cleanupForSceneTexture(texture); @@ -175,10 +177,18 @@ void OpenGLDisplayPlugin::activate() { // Start the present thread if necessary auto presentThread = DependencyManager::get(); if (!presentThread) { + auto widget = _container->getPrimaryWidget(); + + // TODO: write the proper code for linux +#if defined(Q_OS_WIN) + widget->makeCurrent(); + _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); + widget->doneCurrent(); +#endif + DependencyManager::set(); presentThread = DependencyManager::get(); presentThread->setObjectName("Presentation Thread"); - auto widget = _container->getPrimaryWidget(); presentThread->setContext(widget->context()); // Start execution presentThread->start(); @@ -201,10 +211,6 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - // TODO: write the proper code for linux -#if defined(Q_OS_WIN) - _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); -#endif enableVsync(); using namespace oglplus; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 809a52ef7f..747d8b810b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -70,7 +70,6 @@ protected: mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; - bool _vsyncSupported { false }; Mutex _mutex; SimpleMovingAverage _usecsPerFrame { 10 }; @@ -81,6 +80,8 @@ protected: GLTextureEscrow _overlayTextureEscrow; GLTextureEscrow _sceneTextureEscrow; + + static bool _vsyncSupported; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index b9b3566349..7116363e44 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -19,7 +19,6 @@ public: virtual void customizeContext() override; virtual glm::uvec2 getRecommendedRenderSize() const override; - void internalPresent() override; private: diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index f7e71313df..a691f375eb 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -74,7 +74,7 @@ void StereoDisplayPlugin::activate() { if (screen == qApp->primaryScreen()) { checked = true; } - auto action = _container->addMenuItem(MENU_PATH(), name, + auto action = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name, [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); _screenActions[i] = action; } diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 18fdc9ddad..5dd0248224 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -68,7 +68,7 @@ void SixenseManager::activate() { #ifdef HAVE_SIXENSE _container->addMenu(MENU_PATH); - _container->addMenuItem(MENU_PATH, TOGGLE_SMOOTH, + _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, [this] (bool clicked) { setSixenseFilter(clicked); }, true, true); diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index ec0c35cc96..b315a7a3d9 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -60,7 +60,7 @@ void ViveControllerManager::activate() { InputPlugin::activate(); #ifdef Q_OS_WIN _container->addMenu(MENU_PATH); - _container->addMenuItem(MENU_PATH, RENDER_CONTROLLERS, + _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, [this] (bool clicked) { this->setRenderControllers(clicked); }, true, true); diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 8d8259ba4f..036b42f7d7 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -10,6 +10,11 @@ #include #include +enum class PluginType { + DISPLAY_PLUGIN, + INPUT_PLUGIN, +}; + class DisplayPlugin; class InputPlugin; class Plugin; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 25c2bcb11f..337ffd3c57 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -10,6 +10,10 @@ #include #include #include +#include +#include + +#include "Forward.h" class QAction; class QGLWidget; @@ -24,9 +28,11 @@ public: static PluginContainer& getInstance(); PluginContainer(); virtual ~PluginContainer(); + virtual QVector>& currentDisplayActions() = 0; + virtual QVector>& currentInputActions() = 0; virtual void addMenu(const QString& menuName) = 0; virtual void removeMenu(const QString& menuName) = 0; - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; + virtual QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0; virtual bool isOptionChecked(const QString& name) = 0; virtual void setIsOptionChecked(const QString& path, bool checked) = 0; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index b6690ea76e..7c057c7152 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -68,6 +68,7 @@ void OculusBaseDisplayPlugin::deinit() { } void OculusBaseDisplayPlugin::activate() { + WindowOpenGLDisplayPlugin::activate(); #if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { qFatal("Could not init OVR"); @@ -134,8 +135,6 @@ void OculusBaseDisplayPlugin::activate() { qFatal("Could not attach to sensor device"); } #endif - - WindowOpenGLDisplayPlugin::activate(); } void OculusBaseDisplayPlugin::deactivate() { diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 21c318677e..8846b8a6a6 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -144,7 +144,7 @@ static const QString MONO_PREVIEW = "Mono Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; void OculusDisplayPlugin::activate() { - _container->addMenuItem(MENU_PATH(), MONO_PREVIEW, + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), MONO_PREVIEW, [this](bool clicked) { _monoPreview = clicked; }, true, true); From c4514743d16440ee17565af885842ad83eb1f2d9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Dec 2015 17:07:04 -0800 Subject: [PATCH 11/42] Fixing tests, moving some plugin container stuff to base class --- interface/src/PluginContainerProxy.cpp | 7 ------- interface/src/PluginContainerProxy.h | 4 ---- libraries/plugins/src/plugins/PluginContainer.h | 15 +++++++++++++-- tests/controllers/src/main.cpp | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index abb52380d0..aff36fb881 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -185,10 +185,3 @@ void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) { // FIXME implement present thread compositing } -QVector>& PluginContainerProxy::currentDisplayActions() { - return _currentDisplayPluginActions; -} - -QVector>& PluginContainerProxy::currentInputActions() { - return _currentInputPluginActions; -} diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index cd15510885..5d974f0a0c 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -14,8 +14,6 @@ class PluginContainerProxy : public QObject, PluginContainer { Q_OBJECT PluginContainerProxy(); virtual ~PluginContainerProxy(); - virtual QVector>& currentDisplayActions() override; - virtual QVector>& currentInputActions() override; virtual void addMenu(const QString& menuName) override; virtual void removeMenu(const QString& menuName) override; virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; @@ -37,8 +35,6 @@ class PluginContainerProxy : public QObject, PluginContainer { QRect _savedGeometry{ 10, 120, 800, 600 }; std::map _exclusiveGroups; - QVector> _currentDisplayPluginActions; - QVector> _currentInputPluginActions; friend class Application; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 337ffd3c57..6732bc02ba 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -28,8 +28,6 @@ public: static PluginContainer& getInstance(); PluginContainer(); virtual ~PluginContainer(); - virtual QVector>& currentDisplayActions() = 0; - virtual QVector>& currentInputActions() = 0; virtual void addMenu(const QString& menuName) = 0; virtual void removeMenu(const QString& menuName) = 0; virtual QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; @@ -48,4 +46,17 @@ public: virtual QOpenGLContext* getPrimaryContext() = 0; virtual bool isForeground() = 0; virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0; + + QVector>& currentDisplayActions() { + return _currentDisplayPluginActions; + } + + QVector>& currentInputActions() { + return _currentInputPluginActions; + } + +protected: + QVector> _currentDisplayPluginActions; + QVector> _currentInputPluginActions; + }; diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index c5c038fde3..8514d33d2e 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -83,7 +83,7 @@ public: virtual ~PluginContainerProxy() {} virtual void addMenu(const QString& menuName) override {} virtual void removeMenu(const QString& menuName) override {} - virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } + virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override {} virtual bool isOptionChecked(const QString& name) override { return false; } virtual void setIsOptionChecked(const QString& path, bool checked) override {} From 90ef7c6bf763d77be56ced44f4325a22ef673a93 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 3 Dec 2015 23:29:56 -0800 Subject: [PATCH 12/42] Disabling 0.5 Oculus plugin (Mac / Linux) for now --- interface/src/PluginContainerProxy.cpp | 1 - plugins/oculusLegacy/CMakeLists.txt | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index aff36fb881..aae94162ad 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index bf9d22410d..44cee83a7d 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -6,7 +6,8 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -if (NOT WIN32) +#if (NOT WIN32) +if (FALSE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() From 528a17422d4d32b29915191b71417375272d9e90 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 4 Dec 2015 10:33:10 -0800 Subject: [PATCH 13/42] Last of the OpenGL warnings on OS X --- .../src/QOpenGLContextWrapper.cpp | 40 +++++++++++++++++++ .../render-utils/src/QOpenGLContextWrapper.h | 33 +++++++++++++++ .../src/QOpenGLDebugLoggerWrapper.cpp | 24 +++++++++++ .../src/QOpenGLDebugLoggerWrapper.h | 19 +++++++++ tests/gpu-test/src/main.cpp | 24 ++++------- tests/render-utils/src/main.cpp | 25 ++++-------- tests/shaders/src/main.cpp | 25 +++++------- 7 files changed, 141 insertions(+), 49 deletions(-) create mode 100644 libraries/render-utils/src/QOpenGLContextWrapper.cpp create mode 100644 libraries/render-utils/src/QOpenGLContextWrapper.h create mode 100644 libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp create mode 100644 libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h diff --git a/libraries/render-utils/src/QOpenGLContextWrapper.cpp b/libraries/render-utils/src/QOpenGLContextWrapper.cpp new file mode 100644 index 0000000000..64233ea413 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLContextWrapper.cpp @@ -0,0 +1,40 @@ +// +// QOpenGLContextWrapper.cpp +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 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 +// + +#include "QOpenGLContextWrapper.h" + +#include + + +QOpenGLContextWrapper::QOpenGLContextWrapper() : + _context(new QOpenGLContext) +{ +} + +void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { + _context->setFormat(format); +} + +bool QOpenGLContextWrapper::create() { + return _context->create(); +} + +void QOpenGLContextWrapper::swapBuffers(QSurface* surface) { + _context->swapBuffers(surface); +} + +bool QOpenGLContextWrapper::makeCurrent(QSurface* surface) { + return _context->makeCurrent(surface); +} + +void QOpenGLContextWrapper::doneCurrent() { + _context->doneCurrent(); +} \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLContextWrapper.h b/libraries/render-utils/src/QOpenGLContextWrapper.h new file mode 100644 index 0000000000..6c50d5f438 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLContextWrapper.h @@ -0,0 +1,33 @@ +// +// QOpenGLContextWrapper.h +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 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_QOpenGLContextWrapper_h +#define hifi_QOpenGLContextWrapper_h + +class QOpenGLContext; +class QSurface; +class QSurfaceFormat; + +class QOpenGLContextWrapper { +public: + QOpenGLContextWrapper(); + + void setFormat(const QSurfaceFormat& format); + bool create(); + void swapBuffers(QSurface* surface); + bool makeCurrent(QSurface* surface); + void doneCurrent(); + +private: + QOpenGLContext* _context { nullptr }; +}; + +#endif // hifi_QOpenGLContextWrapper_h \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp new file mode 100644 index 0000000000..bd185034f4 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.cpp @@ -0,0 +1,24 @@ +// +// QOpenGLDebugLoggerWrapper.cpp +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 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 +// + +#include "QOpenGLDebugLoggerWrapper.h" + +#include +#include + +void setupDebugLogger(QObject* window) { + QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(window); + logger->initialize(); // initializes in the current context, i.e. ctx + logger->enableMessages(); + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, window, [&](const QOpenGLDebugMessage & debugMessage) { + qDebug() << debugMessage; + }); +} \ No newline at end of file diff --git a/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h new file mode 100644 index 0000000000..e2b1c5d9d4 --- /dev/null +++ b/libraries/render-utils/src/QOpenGLDebugLoggerWrapper.h @@ -0,0 +1,19 @@ +// +// QOpenGLDebugLoggerWrapper.h +// +// +// Created by Clement on 12/4/15. +// Copyright 2015 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_QOpenGLDebugLoggerWrapper_h +#define hifi_QOpenGLDebugLoggerWrapper_h + +class QObject; + +void setupDebugLogger(QObject* window); + +#endif // hifi_QOpenGLDebugLoggerWrapper_h \ No newline at end of file diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 80c2dbf8e9..fbfb49aace 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -33,9 +33,8 @@ #include #include -// Must come after GL headers -#include -#include +#include +#include #include #include @@ -118,7 +117,7 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _qGlContext{ nullptr }; + QOpenGLContextWrapper _qGlContext; QSize _size; gpu::ContextPointer _context; @@ -151,19 +150,12 @@ public: setFormat(format); - _qGlContext = new QOpenGLContext; - _qGlContext->setFormat(format); - _qGlContext->create(); + _qGlContext.setFormat(format); + _qGlContext.create(); show(); makeCurrent(); - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage& message){ - qDebug() << message; - }); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - + setupDebugLogger(this); gpu::Context::init(); _context = std::make_shared(); @@ -371,7 +363,7 @@ public: geometryCache->renderWireCube(batch); _context->render(batch); - _qGlContext->swapBuffers(this); + _qGlContext.swapBuffers(this); fps.increment(); if (fps.elapsed() >= 0.5f) { @@ -381,7 +373,7 @@ public: } void makeCurrent() { - _qGlContext->makeCurrent(this); + _qGlContext.makeCurrent(this); } protected: diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 0fa261db8d..43778c5c56 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -14,8 +14,8 @@ #include -#include -#include +#include +#include #include #include @@ -77,7 +77,7 @@ const QString& getQmlDir() { class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _context{ nullptr }; + QOpenGLContextWrapper _context; QSize _size; //TextRenderer* _textRenderer[4]; RateCounter fps; @@ -104,9 +104,8 @@ public: setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); + _context.setFormat(format); + _context.create(); show(); makeCurrent(); @@ -114,15 +113,7 @@ public: gpu::Context::init(); - { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; - }); - // logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - } + setupDebugLogger(this); qDebug() << (const char*)glGetString(GL_VERSION); //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); @@ -147,7 +138,7 @@ public: void draw(); void makeCurrent() { - _context->makeCurrent(this); + _context.makeCurrent(this); } protected: @@ -185,7 +176,7 @@ void QTestWindow::draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); - _context->swapBuffers(this); + _context.swapBuffers(this); glFinish(); fps.increment(); diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index f65cd9b5aa..658bc9c236 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -10,8 +10,6 @@ #include -#include - #include #include #include @@ -22,6 +20,9 @@ #include +#include +#include + #include "../model/Skybox_vert.h" #include "../model/Skybox_frag.h" @@ -120,7 +121,7 @@ // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { Q_OBJECT - QOpenGLContext* _context{ nullptr }; + QOpenGLContextWrapper _context; protected: void renderText(); @@ -130,22 +131,14 @@ public: setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - _context->create(); + _context.setFormat(format); + _context.create(); show(); makeCurrent(); gpu::Context::init(); - { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; - }); - } + setupDebugLogger(this); makeCurrent(); resize(QSize(800, 600)); } @@ -155,7 +148,7 @@ public: void draw(); void makeCurrent() { - _context->makeCurrent(this); + _context.makeCurrent(this); } }; @@ -248,7 +241,7 @@ void QTestWindow::draw() { testShaderBuild(polyvox_vert, polyvox_frag); }); - _context->swapBuffers(this); + _context.swapBuffers(this); } void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { From 42466728131c63fa6fee072f358466670264cb33 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 4 Dec 2015 10:38:24 -0800 Subject: [PATCH 14/42] enable head-moving of distance grabbing --- examples/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index bcb76716d9..05de977bd8 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -37,7 +37,7 @@ var BUMPER_ON_VALUE = 0.5; var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var MOVE_WITH_HEAD = false; // experimental head-controll of distantly held objects +var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects var NO_INTERSECT_COLOR = { red: 10, From cb67f05103d66eb2234c50a1d8b2d01f696cece5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 4 Dec 2015 10:47:27 -0800 Subject: [PATCH 15/42] Remove unimplemented menu items Developer->Avatar->Show Skeleton/Head Collision Shapes. --- interface/src/Menu.cpp | 2 -- interface/src/Menu.h | 2 -- interface/src/avatar/Avatar.cpp | 10 ---------- 3 files changed, 14 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d0c8b502c5..d48511d990 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -433,8 +433,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6b51987479..8041d2533f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -239,10 +239,8 @@ namespace MenuOption { const QString ReloadContent = "Reload Content (Clears all caches)"; const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString RenderFocusIndicator = "Show Eye Focus"; - const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; const QString RenderLookAtTargets = "Show Look-at Targets"; const QString RenderLookAtVectors = "Show Look-at Vectors"; - const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; const QString RenderResolution = "Scale Resolution"; const QString RenderResolutionOne = "1"; const QString RenderResolutionTwoThird = "2/3"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index bf4ddadb62..cf812db383 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -463,16 +463,6 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } } - /* - // TODO: re-implement these when we have more detailed avatar collision shapes - bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); - if (renderSkeleton) { - } - bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); - if (renderHead && shouldRenderHead(renderArgs)) { - } - */ - bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) { PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes"); From ea65ef964fbd1672803ab729d5eaffda9916b02f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 4 Dec 2015 11:13:36 -0800 Subject: [PATCH 16/42] fix an AC race/crash --- libraries/shared/src/SpatiallyNestable.cpp | 51 +++++++++++++--------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index a0280943de..0d7d2a7652 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -62,6 +62,9 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { // we have a _parentID but no parent pointer, or our parent pointer was to the wrong thing QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + return nullptr; + } _parent = parentFinder->find(_parentID); parent = _parent.lock(); if (parent) { @@ -98,12 +101,14 @@ void SpatiallyNestable::setParentID(const QUuid parentID) { glm::vec3 SpatiallyNestable::worldToLocal(glm::vec3 position, QUuid parentID, int parentJointIndex) { QSharedPointer parentFinder = DependencyManager::get(); - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); Transform parentTransform; - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } } Transform positionTransform; @@ -119,12 +124,14 @@ glm::vec3 SpatiallyNestable::worldToLocal(glm::vec3 position, QUuid parentID, in glm::quat SpatiallyNestable::worldToLocal(glm::quat orientation, QUuid parentID, int parentJointIndex) { QSharedPointer parentFinder = DependencyManager::get(); - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); Transform parentTransform; - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } } Transform orientationTransform; @@ -139,12 +146,14 @@ glm::quat SpatiallyNestable::worldToLocal(glm::quat orientation, QUuid parentID, glm::vec3 SpatiallyNestable::localToWorld(glm::vec3 position, QUuid parentID, int parentJointIndex) { QSharedPointer parentFinder = DependencyManager::get(); - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); Transform parentTransform; - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } } Transform positionTransform; positionTransform.setTranslation(position); @@ -155,12 +164,14 @@ glm::vec3 SpatiallyNestable::localToWorld(glm::vec3 position, QUuid parentID, in glm::quat SpatiallyNestable::localToWorld(glm::quat orientation, QUuid parentID, int parentJointIndex) { QSharedPointer parentFinder = DependencyManager::get(); - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); Transform parentTransform; - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); + if (parentFinder) { + auto parentWP = parentFinder->find(parentID); + auto parent = parentWP.lock(); + if (parent) { + parentTransform = parent->getTransform(parentJointIndex); + parentTransform.setScale(1.0f); + } } Transform orientationTransform; orientationTransform.setRotation(orientation); From 918342df5f1bd2e04d6e036d747f6d181f5e947d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 4 Dec 2015 11:18:46 -0800 Subject: [PATCH 17/42] make sure the NodeList is reset on its own thread --- libraries/networking/src/DomainHandler.cpp | 2 +- libraries/networking/src/NodeList.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 681f971ef5..96a6c9c4a5 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -94,7 +94,7 @@ void DomainHandler::softReset() { clearSettings(); // cancel the failure timeout for any pending requests for settings - QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::AutoConnection); + QMetaObject::invokeMethod(&_settingsTimer, "stop"); } void DomainHandler::hardReset() { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 925c64c77a..38c9d819e2 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -199,6 +199,11 @@ void NodeList::processICEPingPacket(QSharedPointer packet) { } void NodeList::reset() { + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "reset"); + return; + } + LimitedNodeList::reset(); _numNoReplyDomainCheckIns = 0; From d5f79b84fca14cf41ead9471349ed0bc23e2a8ef Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 4 Dec 2015 11:20:46 -0800 Subject: [PATCH 18/42] make the NodeList reset a blocking queued connection --- libraries/networking/src/NodeList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 38c9d819e2..87899d59a8 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -200,7 +200,7 @@ void NodeList::processICEPingPacket(QSharedPointer packet) { void NodeList::reset() { if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "reset"); + QMetaObject::invokeMethod(this, "reset", Qt::BlockingQueuedConnection); return; } From 3bb1dedaf9990a71a55d052c12ad30e270f7a072 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Fri, 4 Dec 2015 11:41:23 -0800 Subject: [PATCH 19/42] fixed syntax error --- unpublishedScripts/hiddenEntityReset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 259cdd066c..ecbeec7a81 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -1367,7 +1367,7 @@ resetMe: { resetMe: true }, - grabbableKey; { + grabbableKey: { invertSolidWhileHeld: true } From 165db97cb22331e04d20ce42031003da6e6c311f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 4 Dec 2015 11:50:27 -0800 Subject: [PATCH 20/42] cleanup double reset debug on assignment-client restart --- libraries/networking/src/LimitedNodeList.cpp | 20 +++++++++++++------- libraries/networking/src/udt/Socket.cpp | 10 ++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index e717856ca2..6942ce1861 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -435,17 +435,23 @@ SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) { } void LimitedNodeList::eraseAllNodes() { - qCDebug(networking) << "Clearing the NodeList. Deleting all nodes in list."; - QSet killedNodes; - eachNode([&killedNodes](const SharedNodePointer& node){ - killedNodes.insert(node); - }); { - // iterate the current nodes, emit that they are dying and remove them from the hash + // iterate the current nodes - grab them so we can emit that they are dying + // and then remove them from the hash QWriteLocker writeLocker(&_nodeMutex); - _nodeHash.clear(); + + if (_nodeHash.size() > 0) { + qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList."; + + auto it = _nodeHash.begin(); + + while (it != _nodeHash.end()) { + killedNodes.insert(it->second); + it = _nodeHash.unsafe_erase(it); + } + } } foreach(const SharedNodePointer& killedNode, killedNodes) { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 6d4a834879..f1e336b431 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -194,10 +194,12 @@ void Socket::clearConnections() { QMetaObject::invokeMethod(this, "clearConnections", Qt::BlockingQueuedConnection); return; } - - // clear all of the current connections in the socket - qDebug() << "Clearing all remaining connections in Socket."; - _connectionsHash.clear(); + + if (_connectionsHash.size() > 0) { + // clear all of the current connections in the socket + qDebug() << "Clearing all remaining connections in Socket."; + _connectionsHash.clear(); + } } void Socket::cleanupConnection(HifiSockAddr sockAddr) { From 11917ca501226493a83e26257d82725b0c3dc086 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 4 Dec 2015 12:46:19 -0800 Subject: [PATCH 21/42] Fixing preview / vsync functionality --- interface/src/Application.cpp | 5 +++++ interface/src/PluginContainerProxy.cpp | 2 +- interface/src/PluginContainerProxy.h | 2 +- .../src/display-plugins/OpenGLDisplayPlugin.cpp | 13 +++---------- .../src/display-plugins/OpenGLDisplayPlugin.h | 2 +- .../display-plugins/WindowOpenGLDisplayPlugin.cpp | 2 +- libraries/gl/src/gl/GLWidget.cpp | 15 +++++++++++++++ libraries/gl/src/gl/GLWidget.h | 6 +++++- libraries/plugins/src/plugins/PluginContainer.h | 4 ++-- tests/controllers/src/main.cpp | 2 +- 10 files changed, 35 insertions(+), 18 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bbc36e8623..9e98b51442 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -620,6 +620,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); + _glWidget->makeCurrent(); + _glWidget->initializeGL(); _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->create(_glWidget->context()->contextHandle()); @@ -1194,6 +1196,9 @@ void Application::paintGL() { QSize size = getDeviceSize(); renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); _applicationOverlay.renderOverlay(&renderArgs); + gpu::FramebufferPointer overlayFramebuffer = _applicationOverlay.getOverlayFramebuffer(); + + } { diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index aae94162ad..048f079653 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -150,7 +150,7 @@ void PluginContainerProxy::showDisplayPluginsTools() { DependencyManager::get()->hmdTools(true); } -QGLWidget* PluginContainerProxy::getPrimaryWidget() { +GLWidget* PluginContainerProxy::getPrimaryWidget() { return qApp->_glWidget; } diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index 5d974f0a0c..3adc696ba9 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -27,7 +27,7 @@ class PluginContainerProxy : public QObject, PluginContainer { virtual bool makeRenderingContextCurrent() override; virtual void releaseSceneTexture(uint32_t texture) override; virtual void releaseOverlayTexture(uint32_t texture) override; - virtual QGLWidget* getPrimaryWidget() override; + virtual GLWidget* getPrimaryWidget() override; virtual QWindow* getPrimaryWindow() override; virtual QOpenGLContext* getPrimaryContext() override; virtual bool isForeground() override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 12aa37cd56..cec84c74d4 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -145,8 +146,6 @@ private: QGLContext* _context { nullptr }; }; -bool OpenGLDisplayPlugin::_vsyncSupported = false; - OpenGLDisplayPlugin::OpenGLDisplayPlugin() { _sceneTextureEscrow.setRecycler([this](GLuint texture){ cleanupForSceneTexture(texture); @@ -172,19 +171,14 @@ void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) { void OpenGLDisplayPlugin::activate() { - _timer.start(2); + _timer.start(1); + _vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported(); // Start the present thread if necessary auto presentThread = DependencyManager::get(); if (!presentThread) { auto widget = _container->getPrimaryWidget(); - // TODO: write the proper code for linux -#if defined(Q_OS_WIN) - widget->makeCurrent(); - _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); - widget->doneCurrent(); -#endif DependencyManager::set(); presentThread = DependencyManager::get(); @@ -195,7 +189,6 @@ void OpenGLDisplayPlugin::activate() { } presentThread->setNewDisplayPlugin(this); DisplayPlugin::activate(); - emit requestRender(); } void OpenGLDisplayPlugin::stop() { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 747d8b810b..ef78374994 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -81,7 +81,7 @@ protected: GLTextureEscrow _overlayTextureEscrow; GLTextureEscrow _sceneTextureEscrow; - static bool _vsyncSupported; + bool _vsyncSupported { false }; }; diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp index b215b19b15..c1922599a5 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -7,7 +7,7 @@ // #include "WindowOpenGLDisplayPlugin.h" -#include +#include #include "plugins/PluginContainer.h" diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 310675c01f..c67dec1e51 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -7,6 +7,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include "GLWidget.h" @@ -16,11 +17,14 @@ #include #include +#include #include #include + #include "GLHelpers.h" + GLWidget::GLWidget() : QGLWidget(getDefaultGLFormat()) { #ifdef Q_OS_LINUX // Cause GLWidget::eventFilter to be called. @@ -42,6 +46,12 @@ void GLWidget::initializeGL() { setAcceptDrops(true); // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. setAutoBufferSwap(false); + + // TODO: write the proper code for linux + makeCurrent(); +#if defined(Q_OS_WIN) + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; +#endif } void GLWidget::paintEvent(QPaintEvent* event) { @@ -112,3 +122,8 @@ bool GLWidget::eventFilter(QObject*, QEvent* event) { } return false; } + +bool GLWidget::isVsyncSupported() const { + return _vsyncSupported; +} + diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index df4a73ac50..5b391aa6cd 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -21,15 +21,19 @@ public: int getDeviceWidth() const; int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } + bool isVsyncSupported() const; + virtual void initializeGL() override; protected: - virtual void initializeGL() override; virtual bool event(QEvent* event) override; virtual void paintEvent(QPaintEvent* event) override; virtual void resizeEvent(QResizeEvent* event) override; private slots: virtual bool eventFilter(QObject*, QEvent* event) override; + +private: + bool _vsyncSupported { false }; }; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 6732bc02ba..19859fd98b 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -16,7 +16,7 @@ #include "Forward.h" class QAction; -class QGLWidget; +class GLWidget; class QScreen; class QOpenGLContext; class QWindow; @@ -41,7 +41,7 @@ public: virtual bool makeRenderingContextCurrent() = 0; virtual void releaseSceneTexture(uint32_t texture) = 0; virtual void releaseOverlayTexture(uint32_t texture) = 0; - virtual QGLWidget* getPrimaryWidget() = 0; + virtual GLWidget* getPrimaryWidget() = 0; virtual QWindow* getPrimaryWindow() = 0; virtual QOpenGLContext* getPrimaryContext() = 0; virtual bool isForeground() = 0; diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 8514d33d2e..fe49c2b385 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -94,7 +94,7 @@ public: virtual bool makeRenderingContextCurrent() override { return true; } virtual void releaseSceneTexture(uint32_t texture) override {} virtual void releaseOverlayTexture(uint32_t texture) override {} - virtual QGLWidget* getPrimaryWidget() override { return nullptr; } + virtual GLWidget* getPrimaryWidget() override { return nullptr; } virtual QWindow* getPrimaryWindow() override { return nullptr; } virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual bool isForeground() override { return true; } From ad4f818b21d41ef51e502853480739749afa9490 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 4 Dec 2015 14:12:41 -0800 Subject: [PATCH 22/42] only attempt to stop QTimer if active --- libraries/networking/src/NodeList.cpp | 8 +++++++- libraries/networking/src/NodeList.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 8748db50d7..253058ae89 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -94,7 +94,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings); connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start())); - connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); + connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::stopKeepalivePingTimer); // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); @@ -646,6 +646,12 @@ void NodeList::activateSocketFromNodeCommunication(QSharedPointer pack } } +void NodeList::stopKeepalivePingTimer() { + if (_keepAlivePingTimer.isActive()) { + _keepAlivePingTimer.stop(); + } +} + void NodeList::sendKeepAlivePings() { eachMatchingNode([this](const SharedNodePointer& node)->bool { return _nodeTypesOfInterest.contains(node->getType()); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 02f49d2918..4d787bac6d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -89,6 +89,7 @@ public slots: signals: void limitOfSilentDomainCheckInsReached(); private slots: + void stopKeepalivePingTimer(); void sendPendingDSPathQuery(); void handleICEConnectionToDomainServer(); From 0a9b42ad3c588616bd4c3987c3ff13bb1248524f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 4 Dec 2015 15:10:46 -0800 Subject: [PATCH 23/42] Fix present rate stats display, which was counting (roughly) twice for 2D display, and flickering. --- interface/src/ui/Stats.cpp | 2 +- .../display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 358dc49bdb..55751d8631 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -121,7 +121,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(serverCount, nodeList->size()); STAT_UPDATE(renderrate, (int)qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { - STAT_UPDATE(presentrate, (int)qApp->getActiveDisplayPlugin()->presentRate()); + STAT_UPDATE(presentrate, (int)round(qApp->getActiveDisplayPlugin()->presentRate())); } else { STAT_UPDATE(presentrate, -1); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index cec84c74d4..8593da1573 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -303,7 +303,6 @@ void OpenGLDisplayPlugin::internalPresent() { glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); drawUnitQuad(); swapBuffers(); - updateFramerate(); } void OpenGLDisplayPlugin::present() { From ba001ef4b0fc44faf1c5d328cf16c40eff050220 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 4 Dec 2015 15:15:18 -0800 Subject: [PATCH 24/42] Rig: split Poses into internal and external sets. In preparation for making some accessors thread safe. --- libraries/animation/src/Rig.cpp | 120 +++++++++++++++++--------------- libraries/animation/src/Rig.h | 21 ++++-- 2 files changed, 81 insertions(+), 60 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c0b399ee0f..5ad000a62c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -158,10 +160,10 @@ void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); _animNode.reset(); - _relativePoses.clear(); - _absolutePoses.clear(); - _overridePoses.clear(); - _overrideFlags.clear(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._absolutePoses.clear(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overrideFlags.clear(); } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { @@ -173,16 +175,16 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _relativePoses.clear(); - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - _overridePoses.clear(); - _overridePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -201,16 +203,16 @@ void Rig::reset(const FBXGeometry& geometry) { computeEyesInRootFrame(_animSkeleton->getRelativeDefaultPoses()); - _relativePoses.clear(); - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses.clear(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - _overridePoses.clear(); - _overridePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._overridePoses.clear(); + _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -228,11 +230,11 @@ void Rig::reset(const FBXGeometry& geometry) { } bool Rig::jointStatesEmpty() { - return _relativePoses.empty(); + return _internalPoseSet._relativePoses.empty(); } int Rig::getJointStateCount() const { - return _relativePoses.size(); + return _internalPoseSet._relativePoses.size(); } int Rig::indexOfJoint(const QString& jointName) const { @@ -262,7 +264,7 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { if (isIndexValid(index)) { - rotation = _relativePoses[index].rot; + rotation = _internalPoseSet._relativePoses[index].rot; return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot); } else { return false; @@ -271,7 +273,7 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { if (isIndexValid(index)) { - translation = _relativePoses[index].trans; + translation = _internalPoseSet._relativePoses[index].trans; return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans); } else { return false; @@ -280,46 +282,46 @@ bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { void Rig::clearJointState(int index) { if (isIndexValid(index)) { - _overrideFlags[index] = false; + _internalPoseSet._overrideFlags[index] = false; } } void Rig::clearJointStates() { - _overrideFlags.clear(); - _overrideFlags.resize(_animSkeleton->getNumJoints()); + _internalPoseSet._overrideFlags.clear(); + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints()); } void Rig::clearJointAnimationPriority(int index) { if (isIndexValid(index)) { - _overrideFlags[index] = false; + _internalPoseSet._overrideFlags[index] = false; } } void Rig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { if (valid) { - assert(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].trans = translation; + assert(_overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].trans = translation; } } } void Rig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { - assert(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].rot = rotation; - _overridePoses[index].trans = translation; + assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].rot = rotation; + _internalPoseSet._overridePoses[index].trans = translation; } } void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { if (isIndexValid(index)) { if (valid) { - ASSERT(_overrideFlags.size() == _overridePoses.size()); - _overrideFlags[index] = true; - _overridePoses[index].rot = rotation; + ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + _internalPoseSet._overrideFlags[index] = true; + _internalPoseSet._overridePoses[index].rot = rotation; } } } @@ -336,7 +338,7 @@ void Rig::restoreJointTranslation(int index, float fraction, float priority) { bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { if (isIndexValid(jointIndex)) { - position = (rotation * _absolutePoses[jointIndex].trans) + translation; + position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans) + translation; return true; } else { return false; @@ -345,7 +347,7 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm: bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { if (isIndexValid(jointIndex)) { - position = _absolutePoses[jointIndex].trans; + position = _internalPoseSet._absolutePoses[jointIndex].trans; return true; } else { return false; @@ -354,7 +356,7 @@ bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const { if (isIndexValid(jointIndex)) { - result = rotation * _absolutePoses[jointIndex].rot; + result = rotation * _internalPoseSet._absolutePoses[jointIndex].rot; return true; } else { return false; @@ -363,7 +365,7 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { if (isIndexValid(jointIndex)) { - rotation = _relativePoses[jointIndex].rot; + rotation = _internalPoseSet._relativePoses[jointIndex].rot; return true; } else { return false; @@ -372,7 +374,7 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { if (isIndexValid(jointIndex)) { - translation = _relativePoses[jointIndex].trans; + translation = _internalPoseSet._relativePoses[jointIndex].trans; return true; } else { return false; @@ -708,21 +710,27 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { // evaluate the animation AnimNode::Triggers triggersOut; - _relativePoses = _animNode->evaluate(_animVars, deltaTime, triggersOut); - if ((int)_relativePoses.size() != _animSkeleton->getNumJoints()) { + _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, deltaTime, triggersOut); + if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. - _relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _animVars.clearTriggers(); for (auto& trigger : triggersOut) { _animVars.setTrigger(trigger); } - computeEyesInRootFrame(_relativePoses); + computeEyesInRootFrame(_internalPoseSet._relativePoses); } applyOverridePoses(); - buildAbsoluteRigPoses(_relativePoses, _absolutePoses); + buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + + // copy internal poses to external poses + { + QWriteLocker writeLock(&_externalPoseSetLock); + _externalPoseSet = _internalPoseSet; + } } void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, @@ -884,7 +892,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm if (isIndexValid(index)) { glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation); glm::mat4 worldToRig = glm::inverse(rigToWorld); - glm::vec3 zAxis = glm::normalize(_absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); + glm::vec3 zAxis = glm::normalize(_internalPoseSet._absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); glm::quat q = rotationBetween(IDENTITY_FRONT, zAxis); // limit rotation @@ -892,7 +900,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm q = glm::angleAxis(glm::clamp(glm::angle(q), -MAX_ANGLE, MAX_ANGLE), glm::axis(q)); // directly set absolutePose rotation - _absolutePoses[index].rot = q; + _internalPoseSet._absolutePoses[index].rot = q; } } @@ -989,13 +997,13 @@ void Rig::applyOverridePoses() { return; } - ASSERT(_animSkeleton->getNumJoints() == (int)_relativePoses.size()); - ASSERT(_animSkeleton->getNumJoints() == (int)_overrideFlags.size()); - ASSERT(_animSkeleton->getNumJoints() == (int)_overridePoses.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._relativePoses.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overrideFlags.size()); + ASSERT(_animSkeleton->getNumJoints() == (int)_internalPoseSet._overridePoses.size()); - for (size_t i = 0; i < _overrideFlags.size(); i++) { - if (_overrideFlags[i]) { - _relativePoses[i] = _overridePoses[i]; + for (size_t i = 0; i < _internalPoseSet._overrideFlags.size(); i++) { + if (_internalPoseSet._overrideFlags[i]) { + _internalPoseSet._relativePoses[i] = _internalPoseSet._overridePoses[i]; } } } @@ -1020,14 +1028,14 @@ void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& a // transform all absolute poses into rig space. AnimPose geometryToRigTransform(_geometryToRigTransform); - for (int i = 0; i < (int)_absolutePoses.size(); i++) { + for (int i = 0; i < (int)absolutePosesOut.size(); i++) { absolutePosesOut[i] = geometryToRigTransform * absolutePosesOut[i]; } } glm::mat4 Rig::getJointTransform(int jointIndex) const { if (isIndexValid(jointIndex)) { - return _absolutePoses[jointIndex]; + return _internalPoseSet._absolutePoses[jointIndex]; } else { return glm::mat4(); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 8d1d768c18..3d8b0f3bc7 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "AnimNode.h" #include "AnimNodeLoader.h" @@ -27,6 +28,9 @@ class Rig; typedef std::shared_ptr RigPointer; +// Rig instances are reentrant. +// However only specific methods thread-safe. Noted below. + class Rig : public QObject, public std::enable_shared_from_this { public: struct StateHandler { @@ -217,10 +221,19 @@ public: AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) - AnimPoseVec _relativePoses; // geometry space relative to parent. - AnimPoseVec _absolutePoses; // rig space, not relative to parent. - AnimPoseVec _overridePoses; // geometry space relative to parent. - std::vector _overrideFlags; + struct PoseSet { + AnimPoseVec _relativePoses; // geometry space relative to parent. + AnimPoseVec _absolutePoses; // rig space, not relative to parent. + AnimPoseVec _overridePoses; // geometry space relative to parent. + std::vector _overrideFlags; + }; + + // Only accessed by the main thread + PoseSet _internalPoseSet; + + // Copy of the _poseSet for external threads. + PoseSet _externalPoseSet; + mutable QReadWriteLock _externalPoseSetLock; AnimPoseVec _absoluteDefaultPoses; // rig space, not relative to parent. From 817f0307a040567b82ec783559b719262658fbaf Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 4 Dec 2015 15:51:19 -0800 Subject: [PATCH 25/42] fix 3d line overlays --- interface/src/ui/overlays/Line3DOverlay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 0acd7ecc1e..9dc609af31 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -12,6 +12,7 @@ #include #include +#include QString const Line3DOverlay::TYPE = "line3d"; @@ -53,6 +54,7 @@ void Line3DOverlay::render(RenderArgs* args) { auto batch = args->_batch; if (batch) { batch->setModelTransform(_transform); + DependencyManager::get()->bindSimpleProgram(*batch); if (getIsDashedLine()) { // TODO: add support for color to renderDashedLine() From 7abfc93ff964cc57db629c1549c5971784b4adf4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 4 Dec 2015 16:16:17 -0800 Subject: [PATCH 26/42] Avatar::getJointRotation and getJointTranslation is thread-safe It can be called from script with minimal blocking, because it inspects a copy of the joint values from the Rig, which is updated atomically. This copy occurs in Rig::updateAnimations() --- interface/src/avatar/Avatar.cpp | 6 ------ libraries/animation/src/Rig.cpp | 12 +++++++----- libraries/animation/src/Rig.h | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 0fecb3a761..2c8d970336 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -857,18 +857,12 @@ QVector Avatar::getJointRotations() const { } glm::quat Avatar::getJointRotation(int index) const { - if (QThread::currentThread() != thread()) { - return AvatarData::getJointRotation(index); - } glm::quat rotation; _skeletonModel.getJointRotation(index, rotation); return rotation; } glm::vec3 Avatar::getJointTranslation(int index) const { - if (QThread::currentThread() != thread()) { - return AvatarData::getJointTranslation(index); - } glm::vec3 translation; _skeletonModel.getJointTranslation(index, translation); return translation; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 5ad000a62c..8fb56ed699 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -300,7 +300,7 @@ void Rig::clearJointAnimationPriority(int index) { void Rig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { if (valid) { - assert(_overrideFlags.size() == _internalPoseSet._overridePoses.size()); + assert(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); _internalPoseSet._overrideFlags[index] = true; _internalPoseSet._overridePoses[index].trans = translation; } @@ -364,8 +364,9 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const } bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { - if (isIndexValid(jointIndex)) { - rotation = _internalPoseSet._relativePoses[jointIndex].rot; + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { + rotation = _externalPoseSet._relativePoses[jointIndex].rot; return true; } else { return false; @@ -373,8 +374,9 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const { } bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const { - if (isIndexValid(jointIndex)) { - translation = _internalPoseSet._relativePoses[jointIndex].trans; + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) { + translation = _externalPoseSet._relativePoses[jointIndex].trans; return true; } else { return false; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 3d8b0f3bc7..64ce8c52ef 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -127,10 +127,10 @@ public: // if rotation is identity, result will be in rig space bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const; - // geometry space + // geometry space (thread-safe) bool getJointRotation(int jointIndex, glm::quat& rotation) const; - // geometry space + // geometry space (thread-safe) bool getJointTranslation(int jointIndex, glm::vec3& translation) const; // legacy From 62b4eac875fa5e76487965ab828e32dec02dfb61 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 4 Dec 2015 16:41:56 -0800 Subject: [PATCH 27/42] fix avatar-mixer parents-related crash --- assignment-client/src/avatars/AvatarMixerClientData.cpp | 8 ++++---- assignment-client/src/avatars/AvatarMixerClientData.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9d78d92463..96df98fdd0 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -18,7 +18,7 @@ int AvatarMixerClientData::parseData(NLPacket& packet) { packet.readPrimitive(&_lastReceivedSequenceNumber); // compute the offset to the data payload - return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); + return _avatar->parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); } bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { @@ -40,7 +40,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { - jsonObject["display_name"] = _avatar.getDisplayName(); + jsonObject["display_name"] = _avatar->getDisplayName(); jsonObject["full_rate_distance"] = _fullRateDistance; jsonObject["max_av_distance"] = _maxAvatarDistance; jsonObject["num_avs_sent_last_frame"] = _numAvatarsSentLastFrame; @@ -49,7 +49,7 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); - jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; + jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; - jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate(); + jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate(); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 1f5e8fa77a..09100010e0 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -34,7 +34,7 @@ class AvatarMixerClientData : public NodeData { Q_OBJECT public: int parseData(NLPacket& packet); - AvatarData& getAvatar() { return _avatar; } + AvatarData& getAvatar() { return *_avatar; } bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); @@ -80,7 +80,7 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; private: - AvatarData _avatar; + AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; From 3b6cd4c2d10e50389278725d530b666dfc0fd8dc Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 14:11:59 -0800 Subject: [PATCH 28/42] initial live earthqukes commit --- .../data_visualization/earthquakes_live.js | 173 +++ examples/data_visualization/testQuakes.json | 153 +++ examples/libraries/tinyColor.js | 1155 +++++++++++++++++ 3 files changed, 1481 insertions(+) create mode 100644 examples/data_visualization/earthquakes_live.js create mode 100644 examples/data_visualization/testQuakes.json create mode 100644 examples/libraries/tinyColor.js diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js new file mode 100644 index 0000000000..76f0fad5b0 --- /dev/null +++ b/examples/data_visualization/earthquakes_live.js @@ -0,0 +1,173 @@ +//earthquakes_live.js +//created by james b. pollack @imgntn on 12/5/2015 +Script.include('../libraries/promise.js'); +var Promise = loadPromise(); + +Script.include('../libraries/tinyColor.js'); +var tinyColor = loadTinyColor(); + +var EARTH_SPHERE_RADIUS = 5; +var EARTH_CENTER_POSITION = Vec3.sum(MyAvatar.position, { + x: 5, + y: 0, + z: 0 +}); + +var EARTH_MODEL_URL='http://public.highfidelity.io/marketplace/hificontent/Scripts/planets/planets/earth.fbx'; +//USGS updates the data every five minutes +var CHECK_QUAKE_FREQUENCY = 300 * 1000; + +var QUAKE_MARKER_DIMENSIONS = { + x: 0.1, + y: 0.1, + z: 0.1 +}; + +function createEarth() { + var earthProperties = { + name: 'Earth', + type: 'Model', + modelURL:EARTH_MODEL_URL, + position: EARTH_CENTER_POSITION, + dimensions: { + x: EARTH_SPHERE_RADIUS, + y: EARTH_SPHERE_RADIUS, + z: EARTH_SPHERE_RADIUS + }, + // color: { + // red: 0, + // green: 100, + // blue: 150 + // }, + collisionsWillMove: false, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + } + + return Entities.addEntity(earthProperties) +} + +function plotLatitudeLongitude(radiusOfSphere, latitude, longitude) { + var tx = radiusOfSphere * Math.cos(latitude) * Math.cos(longitude); + var ty = radiusOfSphere * -Math.sin(latitude); + var tz = radiusOfSphere * Math.cos(latitude) * Math.sin(longitude); + return { + x: tx, + y: ty, + z: tz + } +} + +function getQuakePosition(earthquake) { + var latitude = earthquake.geometry.coordinates[0]; + var longitude = earthquake.geometry.coordinates[1]; + var latlng = plotLatitudeLongitude(2.5, latitude, longitude); + + var position = EARTH_CENTER_POSITION; + var finalPosition = Vec3.sum(position, latlng); + + // print('finalpos::' + JSON.stringify(finalPosition)) + return finalPosition +} + +var QUAKE_URL = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson' + +function get(url) { + print('getting' + url) + // Return a new promise. + return new Promise(function(resolve, reject) { + // Do the usual XHR stuff + var req = new XMLHttpRequest(); + req.open('GET', url); + req.onreadystatechange = function() { + print('req status:: ' + JSON.stringify(req.status)) + + if (req.readyState == 4 && req.status == 200) { + var myArr = JSON.parse(req.responseText); + resolve(myArr); + } + }; + + req.send(); + }); +} + +function showEarthquake(snapshot) { + var earthquake = snapshot.val(); + print("Mag " + earthquake.mag + " at " + earthquake.place); +} + +function createQuakeMarker(earthquake) { + var markerProperties = { + name: 'Marker', + type: 'Sphere', + dimensions: QUAKE_MARKER_DIMENSIONS, + position: getQuakePosition(earthquake), + lifetime: 6000, + color: getQuakeMarkerColor(earthquake) + } + + //print('marker properties::' + JSON.stringify(markerProperties)) + return Entities.addEntity(markerProperties); +} + +function getQuakeMarkerColor(earthquake) { + var color = {}; + var magnitude = earthquake.properties.mag; + //realistic but will never get full red coloring and will probably be pretty dull for most. must experiment + var sValue = scale(magnitude, 0, 10, 0, 100); + var HSL_string = "hsl(0, " + sValue + "%, 50%)" + var color = tinyColor(HSL_string); + var finalColor = { + red: color._r, + green: color._g, + blue: color._b + } + + return finalColor +} + +function scale(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +function processQuakes(earthquakes) { + print('quakers length' + earthquakes.length) + earthquakes.forEach(function(quake) { + // print('PROCESSING A QUAKE') + var marker = createQuakeMarker(quake); + markers.push(marker); + }) + print('markers length:' + markers.length) +} + +var quakea; +var markers = []; + +var earth = createEarth(); + +get(QUAKE_URL).then(function(response) { + print('got it::' + response.features.length) + quakes = response.features; + processQuakes(quakes); + //print("Success!" + JSON.stringify(response)); +}, function(error) { + print('error getting quakes') +}); + +function cleanupMarkers() { + print('CLEANING UP MARKERS') + while (markers.length > 0) { + Entities.deleteEntity(markers.pop()); + } +} + +function cleanupEarth() { + Entities.deleteEntity(earth); +} + +Script.scriptEnding.connect(cleanupMarkers); +Script.scriptEnding.connect(cleanupEarth); \ No newline at end of file diff --git a/examples/data_visualization/testQuakes.json b/examples/data_visualization/testQuakes.json new file mode 100644 index 0000000000..a6429b79c8 --- /dev/null +++ b/examples/data_visualization/testQuakes.json @@ -0,0 +1,153 @@ +{ + "type": "FeatureCollection", + "metadata": { + "generated": 1449347736000, + "url": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson", + "title": "USGS All Earthquakes, Past Hour", + "status": 200, + "api": "1.1.0", + "count": 4 + }, + "features": [{ + "type": "Feature", + "properties": { + "mag": 1.39, + "place": "17km ESE of Julian, California", + "time": 1449345604050, + "updated": 1449345856673, + "tz": -480, + "url": "http://earthquake.usgs.gov/earthquakes/eventpage/ci37498832", + "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/ci37498832.geojson", + "felt": null, + "cdi": null, + "mmi": null, + "alert": null, + "status": "automatic", + "tsunami": 0, + "sig": 30, + "net": "ci", + "code": "37498832", + "ids": ",ci37498832,", + "sources": ",ci,", + "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", + "nst": 32, + "dmin": 0.1379, + "rms": 0.21, + "gap": 68, + "magType": "ml", + "type": "earthquake", + "title": "M 1.4 - 17km ESE of Julian, California" + }, + "geometry": { + "type": "Point", + "coordinates": [-116.4293333, 33.0301667, 7.84] + }, + "id": "ci37498832" + }, { + "type": "Feature", + "properties": { + "mag": 1.12, + "place": "3km ESE of The Geysers, California", + "time": 1449344686690, + "updated": 1449344783260, + "tz": -480, + "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564866", + "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564866.geojson", + "felt": null, + "cdi": null, + "mmi": null, + "alert": null, + "status": "automatic", + "tsunami": 0, + "sig": 19, + "net": "nc", + "code": "72564866", + "ids": ",nc72564866,", + "sources": ",nc,", + "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", + "nst": 11, + "dmin": 0.01342, + "rms": 0.03, + "gap": 154, + "magType": "md", + "type": "earthquake", + "title": "M 1.1 - 3km ESE of The Geysers, California" + }, + "geometry": { + "type": "Point", + "coordinates": [-122.7176666, 38.7598343, 1.48] + }, + "id": "nc72564866" + }, { + "type": "Feature", + "properties": { + "mag": 1.99, + "place": "3km SE of The Geysers, California", + "time": 1449344287500, + "updated": 1449344383210, + "tz": -480, + "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564856", + "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564856.geojson", + "felt": null, + "cdi": null, + "mmi": null, + "alert": null, + "status": "automatic", + "tsunami": 0, + "sig": 61, + "net": "nc", + "code": "72564856", + "ids": ",nc72564856,", + "sources": ",nc,", + "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", + "nst": 13, + "dmin": 0.01626, + "rms": 0.02, + "gap": 72, + "magType": "md", + "type": "earthquake", + "title": "M 2.0 - 3km SE of The Geysers, California" + }, + "geometry": { + "type": "Point", + "coordinates": [-122.7203369, 38.7571678, 0.3] + }, + "id": "nc72564856" + }, { + "type": "Feature", + "properties": { + "mag": 2.36, + "place": "3km ESE of The Geysers, California", + "time": 1449344196380, + "updated": 1449344459434, + "tz": -480, + "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564836", + "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564836.geojson", + "felt": 0, + "cdi": 1, + "mmi": null, + "alert": null, + "status": "automatic", + "tsunami": 0, + "sig": 86, + "net": "nc", + "code": "72564836", + "ids": ",nc72564836,", + "sources": ",nc,", + "types": ",dyfi,focal-mechanism,general-link,geoserve,nearby-cities,origin,phase-data,scitech-link,", + "nst": 36, + "dmin": 0.01395, + "rms": 0.07, + "gap": 38, + "magType": "md", + "type": "earthquake", + "title": "M 2.4 - 3km ESE of The Geysers, California" + }, + "geometry": { + "type": "Point", + "coordinates": [-122.722168, 38.7598343, 1.21] + }, + "id": "nc72564836" + }], + "bbox": [-122.722168, 33.0301667, 0.3, -116.4293333, 38.7598343, 7.84] +} \ No newline at end of file diff --git a/examples/libraries/tinyColor.js b/examples/libraries/tinyColor.js new file mode 100644 index 0000000000..18aa462010 --- /dev/null +++ b/examples/libraries/tinyColor.js @@ -0,0 +1,1155 @@ +// TinyColor v1.3.0 +// https://github.com/bgrins/TinyColor +// Brian Grinstead, MIT License + + + +var trimLeft = /^\s+/, + trimRight = /\s+$/, + tinyCounter = 0, + math = Math, + mathRound = math.round, + mathMin = math.min, + mathMax = math.max, + mathRandom = math.random; + +function tinycolor (color, opts) { + + color = (color) ? color : ''; + opts = opts || { }; + + // If input is already a tinycolor, return itself + if (color instanceof tinycolor) { + return color; + } + // If we are called as a function, call using new instead + if (!(this instanceof tinycolor)) { + return new tinycolor(color, opts); + } + + var rgb = inputToRGB(color); + this._originalInput = color, + this._r = rgb.r, + this._g = rgb.g, + this._b = rgb.b, + this._a = rgb.a, + this._roundA = mathRound(100*this._a) / 100, + this._format = opts.format || rgb.format; + this._gradientType = opts.gradientType; + + // Don't let the range of [0,255] come back in [0,1]. + // Potentially lose a little bit of precision here, but will fix issues where + // .5 gets interpreted as half of the total, instead of half of 1 + // If it was supposed to be 128, this was already taken care of by `inputToRgb` + if (this._r < 1) { this._r = mathRound(this._r); } + if (this._g < 1) { this._g = mathRound(this._g); } + if (this._b < 1) { this._b = mathRound(this._b); } + + this._ok = rgb.ok; + this._tc_id = tinyCounter++; +} + +tinycolor.prototype = { + isDark: function() { + return this.getBrightness() < 128; + }, + isLight: function() { + return !this.isDark(); + }, + isValid: function() { + return this._ok; + }, + getOriginalInput: function() { + return this._originalInput; + }, + getFormat: function() { + return this._format; + }, + getAlpha: function() { + return this._a; + }, + getBrightness: function() { + //http://www.w3.org/TR/AERT#color-contrast + var rgb = this.toRgb(); + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; + }, + getLuminance: function() { + //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + var rgb = this.toRgb(); + var RsRGB, GsRGB, BsRGB, R, G, B; + RsRGB = rgb.r/255; + GsRGB = rgb.g/255; + BsRGB = rgb.b/255; + + if (RsRGB <= 0.03928) {R = RsRGB / 12.92;} else {R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4);} + if (GsRGB <= 0.03928) {G = GsRGB / 12.92;} else {G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4);} + if (BsRGB <= 0.03928) {B = BsRGB / 12.92;} else {B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4);} + return (0.2126 * R) + (0.7152 * G) + (0.0722 * B); + }, + setAlpha: function(value) { + this._a = boundAlpha(value); + this._roundA = mathRound(100*this._a) / 100; + return this; + }, + toHsv: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; + }, + toHsvString: function() { + var hsv = rgbToHsv(this._r, this._g, this._b); + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); + return (this._a == 1) ? + "hsv(" + h + ", " + s + "%, " + v + "%)" : + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; + }, + toHsl: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; + }, + toHslString: function() { + var hsl = rgbToHsl(this._r, this._g, this._b); + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); + return (this._a == 1) ? + "hsl(" + h + ", " + s + "%, " + l + "%)" : + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; + }, + toHex: function(allow3Char) { + return rgbToHex(this._r, this._g, this._b, allow3Char); + }, + toHexString: function(allow3Char) { + return '#' + this.toHex(allow3Char); + }, + toHex8: function() { + return rgbaToHex(this._r, this._g, this._b, this._a); + }, + toHex8String: function() { + return '#' + this.toHex8(); + }, + toRgb: function() { + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; + }, + toRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; + }, + toPercentageRgb: function() { + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; + }, + toPercentageRgbString: function() { + return (this._a == 1) ? + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; + }, + toName: function() { + if (this._a === 0) { + return "transparent"; + } + + if (this._a < 1) { + return false; + } + + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; + }, + toFilter: function(secondColor) { + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); + var secondHex8String = hex8String; + var gradientType = this._gradientType ? "GradientType = 1, " : ""; + + if (secondColor) { + var s = tinycolor(secondColor); + secondHex8String = s.toHex8String(); + } + + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; + }, + toString: function(format) { + var formatSet = !!format; + format = format || this._format; + + var formattedString = false; + var hasAlpha = this._a < 1 && this._a >= 0; + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); + + if (needsAlphaFormat) { + // Special case for "transparent", all other non-alpha formats + // will return rgba when there is transparency. + if (format === "name" && this._a === 0) { + return this.toName(); + } + return this.toRgbString(); + } + if (format === "rgb") { + formattedString = this.toRgbString(); + } + if (format === "prgb") { + formattedString = this.toPercentageRgbString(); + } + if (format === "hex" || format === "hex6") { + formattedString = this.toHexString(); + } + if (format === "hex3") { + formattedString = this.toHexString(true); + } + if (format === "hex8") { + formattedString = this.toHex8String(); + } + if (format === "name") { + formattedString = this.toName(); + } + if (format === "hsl") { + formattedString = this.toHslString(); + } + if (format === "hsv") { + formattedString = this.toHsvString(); + } + + return formattedString || this.toHexString(); + }, + clone: function() { + return tinycolor(this.toString()); + }, + + _applyModification: function(fn, args) { + var color = fn.apply(null, [this].concat([].slice.call(args))); + this._r = color._r; + this._g = color._g; + this._b = color._b; + this.setAlpha(color._a); + return this; + }, + lighten: function() { + return this._applyModification(lighten, arguments); + }, + brighten: function() { + return this._applyModification(brighten, arguments); + }, + darken: function() { + return this._applyModification(darken, arguments); + }, + desaturate: function() { + return this._applyModification(desaturate, arguments); + }, + saturate: function() { + return this._applyModification(saturate, arguments); + }, + greyscale: function() { + return this._applyModification(greyscale, arguments); + }, + spin: function() { + return this._applyModification(spin, arguments); + }, + + _applyCombination: function(fn, args) { + return fn.apply(null, [this].concat([].slice.call(args))); + }, + analogous: function() { + return this._applyCombination(analogous, arguments); + }, + complement: function() { + return this._applyCombination(complement, arguments); + }, + monochromatic: function() { + return this._applyCombination(monochromatic, arguments); + }, + splitcomplement: function() { + return this._applyCombination(splitcomplement, arguments); + }, + triad: function() { + return this._applyCombination(triad, arguments); + }, + tetrad: function() { + return this._applyCombination(tetrad, arguments); + } +}; + +// If input is an object, force 1 into "1.0" to handle ratios properly +// String input requires "1.0" as input, so 1 will be treated as 1 +tinycolor.fromRatio = function(color, opts) { + if (typeof color == "object") { + var newColor = {}; + for (var i in color) { + if (color.hasOwnProperty(i)) { + if (i === "a") { + newColor[i] = color[i]; + } + else { + newColor[i] = convertToPercentage(color[i]); + } + } + } + color = newColor; + } + + return tinycolor(color, opts); +}; + +// Given a string or object, convert that input to RGB +// Possible string inputs: +// +// "red" +// "#f00" or "f00" +// "#ff0000" or "ff0000" +// "#ff000000" or "ff000000" +// "rgb 255 0 0" or "rgb (255, 0, 0)" +// "rgb 1.0 0 0" or "rgb (1, 0, 0)" +// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" +// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" +// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" +// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" +// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" +// +function inputToRGB(color) { + + var rgb = { r: 0, g: 0, b: 0 }; + var a = 1; + var ok = false; + var format = false; + + if (typeof color == "string") { + color = stringInputToObject(color); + } + + if (typeof color == "object") { + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { + rgb = rgbToRgb(color.r, color.g, color.b); + ok = true; + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { + color.s = convertToPercentage(color.s); + color.v = convertToPercentage(color.v); + rgb = hsvToRgb(color.h, color.s, color.v); + ok = true; + format = "hsv"; + } + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { + color.s = convertToPercentage(color.s); + color.l = convertToPercentage(color.l); + rgb = hslToRgb(color.h, color.s, color.l); + ok = true; + format = "hsl"; + } + + if (color.hasOwnProperty("a")) { + a = color.a; + } + } + + a = boundAlpha(a); + + return { + ok: ok, + format: color.format || format, + r: mathMin(255, mathMax(rgb.r, 0)), + g: mathMin(255, mathMax(rgb.g, 0)), + b: mathMin(255, mathMax(rgb.b, 0)), + a: a + }; +} + + +// Conversion Functions +// -------------------- + +// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: +// + +// `rgbToRgb` +// Handle bounds / percentage checking to conform to CSS color spec +// +// *Assumes:* r, g, b in [0, 255] or [0, 1] +// *Returns:* { r, g, b } in [0, 255] +function rgbToRgb(r, g, b){ + return { + r: bound01(r, 255) * 255, + g: bound01(g, 255) * 255, + b: bound01(b, 255) * 255 + }; +} + +// `rgbToHsl` +// Converts an RGB color value to HSL. +// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] +// *Returns:* { h, s, l } in [0,1] +function rgbToHsl(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + + return { h: h, s: s, l: l }; +} + +// `hslToRgb` +// Converts an HSL color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] +function hslToRgb(h, s, l) { + var r, g, b; + + h = bound01(h, 360); + s = bound01(s, 100); + l = bound01(l, 100); + + function hue2rgb(p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + if(s === 0) { + r = g = b = l; // achromatic + } + else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHsv` +// Converts an RGB color value to HSV +// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] +// *Returns:* { h, s, v } in [0,1] +function rgbToHsv(r, g, b) { + + r = bound01(r, 255); + g = bound01(g, 255); + b = bound01(b, 255); + + var max = mathMax(r, g, b), min = mathMin(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max === 0 ? 0 : d / max; + + if(max == min) { + h = 0; // achromatic + } + else { + switch(max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h, s: s, v: v }; +} + +// `hsvToRgb` +// Converts an HSV color value to RGB. +// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] +// *Returns:* { r, g, b } in the set [0, 255] + function hsvToRgb(h, s, v) { + + h = bound01(h, 360) * 6; + s = bound01(s, 100); + v = bound01(v, 100); + + var i = math.floor(h), + f = h - i, + p = v * (1 - s), + q = v * (1 - f * s), + t = v * (1 - (1 - f) * s), + mod = i % 6, + r = [v, q, p, p, t, v][mod], + g = [t, v, v, q, p, p][mod], + b = [p, p, t, v, v, q][mod]; + + return { r: r * 255, g: g * 255, b: b * 255 }; +} + +// `rgbToHex` +// Converts an RGB color to hex +// Assumes r, g, and b are contained in the set [0, 255] +// Returns a 3 or 6 character hex +function rgbToHex(r, g, b, allow3Char) { + + var hex = [ + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + // Return a 3 character hex if possible + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); + } + + return hex.join(""); +} + +// `rgbaToHex` +// Converts an RGBA color plus alpha transparency to hex +// Assumes r, g, b and a are contained in the set [0, 255] +// Returns an 8 character hex +function rgbaToHex(r, g, b, a) { + + var hex = [ + pad2(convertDecimalToHex(a)), + pad2(mathRound(r).toString(16)), + pad2(mathRound(g).toString(16)), + pad2(mathRound(b).toString(16)) + ]; + + return hex.join(""); +} + +// `equals` +// Can be called with any tinycolor input +tinycolor.equals = function (color1, color2) { + if (!color1 || !color2) { return false; } + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); +}; + +tinycolor.random = function() { + return tinycolor.fromRatio({ + r: mathRandom(), + g: mathRandom(), + b: mathRandom() + }); +}; + + +// Modification Functions +// ---------------------- +// Thanks to less.js for some of the basics here +// + +function desaturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s -= amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function saturate(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.s += amount / 100; + hsl.s = clamp01(hsl.s); + return tinycolor(hsl); +} + +function greyscale(color) { + return tinycolor(color).desaturate(100); +} + +function lighten (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l += amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +function brighten(color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var rgb = tinycolor(color).toRgb(); + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); + return tinycolor(rgb); +} + +function darken (color, amount) { + amount = (amount === 0) ? 0 : (amount || 10); + var hsl = tinycolor(color).toHsl(); + hsl.l -= amount / 100; + hsl.l = clamp01(hsl.l); + return tinycolor(hsl); +} + +// Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. +// Values outside of this range will be wrapped into this range. +function spin(color, amount) { + var hsl = tinycolor(color).toHsl(); + var hue = (hsl.h + amount) % 360; + hsl.h = hue < 0 ? 360 + hue : hue; + return tinycolor(hsl); +} + +// Combination Functions +// --------------------- +// Thanks to jQuery xColor for some of the ideas behind these +// + +function complement(color) { + var hsl = tinycolor(color).toHsl(); + hsl.h = (hsl.h + 180) % 360; + return tinycolor(hsl); +} + +function triad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function tetrad(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) + ]; +} + +function splitcomplement(color) { + var hsl = tinycolor(color).toHsl(); + var h = hsl.h; + return [ + tinycolor(color), + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) + ]; +} + +function analogous(color, results, slices) { + results = results || 6; + slices = slices || 30; + + var hsl = tinycolor(color).toHsl(); + var part = 360 / slices; + var ret = [tinycolor(color)]; + + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { + hsl.h = (hsl.h + part) % 360; + ret.push(tinycolor(hsl)); + } + return ret; +} + +function monochromatic(color, results) { + results = results || 6; + var hsv = tinycolor(color).toHsv(); + var h = hsv.h, s = hsv.s, v = hsv.v; + var ret = []; + var modification = 1 / results; + + while (results--) { + ret.push(tinycolor({ h: h, s: s, v: v})); + v = (v + modification) % 1; + } + + return ret; +} + +// Utility Functions +// --------------------- + +tinycolor.mix = function(color1, color2, amount) { + amount = (amount === 0) ? 0 : (amount || 50); + + var rgb1 = tinycolor(color1).toRgb(); + var rgb2 = tinycolor(color2).toRgb(); + + var p = amount / 100; + var w = p * 2 - 1; + var a = rgb2.a - rgb1.a; + + var w1; + + if (w * a == -1) { + w1 = w; + } else { + w1 = (w + a) / (1 + w * a); + } + + w1 = (w1 + 1) / 2; + + var w2 = 1 - w1; + + var rgba = { + r: rgb2.r * w1 + rgb1.r * w2, + g: rgb2.g * w1 + rgb1.g * w2, + b: rgb2.b * w1 + rgb1.b * w2, + a: rgb2.a * p + rgb1.a * (1 - p) + }; + + return tinycolor(rgba); +}; + + +// Readability Functions +// --------------------- +// false +// tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false +tinycolor.isReadable = function(color1, color2, wcag2) { + var readability = tinycolor.readability(color1, color2); + var wcag2Parms, out; + + out = false; + + wcag2Parms = validateWCAG2Parms(wcag2); + switch (wcag2Parms.level + wcag2Parms.size) { + case "AAsmall": + case "AAAlarge": + out = readability >= 4.5; + break; + case "AAlarge": + out = readability >= 3; + break; + case "AAAsmall": + out = readability >= 7; + break; + } + return out; + +}; + +// `mostReadable` +// Given a base color and a list of possible foreground or background +// colors for that base, returns the most readable color. +// Optionally returns Black or White if the most readable color is unreadable. +// *Example* +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255" +// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3" +// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff" +tinycolor.mostReadable = function(baseColor, colorList, args) { + var bestColor = null; + var bestScore = 0; + var readability; + var includeFallbackColors, level, size ; + args = args || {}; + includeFallbackColors = args.includeFallbackColors ; + level = args.level; + size = args.size; + + for (var i= 0; i < colorList.length ; i++) { + readability = tinycolor.readability(baseColor, colorList[i]); + if (readability > bestScore) { + bestScore = readability; + bestColor = tinycolor(colorList[i]); + } + } + + if (tinycolor.isReadable(baseColor, bestColor, {"level":level,"size":size}) || !includeFallbackColors) { + return bestColor; + } + else { + args.includeFallbackColors=false; + return tinycolor.mostReadable(baseColor,["#fff", "#000"],args); + } +}; + + +// Big List of Colors +// ------------------ +// +var names = tinycolor.names = { + aliceblue: "f0f8ff", + antiquewhite: "faebd7", + aqua: "0ff", + aquamarine: "7fffd4", + azure: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "000", + blanchedalmond: "ffebcd", + blue: "00f", + blueviolet: "8a2be2", + brown: "a52a2a", + burlywood: "deb887", + burntsienna: "ea7e5d", + cadetblue: "5f9ea0", + chartreuse: "7fff00", + chocolate: "d2691e", + coral: "ff7f50", + cornflowerblue: "6495ed", + cornsilk: "fff8dc", + crimson: "dc143c", + cyan: "0ff", + darkblue: "00008b", + darkcyan: "008b8b", + darkgoldenrod: "b8860b", + darkgray: "a9a9a9", + darkgreen: "006400", + darkgrey: "a9a9a9", + darkkhaki: "bdb76b", + darkmagenta: "8b008b", + darkolivegreen: "556b2f", + darkorange: "ff8c00", + darkorchid: "9932cc", + darkred: "8b0000", + darksalmon: "e9967a", + darkseagreen: "8fbc8f", + darkslateblue: "483d8b", + darkslategray: "2f4f4f", + darkslategrey: "2f4f4f", + darkturquoise: "00ced1", + darkviolet: "9400d3", + deeppink: "ff1493", + deepskyblue: "00bfff", + dimgray: "696969", + dimgrey: "696969", + dodgerblue: "1e90ff", + firebrick: "b22222", + floralwhite: "fffaf0", + forestgreen: "228b22", + fuchsia: "f0f", + gainsboro: "dcdcdc", + ghostwhite: "f8f8ff", + gold: "ffd700", + goldenrod: "daa520", + gray: "808080", + green: "008000", + greenyellow: "adff2f", + grey: "808080", + honeydew: "f0fff0", + hotpink: "ff69b4", + indianred: "cd5c5c", + indigo: "4b0082", + ivory: "fffff0", + khaki: "f0e68c", + lavender: "e6e6fa", + lavenderblush: "fff0f5", + lawngreen: "7cfc00", + lemonchiffon: "fffacd", + lightblue: "add8e6", + lightcoral: "f08080", + lightcyan: "e0ffff", + lightgoldenrodyellow: "fafad2", + lightgray: "d3d3d3", + lightgreen: "90ee90", + lightgrey: "d3d3d3", + lightpink: "ffb6c1", + lightsalmon: "ffa07a", + lightseagreen: "20b2aa", + lightskyblue: "87cefa", + lightslategray: "789", + lightslategrey: "789", + lightsteelblue: "b0c4de", + lightyellow: "ffffe0", + lime: "0f0", + limegreen: "32cd32", + linen: "faf0e6", + magenta: "f0f", + maroon: "800000", + mediumaquamarine: "66cdaa", + mediumblue: "0000cd", + mediumorchid: "ba55d3", + mediumpurple: "9370db", + mediumseagreen: "3cb371", + mediumslateblue: "7b68ee", + mediumspringgreen: "00fa9a", + mediumturquoise: "48d1cc", + mediumvioletred: "c71585", + midnightblue: "191970", + mintcream: "f5fffa", + mistyrose: "ffe4e1", + moccasin: "ffe4b5", + navajowhite: "ffdead", + navy: "000080", + oldlace: "fdf5e6", + olive: "808000", + olivedrab: "6b8e23", + orange: "ffa500", + orangered: "ff4500", + orchid: "da70d6", + palegoldenrod: "eee8aa", + palegreen: "98fb98", + paleturquoise: "afeeee", + palevioletred: "db7093", + papayawhip: "ffefd5", + peachpuff: "ffdab9", + peru: "cd853f", + pink: "ffc0cb", + plum: "dda0dd", + powderblue: "b0e0e6", + purple: "800080", + rebeccapurple: "663399", + red: "f00", + rosybrown: "bc8f8f", + royalblue: "4169e1", + saddlebrown: "8b4513", + salmon: "fa8072", + sandybrown: "f4a460", + seagreen: "2e8b57", + seashell: "fff5ee", + sienna: "a0522d", + silver: "c0c0c0", + skyblue: "87ceeb", + slateblue: "6a5acd", + slategray: "708090", + slategrey: "708090", + snow: "fffafa", + springgreen: "00ff7f", + steelblue: "4682b4", + tan: "d2b48c", + teal: "008080", + thistle: "d8bfd8", + tomato: "ff6347", + turquoise: "40e0d0", + violet: "ee82ee", + wheat: "f5deb3", + white: "fff", + whitesmoke: "f5f5f5", + yellow: "ff0", + yellowgreen: "9acd32" +}; + +// Make it easy to access colors via `hexNames[hex]` +var hexNames = tinycolor.hexNames = flip(names); + + +// Utilities +// --------- + +// `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` +function flip(o) { + var flipped = { }; + for (var i in o) { + if (o.hasOwnProperty(i)) { + flipped[o[i]] = i; + } + } + return flipped; +} + +// Return a valid alpha value [0,1] with all invalid values being set to 1 +function boundAlpha(a) { + a = parseFloat(a); + + if (isNaN(a) || a < 0 || a > 1) { + a = 1; + } + + return a; +} + +// Take input from [0, n] and return it as [0, 1] +function bound01(n, max) { + if (isOnePointZero(n)) { n = "100%"; } + + var processPercent = isPercentage(n); + n = mathMin(max, mathMax(0, parseFloat(n))); + + // Automatically convert percentage into number + if (processPercent) { + n = parseInt(n * max, 10) / 100; + } + + // Handle floating point rounding errors + if ((math.abs(n - max) < 0.000001)) { + return 1; + } + + // Convert into [0, 1] range if it isn't already + return (n % max) / parseFloat(max); +} + +// Force a number between 0 and 1 +function clamp01(val) { + return mathMin(1, mathMax(0, val)); +} + +// Parse a base-16 hex value into a base-10 integer +function parseIntFromHex(val) { + return parseInt(val, 16); +} + +// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 +// +function isOnePointZero(n) { + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; +} + +// Check to see if string passed in is a percentage +function isPercentage(n) { + return typeof n === "string" && n.indexOf('%') != -1; +} + +// Force a hex value to have 2 characters +function pad2(c) { + return c.length == 1 ? '0' + c : '' + c; +} + +// Replace a decimal with it's percentage value +function convertToPercentage(n) { + if (n <= 1) { + n = (n * 100) + "%"; + } + + return n; +} + +// Converts a decimal to a hex value +function convertDecimalToHex(d) { + return Math.round(parseFloat(d) * 255).toString(16); +} +// Converts a hex value to a decimal +function convertHexToDecimal(h) { + return (parseIntFromHex(h) / 255); +} + +var matchers = (function() { + + // + var CSS_INTEGER = "[-\\+]?\\d+%?"; + + // + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; + + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; + + // Actual matching. + // Parentheses and commas are optional, but not required. + // Whitespace can take the place of commas or opening paren + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; + + return { + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), + hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), + hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, + hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ + }; +})(); + +// `stringInputToObject` +// Permissive string parsing. Take in a number of formats, and output an object +// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` +function stringInputToObject(color) { + + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); + var named = false; + if (names[color]) { + color = names[color]; + named = true; + } + else if (color == 'transparent') { + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; + } + + // Try to match string input using regular expressions. + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] + // Just return an object and let the conversion functions handle that. + // This way the result will be the same whether the tinycolor is initialized with string or object. + var match; + if ((match = matchers.rgb.exec(color))) { + return { r: match[1], g: match[2], b: match[3] }; + } + if ((match = matchers.rgba.exec(color))) { + return { r: match[1], g: match[2], b: match[3], a: match[4] }; + } + if ((match = matchers.hsl.exec(color))) { + return { h: match[1], s: match[2], l: match[3] }; + } + if ((match = matchers.hsla.exec(color))) { + return { h: match[1], s: match[2], l: match[3], a: match[4] }; + } + if ((match = matchers.hsv.exec(color))) { + return { h: match[1], s: match[2], v: match[3] }; + } + if ((match = matchers.hsva.exec(color))) { + return { h: match[1], s: match[2], v: match[3], a: match[4] }; + } + if ((match = matchers.hex8.exec(color))) { + return { + a: convertHexToDecimal(match[1]), + r: parseIntFromHex(match[2]), + g: parseIntFromHex(match[3]), + b: parseIntFromHex(match[4]), + format: named ? "name" : "hex8" + }; + } + if ((match = matchers.hex6.exec(color))) { + return { + r: parseIntFromHex(match[1]), + g: parseIntFromHex(match[2]), + b: parseIntFromHex(match[3]), + format: named ? "name" : "hex" + }; + } + if ((match = matchers.hex3.exec(color))) { + return { + r: parseIntFromHex(match[1] + '' + match[1]), + g: parseIntFromHex(match[2] + '' + match[2]), + b: parseIntFromHex(match[3] + '' + match[3]), + format: named ? "name" : "hex" + }; + } + + return false; +} + +function validateWCAG2Parms(parms) { + // return valid WCAG2 parms for isReadable. + // If input parms are invalid, return {"level":"AA", "size":"small"} + var level, size; + parms = parms || {"level":"AA", "size":"small"}; + level = (parms.level || "AA").toUpperCase(); + size = (parms.size || "small").toLowerCase(); + if (level !== "AA" && level !== "AAA") { + level = "AA"; + } + if (size !== "small" && size !== "large") { + size = "small"; + } + return {"level":level, "size":size}; +} + +loadTinyColor = function(){ + return tinycolor +} \ No newline at end of file From 1f2d7262111187e1888b6e3314ed6e921a80d687 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 16:16:58 -0800 Subject: [PATCH 29/42] awesome stuff --- .../data_visualization/earthquakes_live.js | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 76f0fad5b0..fa464b9e13 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -1,44 +1,49 @@ -//earthquakes_live.js -//created by james b. pollack @imgntn on 12/5/2015 +// earthquakes_live.js +// exploratory implementation in prep for abstract latlong to earth graphing tool for VR +// shows all of the quakes in the past 24 hours reported by the USGS +// created by james b. pollack @imgntn on 12/5/2015 +// working notes: maybe try doing markers as boxes,rotated to the sphere normal, and with the height representing some value + Script.include('../libraries/promise.js'); var Promise = loadPromise(); Script.include('../libraries/tinyColor.js'); var tinyColor = loadTinyColor(); -var EARTH_SPHERE_RADIUS = 5; +//you could make it the size of the actual earth. +var EARTH_SPHERE_RADIUS = 6371; +var EARTH_SPHERE_RADIUS = 2; + var EARTH_CENTER_POSITION = Vec3.sum(MyAvatar.position, { - x: 5, + x: 0, y: 0, z: 0 }); -var EARTH_MODEL_URL='http://public.highfidelity.io/marketplace/hificontent/Scripts/planets/planets/earth.fbx'; +var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; + +var POLL_FOR_CHANGES = false; //USGS updates the data every five minutes -var CHECK_QUAKE_FREQUENCY = 300 * 1000; +var CHECK_QUAKE_FREQUENCY = 5 * 60 * 1000; var QUAKE_MARKER_DIMENSIONS = { - x: 0.1, - y: 0.1, - z: 0.1 + x: 0.01, + y: 0.01, + z: 0.01 }; function createEarth() { var earthProperties = { name: 'Earth', type: 'Model', - modelURL:EARTH_MODEL_URL, + modelURL: EARTH_MODEL_URL, position: EARTH_CENTER_POSITION, dimensions: { x: EARTH_SPHERE_RADIUS, y: EARTH_SPHERE_RADIUS, z: EARTH_SPHERE_RADIUS }, - // color: { - // red: 0, - // green: 100, - // blue: 150 - // }, + rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), collisionsWillMove: false, userData: JSON.stringify({ grabbableKey: { @@ -50,26 +55,32 @@ function createEarth() { return Entities.addEntity(earthProperties) } -function plotLatitudeLongitude(radiusOfSphere, latitude, longitude) { - var tx = radiusOfSphere * Math.cos(latitude) * Math.cos(longitude); - var ty = radiusOfSphere * -Math.sin(latitude); - var tz = radiusOfSphere * Math.cos(latitude) * Math.sin(longitude); +function latLongToVector3(lat, lon, radius, height) { + var phi = (lat) * Math.PI / 180; + var theta = (lon - 180) * Math.PI / 180; + + var x = -(radius + height) * Math.cos(phi) * Math.cos(theta); + var y = (radius + height) * Math.sin(phi); + var z = (radius + height) * Math.cos(phi) * Math.sin(theta); + return { - x: tx, - y: ty, - z: tz - } + x: x, + y: y, + z: z + }; } function getQuakePosition(earthquake) { - var latitude = earthquake.geometry.coordinates[0]; - var longitude = earthquake.geometry.coordinates[1]; - var latlng = plotLatitudeLongitude(2.5, latitude, longitude); + var longitude = earthquake.geometry.coordinates[0]; + var latitude = earthquake.geometry.coordinates[1]; + var depth = earthquake.geometry.coordinates[2]; + + var latlng = latLongToVector3(latitude, longitude, EARTH_SPHERE_RADIUS / 2, 0); var position = EARTH_CENTER_POSITION; var finalPosition = Vec3.sum(position, latlng); - // print('finalpos::' + JSON.stringify(finalPosition)) + print('finalpos::' + JSON.stringify(finalPosition)) return finalPosition } @@ -95,14 +106,9 @@ function get(url) { }); } -function showEarthquake(snapshot) { - var earthquake = snapshot.val(); - print("Mag " + earthquake.mag + " at " + earthquake.place); -} - function createQuakeMarker(earthquake) { var markerProperties = { - name: 'Marker', + name: earthquake.properties.place, type: 'Sphere', dimensions: QUAKE_MARKER_DIMENSIONS, position: getQuakePosition(earthquake), @@ -110,7 +116,7 @@ function createQuakeMarker(earthquake) { color: getQuakeMarkerColor(earthquake) } - //print('marker properties::' + JSON.stringify(markerProperties)) + print('marker properties::' + JSON.stringify(markerProperties)) return Entities.addEntity(markerProperties); } @@ -137,7 +143,7 @@ function scale(value, min1, max1, min2, max2) { function processQuakes(earthquakes) { print('quakers length' + earthquakes.length) earthquakes.forEach(function(quake) { - // print('PROCESSING A QUAKE') + // print('PROCESSING A QUAKE') var marker = createQuakeMarker(quake); markers.push(marker); }) @@ -147,16 +153,18 @@ function processQuakes(earthquakes) { var quakea; var markers = []; -var earth = createEarth(); +var earth = createEarth(); -get(QUAKE_URL).then(function(response) { - print('got it::' + response.features.length) - quakes = response.features; - processQuakes(quakes); - //print("Success!" + JSON.stringify(response)); -}, function(error) { - print('error getting quakes') -}); +function getThenProcessQuakes() { + get(QUAKE_URL).then(function(response) { + print('got it::' + response.features.length) + quakes = response.features; + processQuakes(quakes); + //print("Success!" + JSON.stringify(response)); + }, function(error) { + print('error getting quakes') + }); +} function cleanupMarkers() { print('CLEANING UP MARKERS') @@ -170,4 +178,16 @@ function cleanupEarth() { } Script.scriptEnding.connect(cleanupMarkers); -Script.scriptEnding.connect(cleanupEarth); \ No newline at end of file +Script.scriptEnding.connect(cleanupEarth); + +//first draw +getThenProcessQuakes(); + + +var pollingInterval; +if (POLL_FOR_CHANGES === true) { + pollingInterval = Script.setInterval(function() { + cleanupMarkers(); + getThenProcessQuakes() + }, CHECK_QUAKE_FREQUENCY) +} \ No newline at end of file From f33f4cbcaefb08ae885baabf20f59d555189d58f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 16:22:17 -0800 Subject: [PATCH 30/42] cleanup --- examples/data_visualization/testQuakes.json | 153 -------------------- 1 file changed, 153 deletions(-) delete mode 100644 examples/data_visualization/testQuakes.json diff --git a/examples/data_visualization/testQuakes.json b/examples/data_visualization/testQuakes.json deleted file mode 100644 index a6429b79c8..0000000000 --- a/examples/data_visualization/testQuakes.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "type": "FeatureCollection", - "metadata": { - "generated": 1449347736000, - "url": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson", - "title": "USGS All Earthquakes, Past Hour", - "status": 200, - "api": "1.1.0", - "count": 4 - }, - "features": [{ - "type": "Feature", - "properties": { - "mag": 1.39, - "place": "17km ESE of Julian, California", - "time": 1449345604050, - "updated": 1449345856673, - "tz": -480, - "url": "http://earthquake.usgs.gov/earthquakes/eventpage/ci37498832", - "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/ci37498832.geojson", - "felt": null, - "cdi": null, - "mmi": null, - "alert": null, - "status": "automatic", - "tsunami": 0, - "sig": 30, - "net": "ci", - "code": "37498832", - "ids": ",ci37498832,", - "sources": ",ci,", - "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", - "nst": 32, - "dmin": 0.1379, - "rms": 0.21, - "gap": 68, - "magType": "ml", - "type": "earthquake", - "title": "M 1.4 - 17km ESE of Julian, California" - }, - "geometry": { - "type": "Point", - "coordinates": [-116.4293333, 33.0301667, 7.84] - }, - "id": "ci37498832" - }, { - "type": "Feature", - "properties": { - "mag": 1.12, - "place": "3km ESE of The Geysers, California", - "time": 1449344686690, - "updated": 1449344783260, - "tz": -480, - "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564866", - "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564866.geojson", - "felt": null, - "cdi": null, - "mmi": null, - "alert": null, - "status": "automatic", - "tsunami": 0, - "sig": 19, - "net": "nc", - "code": "72564866", - "ids": ",nc72564866,", - "sources": ",nc,", - "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", - "nst": 11, - "dmin": 0.01342, - "rms": 0.03, - "gap": 154, - "magType": "md", - "type": "earthquake", - "title": "M 1.1 - 3km ESE of The Geysers, California" - }, - "geometry": { - "type": "Point", - "coordinates": [-122.7176666, 38.7598343, 1.48] - }, - "id": "nc72564866" - }, { - "type": "Feature", - "properties": { - "mag": 1.99, - "place": "3km SE of The Geysers, California", - "time": 1449344287500, - "updated": 1449344383210, - "tz": -480, - "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564856", - "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564856.geojson", - "felt": null, - "cdi": null, - "mmi": null, - "alert": null, - "status": "automatic", - "tsunami": 0, - "sig": 61, - "net": "nc", - "code": "72564856", - "ids": ",nc72564856,", - "sources": ",nc,", - "types": ",general-link,geoserve,nearby-cities,origin,phase-data,", - "nst": 13, - "dmin": 0.01626, - "rms": 0.02, - "gap": 72, - "magType": "md", - "type": "earthquake", - "title": "M 2.0 - 3km SE of The Geysers, California" - }, - "geometry": { - "type": "Point", - "coordinates": [-122.7203369, 38.7571678, 0.3] - }, - "id": "nc72564856" - }, { - "type": "Feature", - "properties": { - "mag": 2.36, - "place": "3km ESE of The Geysers, California", - "time": 1449344196380, - "updated": 1449344459434, - "tz": -480, - "url": "http://earthquake.usgs.gov/earthquakes/eventpage/nc72564836", - "detail": "http://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/nc72564836.geojson", - "felt": 0, - "cdi": 1, - "mmi": null, - "alert": null, - "status": "automatic", - "tsunami": 0, - "sig": 86, - "net": "nc", - "code": "72564836", - "ids": ",nc72564836,", - "sources": ",nc,", - "types": ",dyfi,focal-mechanism,general-link,geoserve,nearby-cities,origin,phase-data,scitech-link,", - "nst": 36, - "dmin": 0.01395, - "rms": 0.07, - "gap": 38, - "magType": "md", - "type": "earthquake", - "title": "M 2.4 - 3km ESE of The Geysers, California" - }, - "geometry": { - "type": "Point", - "coordinates": [-122.722168, 38.7598343, 1.21] - }, - "id": "nc72564836" - }], - "bbox": [-122.722168, 33.0301667, 0.3, -116.4293333, 38.7598343, 7.84] -} \ No newline at end of file From 4999b34327822655d2063d2ec1bac96949997ead Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 16:54:55 -0800 Subject: [PATCH 31/42] cleanup interval --- examples/data_visualization/earthquakes_live.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index fa464b9e13..cb88ec33dc 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -80,7 +80,7 @@ function getQuakePosition(earthquake) { var position = EARTH_CENTER_POSITION; var finalPosition = Vec3.sum(position, latlng); - print('finalpos::' + JSON.stringify(finalPosition)) + //print('finalpos::' + JSON.stringify(finalPosition)) return finalPosition } @@ -116,7 +116,7 @@ function createQuakeMarker(earthquake) { color: getQuakeMarkerColor(earthquake) } - print('marker properties::' + JSON.stringify(markerProperties)) + // print('marker properties::' + JSON.stringify(markerProperties)) return Entities.addEntity(markerProperties); } @@ -177,14 +177,20 @@ function cleanupEarth() { Entities.deleteEntity(earth); } +function cleanupInterval() { + if (pollingInterval !== null) { + Script.clearInterval(pollingInterval) + } +} + Script.scriptEnding.connect(cleanupMarkers); Script.scriptEnding.connect(cleanupEarth); - +Script.scriptEnding.connect(cleanupInterval); //first draw getThenProcessQuakes(); +var pollingInterval = null; -var pollingInterval; if (POLL_FOR_CHANGES === true) { pollingInterval = Script.setInterval(function() { cleanupMarkers(); From 4a5b9bd523391320a08aa203091c24b74779c2fa Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 16:57:09 -0800 Subject: [PATCH 32/42] start position --- examples/data_visualization/earthquakes_live.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index cb88ec33dc..eae1333089 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -14,11 +14,17 @@ var tinyColor = loadTinyColor(); var EARTH_SPHERE_RADIUS = 6371; var EARTH_SPHERE_RADIUS = 2; -var EARTH_CENTER_POSITION = Vec3.sum(MyAvatar.position, { +var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, - y: 0, + y: 0.5, z: 0 -}); +}), Vec3.multiply(EARTH_SPHERE_RADIUS, Quat.getFront(Camera.getOrientation()))); + +// var EARTH_CENTER_POSITION = Vec3.sum(MyAvatar.position, { +// x: 0, +// y: 0, +// z: 0 +// }); var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; From 7fc3d0f34edebde14f630bfea9c608fa3e382142 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 16:58:50 -0800 Subject: [PATCH 33/42] header --- examples/data_visualization/earthquakes_live.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index eae1333089..3090b00e44 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -1,7 +1,13 @@ // earthquakes_live.js +// // exploratory implementation in prep for abstract latlong to earth graphing tool for VR // shows all of the quakes in the past 24 hours reported by the USGS +// // created by james b. pollack @imgntn on 12/5/2015 +// Copyright 2015 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 +// // working notes: maybe try doing markers as boxes,rotated to the sphere normal, and with the height representing some value Script.include('../libraries/promise.js'); From d518def97477cd11e45fecdc842e47c220b68f39 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:01:54 -0800 Subject: [PATCH 34/42] poll by default --- examples/data_visualization/earthquakes_live.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 3090b00e44..2457a0646a 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -34,7 +34,7 @@ var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; -var POLL_FOR_CHANGES = false; +var POLL_FOR_CHANGES = true; //USGS updates the data every five minutes var CHECK_QUAKE_FREQUENCY = 5 * 60 * 1000; From 945b5bd00f5c9d0a1caf2c5bf90347ac125411fe Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:11:27 -0800 Subject: [PATCH 35/42] cleanup --- examples/data_visualization/earthquakes_live.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 2457a0646a..826c833243 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -26,12 +26,6 @@ var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { z: 0 }), Vec3.multiply(EARTH_SPHERE_RADIUS, Quat.getFront(Camera.getOrientation()))); -// var EARTH_CENTER_POSITION = Vec3.sum(MyAvatar.position, { -// x: 0, -// y: 0, -// z: 0 -// }); - var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; var POLL_FOR_CHANGES = true; From a899fe0e1b3eacca35d79c2cd0b4ce889d0dae5f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:26:11 -0800 Subject: [PATCH 36/42] fix typo --- examples/data_visualization/earthquakes_live.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 826c833243..272c933696 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -44,6 +44,7 @@ function createEarth() { type: 'Model', modelURL: EARTH_MODEL_URL, position: EARTH_CENTER_POSITION, + collisionsWillMove:true, dimensions: { x: EARTH_SPHERE_RADIUS, y: EARTH_SPHERE_RADIUS, @@ -116,6 +117,7 @@ function createQuakeMarker(earthquake) { var markerProperties = { name: earthquake.properties.place, type: 'Sphere', + parentID:earth, dimensions: QUAKE_MARKER_DIMENSIONS, position: getQuakePosition(earthquake), lifetime: 6000, @@ -156,7 +158,7 @@ function processQuakes(earthquakes) { print('markers length:' + markers.length) } -var quakea; +var quakes; var markers = []; var earth = createEarth(); From ade32cbf6137fada8eb5661c846779ccd88bc015 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:30:38 -0800 Subject: [PATCH 37/42] remove collisions and add parenting --- examples/data_visualization/earthquakes_live.js | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 272c933696..69d639238c 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -44,7 +44,6 @@ function createEarth() { type: 'Model', modelURL: EARTH_MODEL_URL, position: EARTH_CENTER_POSITION, - collisionsWillMove:true, dimensions: { x: EARTH_SPHERE_RADIUS, y: EARTH_SPHERE_RADIUS, From 30b32765b915f7e4f5947883fdfc601e4bae9f86 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:32:02 -0800 Subject: [PATCH 38/42] make earth grabbable to show parenting --- examples/data_visualization/earthquakes_live.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 69d639238c..579c981b2d 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -51,11 +51,11 @@ function createEarth() { }, rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), collisionsWillMove: false, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) + // userData: JSON.stringify({ + // grabbableKey: { + // grabbable: false + // } + // }) } return Entities.addEntity(earthProperties) From 906540272ec77dfa1cb96ae46cff1d6de8004242 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:39:43 -0800 Subject: [PATCH 39/42] disable collisions for now because of shapetype --- examples/data_visualization/earthquakes_live.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 579c981b2d..3498885ff6 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -50,7 +50,9 @@ function createEarth() { z: EARTH_SPHERE_RADIUS }, rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), - collisionsWillMove: false, + // collisionsWillMove: true, + //if you have a shapetype it blocks the smaller markers + // shapeType:'sphere' // userData: JSON.stringify({ // grabbableKey: { // grabbable: false @@ -119,6 +121,7 @@ function createQuakeMarker(earthquake) { parentID:earth, dimensions: QUAKE_MARKER_DIMENSIONS, position: getQuakePosition(earthquake), + ignoreForCollisions:true, lifetime: 6000, color: getQuakeMarkerColor(earthquake) } From 7859241ad31d1ad467eb22e1b4512f6178f57e10 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:43:13 -0800 Subject: [PATCH 40/42] spin the earth for fun --- .../data_visualization/earthquakes_live.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index 3498885ff6..f625f9afb0 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -185,6 +185,7 @@ function cleanupMarkers() { function cleanupEarth() { Entities.deleteEntity(earth); + Script.update.disconnect(spinEarth); } function cleanupInterval() { @@ -196,7 +197,7 @@ function cleanupInterval() { Script.scriptEnding.connect(cleanupMarkers); Script.scriptEnding.connect(cleanupEarth); Script.scriptEnding.connect(cleanupInterval); -//first draw + getThenProcessQuakes(); var pollingInterval = null; @@ -206,4 +207,17 @@ if (POLL_FOR_CHANGES === true) { cleanupMarkers(); getThenProcessQuakes() }, CHECK_QUAKE_FREQUENCY) -} \ No newline at end of file +} + + +function spinEarth(){ +Entities.editEntity(earth,{ + angularVelocity:{ + x:0, + y:0.5, + z:0 + } +}) +} + +Script.update.connect(spinEarth) \ No newline at end of file From 378b71d7011a097262f5f31764981c99551606fa Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 5 Dec 2015 17:51:55 -0800 Subject: [PATCH 41/42] dont spin by default --- examples/data_visualization/earthquakes_live.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/data_visualization/earthquakes_live.js b/examples/data_visualization/earthquakes_live.js index f625f9afb0..8594f827a0 100644 --- a/examples/data_visualization/earthquakes_live.js +++ b/examples/data_visualization/earthquakes_live.js @@ -28,6 +28,7 @@ var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx'; +var SHOULD_SPIN=false; var POLL_FOR_CHANGES = true; //USGS updates the data every five minutes var CHECK_QUAKE_FREQUENCY = 5 * 60 * 1000; @@ -214,10 +215,12 @@ function spinEarth(){ Entities.editEntity(earth,{ angularVelocity:{ x:0, - y:0.5, + y:0.25, z:0 } }) } -Script.update.connect(spinEarth) \ No newline at end of file +if(SHOULD_SPIN===true){ + Script.update.connect(spinEarth); +} From 22bdc1ae840c56fc4adb0762376a3ef1654fbf42 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 7 Dec 2015 13:49:49 +0100 Subject: [PATCH 42/42] Make it possible to use the focus-right-click-drag-camera in the edit.js script again. Still keep right-clicking ignore Toolbar and entity selection events. --- examples/edit.js | 9 +++++---- examples/libraries/entitySelectionTool.js | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 9b83079198..5d5d642f47 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -392,6 +392,11 @@ var toolBar = (function() { url, file; + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y @@ -738,10 +743,6 @@ var mousePressStartPosition = { var mouseDown = false; function mousePressEvent(event) { - if (!event.isLeftButton) { - // If another mouse button than left is pressed ignore it - return false; - } mouseDown = true; mousePressStartPosition = { x: event.x, diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 61aa5b0394..6edbe6844b 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -2340,6 +2340,11 @@ SelectionDisplay = (function () { that.mousePressEvent = function(event) { + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + var somethingClicked = false; var pickRay = Camera.computePickRay(event.x, event.y);