From 8ebf34e93fed02bb5771cb0ec2d5e137eec3a828 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Aug 2015 15:15:11 -0700 Subject: [PATCH 01/15] Fix BillboardOverlay breaking when changing url --- interface/src/ui/overlays/BillboardOverlay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 58790c4722..9f1ffd619b 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -53,12 +53,12 @@ void BillboardOverlay::update(float deltatime) { } void BillboardOverlay::render(RenderArgs* args) { - if (!_texture) { + if (!_isLoaded) { _isLoaded = true; _texture = DependencyManager::get()->getTexture(_url); } - if (!_visible || !_texture || !_texture->isLoaded()) { + if (!_visible || !_texture->isLoaded()) { return; } From dd2c0be2d292a6a78875d180ae538beefdf21063 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Aug 2015 15:43:02 -0700 Subject: [PATCH 02/15] Add check in BillboardOverlay::render to make sure _texture is valid --- interface/src/ui/overlays/BillboardOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 9f1ffd619b..891969e86b 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -58,7 +58,7 @@ void BillboardOverlay::render(RenderArgs* args) { _texture = DependencyManager::get()->getTexture(_url); } - if (!_visible || !_texture->isLoaded()) { + if (!_visible || !_texture || !_texture->isLoaded()) { return; } From 488f6aa7d85f2c64f39408ac21a5bdfd46c7dd62 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 13:34:23 -0700 Subject: [PATCH 03/15] fix bug in ElbowConstraint::apply() --- libraries/animation/src/ElbowConstraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/ElbowConstraint.cpp b/libraries/animation/src/ElbowConstraint.cpp index 8c7fec7b6f..a6b2210644 100644 --- a/libraries/animation/src/ElbowConstraint.cpp +++ b/libraries/animation/src/ElbowConstraint.cpp @@ -67,7 +67,7 @@ bool ElbowConstraint::apply(glm::quat& rotation) const { // update rotation const float MIN_SWING_REAL_PART = 0.99999f; - if (twistWasClamped || fabsf(swingRotation.w < MIN_SWING_REAL_PART)) { + if (twistWasClamped || fabsf(swingRotation.w) < MIN_SWING_REAL_PART) { if (twistWasClamped) { twistRotation = glm::angleAxis(clampedTwistAngle, _axis); } From ba5346aee7e52e43fb1dcc69aeee84d76128faa4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 13:37:59 -0700 Subject: [PATCH 04/15] move GlmTestUtils.h to GLMTestUtils.h --- tests/physics/src/BulletUtilTests.cpp | 2 +- tests/physics/src/{GlmTestUtils.h => GLMTestUtils.h} | 6 +++--- tests/physics/src/MeshMassPropertiesTests.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename tests/physics/src/{GlmTestUtils.h => GLMTestUtils.h} (89%) diff --git a/tests/physics/src/BulletUtilTests.cpp b/tests/physics/src/BulletUtilTests.cpp index 181d22327e..a52e407aab 100644 --- a/tests/physics/src/BulletUtilTests.cpp +++ b/tests/physics/src/BulletUtilTests.cpp @@ -17,7 +17,7 @@ #include // Add additional qtest functionality (the include order is important!) -#include "GlmTestUtils.h" +#include "GLMTestUtils.h" #include "../QTestExtensions.h" // Constants diff --git a/tests/physics/src/GlmTestUtils.h b/tests/physics/src/GLMTestUtils.h similarity index 89% rename from tests/physics/src/GlmTestUtils.h rename to tests/physics/src/GLMTestUtils.h index 20bc14d5e0..ef63689419 100644 --- a/tests/physics/src/GlmTestUtils.h +++ b/tests/physics/src/GLMTestUtils.h @@ -1,5 +1,5 @@ // -// GlmTestUtils.h +// GLMTestUtils.h // tests/physics/src // // Created by Seiji Emery on 6/22/15 @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_GlmTestUtils_h -#define hifi_GlmTestUtils_h +#ifndef hifi_GLMTestUtils_h +#define hifi_GLMTestUtils_h #include #include diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index e88bcae1b7..8295abfef8 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -16,7 +16,7 @@ // Add additional qtest functionality (the include order is important!) #include "BulletTestUtils.h" -#include "GlmTestUtils.h" +#include "GLMTestUtils.h" #include "../QTestExtensions.h" const btScalar acceptableRelativeError(1.0e-5f); From cae77cfd767c2788f15f791528c991096acc98fe Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 13:51:40 -0700 Subject: [PATCH 05/15] move GLMTestUtils.h where all tests can find it --- tests/CMakeLists.txt | 4 ++-- tests/{physics/src => }/GLMTestUtils.h | 1 + tests/QTestExtensions.h | 2 ++ tests/physics/src/BulletUtilTests.cpp | 1 - tests/physics/src/MeshMassPropertiesTests.cpp | 1 - 5 files changed, 5 insertions(+), 4 deletions(-) rename tests/{physics/src => }/GLMTestUtils.h (99%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c352b55515..1593b649a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,7 +12,7 @@ foreach(DIR ${TEST_SUBDIRS}) endif() endforeach() -file(GLOB SHARED_TEST_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp") +file(GLOB SHARED_TEST_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.h " "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp") add_custom_target("test-extensions" SOURCES "${SHARED_TEST_HEADER_FILES}") @@ -34,4 +34,4 @@ add_custom_target("all-tests" set_target_properties("all-tests" PROPERTIES FOLDER "hidden/test-targets") set_target_properties("all-tests" PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE - EXCLUDE_FROM_ALL TRUE) \ No newline at end of file + EXCLUDE_FROM_ALL TRUE) diff --git a/tests/physics/src/GLMTestUtils.h b/tests/GLMTestUtils.h similarity index 99% rename from tests/physics/src/GLMTestUtils.h rename to tests/GLMTestUtils.h index ef63689419..6559e9f3c9 100644 --- a/tests/physics/src/GLMTestUtils.h +++ b/tests/GLMTestUtils.h @@ -20,6 +20,7 @@ inline float getErrorDifference(const glm::vec3& a, const glm::vec3& b) { return glm::distance(a, b); } + inline QTextStream& operator<<(QTextStream& stream, const glm::vec3& v) { return stream << "glm::vec3 { " << v.x << ", " << v.y << ", " << v.z << " }"; } diff --git a/tests/QTestExtensions.h b/tests/QTestExtensions.h index c034e9c290..d1918ebed8 100644 --- a/tests/QTestExtensions.h +++ b/tests/QTestExtensions.h @@ -15,6 +15,8 @@ #include #include +#include "GLMTestUtils.h" + // Implements several extensions to QtTest. // // Problems with QtTest: diff --git a/tests/physics/src/BulletUtilTests.cpp b/tests/physics/src/BulletUtilTests.cpp index a52e407aab..132d655274 100644 --- a/tests/physics/src/BulletUtilTests.cpp +++ b/tests/physics/src/BulletUtilTests.cpp @@ -17,7 +17,6 @@ #include // Add additional qtest functionality (the include order is important!) -#include "GLMTestUtils.h" #include "../QTestExtensions.h" // Constants diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 8295abfef8..825721641f 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -16,7 +16,6 @@ // Add additional qtest functionality (the include order is important!) #include "BulletTestUtils.h" -#include "GLMTestUtils.h" #include "../QTestExtensions.h" const btScalar acceptableRelativeError(1.0e-5f); From 6dfbf7f70a01666ef822fb63fb9c361c6d23b517 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 14:13:28 -0700 Subject: [PATCH 06/15] coalesce scattered dupes of unit-test utilities --- tests/GLMTestUtils.h | 34 +++++++++++++++++++ tests/QTestExtensions.h | 21 ++++++++++++ .../animation/src/RotationConstraintTests.cpp | 26 -------------- tests/shared/src/AngularConstraintTests.cpp | 10 ++---- tests/shared/src/GeometryUtilTests.cpp | 7 ---- tests/shared/src/MovingMinMaxAvgTests.cpp | 5 +++ tests/shared/src/MovingMinMaxAvgTests.h | 9 ----- tests/shared/src/TransformTests.cpp | 7 ++-- tests/shared/src/TransformTests.h | 27 --------------- 9 files changed, 67 insertions(+), 79 deletions(-) diff --git a/tests/GLMTestUtils.h b/tests/GLMTestUtils.h index 6559e9f3c9..7dbc4a840f 100644 --- a/tests/GLMTestUtils.h +++ b/tests/GLMTestUtils.h @@ -14,15 +14,49 @@ #include #include +#include // Implements functionality in QTestExtensions.h for glm types +// Computes the error value between two quaternions (using glm::dot) +float getErrorDifference(const glm::quat& a, const glm::quat& b) { + return fabsf(glm::dot(a, b)) - 1.0f; +} + inline float getErrorDifference(const glm::vec3& a, const glm::vec3& b) { return glm::distance(a, b); } +inline float getErrorDifference(const glm::mat4& a, const glm::mat4& b) { + float maxDiff = 0; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + float diff = fabs(a[i][j] - b[i][j]); + maxDiff = std::max(diff, maxDiff); + } + } + return maxDiff; +} + inline QTextStream& operator<<(QTextStream& stream, const glm::vec3& v) { return stream << "glm::vec3 { " << v.x << ", " << v.y << ", " << v.z << " }"; } +QTextStream& operator<<(QTextStream& stream, const glm::quat& q) { + return stream << "glm::quat { " << q.x << ", " << q.y << ", " << q.z << ", " << q.w << " }"; +} + +inline QTextStream& operator<< (QTextStream& stream, const glm::mat4& matrix) { + stream << "[\n\t\t"; + stream.setFieldWidth(15); + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + stream << matrix[c][r]; + } + stream << "\n\t\t"; + } + stream.setFieldWidth(0); + stream << "]\n\t"; // hacky as hell, but this should work... + return stream; +} #endif diff --git a/tests/QTestExtensions.h b/tests/QTestExtensions.h index d1918ebed8..16e51b41ee 100644 --- a/tests/QTestExtensions.h +++ b/tests/QTestExtensions.h @@ -37,6 +37,10 @@ // +float getErrorDifference(const float& a, const float& b) { + return fabsf(a - b); +} + // Generates a QCOMPARE-style failure message that can be passed to QTest::qFail. // // Formatting looks like this: @@ -281,3 +285,20 @@ bool compareData (const char* data, const char* expectedData, size_t length) { #define COMPARE_DATA(actual, expected, length) \ QCOMPARE_WITH_EXPR((ByteData ( actual, length )), (ByteData ( expected, length )), compareData(actual, expected, length)) + + +// Produces a relative error test for float usable QCOMPARE_WITH_LAMBDA. +inline auto errorTest (float actual, float expected, float acceptableRelativeError) +-> std::function { + return [actual, expected, acceptableRelativeError] () { + if (fabsf(expected) <= acceptableRelativeError) { + return fabsf(actual - expected) < fabsf(acceptableRelativeError); + } + return fabsf((actual - expected) / expected) < fabsf(acceptableRelativeError); + }; +} + +#define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \ + QCOMPARE_WITH_LAMBDA(actual, expected, errorTest(actual, expected, relativeError)) + + diff --git a/tests/animation/src/RotationConstraintTests.cpp b/tests/animation/src/RotationConstraintTests.cpp index 2cdb44ee8f..4aac875f2a 100644 --- a/tests/animation/src/RotationConstraintTests.cpp +++ b/tests/animation/src/RotationConstraintTests.cpp @@ -16,34 +16,8 @@ #include #include -// HACK -- these helper functions need to be defined BEFORE including magic inside QTestExtensions.h -// TODO: fix QTestExtensions so we don't need to do this in every test. - -// Computes the error value between two quaternions (using glm::dot) -float getErrorDifference(const glm::quat& a, const glm::quat& b) { - return fabsf(glm::dot(a, b)) - 1.0f; -} - -QTextStream& operator<<(QTextStream& stream, const glm::quat& q) { - return stream << "glm::quat { " << q.x << ", " << q.y << ", " << q.z << ", " << q.w << " }"; -} - -// Produces a relative error test for float usable QCOMPARE_WITH_LAMBDA. -inline auto errorTest (float actual, float expected, float acceptableRelativeError) --> std::function { - return [actual, expected, acceptableRelativeError] () { - if (fabsf(expected) <= acceptableRelativeError) { - return fabsf(actual - expected) < fabsf(acceptableRelativeError); - } - return fabsf((actual - expected) / expected) < fabsf(acceptableRelativeError); - }; -} - #include "../QTestExtensions.h" -#define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \ - QCOMPARE_WITH_LAMBDA(actual, expected, errorTest(actual, expected, relativeError)) - QTEST_MAIN(RotationConstraintTests) diff --git a/tests/shared/src/AngularConstraintTests.cpp b/tests/shared/src/AngularConstraintTests.cpp index 4dcf3d7296..a711bac709 100644 --- a/tests/shared/src/AngularConstraintTests.cpp +++ b/tests/shared/src/AngularConstraintTests.cpp @@ -9,22 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AngularConstraintTests.h" + #include #include #include #include -#include "AngularConstraintTests.h" #include "../QTestExtensions.h" -// Computes the error value between two quaternions (using glm::dot) -float getErrorDifference(const glm::quat& a, const glm::quat& b) { - return fabsf(glm::dot(a, b) - 1.0f); -} -QTextStream& operator<<(QTextStream& stream, const glm::quat& q) { - return stream << "glm::quat { " << q.x << ", " << q.y << ", " << q.z << ", " << q.w << " }"; -} QTEST_MAIN(AngularConstraintTests) diff --git a/tests/shared/src/GeometryUtilTests.cpp b/tests/shared/src/GeometryUtilTests.cpp index 44a540b478..eaf1e7cd8a 100644 --- a/tests/shared/src/GeometryUtilTests.cpp +++ b/tests/shared/src/GeometryUtilTests.cpp @@ -22,13 +22,6 @@ QTEST_MAIN(GeometryUtilTests) -float getErrorDifference(const float& a, const float& b) { - return fabsf(a - b); -} - -float getErrorDifference(const glm::vec3& a, const glm::vec3& b) { - return glm::distance(a, b); -} void GeometryUtilTests::testLocalRayRectangleIntersection() { glm::vec3 xAxis(1.0f, 0.0f, 0.0f); diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp index 48c3c59058..f3ba3239ee 100644 --- a/tests/shared/src/MovingMinMaxAvgTests.cpp +++ b/tests/shared/src/MovingMinMaxAvgTests.cpp @@ -13,7 +13,12 @@ #include +#include #include +#include + +#include "../QTestExtensions.h" + QTEST_MAIN(MovingMinMaxAvgTests) diff --git a/tests/shared/src/MovingMinMaxAvgTests.h b/tests/shared/src/MovingMinMaxAvgTests.h index c64b087c15..1922bd207a 100644 --- a/tests/shared/src/MovingMinMaxAvgTests.h +++ b/tests/shared/src/MovingMinMaxAvgTests.h @@ -14,15 +14,6 @@ #include -inline float getErrorDifference(float a, float b) { - return fabsf(a - b); -} - -#include "../QTestExtensions.h" - -#include "MovingMinMaxAvg.h" -#include "SharedUtil.h" - class MovingMinMaxAvgTests : public QObject { private slots: diff --git a/tests/shared/src/TransformTests.cpp b/tests/shared/src/TransformTests.cpp index be22914b9d..b936d45555 100644 --- a/tests/shared/src/TransformTests.cpp +++ b/tests/shared/src/TransformTests.cpp @@ -10,10 +10,13 @@ #include "TransformTests.h" +#include +#include + +#include #include -#include "SharedLogging.h" -#include <../QTestExtensions.h> +#include "../QTestExtensions.h" using namespace glm; diff --git a/tests/shared/src/TransformTests.h b/tests/shared/src/TransformTests.h index a4d9b2a6c0..06cdbcd3cb 100644 --- a/tests/shared/src/TransformTests.h +++ b/tests/shared/src/TransformTests.h @@ -12,33 +12,6 @@ #define hifi_TransformTests_h #include -#include -#include - -inline float getErrorDifference(const glm::mat4& a, const glm::mat4& b) { - float maxDiff = 0; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - float diff = fabs(a[i][j] - b[i][j]); - maxDiff = std::max(diff, maxDiff); - } - } - return maxDiff; -} - -inline QTextStream& operator<< (QTextStream& stream, const glm::mat4& matrix) { - stream << "[\n\t\t"; - stream.setFieldWidth(15); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix[c][r]; - } - stream << "\n\t\t"; - } - stream.setFieldWidth(0); - stream << "]\n\t"; // hacky as hell, but this should work... - return stream; -} class TransformTests : public QObject { Q_OBJECT From f4e77b82002a6a6012e8f3615b34924dde155d2d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 14:15:49 -0700 Subject: [PATCH 07/15] remove another dupe unit-test util --- tests/physics/src/BulletTestUtils.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/physics/src/BulletTestUtils.h b/tests/physics/src/BulletTestUtils.h index 01a4fd5973..9166f80ba1 100644 --- a/tests/physics/src/BulletTestUtils.h +++ b/tests/physics/src/BulletTestUtils.h @@ -23,10 +23,6 @@ // (used by QCOMPARE_WITH_RELATIVE_ERROR via QCOMPARE_WITH_LAMBDA) // (this is only used by btMatrix3x3 in MeshMassPropertiesTests.cpp, so it's only defined for the Mat3 type) -// Return the error between values a and b; used to implement QCOMPARE_WITH_ABS_ERROR -inline btScalar getErrorDifference(const btScalar& a, const btScalar& b) { - return fabs(a - b); -} // Return the error between values a and b; used to implement QCOMPARE_WITH_ABS_ERROR inline btScalar getErrorDifference(const btVector3& a, const btVector3& b) { return (a - b).length(); From 0fa304423146df9afe0099c163d1e2737db9eca4 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 11 Aug 2015 16:44:24 -0700 Subject: [PATCH 08/15] QML framerate improvments --- interface/src/Application.cpp | 1 + interface/src/ui/ApplicationOverlay.cpp | 7 +- .../src/RenderableWebEntityItem.cpp | 51 +- .../src/RenderableWebEntityItem.h | 3 +- libraries/render-utils/CMakeLists.txt | 8 + libraries/render-utils/src/GLEscrow.cpp | 9 + libraries/render-utils/src/GLEscrow.h | 222 ++++++++ .../render-utils/src/OffscreenQmlSurface.cpp | 475 ++++++++++++------ .../render-utils/src/OffscreenQmlSurface.h | 23 +- libraries/render-utils/src/OglplusHelpers.cpp | 71 +++ libraries/render-utils/src/OglplusHelpers.h | 32 ++ libraries/ui/src/OffscreenUi.cpp | 4 +- tests/ui/src/main.cpp | 6 - 13 files changed, 714 insertions(+), 198 deletions(-) create mode 100644 libraries/render-utils/src/GLEscrow.cpp create mode 100644 libraries/render-utils/src/GLEscrow.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dba2ed3234..ddef3396a2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -77,6 +77,7 @@ #include #include #include +#include #include #include #include diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 80f5ce2b69..6f18cac127 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -46,12 +46,7 @@ ApplicationOverlay::ApplicationOverlay() // then release it back to the UI for re-use auto offscreenUi = DependencyManager::get(); connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->lockTexture(textureId); - std::swap(_uiTexture, textureId); - if (textureId) { - offscreenUi->releaseTexture(textureId); - } + _uiTexture = textureId; }); } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 5a08abbb22..ce5b389333 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -43,20 +43,12 @@ RenderableWebEntityItem::~RenderableWebEntityItem() { if (_webSurface) { _webSurface->pause(); _webSurface->disconnect(_connection); - // After the disconnect, ensure that we have the latest texture by acquiring the - // lock used when updating the _texture value - _textureLock.lock(); - _textureLock.unlock(); // The lifetime of the QML surface MUST be managed by the main thread // Additionally, we MUST use local variables copied by value, rather than // member variables, since they would implicitly refer to a this that // is no longer valid auto webSurface = _webSurface; - auto texture = _texture; - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface, texture] { - if (texture) { - webSurface->releaseTexture(texture); - } + AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { webSurface->deleteLater(); }); } @@ -74,23 +66,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) { _webSurface->resume(); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { - _webSurface->lockTexture(textureId); - assert(!glGetError()); - // TODO change to atomic? - withLock(_textureLock, [&] { - std::swap(_texture, textureId); - }); - if (textureId) { - _webSurface->releaseTexture(textureId); - } - if (_texture) { - _webSurface->makeCurrent(); - glBindTexture(GL_TEXTURE_2D, _texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - _webSurface->doneCurrent(); - } + _texture = textureId; }); auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) { @@ -145,6 +121,19 @@ void RenderableWebEntityItem::render(RenderArgs* args) { point += 0.5f; point.y = 1.0f - point.y; point *= getDimensions() * METERS_TO_INCHES * DPI; + + if (event->button() == Qt::MouseButton::LeftButton) { + if (event->type() == QEvent::MouseButtonPress) { + this->_pressed = true; + this->_lastMove = ivec2((int)point.x, (int)point.y); + } else if (event->type() == QEvent::MouseButtonRelease) { + this->_pressed = false; + } + } + if (event->type() == QEvent::MouseMove) { + this->_lastMove = ivec2((int)point.x, (int)point.y); + } + // Forward the mouse event. QMouseEvent mappedEvent(event->type(), QPoint((int)point.x, (int)point.y), @@ -158,6 +147,16 @@ void RenderableWebEntityItem::render(RenderArgs* args) { QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent); QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent); QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent); + QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { + if (this->_pressed && this->getID() == entityItemID) { + // If the user mouses off the entity while the button is down, simulate a mouse release + QMouseEvent mappedEvent(QEvent::MouseButtonRelease, + QPoint(_lastMove.x, _lastMove.y), + Qt::MouseButton::LeftButton, + Qt::MouseButtons(), Qt::KeyboardModifiers()); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent); + } + }); } glm::vec2 dims = glm::vec2(getDimensions()); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 47e847a166..ee9c2531f1 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -34,7 +34,8 @@ private: QMetaObject::Connection _connection; uint32_t _texture{ 0 }; ivec2 _lastPress{ INT_MIN }; - QMutex _textureLock; + bool _pressed{ false }; + ivec2 _lastMove{ INT_MIN }; }; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 973e8b562a..0ea71e54e3 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -32,4 +32,12 @@ if (WIN32) endif() endif (WIN32) +add_dependency_external_projects(boostconfig) +find_package(BoostConfig REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS}) + +add_dependency_external_projects(oglplus) +find_package(OGLPLUS REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) + link_hifi_libraries(animation fbx shared gpu model render environment) diff --git a/libraries/render-utils/src/GLEscrow.cpp b/libraries/render-utils/src/GLEscrow.cpp new file mode 100644 index 0000000000..253af35d92 --- /dev/null +++ b/libraries/render-utils/src/GLEscrow.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis on 2015/08/06. +// 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 "GLEscrow.h" diff --git a/libraries/render-utils/src/GLEscrow.h b/libraries/render-utils/src/GLEscrow.h new file mode 100644 index 0000000000..54b124ae3c --- /dev/null +++ b/libraries/render-utils/src/GLEscrow.h @@ -0,0 +1,222 @@ +// +// Created by Bradley Austin Davis on 2015/08/06. +// 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 +// + +#pragma once +#ifndef hifi_GLEscrow_h +#define hifi_GLEscrow_h + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// The GLEscrow class provides a simple mechanism for producer GL contexts to provide +// content to a consumer where the consumer is assumed to be connected to a display and +// therefore must never be blocked. +// +// So we need to accomplish a few things. +// +// First the producer context needs to be able to supply content to the primary thread +// in such a way that the consumer only gets it when it's actually valid for reading +// (meaning that the async writing operations have been completed) +// +// Second, the client thread should be able to release the resource when it's finished +// using it (but again the reading of the resource is likely asyncronous) +// +// Finally, blocking operations need to be minimal, and any potentially blocking operations +// that can't be avoided need to be pushed to the submission context to avoid impacting +// the framerate of the consumer +// +// This class acts as a kind of border guard and holding pen between the two contexts +// to hold resources which the CPU is no longer using, but which might still be +// in use by the GPU. Fence sync objects are used to moderate the actual release of +// resources in either direction. +template < + typename T, + // Only accept numeric types + typename = typename std::enable_if::value, T>::type +> +class GLEscrow { +public: + + struct Item { + T _value; + GLsync _sync; + uint64_t _created; + + Item(T value, GLsync sync) : + _value(value), _sync(sync), _created(usecTimestampNow()) + { + } + + uint64_t age() { + return usecTimestampNow() - _created; + } + + bool signaled() { + auto result = glClientWaitSync(_sync, 0, 0); + if (GL_TIMEOUT_EXPIRED != result && GL_WAIT_FAILED != result) { + return true; + } + if (age() > (USECS_PER_SECOND / 2)) { + qWarning() << "Long unsignaled sync"; + } + return false; + } + }; + + using Mutex = std::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 + using Deque = std::deque; + using List = std::forward_list; + + void setRecycler(Recycler recycler) { + _recycler = recycler; + } + + // Submit a new resource from the producer context + // returns the number of prior submissions that were + // never consumed before becoming available. + // producers should self-limit if they start producing more + // work than is being consumed; + size_t submit(T t, GLsync writeSync = 0) { + if (!writeSync) { + // FIXME should the release and submit actually force the creation of a fence? + writeSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + } + + { + Lock lock(_mutex); + _submits.push_back(Item(t, writeSync)); + } + + return cleanTrash(); + } + + // 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 + T fetch() { + T result{0}; + // On the one hand using try_lock() reduces the chance of blocking the consumer thread, + // but if the produce thread is going fast enough, it could effectively + // starve the consumer out of ever actually getting resources. + if (_mutex.try_lock()) { + if (signaled(_submits, 0)) { + result = _submits.at(0)._value; + _submits.pop_front(); + } + _mutex.unlock(); + } + 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) { + if (!readSync) { + // FIXME should the release and submit actually force the creation of a fence? + readSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + } + + Lock lock(_mutex); + _releases.push_back(Item(t, readSync)); + } + +private: + size_t cleanTrash() { + size_t wastedWork{ 0 }; + List trash; + { + // We only ever need one ready item available in the list, so if the + // second item is signaled (implying the first is as well, remove the first + // item. Iterate until the SECOND item in the list is not in the ready state + // The signaled function takes care of checking against the deque size + while (signaled(_submits, 1)) { + pop(_submits); + ++wastedWork; + } + + // Stuff in the release queue can be cleared out as soon as it's signaled + while (signaled(_releases, 0)) { + pop(_releases); + } + + trash.swap(_trash); + } + + // FIXME maybe doing a timing on the deleters and warn if it's taking excessive time? + // although we are out of the lock, so it shouldn't be blocking anything + std::for_each(trash.begin(), trash.end(), [&](typename List::const_reference item) { + if (item._value) { + _recycler(item._value); + } + if (item._sync) { + glDeleteSync(item._sync); + } + }); + return wastedWork; + } + + // May be called on any thread, but must be inside a locked section + void pop(Deque& deque) { + auto& item = deque.front(); + _trash.push_front(item); + deque.pop_front(); + } + + // May be called on any thread, but must be inside a locked section + bool signaled(Deque& deque, size_t i) { + if (i >= deque.size()) { + return false; + } + + auto& item = deque.at(i); + // If there's no sync object, either it's not required or it's already been found to be signaled + if (!item._sync) { + return true; + } + + // Check the sync value using a zero timeout to ensure we don't block + // This is critically important as this is the only GL function we'll call + // inside the locked sections, so it cannot have any latency + if (item.signaled()) { + // if the sync is signaled, queue it for deletion + _trash.push_front(Item(0, item._sync)); + // And change the stored value to 0 so we don't check it again + item._sync = 0; + return true; + } + + return false; + } + + Mutex _mutex; + Recycler _recycler; + // Items coming from the submission / writer context + Deque _submits; + // Items coming from the client context. + Deque _releases; + // Items which are no longer in use. + List _trash; +}; + +using GLTextureEscrow = GLEscrow; + +#endif + diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index d5c495b414..78edb8e899 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -6,21 +6,34 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenQmlSurface.h" +#include "OglplusHelpers.h" -#include -#include -#include +#include #include #include #include #include #include #include +#include +#include -#include "FboCache.h" #include #include +#include "GLEscrow.h" +#include "OffscreenGlCanvas.h" +#include "AbstractViewStateInterface.h" + +// FIXME move to threaded rendering with Qt 5.5 +// #define QML_THREADED + +// Time between receiving a request to render the offscreen UI actually triggering +// the render. Could possibly be increased depending on the framerate we expect to +// achieve. +// This has the effect of capping the framerate at 200 +static const int MIN_TIMER_MS = 5; + class QMyQuickRenderControl : public QQuickRenderControl { protected: QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{ @@ -35,119 +48,324 @@ protected: private: QWindow* _renderWindow{ nullptr }; + friend class OffscreenQmlRenderer; friend class OffscreenQmlSurface; }; -#include "AbstractViewStateInterface.h" + Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") -// Time between receiving a request to render the offscreen UI actually triggering -// the render. Could possibly be increased depending on the framerate we expect to -// achieve. -static const int MAX_QML_FRAMERATE = 10; -static const int MIN_RENDER_INTERVAL_US = USECS_PER_SECOND / MAX_QML_FRAMERATE; -static const int MIN_TIMER_MS = 5; +#ifdef QML_THREADED +static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1); +static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); +static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3); +static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); +static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5); +#endif + +class OffscreenQmlRenderer : public OffscreenGlCanvas { + friend class OffscreenQmlSurface; +public: + + OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { + OffscreenGlCanvas::create(shareContext); +#ifdef QML_THREADED + // Qt 5.5 + // _renderControl->prepareThread(_renderThread); + _context->moveToThread(&_thread); + moveToThread(&_thread); + _thread.setObjectName("QML Thread"); + _thread.start(); + post(INIT); +#else + init(); +#endif + } + +#ifdef QML_THREADED + bool event(QEvent *e) + { + switch (int(e->type())) { + case INIT: + { + QMutexLocker lock(&_mutex); + init(); + } + return true; + case RENDER: + { + QMutexLocker lock(&_mutex); + render(&lock); + } + return true; + case RESIZE: + { + QMutexLocker lock(&_mutex); + resize(); + } + return true; + case STOP: + { + QMutexLocker lock(&_mutex); + cleanup(); + } + return true; + default: + return QObject::event(e); + } + } + + void post(const QEvent::Type& type) { + QCoreApplication::postEvent(this, new QEvent(type)); + } + +#endif + +private: + + void setupFbo() { + using namespace oglplus; + _textures.setSize(_size); + _depthStencil.reset(new Renderbuffer()); + Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil) + .Storage( + PixelDataInternalFormat::DepthComponent, + _size.x, _size.y); + + _fbo.reset(new Framebuffer()); + _fbo->Bind(Framebuffer::Target::Draw); + _fbo->AttachRenderbuffer(Framebuffer::Target::Draw, + FramebufferAttachment::Depth, *_depthStencil); + DefaultFramebuffer().Bind(Framebuffer::Target::Draw); + } -OffscreenQmlSurface::OffscreenQmlSurface() : - _renderControl(new QMyQuickRenderControl), _fboCache(new FboCache) { + + void init() { + _renderControl = new QMyQuickRenderControl(); + connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); + connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); + + // Create a QQuickWindow that is associated with out render control. Note that this + // window never gets created or shown, meaning that it will never get an underlying + // native (platform) window. + QQuickWindow::setDefaultAlphaBuffer(true); + // Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render + // because it retains an internal 'context' object that retains the thread it was created on, + // regardless of whether you later move it to another thread. + _quickWindow = new QQuickWindow(_renderControl); + _quickWindow->setColor(QColor(255, 255, 255, 0)); + _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); + +#ifdef QML_THREADED + // However, because we want to use synchronous events with the quickwindow, we need to move it back to the main + // thread after it's created. + _quickWindow->moveToThread(qApp->thread()); +#endif + + if (!makeCurrent()) { + qWarning("Failed to make context current on render thread"); + return; + } + _renderControl->initialize(_context); + setupFbo(); + _escrow.setRecycler([this](GLuint texture){ + _textures.recycleTexture(texture); + }); + doneCurrent(); + } + + void cleanup() { + if (!makeCurrent()) { + qFatal("Failed to make context current on render thread"); + return; + } + _renderControl->invalidate(); + + _fbo.reset(); + _depthStencil.reset(); + _textures.clear(); + + doneCurrent(); + +#ifdef QML_THREADED + _context->moveToThread(QCoreApplication::instance()->thread()); + _cond.wakeOne(); +#endif + } + + void resize(const QSize& newSize) { + // Update our members + if (_quickWindow) { + _quickWindow->setGeometry(QRect(QPoint(), newSize)); + _quickWindow->contentItem()->setSize(newSize); + } + + // Qt bug in 5.4 forces this check of pixel ratio, + // even though we're rendering offscreen. + qreal pixelRatio = 1.0; + if (_renderControl && _renderControl->_renderWindow) { + pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); + } else { + pixelRatio = AbstractViewStateInterface::instance()->getDevicePixelRatio(); + } + + uvec2 newOffscreenSize = toGlm(newSize * pixelRatio); + _textures.setSize(newOffscreenSize); + if (newOffscreenSize == _size) { + return; + } + _size = newOffscreenSize; + + // Clear out any fbos with the old size + if (!makeCurrent()) { + qWarning("Failed to make context current on render thread"); + return; + } + + qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; + setupFbo(); + doneCurrent(); + } + + void render(QMutexLocker *lock) { + if (_surface->_paused) { + return; + } + + if (!makeCurrent()) { + qWarning("Failed to make context current on render thread"); + return; + } + + Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size); + //Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _textures._size); + + _renderControl->sync(); +#ifdef QML_THREADED + _cond.wakeOne(); + lock->unlock(); +#endif + + + using namespace oglplus; + + _quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y)); + + TexturePtr texture = _textures.getNextTexture(); + _fbo->Bind(Framebuffer::Target::Draw); + _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); + _fbo->Complete(Framebuffer::Target::Draw); + //Context::Clear().ColorBuffer(); + { + _renderControl->render(); + // FIXME The web browsers seem to be leaving GL in an error state. + // Need a debug context with sync logging to figure out why. + // for now just clear the errors + glGetError(); + } + // FIXME probably unecessary + DefaultFramebuffer().Bind(Framebuffer::Target::Draw); + _quickWindow->resetOpenGLState(); + _escrow.submit(GetName(*texture)); + _lastRenderTime = usecTimestampNow(); + } + + void aboutToQuit() { +#ifdef QML_THREADED + QMutexLocker lock(&_quitMutex); + _quit = true; +#endif + } + + void stop() { +#ifdef QML_THREADED + QMutexLocker lock(&_quitMutex); + post(STOP); + _cond.wait(&_mutex); +#else + cleanup(); +#endif + } + + bool allowNewFrame(uint8_t fps) { + auto minRenderInterval = USECS_PER_SECOND / fps; + auto lastInterval = usecTimestampNow() - _lastRenderTime; + return (lastInterval > minRenderInterval); + } + + OffscreenQmlSurface* _surface{ nullptr }; + QQuickWindow* _quickWindow{ nullptr }; + QMyQuickRenderControl* _renderControl{ nullptr }; + +#ifdef QML_THREADED + QThread _thread; + QMutex _mutex; + QWaitCondition _cond; + QMutex _quitMutex; +#endif + + bool _quit; + FramebufferPtr _fbo; + RenderbufferPtr _depthStencil; + uvec2 _size{ 1920, 1080 }; + uint64_t _lastRenderTime{ 0 }; + TextureRecycler _textures; + GLTextureEscrow _escrow; +}; + +OffscreenQmlSurface::OffscreenQmlSurface() { } OffscreenQmlSurface::~OffscreenQmlSurface() { - // Make sure the context is current while doing cleanup. Note that we use the - // offscreen surface here because passing 'this' at this point is not safe: the - // underlying platform window may already be destroyed. To avoid all the trouble, use - // another surface that is valid for sure. - makeCurrent(); - - // Delete the render control first since it will free the scenegraph resources. - // Destroy the QQuickWindow only afterwards. - delete _renderControl; + _renderer->stop(); + delete _renderer; delete _qmlComponent; - delete _quickWindow; delete _qmlEngine; - - doneCurrent(); - delete _fboCache; } void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { - OffscreenGlCanvas::create(shareContext); + _renderer = new OffscreenQmlRenderer(this, shareContext); - makeCurrent(); - - // Create a QQuickWindow that is associated with out render control. Note that this - // window never gets created or shown, meaning that it will never get an underlying - // native (platform) window. - QQuickWindow::setDefaultAlphaBuffer(true); - _quickWindow = new QQuickWindow(_renderControl); - _quickWindow->setColor(QColor(255, 255, 255, 0)); - _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); // Create a QML engine. _qmlEngine = new QQmlEngine; if (!_qmlEngine->incubationController()) { - _qmlEngine->setIncubationController(_quickWindow->incubationController()); + _qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController()); } // When Quick says there is a need to render, we will not render immediately. Instead, // a timer with a small interval is used to get better performance. - _updateTimer.setSingleShot(true); + _updateTimer.setInterval(MIN_TIMER_MS); connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick); - - // Now hook up the signals. For simplicy we don't differentiate between - // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync - // is needed too). - connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenQmlSurface::requestRender); - connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate); - -#ifdef DEBUG - connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{ - qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject(); - }); - connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] { - qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem(); - }); -#endif + _updateTimer.start(); _qmlComponent = new QQmlComponent(_qmlEngine); - // Initialize the render control and our OpenGL resources. - makeCurrent(); - _renderControl->initialize(_context); } void OffscreenQmlSurface::resize(const QSize& newSize) { - // Qt bug in 5.4 forces this check of pixel ratio, - // even though we're rendering offscreen. - qreal pixelRatio = 1.0; +#ifdef QML_THREADED + QMutexLocker _locker(&(_renderer->_mutex)); +#endif + if (!_renderer || !_renderer->_quickWindow) { + QSize currentSize = _renderer->_quickWindow->geometry().size(); + if (newSize == currentSize) { + return; + } + } + _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize); - if (_renderControl && _renderControl->_renderWindow) { - pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); - } else { - pixelRatio = AbstractViewStateInterface::instance()->getDevicePixelRatio(); - } - QSize newOffscreenSize = newSize * pixelRatio; - if (newOffscreenSize == _fboCache->getSize()) { - return; - } - // Clear out any fbos with the old size - makeCurrent(); - qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; - _fboCache->setSize(newSize * pixelRatio); - - if (_quickWindow) { - _quickWindow->setGeometry(QRect(QPoint(), newSize)); - _quickWindow->contentItem()->setSize(newSize); - } - - // Update our members if (_rootItem) { _rootItem->setSize(newSize); } - doneCurrent(); +#ifdef QML_THREADED + _renderer->post(RESIZE); +#else + _renderer->resize(newSize); +#endif } QQuickItem* OffscreenQmlSurface::getRootItem() { @@ -173,20 +391,11 @@ QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function MIN_RENDER_INTERVAL_US) { - _updateTimer.setInterval(MIN_TIMER_MS); - } else { - _updateTimer.setInterval((MIN_RENDER_INTERVAL_US - lastInterval) / USECS_PER_MSEC); - } - _updateTimer.start(); - } + _render = true; } QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) { @@ -240,54 +449,38 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionsetParentItem(_quickWindow->contentItem()); - _rootItem->setSize(_quickWindow->renderTargetSize()); + _rootItem->setParentItem(_renderer->_quickWindow->contentItem()); + _rootItem->setSize(_renderer->_quickWindow->renderTargetSize()); return _rootItem; } - void OffscreenQmlSurface::updateQuick() { - PerformanceTimer perfTimer("qmlUpdate"); - if (_paused) { - return; - } - - if (!makeCurrent()) { + if (!_renderer || !_renderer->allowNewFrame(_maxFps)) { return; } - // Polish, synchronize and render the next frame (into our fbo). In this example - // everything happens on the same thread and therefore all three steps are performed - // in succession from here. In a threaded setup the render() call would happen on a - // separate thread. if (_polish) { - _renderControl->polishItems(); - _renderControl->sync(); + _renderer->_renderControl->polishItems(); _polish = false; } - QOpenGLFramebufferObject* fbo = _fboCache->getReadyFbo(); + if (_render) { +#ifdef QML_THREADED + _renderer->post(RENDER); +#else + _renderer->render(nullptr); +#endif + _render = false; + } - _quickWindow->setRenderTarget(fbo); - fbo->bind(); - - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - _renderControl->render(); - // FIXME The web browsers seem to be leaving GL in an error state. - // Need a debug context with sync logging to figure out why. - // for now just clear the errors - glGetError(); - - _quickWindow->resetOpenGLState(); - - QOpenGLFramebufferObject::bindDefault(); - _lastRenderTime = usecTimestampNow(); - // Force completion of all the operations before we emit the texture as being ready for use - glFinish(); - - emit textureUpdated(fbo->texture()); + GLuint newTexture = _renderer->_escrow.fetch(); + if (newTexture) { + if (_currentTexture) { + _renderer->_escrow.release(_currentTexture); + } + _currentTexture = newTexture; + emit textureUpdated(_currentTexture); + } } QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { @@ -299,7 +492,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec } vec2 offscreenPosition = toGlm(sourcePosition); offscreenPosition /= sourceSize; - offscreenPosition *= vec2(toGlm(_quickWindow->size())); + offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size())); return QPointF(offscreenPosition.x, offscreenPosition.y); } @@ -309,7 +502,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec // bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) { - if (_quickWindow == originalDestination) { + if (_renderer->_quickWindow == originalDestination) { return false; } // Only intercept events while we're in an active state @@ -321,7 +514,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even // Don't intercept our own events, or we enter an infinite recursion QObject* recurseTest = originalDestination; while (recurseTest) { - Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow); + Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow); recurseTest = recurseTest->parent(); } #endif @@ -330,7 +523,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even switch (event->type()) { case QEvent::Resize: { QResizeEvent* resizeEvent = static_cast(event); - QGLWidget* widget = dynamic_cast(originalDestination); + QWidget* widget = dynamic_cast(originalDestination); if (widget) { this->resize(resizeEvent->size()); } @@ -340,7 +533,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even case QEvent::KeyPress: case QEvent::KeyRelease: { event->ignore(); - if (QCoreApplication::sendEvent(_quickWindow, event)) { + if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) { return event->isAccepted(); } break; @@ -353,7 +546,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->modifiers(), wheelEvent->orientation()); mappedEvent.ignore(); - if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { + if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { return mappedEvent.isAccepted(); } break; @@ -376,7 +569,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos); } mappedEvent.ignore(); - if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { + if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { return mappedEvent.isAccepted(); } break; @@ -389,14 +582,6 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even return false; } -void OffscreenQmlSurface::lockTexture(int texture) { - _fboCache->lockTexture(texture); -} - -void OffscreenQmlSurface::releaseTexture(int texture) { - _fboCache->releaseTexture(texture); -} - void OffscreenQmlSurface::pause() { _paused = true; } @@ -411,13 +596,13 @@ bool OffscreenQmlSurface::isPaused() const { } void OffscreenQmlSurface::setProxyWindow(QWindow* window) { - _renderControl->_renderWindow = window; + _renderer->_renderControl->_renderWindow = window; } QQuickWindow* OffscreenQmlSurface::getWindow() { - return _quickWindow; + return _renderer->_quickWindow; } QSize OffscreenQmlSurface::size() const { - return _quickWindow->geometry().size(); + return _renderer->_quickWindow->geometry().size(); } diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h index 6eb886044b..1a1827b63f 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.h +++ b/libraries/render-utils/src/OffscreenQmlSurface.h @@ -17,18 +17,18 @@ #include #include -#include "OffscreenGlCanvas.h" - class QWindow; class QMyQuickRenderControl; +class QOpenGLContext; class QQmlEngine; class QQmlContext; class QQmlComponent; class QQuickWindow; class QQuickItem; -class FboCache; -class OffscreenQmlSurface : public OffscreenGlCanvas { +class OffscreenQmlRenderer; + +class OffscreenQmlSurface : public QObject { Q_OBJECT public: @@ -45,6 +45,7 @@ public: return load(QUrl(qmlSourceFile), f); } + void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling void setProxyWindow(QWindow* window); void setMouseTranslator(MouseTranslator mouseTranslator) { @@ -67,8 +68,6 @@ signals: public slots: void requestUpdate(); void requestRender(); - void lockTexture(int texture); - void releaseTexture(int texture); private: QObject* finishQmlLoad(std::function f); @@ -77,20 +76,20 @@ private: private slots: void updateQuick(); -protected: - QQuickWindow* _quickWindow{ nullptr }; - private: - QMyQuickRenderControl* _renderControl{ nullptr }; + friend class OffscreenQmlRenderer; + OffscreenQmlRenderer* _renderer{ nullptr }; QQmlEngine* _qmlEngine{ nullptr }; QQmlComponent* _qmlComponent{ nullptr }; QQuickItem* _rootItem{ nullptr }; QTimer _updateTimer; - FboCache* _fboCache; - quint64 _lastRenderTime{ 0 }; + uint32_t _currentTexture{ 0 }; + bool _render{ false }; bool _polish{ true }; bool _paused{ true }; + uint8_t _maxFps{ 60 }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } }; + }; #endif diff --git a/libraries/render-utils/src/OglplusHelpers.cpp b/libraries/render-utils/src/OglplusHelpers.cpp index 7d4a2f18bf..86769436e6 100644 --- a/libraries/render-utils/src/OglplusHelpers.cpp +++ b/libraries/render-utils/src/OglplusHelpers.cpp @@ -7,6 +7,7 @@ // #include "OglplusHelpers.h" #include +#include using namespace oglplus; using namespace oglplus::shapes; @@ -317,3 +318,73 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i new shapes::ShapeWrapper({ "Position", "TexCoord" }, SphereSection(fov, aspect, slices, stacks), *program) ); } + +void TextureRecycler::setSize(const uvec2& size) { + if (size == _size) { + return; + } + _size = size; + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + std::set toDelete; + std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) { + if (!item.second._active && item.second._size != _size) { + toDelete.insert(item.first); + } + }); + std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) { + _allTextures.erase(key); + }); +} + +void TextureRecycler::clear() { + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + _allTextures.clear(); +} + +TexturePtr TextureRecycler::getNextTexture() { + using namespace oglplus; + if (_readyTextures.empty()) { + TexturePtr newTexture(new Texture()); + Context::Bound(oglplus::Texture::Target::_2D, *newTexture) + .MinFilter(TextureMinFilter::Linear) + .MagFilter(TextureMagFilter::Linear) + .WrapS(TextureWrap::ClampToEdge) + .WrapT(TextureWrap::ClampToEdge) + .Image2D( + 0, PixelDataInternalFormat::RGBA8, + _size.x, _size.y, + 0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr + ); + GLuint texId = GetName(*newTexture); + _allTextures[texId] = TexInfo{ newTexture, _size }; + _readyTextures.push(newTexture); + } + + TexturePtr result = _readyTextures.front(); + _readyTextures.pop(); + + GLuint texId = GetName(*result); + auto& item = _allTextures[texId]; + item._active = true; + + return result; +} + +void TextureRecycler::recycleTexture(GLuint texture) { + Q_ASSERT(_allTextures.count(texture)); + auto& item = _allTextures[texture]; + Q_ASSERT(item._active); + item._active = false; + if (item._size != _size) { + // Buh-bye + _allTextures.erase(texture); + return; + } + + _readyTextures.push(item._tex); +} + diff --git a/libraries/render-utils/src/OglplusHelpers.h b/libraries/render-utils/src/OglplusHelpers.h index 569e0be7a3..99232b97cb 100644 --- a/libraries/render-utils/src/OglplusHelpers.h +++ b/libraries/render-utils/src/OglplusHelpers.h @@ -10,6 +10,10 @@ // FIXME support oglplus on all platforms // For now it's a convenient helper for Windows +#include +#include + + #include #include "GLMHelpers.h" @@ -33,6 +37,8 @@ #include "NumericalConstants.h" using FramebufferPtr = std::shared_ptr; +using RenderbufferPtr = std::shared_ptr; +using TexturePtr = std::shared_ptr; using ShapeWrapperPtr = std::shared_ptr; using BufferPtr = std::shared_ptr; using VertexArrayPtr = std::shared_ptr; @@ -151,3 +157,29 @@ protected: }; using BasicFramebufferWrapperPtr = std::shared_ptr; + +class TextureRecycler { +public: + void setSize(const uvec2& size); + void clear(); + TexturePtr getNextTexture(); + void recycleTexture(GLuint texture); + +private: + + struct TexInfo { + TexturePtr _tex; + uvec2 _size; + bool _active{ false }; + + TexInfo() {} + TexInfo(TexturePtr tex, const uvec2& size) : _tex(tex), _size(size) {} + }; + + using Map = std::map; + using Queue = std::queue; + + Map _allTextures; + Queue _readyTextures; + uvec2 _size{ 1920, 1080 }; +}; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index d581f12473..a1f00ab5ad 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -38,8 +38,8 @@ public: // so I think it's OK for the time being. bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { Q_ASSERT(event->type() == QEvent::ShortcutOverride); - QObject* focusObject = _quickWindow->focusObject(); - if (focusObject != _quickWindow && focusObject != getRootItem()) { + QObject* focusObject = getWindow()->focusObject(); + if (focusObject != getWindow() && focusObject != getRootItem()) { //qDebug() << "Swallowed shortcut " << static_cast(event)->key(); event->accept(); return true; diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 3fe0f4c11d..dab22999d1 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -186,13 +186,7 @@ public: auto offscreenUi = DependencyManager::get(); offscreenUi->create(_context); connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { - offscreenUi->lockTexture(textureId); - assert(!glGetError()); - GLuint oldTexture = testQmlTexture; testQmlTexture = textureId; - if (oldTexture) { - offscreenUi->releaseTexture(oldTexture); - } }); makeCurrent(); From f64ab8e6b08cd0c552c780e1190b148fbc9bb69d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Aug 2015 17:52:54 -0700 Subject: [PATCH 09/15] add QCOMPARE_QUATS macro for easier tests --- tests/GLMTestUtils.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/GLMTestUtils.h b/tests/GLMTestUtils.h index 7dbc4a840f..f353255cc1 100644 --- a/tests/GLMTestUtils.h +++ b/tests/GLMTestUtils.h @@ -59,4 +59,8 @@ inline QTextStream& operator<< (QTextStream& stream, const glm::mat4& matrix) { stream << "]\n\t"; // hacky as hell, but this should work... return stream; } + +#define QCOMPARE_QUATS(rotationA, rotationB, angle) \ + QVERIFY(fabsf(1.0f - fabsf(glm::dot(rotationA, rotationB))) < 2.0f * sinf(angle)) + #endif From 0daa9d4871336866d6e5b7dbbf2a9b2ea559a205 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 13 Aug 2015 09:38:21 -0700 Subject: [PATCH 10/15] Remove extra rotation in image3d --- interface/src/ui/overlays/Image3DOverlay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index c13dbbb139..d5ad3d511f 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -90,7 +90,6 @@ void Image3DOverlay::render(RenderArgs* args) { applyTransformTo(_transform, true); Transform transform = _transform; transform.postScale(glm::vec3(getDimensions(), 1.0f)); - transform.postRotate(glm::angleAxis(glm::pi(), IDENTITY_UP)); batch->setModelTransform(transform); batch->setResourceTexture(0, _texture->getGPUTexture()); From 1133e053d1aa2943d4f70ddf862cb06e9700e401 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 14 Aug 2015 07:33:36 -0700 Subject: [PATCH 11/15] Developer->Avatar->Fix Gaze menu option to not move the eyes around while we're debugging. --- interface/src/Application.cpp | 4 +++- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Head.cpp | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d8dad73d82..2f6b038e40 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2397,7 +2397,9 @@ void Application::updateMyAvatarLookAtPosition() { if (faceAngle < MAXIMUM_FACE_ANGLE) { // Randomly look back and forth between look targets - switch (_myAvatar->getEyeContactTarget()) { + eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? + LEFT_EYE : _myAvatar->getEyeContactTarget(); + switch (target) { case LEFT_EYE: lookAtSpot = lookingAtHead->getLeftEyePosition(); break; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6ff479778b..a31353fa4c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -453,6 +453,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 2231ddbe9a..789fccecd8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -193,6 +193,7 @@ namespace MenuOption { const QString Faceshift = "Faceshift"; const QString FirstPerson = "First Person"; const QString FivePointCalibration = "5 Point Calibration"; + const QString FixGaze = "Fix Gaze (no saccade)"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; const QString FullscreenMirror = "Fullscreen Mirror"; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 2cd87588d8..c7ee4a2e01 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -229,6 +229,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { } else { _saccade = glm::vec3(); } + if (Menu::getInstance()->isOptionChecked(MenuOption::FixGaze)) { // if debug menu turns off, use no saccade + _saccade = glm::vec3(); + } if (!isMine) { _faceModel.setLODDistance(static_cast(_owningAvatar)->getLODDistance()); From 6b27f854b23b4baa48405922f755769e7a31dc35 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 14 Aug 2015 07:51:04 -0700 Subject: [PATCH 12/15] Eyelid position is on sender side and should use lookAt spot, not the receiver side. --- interface/src/avatar/Head.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index c7ee4a2e01..cec9079443 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -277,7 +277,7 @@ void Head::calculateMouthShapes() { void Head::applyEyelidOffset(glm::quat headOrientation) { // Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches. - glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getCorrectedLookAtPosition() - _eyePosition); + glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getLookAtPosition() - _eyePosition); eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head float eyePitch = safeEulerAngles(eyeRotation).x; From 208e41efd19d12eff4c2d25f83561315c8f445b9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 13 Aug 2015 16:22:26 -0700 Subject: [PATCH 13/15] WebEntity keyboard input with mouse hover --- interface/src/Application.cpp | 51 ++++++++++++++++++- interface/src/Application.h | 2 + .../src/RenderableWebEntityItem.cpp | 8 +++ .../src/RenderableWebEntityItem.h | 5 ++ .../render-utils/src/OffscreenQmlSurface.cpp | 4 ++ .../render-utils/src/OffscreenQmlSurface.h | 1 + 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 598498e2d0..e7783c779e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -98,6 +98,8 @@ #include #include +#include + #include "AudioClient.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" @@ -146,9 +148,8 @@ #include "ui/AddressBarDialog.h" #include "ui/UpdateDialog.h" -//#include - // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU +// FIXME seems to be broken. #if defined(Q_OS_WIN) extern "C" { _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; @@ -671,6 +672,28 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); + + auto entityScriptingInterface = DependencyManager::get(); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) { + if (_keyboardFocusedItem != entityItemID) { + _keyboardFocusedItem = UNKNOWN_ENTITY_ID; + auto properties = entityScriptingInterface->getEntityProperties(entityItemID); + if (EntityTypes::Web == properties.getType() && !properties.getLocked()) { + auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID); + RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); + if (webEntity) { + webEntity->setProxyWindow(_window->windowHandle()); + _keyboardFocusedItem = entityItemID; + } + } + } + }); + + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { + if (_keyboardFocusedItem == entityItemID) { + _keyboardFocusedItem = UNKNOWN_ENTITY_ID; + } + }); } void Application::aboutToQuit() { @@ -854,6 +877,10 @@ void Application::initializeGL() { InfoView::show(INFO_HELP_PATH, true); } +QWindow* getProxyWindow() { + return qApp->getWindow()->windowHandle(); +} + void Application::initializeUi() { AddressBarDialog::registerType(); ErrorDialog::registerType(); @@ -1232,6 +1259,26 @@ bool Application::event(QEvent* event) { return true; } + if (!_keyboardFocusedItem.isInvalidID()) { + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + auto entityScriptingInterface = DependencyManager::get(); + auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem); + RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); + if (webEntity && webEntity->getEventHandler()) { + event->setAccepted(false); + QCoreApplication::sendEvent(webEntity->getEventHandler(), event); + if (event->isAccepted()) { + return true; + } + } + } + break; + } + } + switch (event->type()) { case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); diff --git a/interface/src/Application.h b/interface/src/Application.h index 239b440822..cec1325baf 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -678,6 +678,8 @@ private: bool _overlayEnabled = true; QRect _savedGeometry; DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface(); + + EntityItemID _keyboardFocusedItem; }; #endif // hifi_Application_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index ce5b389333..73aa6f2718 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -195,3 +195,11 @@ void RenderableWebEntityItem::setSourceUrl(const QString& value) { } } } + +void RenderableWebEntityItem::setProxyWindow(QWindow* proxyWindow) { + _webSurface->setProxyWindow(proxyWindow); +} + +QObject* RenderableWebEntityItem::getEventHandler() { + return _webSurface->getEventHandler(); +} diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index ee9c2531f1..63418a890f 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -16,6 +16,8 @@ #include "RenderableEntityItem.h" class OffscreenQmlSurface; +class QWindow; +class QObject; class RenderableWebEntityItem : public WebEntityItem { public: @@ -26,6 +28,9 @@ public: virtual void render(RenderArgs* args); virtual void setSourceUrl(const QString& value); + + void setProxyWindow(QWindow* proxyWindow); + QObject* getEventHandler(); SIMPLE_RENDERABLE(); diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 78edb8e899..0dfd95a725 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -599,6 +599,10 @@ void OffscreenQmlSurface::setProxyWindow(QWindow* window) { _renderer->_renderControl->_renderWindow = window; } +QObject* OffscreenQmlSurface::getEventHandler() { + return getWindow(); +} + QQuickWindow* OffscreenQmlSurface::getWindow() { return _renderer->_quickWindow; } diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h index 1a1827b63f..13e467bccd 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.h +++ b/libraries/render-utils/src/OffscreenQmlSurface.h @@ -59,6 +59,7 @@ public: void setBaseUrl(const QUrl& baseUrl); QQuickItem* getRootItem(); QQuickWindow* getWindow(); + QObject* getEventHandler(); virtual bool eventFilter(QObject* originalDestination, QEvent* event); From db95657455363a97db19c86da52bf783e14ba8bd Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 14 Aug 2015 16:40:53 -0700 Subject: [PATCH 14/15] PR comments --- interface/src/Application.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e7783c779e..ca7ee8d98c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1262,8 +1262,7 @@ bool Application::event(QEvent* event) { if (!_keyboardFocusedItem.isInvalidID()) { switch (event->type()) { case QEvent::KeyPress: - case QEvent::KeyRelease: - { + case QEvent::KeyRelease: { auto entityScriptingInterface = DependencyManager::get(); auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem); RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); @@ -1274,7 +1273,10 @@ bool Application::event(QEvent* event) { return true; } } + break; } + + default: break; } } From dc2ddd0303a1f9ab8c6ada32bd86a0a3a9eae7d4 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 14 Aug 2015 17:12:00 -0700 Subject: [PATCH 15/15] switch statement spacing --- interface/src/Application.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ca7ee8d98c..b8c9b78cc5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1263,18 +1263,18 @@ bool Application::event(QEvent* event) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: { - auto entityScriptingInterface = DependencyManager::get(); - auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem); - RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); - if (webEntity && webEntity->getEventHandler()) { - event->setAccepted(false); - QCoreApplication::sendEvent(webEntity->getEventHandler(), event); - if (event->isAccepted()) { - return true; - } + auto entityScriptingInterface = DependencyManager::get(); + auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem); + RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); + if (webEntity && webEntity->getEventHandler()) { + event->setAccepted(false); + QCoreApplication::sendEvent(webEntity->getEventHandler(), event); + if (event->isAccepted()) { + return true; } - break; } + break; + } default: break;