From 284fcc8a0b3118aa28f77ca8e3a951565febf6a8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 23 Jun 2016 13:50:17 -0700 Subject: [PATCH 01/41] better check for oculus display in oculus legacy plugin because the sdk is buggy --- .../src/OculusLegacyDisplayPlugin.cpp | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index f1a803ee19..a994198957 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -66,18 +66,47 @@ bool OculusLegacyDisplayPlugin::isSupported() const { } auto hmd = ovrHmd_Create(0); + + // The Oculus SDK seems to have trouble finding the right screen sometimes, so we have to guess + // Guesses, in order of best match: + // - resolution and position match + // - resolution and one component of position match + // - resolution matches + // - position matches + QList matches({ -1, -1, -1, -1 }); if (hmd) { QPoint targetPosition{ hmd->WindowsPos.x, hmd->WindowsPos.y }; + QSize targetResolution{ hmd->Resolution.w, hmd->Resolution.h }; auto screens = qApp->screens(); for(int i = 0; i < screens.size(); ++i) { auto screen = screens[i]; QPoint position = screen->geometry().topLeft(); - if (position == targetPosition) { - _hmdScreen = i; - break; + QSize resolution = screen->geometry().size(); + + if (position == targetPosition && resolution == targetResolution) { + matches[0] = i; + } else if ((position.x() == targetPosition.x() || position.y() == targetPosition.y()) && + resolution == targetResolution) { + matches[1] = i; + } else if (resolution == targetResolution) { + matches[2] = i; + } else if (position == targetPosition) { + matches[3] = i; } } } + + for (int screen : matches) { + if (screen != -1) { + _hmdScreen = screen; + break; + } + } + + if (_hmdScreen == -1) { + qDebug() << "Could not find Rift screen"; + result = false; + } ovr_Shutdown(); return result; From 0bfe3ea817ef96159d125c6cca45f53729030b2c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 27 Jun 2016 11:34:44 -0700 Subject: [PATCH 02/41] added support for disabling vsync on macs, but doesn't work with the rift --- .../display-plugins/OpenGLDisplayPlugin.cpp | 18 ++++++++++++++++-- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +++ libraries/gl/src/gl/Config.h | 1 + libraries/gl/src/gl/GLWidget.cpp | 11 +++++++---- .../src/OculusLegacyDisplayPlugin.cpp | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 4bca48aeb0..eb10d4ed5e 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -16,6 +16,9 @@ #include #include +#if defined(Q_OS_MAC) +#include +#endif #include #include #include @@ -597,8 +600,14 @@ void OpenGLDisplayPlugin::enableVsync(bool enable) { if (!_vsyncSupported) { return; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) wglSwapIntervalEXT(enable ? 1 : 0); +#elif defined(Q_OS_MAC) + GLint interval = enable ? 1 : 0; + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else + // TODO: Fill in for linux + return; #endif } @@ -606,9 +615,14 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { if (!_vsyncSupported) { return true; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) return wglGetSwapIntervalEXT() != 0; +#elif defined(Q_OS_MAC) + GLint interval; + CGLGetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); + return interval != 0; #else + // TODO: Fill in for linux return true; #endif } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index b29348f646..41af4ed543 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -200,7 +200,10 @@ static ProgramPtr getReprojectionProgram() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled + // On Mac, this won't work due to how the contexts are handled, so don't try +#if !defined(Q_OS_MAC) enableVsync(false); +#endif _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index 593537a291..7947bd45df 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -20,6 +20,7 @@ #include #include +#include #endif diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index f113be1cfb..6fc9c41160 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -47,13 +47,16 @@ void GLWidget::initializeGL() { // 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) if (isValid() && context() && context()->contextHandle()) { - _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; - } +#if defined(Q_OS_WIN) + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control"); +#elif defined(Q_OS_MAC) + _vsyncSupported = true; +#else + // TODO: write the proper code for linux #endif + } } void GLWidget::paintEvent(QPaintEvent* event) { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index a994198957..e439b3fabf 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -73,6 +73,7 @@ bool OculusLegacyDisplayPlugin::isSupported() const { // - resolution and one component of position match // - resolution matches // - position matches + // If it still picks the wrong screen, you'll have to mess with your monitor configuration QList matches({ -1, -1, -1, -1 }); if (hmd) { QPoint targetPosition{ hmd->WindowsPos.x, hmd->WindowsPos.y }; From 8f0d9668105fe9ae8f5434c10f097b4e0484a21a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 28 Jun 2016 13:53:57 -0700 Subject: [PATCH 03/41] working on enabling preview image --- libraries/display-plugins/CMakeLists.txt | 2 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 67 +++++++++++++++++-- .../display-plugins/hmd/HmdDisplayPlugin.h | 10 +++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index fe08647074..7d82b3b665 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui) +link_hifi_libraries(shared plugins render-utils ui-plugins gl gpu-gl ui) target_opengl() diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 41af4ed543..5609a071a0 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -20,6 +20,10 @@ #include #include +#include +#include +#include + #include "../Logging.h" #include "../CompositorHelper.h" @@ -58,9 +62,38 @@ bool HmdDisplayPlugin::internalActivate() { _eyeInverseProjections[eye] = glm::inverse(_eyeProjections[eye]); }); + _firstPreview = true; + if (_previewTextureID == 0) { + const QString url("https://hifi-content.s3.amazonaws.com/samuel/preview.png"); + _previewTexture = DependencyManager::get()->getTexture(url); + +// const QString path("/Users/computer33/Documents/preview.png"); +// QImage previewTexture(path); +// if (!previewTexture.isNull()) { + if (_previewTexture && _previewTexture->isLoaded()) { +// previewTexture = previewTexture.mirrored(false, true); + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _previewTexture->getWidth(), _previewTexture->getHeight(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, _previewTexture->getGPUTexture()->accessStoredMipFace(0)->readData()); + using namespace oglplus; + oglplus::Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + oglplus::Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + } + } + return Parent::internalActivate(); } +void HmdDisplayPlugin::internalDeactivate() { + if (_previewTextureID != 0) { + glDeleteTextures(1, &_previewTextureID); + _previewTextureID = 0; + } + Parent::internalDeactivate(); +} + static const char * REPROJECTION_VS = R"VS(#version 410 core in vec3 Position; @@ -196,6 +229,7 @@ static ProgramPtr getReprojectionProgram() { } #endif +static GLint PREVIEW_TEXTURE_LOCATION = -1; void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); @@ -206,9 +240,14 @@ void HmdDisplayPlugin::customizeContext() { #endif _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); - compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); using namespace oglplus; + if (!_enablePreview) { + compileProgram(_previewProgram, DrawUnitQuadTexcoord_vert, DrawTexture_frag); + PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); + } + + compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); @@ -217,6 +256,7 @@ void HmdDisplayPlugin::customizeContext() { void HmdDisplayPlugin::uncustomizeContext() { _sphereSection.reset(); _compositeFramebuffer.reset(); + _previewProgram.reset(); _reprojectionProgram.reset(); Parent::uncustomizeContext(); } @@ -241,8 +281,8 @@ void HmdDisplayPlugin::compositeScene() { useProgram(_reprojectionProgram); using namespace oglplus; - Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); - Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + oglplus::Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + oglplus::Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); Uniform(*_reprojectionProgram, REPROJECTION_MATRIX_LOCATION).Set(_currentPresentFrameInfo.presentReprojection); //Uniform(*_reprojectionProgram, PROJECTION_MATRIX_LOCATION).Set(_eyeProjections); //Uniform(*_reprojectionProgram, INVERSE_PROJECTION_MATRIX_LOCATION).Set(_eyeInverseProjections); @@ -312,12 +352,13 @@ void HmdDisplayPlugin::internalPresent() { hmdPresent(); // screen preview mirroring + auto window = _container->getPrimaryWidget(); + auto windowSize = toGlm(window->size()); + auto devicePixelRatio = window->devicePixelRatio(); if (_enablePreview) { - auto window = _container->getPrimaryWidget(); - auto windowSize = toGlm(window->size()); float windowAspect = aspect(windowSize); float sceneAspect = aspect(_renderTargetSize); - if (_monoPreview) { + if (_enablePreview && _monoPreview) { sceneAspect /= 2.0f; } float aspectRatio = sceneAspect / windowAspect; @@ -350,6 +391,20 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); + } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { + if (_previewTexture) qDebug() << _previewTexture->getBytesReceived(); + if (PREVIEW_TEXTURE_LOCATION != -1 && _previewTextureID != 0) { + useProgram(_previewProgram); + glViewport(0, 0, windowSize.x, windowSize.y); + glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + swapBuffers(); + _firstPreview = false; + _prevWindowSize = windowSize; + _prevDevicePixelRatio = devicePixelRatio; + } } postPreview(); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e6ceb7e376..7307c6968f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -13,6 +13,9 @@ #include "../OpenGLDisplayPlugin.h" +class NetworkTexture; +using NetworkTexturePointer = QSharedPointer; + class HmdDisplayPlugin : public OpenGLDisplayPlugin { using Parent = OpenGLDisplayPlugin; public: @@ -39,6 +42,7 @@ protected: virtual void updatePresentPose(); bool internalActivate() override; + void internalDeactivate() override; void compositeScene() override; void compositeOverlay() override; void compositePointer() override; @@ -73,6 +77,12 @@ private: bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; + bool _firstPreview { true }; + ProgramPtr _previewProgram; + GLuint _previewTextureID { 0 }; + NetworkTexturePointer _previewTexture { nullptr }; + glm::uvec2 _prevWindowSize { 0, 0 }; + qreal _prevDevicePixelRatio { 0 }; ShapeWrapperPtr _sphereSection; ProgramPtr _reprojectionProgram; }; From a82930cb7a07a74dc04f89b63906b182acc41c85 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 28 Jun 2016 15:59:58 -0700 Subject: [PATCH 04/41] cleanup --- libraries/display-plugins/CMakeLists.txt | 2 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 62 ++++++++++++------- .../display-plugins/hmd/HmdDisplayPlugin.h | 7 ++- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 7d82b3b665..fe08647074 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins render-utils ui-plugins gl gpu-gl ui) +link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui) target_opengl() diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 082fed787d..6ab6c71211 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -21,7 +21,9 @@ #include #include -#include +#include +#include +#include #include #include @@ -64,28 +66,39 @@ bool HmdDisplayPlugin::internalActivate() { _firstPreview = true; if (_previewTextureID == 0) { - const QString url("https://hifi-content.s3.amazonaws.com/samuel/preview.png"); - _previewTexture = DependencyManager::get()->getTexture(url); - -// const QString path("/Users/computer33/Documents/preview.png"); -// QImage previewTexture(path); -// if (!previewTexture.isNull()) { - if (_previewTexture && _previewTexture->isLoaded()) { -// previewTexture = previewTexture.mirrored(false, true); - glGenTextures(1, &_previewTextureID); - glBindTexture(GL_TEXTURE_2D, _previewTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _previewTexture->getWidth(), _previewTexture->getHeight(), 0, - GL_BGRA, GL_UNSIGNED_BYTE, _previewTexture->getGPUTexture()->accessStoredMipFace(0)->readData()); - using namespace oglplus; - oglplus::Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); - oglplus::Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); - glBindTexture(GL_TEXTURE_2D, 0); - } + const QUrl previewURL("https://hifi-content.s3.amazonaws.com/samuel/preview.png"); + QNetworkAccessManager& manager = NetworkAccessManager::getInstance(); + QNetworkRequest request(previewURL); + request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); +// connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); + manager.get(request); } return Parent::internalActivate(); } +void HmdDisplayPlugin::downloadFinished(QNetworkReply* reply) { + if (reply->error() != QNetworkReply::NetworkError::NoError) { + qDebug() << "HMDDisplayPlugin: error downloading preview image" << reply->errorString(); + return; + } + + QImage previewTexture; + previewTexture.loadFromData(reply->readAll()); + + if (!previewTexture.isNull()) { + previewTexture = previewTexture.mirrored(false, true); + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, previewTexture.width(), previewTexture.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, previewTexture.bits()); + using namespace oglplus; + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + void HmdDisplayPlugin::internalDeactivate() { if (_previewTextureID != 0) { glDeleteTextures(1, &_previewTextureID); @@ -270,7 +283,8 @@ void HmdDisplayPlugin::customizeContext() { using namespace oglplus; if (!_enablePreview) { - compileProgram(_previewProgram, DrawUnitQuadTexcoord_vert, DrawTexture_frag); + std::string version("#version 410 core\n"); + compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); } @@ -313,8 +327,8 @@ void HmdDisplayPlugin::compositeScene() { useProgram(_reprojectionProgram); using namespace oglplus; - oglplus::Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); - oglplus::Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); Uniform(*_reprojectionProgram, REPROJECTION_MATRIX_LOCATION).Set(_currentPresentFrameInfo.presentReprojection); //Uniform(*_reprojectionProgram, PROJECTION_MATRIX_LOCATION).Set(_eyeProjections); //Uniform(*_reprojectionProgram, INVERSE_PROJECTION_MATRIX_LOCATION).Set(_eyeInverseProjections); @@ -385,7 +399,7 @@ void HmdDisplayPlugin::internalPresent() { if (_enablePreview) { float windowAspect = aspect(windowSize); float sceneAspect = aspect(_renderTargetSize); - if (_enablePreview && _monoPreview) { + if (_monoPreview) { sceneAspect /= 2.0f; } float aspectRatio = sceneAspect / windowAspect; @@ -419,9 +433,9 @@ void HmdDisplayPlugin::internalPresent() { }); swapBuffers(); } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { - if (_previewTexture) qDebug() << _previewTexture->getBytesReceived(); - if (PREVIEW_TEXTURE_LOCATION != -1 && _previewTextureID != 0) { + if (_previewTextureID != 0) { useProgram(_previewProgram); + windowSize *= devicePixelRatio; glViewport(0, 0, windowSize.x, windowSize.y); glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); glActiveTexture(GL_TEXTURE0); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index ad2fc3509f..3be05ffbe6 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -14,8 +14,7 @@ #include "../OpenGLDisplayPlugin.h" -class NetworkTexture; -using NetworkTexturePointer = QSharedPointer; +class QNetworkReply; class HmdDisplayPlugin : public OpenGLDisplayPlugin { using Parent = OpenGLDisplayPlugin; @@ -89,6 +88,9 @@ protected: FrameInfo _currentPresentFrameInfo; FrameInfo _currentRenderFrameInfo; +public slots: + void downloadFinished(QNetworkReply* reply); + private: bool _enablePreview { false }; bool _monoPreview { true }; @@ -96,7 +98,6 @@ private: bool _firstPreview { true }; ProgramPtr _previewProgram; GLuint _previewTextureID { 0 }; - NetworkTexturePointer _previewTexture { nullptr }; glm::uvec2 _prevWindowSize { 0, 0 }; qreal _prevDevicePixelRatio { 0 }; ShapeWrapperPtr _sphereSection; From 55c5d5364029c3c24712f096d9d802490760f0f3 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 28 Jun 2016 16:58:42 -0700 Subject: [PATCH 05/41] fixed signal --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 35 ++++++++++--------- .../display-plugins/hmd/HmdDisplayPlugin.h | 3 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6ab6c71211..8ff527b023 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -70,14 +70,16 @@ bool HmdDisplayPlugin::internalActivate() { QNetworkAccessManager& manager = NetworkAccessManager::getInstance(); QNetworkRequest request(previewURL); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); -// connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); - manager.get(request); + auto rep = manager.get(request); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } return Parent::internalActivate(); } -void HmdDisplayPlugin::downloadFinished(QNetworkReply* reply) { +void HmdDisplayPlugin::downloadFinished() { + QNetworkReply* reply = static_cast(sender()); + if (reply->error() != QNetworkReply::NetworkError::NoError) { qDebug() << "HMDDisplayPlugin: error downloading preview image" << reply->errorString(); return; @@ -432,20 +434,19 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); - } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { - if (_previewTextureID != 0) { - useProgram(_previewProgram); - windowSize *= devicePixelRatio; - glViewport(0, 0, windowSize.x, windowSize.y); - glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, _previewTextureID); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - swapBuffers(); - _firstPreview = false; - _prevWindowSize = windowSize; - _prevDevicePixelRatio = devicePixelRatio; - } + } else if (_previewTextureID != 0 && (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio)) { + useProgram(_previewProgram); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, windowSize.x * devicePixelRatio, windowSize.y * devicePixelRatio); + glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + swapBuffers(); + _firstPreview = false; + _prevWindowSize = windowSize; + _prevDevicePixelRatio = devicePixelRatio; } postPreview(); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 3be05ffbe6..374b41727b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -17,6 +17,7 @@ class QNetworkReply; class HmdDisplayPlugin : public OpenGLDisplayPlugin { + Q_OBJECT using Parent = OpenGLDisplayPlugin; public: bool isHmd() const override final { return true; } @@ -89,7 +90,7 @@ protected: FrameInfo _currentRenderFrameInfo; public slots: - void downloadFinished(QNetworkReply* reply); + void downloadFinished(); private: bool _enablePreview { false }; From a741ae5069d8fae41a4823d821236f2f30015a8c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 28 Jun 2016 17:43:15 -0700 Subject: [PATCH 06/41] moved _firstPreview after download --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 8ff527b023..8f8132926f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -64,7 +64,6 @@ bool HmdDisplayPlugin::internalActivate() { _eyeInverseProjections[eye] = glm::inverse(_eyeProjections[eye]); }); - _firstPreview = true; if (_previewTextureID == 0) { const QUrl previewURL("https://hifi-content.s3.amazonaws.com/samuel/preview.png"); QNetworkAccessManager& manager = NetworkAccessManager::getInstance(); @@ -98,6 +97,7 @@ void HmdDisplayPlugin::downloadFinished() { Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); glBindTexture(GL_TEXTURE_2D, 0); + _firstPreview = true; } } From af2a0ed924865686546f24f8e4da84aa5055eeac Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 29 Jun 2016 11:02:43 -0700 Subject: [PATCH 07/41] preserve preview aspect ratio, fix device pixel ratio scaling --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 48 ++++++++++--------- .../display-plugins/hmd/HmdDisplayPlugin.h | 1 + 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 8f8132926f..cbd0026d54 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -97,6 +97,7 @@ void HmdDisplayPlugin::downloadFinished() { Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); glBindTexture(GL_TEXTURE_2D, 0); + _previewAspect = ((float)previewTexture.width())/((float)previewTexture.height()); _firstPreview = true; } } @@ -396,30 +397,31 @@ void HmdDisplayPlugin::internalPresent() { // screen preview mirroring auto window = _container->getPrimaryWidget(); - auto windowSize = toGlm(window->size()); auto devicePixelRatio = window->devicePixelRatio(); + auto windowSize = toGlm(window->size()); + windowSize *= devicePixelRatio; + float windowAspect = aspect(windowSize); + float sceneAspect = _enablePreview ? aspect(_renderTargetSize) : _previewAspect; + if (_enablePreview && _monoPreview) { + sceneAspect /= 2.0f; + } + float aspectRatio = sceneAspect / windowAspect; + + uvec2 targetViewportSize = windowSize; + if (aspectRatio < 1.0f) { + targetViewportSize.x *= aspectRatio; + } else { + targetViewportSize.y /= aspectRatio; + } + + uvec2 targetViewportPosition; + if (targetViewportSize.x < windowSize.x) { + targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; + } else if (targetViewportSize.y < windowSize.y) { + targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; + } + if (_enablePreview) { - float windowAspect = aspect(windowSize); - float sceneAspect = aspect(_renderTargetSize); - if (_monoPreview) { - sceneAspect /= 2.0f; - } - float aspectRatio = sceneAspect / windowAspect; - - uvec2 targetViewportSize = windowSize; - if (aspectRatio < 1.0f) { - targetViewportSize.x *= aspectRatio; - } else { - targetViewportSize.y /= aspectRatio; - } - - uvec2 targetViewportPosition; - if (targetViewportSize.x < windowSize.x) { - targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; - } else if (targetViewportSize.y < windowSize.y) { - targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; - } - using namespace oglplus; Context::Clear().ColorBuffer(); auto sourceSize = _compositeFramebuffer->size; @@ -438,7 +440,7 @@ void HmdDisplayPlugin::internalPresent() { useProgram(_previewProgram); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, windowSize.x * devicePixelRatio, windowSize.y * devicePixelRatio); + glViewport(targetViewportPosition.x, targetViewportPosition.y, targetViewportSize.x, targetViewportSize.y); glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _previewTextureID); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 374b41727b..dfab3e8d84 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -101,6 +101,7 @@ private: GLuint _previewTextureID { 0 }; glm::uvec2 _prevWindowSize { 0, 0 }; qreal _prevDevicePixelRatio { 0 }; + float _previewAspect { 0 }; ShapeWrapperPtr _sphereSection; ProgramPtr _reprojectionProgram; ProgramPtr _laserProgram; From 15d09bb4fa4aecc8c8ed1b92bc2f1fb6064167fe Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 29 Jun 2016 13:33:33 -0700 Subject: [PATCH 08/41] cleanup, fixed texture corruption bug --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 32 +++++++++---------- .../display-plugins/hmd/HmdDisplayPlugin.h | 10 +++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index cbd0026d54..ca6a03f93b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -84,20 +84,10 @@ void HmdDisplayPlugin::downloadFinished() { return; } - QImage previewTexture; - previewTexture.loadFromData(reply->readAll()); + _previewTexture.loadFromData(reply->readAll()); - if (!previewTexture.isNull()) { - previewTexture = previewTexture.mirrored(false, true); - glGenTextures(1, &_previewTextureID); - glBindTexture(GL_TEXTURE_2D, _previewTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, previewTexture.width(), previewTexture.height(), 0, - GL_BGRA, GL_UNSIGNED_BYTE, previewTexture.bits()); - using namespace oglplus; - Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); - Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); - glBindTexture(GL_TEXTURE_2D, 0); - _previewAspect = ((float)previewTexture.width())/((float)previewTexture.height()); + if (!_previewTexture.isNull()) { + _previewAspect = ((float)_previewTexture.width())/((float)_previewTexture.height()); _firstPreview = true; } } @@ -286,7 +276,7 @@ void HmdDisplayPlugin::customizeContext() { using namespace oglplus; if (!_enablePreview) { - std::string version("#version 410 core\n"); + const std::string version("#version 410 core\n"); compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); } @@ -436,7 +426,18 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); - } else if (_previewTextureID != 0 && (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio)) { + } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { + if (_firstPreview) { + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _previewTexture.width(), _previewTexture.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, _previewTexture.mirrored(false, true).bits()); + using namespace oglplus; + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + _firstPreview = false; + } useProgram(_previewProgram); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -446,7 +447,6 @@ void HmdDisplayPlugin::internalPresent() { glBindTexture(GL_TEXTURE_2D, _previewTextureID); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); swapBuffers(); - _firstPreview = false; _prevWindowSize = windowSize; _prevDevicePixelRatio = devicePixelRatio; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index dfab3e8d84..61b352b17b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -14,8 +14,6 @@ #include "../OpenGLDisplayPlugin.h" -class QNetworkReply; - class HmdDisplayPlugin : public OpenGLDisplayPlugin { Q_OBJECT using Parent = OpenGLDisplayPlugin; @@ -97,13 +95,17 @@ private: bool _monoPreview { true }; bool _enableReprojection { true }; bool _firstPreview { true }; + ProgramPtr _previewProgram; + QImage _previewTexture; + float _previewAspect { 0 }; GLuint _previewTextureID { 0 }; glm::uvec2 _prevWindowSize { 0, 0 }; qreal _prevDevicePixelRatio { 0 }; - float _previewAspect { 0 }; - ShapeWrapperPtr _sphereSection; + ProgramPtr _reprojectionProgram; + ShapeWrapperPtr _sphereSection; + ProgramPtr _laserProgram; ShapeWrapperPtr _laserGeometry; }; From 057fc8adcecfc059ebfc9898da3cc9b174e4c1aa Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 30 Jun 2016 10:25:49 -0700 Subject: [PATCH 09/41] endable blending --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index ca6a03f93b..f1f300c72b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -439,6 +439,8 @@ void HmdDisplayPlugin::internalPresent() { _firstPreview = false; } useProgram(_previewProgram); + glEnable (GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(targetViewportPosition.x, targetViewportPosition.y, targetViewportSize.x, targetViewportSize.y); From b9f9b180838a94d86c821b8d0bd6fb00b1e2ba25 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 30 Jun 2016 15:41:58 -0700 Subject: [PATCH 10/41] if an edit packet doesn't include a change to lifetime, don't attempt to cap the lifetime --- libraries/entities/src/EntityTree.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 21e5865c09..ef0401ceaf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -904,7 +904,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endDecode = usecTimestampNow(); const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec - if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { + if ((message.getType() == PacketType::EntityAdd || + (message.getType() == PacketType::EntityEdit && properties.lifetimeChanged())) && + !senderNode->getCanRez() && senderNode->getCanRezTmp()) { // this node is only allowed to rez temporary entities. if need be, cap the lifetime. if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { From d6250d4c5af23014da55b30a54380bce448de65a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 4 Jul 2016 21:19:14 -0700 Subject: [PATCH 11/41] Fix export entities by region --- interface/src/Application.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1c9ec94dc4..82cbaf93c2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3049,17 +3049,20 @@ bool Application::exportEntities(const QString& filename, const QVector entities; QVector ids; auto entityTree = getEntities()->getTree(); entityTree->withReadLock([&] { - entityTree->findEntities(AACube(offset, scale), entities); + entityTree->findEntities(boundingCube, entities); foreach(EntityItemPointer entity, entities) { ids << entity->getEntityItemID(); } }); - return exportEntities(filename, ids, &offset); + return exportEntities(filename, ids, ¢er); } void Application::loadSettings() { From b8e6572ebfe490cd587922b2a7617b2575915f4c Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 5 Jul 2016 17:00:54 -0700 Subject: [PATCH 12/41] basic hand-controller editing --- .../system/controllers/handControllerGrab.js | 31 +++-- scripts/system/edit.js | 8 ++ .../system/libraries/entitySelectionTool.js | 115 +++++++++++++----- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ecece8c4f7..dc6b78de8e 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -221,6 +221,14 @@ function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } +function findRayIntersection(pickRay, precise, include, exclude) { + var entities = Entities.findRayIntersection(pickRay, precise, include, exclude); + var overlays = Overlays.findRayIntersection(pickRay); + if (!overlays.intersects || (entities.distance <= overlays.distance)) { + return entities; + } + return overlays; +} function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -251,6 +259,10 @@ function propsArePhysical(props) { // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; +var EDIT_SETTING = "io.highfidelity.isEditting"; +function isEditing() { + return EXTERNALLY_MANAGED_2D_MINOR_MODE && Settings.getValue(EDIT_SETTING); +} function isIn2DMode() { // In this version, we make our own determination of whether we're aimed a HUD element, // because other scripts (such as handControllerPointer) might be using some other visualization @@ -1069,20 +1081,15 @@ function MyController(hand) { var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); + intersection = findRayIntersection(pickRayBacked, true, [], blacklist); } else { - intersection = Entities.findRayIntersection(pickRayBacked, true); - } - - var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); - if (!intersection.intersects || - (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { - intersection = overlayIntersection; + intersection = findRayIntersection(pickRayBacked, true); } if (intersection.intersects) { return { entityID: intersection.entityID, + overlayID: intersection.overlayID, searchRay: pickRay, distance: Vec3.distance(pickRay.origin, intersection.intersection) }; @@ -1326,6 +1333,8 @@ function MyController(hand) { if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { grabbableEntities.push(rayPickInfo.entityID); } + } else if (rayPickInfo.overlayID) { + this.intersectionDistance = rayPickInfo.distance; } else { this.intersectionDistance = 0; } @@ -1382,7 +1391,7 @@ function MyController(hand) { // TODO: highlight the far-triggerable object? } } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { - if (this.triggerSmoothedGrab()) { + if (this.triggerSmoothedGrab() && !isEditing()) { this.grabbedEntity = entity; this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; @@ -1942,8 +1951,8 @@ function MyController(hand) { var now = Date.now(); if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - if (intersection.accurate) { + var intersection = findRayIntersection(pickRay, true); + if (intersection.accurate || intersection.overlayID) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { this.callEntityMethodOnGrabbed("stopFarTrigger"); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 9d5585e353..f746fa2885 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -332,6 +332,8 @@ var toolBar = (function() { entityListTool.clearEntityList(); }; + var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + Settings.setValue(EDIT_SETTING, false); that.setActive = function(active) { if (active != isActive) { if (active && !Entities.canRez() && !Entities.canRezTmp()) { @@ -341,6 +343,7 @@ var toolBar = (function() { enabled: active })); isActive = active; + Settings.setValue(EDIT_SETTING, active); if (!isActive) { entityListTool.setVisible(false); gridTool.setVisible(false); @@ -348,6 +351,7 @@ var toolBar = (function() { propertiesTool.setVisible(false); selectionManager.clearSelections(); cameraManager.disable(); + selectionDisplay.triggerMapping.disable(); } else { UserActivityLogger.enabledEdit(); hasShownPropertiesTool = false; @@ -356,6 +360,7 @@ var toolBar = (function() { grid.setEnabled(true); propertiesTool.setVisible(true); // Not sure what the following was meant to accomplish, but it currently causes + selectionDisplay.triggerMapping.enable(); // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); } @@ -1438,6 +1443,9 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) { var entityID = SelectionManager.selections[i]; var initialProperties = SelectionManager.savedProperties[entityID]; var currentProperties = Entities.getEntityProperties(entityID); + if (!initialProperties) { + continue; + } undoData.setProperties.push({ entityID: entityID, properties: { diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index e46306ca31..2003df3652 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1005,6 +1005,56 @@ SelectionDisplay = (function() { var activeTool = null; var grabberTools = {}; + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we dont' get mousePressEvents. + that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + Script.scriptEnding.connect(that.triggerMapping.disable); + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_ON_VALUE = 0.4; + that.TRIGGER_OFF_VALUE = 0.15; + that.triggered = false; + var activeHand = Controller.Standard.RightHand; + function makeTriggerHandler(hand) { + return function (value) { + if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth? + that.triggered = true; + if (activeHand !== hand) { + // No switching while the other is already triggered, so no need to release. + activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + } + var eventResult = that.mousePressEvent({}); + if (!eventResult || (eventResult === 'selectionBox')) { + var pickRay = controllerComputePickRay(); + if (pickRay) { + var entityIntersection = Entities.findRayIntersection(pickRay, true); + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (entityIntersection.intersects && + (!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) { + selectionManager.setSelections([entityIntersection.entityID]); + } + } + } + } else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) { + that.triggered = false; + that.mouseReleaseEvent({}); + } + }; + } + that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); + that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + function controllerComputePickRay() { + var controllerPose = Controller.getPoseValue(activeHand); + if (controllerPose.valid && that.triggered) { + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } function addGrabberTool(overlay, tool) { grabberTools[overlay] = { mode: tool.mode, @@ -1047,7 +1097,7 @@ SelectionDisplay = (function() { lastCameraOrientation = Camera.getOrientation(); if (event !== false) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var wantDebug = false; if (wantDebug) { @@ -2269,7 +2319,7 @@ SelectionDisplay = (function() { startPosition = SelectionManager.worldPosition; var dimensions = SelectionManager.worldDimensions; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, @@ -2312,7 +2362,7 @@ SelectionDisplay = (function() { }, onMove: function(event) { var wantDebug = false; - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, @@ -2422,6 +2472,9 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + if (!properties) { + continue; + } var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, @@ -2448,7 +2501,7 @@ SelectionDisplay = (function() { addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); upDownPickNormal = Quat.getFront(lastCameraOrientation); // Remove y component so the y-axis lies along the plane we picking on - this will @@ -2481,7 +2534,7 @@ SelectionDisplay = (function() { pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); @@ -2690,7 +2743,7 @@ SelectionDisplay = (function() { } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2726,7 +2779,7 @@ SelectionDisplay = (function() { rotation = SelectionManager.worldRotation; } - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2782,10 +2835,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - Vec3.print(" newIntersection:", newIntersection); + //Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" oldPOS:", oldPOS); - Vec3.print(" newPOS:", newPOS); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -3350,7 +3403,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3520,7 +3573,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3682,7 +3735,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3797,13 +3850,13 @@ SelectionDisplay = (function() { that.mousePressEvent = function(event) { var wantDebug = false; - if (!event.isLeftButton) { + if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; } var somethingClicked = false; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); // before we do a ray test for grabbers, disable the ray intersection for our selection box Overlays.editOverlay(selectionBox, { @@ -3837,7 +3890,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3845,7 +3898,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; - somethingClicked = true; + somethingClicked = mode; // in translate mode, we hide our stretch handles... for (var i = 0; i < stretchHandles.length; i++) { @@ -3860,34 +3913,34 @@ SelectionDisplay = (function() { case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_NEAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberFAR: case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_FAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberTOP: mode = "STRETCH_TOP"; - somethingClicked = true; + somethingClicked = mode; break; case grabberBOTTOM: mode = "STRETCH_BOTTOM"; - somethingClicked = true; + somethingClicked = mode; break; case grabberRIGHT: case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? mode = "STRETCH_RIGHT"; - somethingClicked = true; + somethingClicked = mode; break; case grabberLEFT: case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? mode = "STRETCH_LEFT"; - somethingClicked = true; + somethingClicked = mode; break; default: @@ -3955,7 +4008,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3963,7 +4016,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case yawHandle: mode = "ROTATE_YAW"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = yawHandleRotation; overlayCenter = yawCenter; yawZero = result.intersection; @@ -3973,7 +4026,7 @@ SelectionDisplay = (function() { case pitchHandle: mode = "ROTATE_PITCH"; initialPosition = SelectionManager.worldPosition; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = pitchHandleRotation; overlayCenter = pitchCenter; pitchZero = result.intersection; @@ -3982,7 +4035,7 @@ SelectionDisplay = (function() { case rollHandle: mode = "ROTATE_ROLL"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = rollHandleRotation; overlayCenter = rollCenter; rollZero = result.intersection; @@ -4156,7 +4209,7 @@ SelectionDisplay = (function() { mode = translateXZTool.mode; activeTool.onBegin(event); - somethingClicked = true; + somethingClicked = 'selectionBox'; break; default: if (wantDebug) { @@ -4169,7 +4222,7 @@ SelectionDisplay = (function() { } if (somethingClicked) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); if (wantDebug) { print("mousePressEvent()...... " + overlayNames[result.overlayID]); } @@ -4201,7 +4254,7 @@ SelectionDisplay = (function() { } // if no tool is active, then just look for handles to highlight... - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var result = Overlays.findRayIntersection(pickRay); var pickedColor; var pickedAlpha; @@ -4320,7 +4373,7 @@ SelectionDisplay = (function() { that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 2; + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; var dimensions = SelectionManager.worldDimensions; var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; grabberSize = Math.min(grabberSize, avgDimension / 10); From 431043e1957e78ccce079e78fe8945e9f3fb3886 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 10:50:51 -0700 Subject: [PATCH 13/41] declare new shape types --- libraries/entities/src/EntityItemProperties.cpp | 4 ++++ libraries/shared/src/ShapeInfo.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a62f4b182a..dcd7e25bc1 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -101,6 +101,8 @@ const char* shapeTypeNames[] = { "hull", "plane", "compound", + "simple-hull", + "simple-compound", "static-mesh" }; @@ -123,6 +125,8 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_HULL); addShapeType(SHAPE_TYPE_PLANE); addShapeType(SHAPE_TYPE_COMPOUND); + addShapeType(SHAPE_TYPE_SIMPLE_HULL); + addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); addShapeType(SHAPE_TYPE_STATIC_MESH); } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 96132a4b23..7bf145412a 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -39,6 +39,8 @@ enum ShapeType { SHAPE_TYPE_HULL, SHAPE_TYPE_PLANE, SHAPE_TYPE_COMPOUND, + SHAPE_TYPE_SIMPLE_HULL, + SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH }; From 33d732c446771cc7a9cf575926e324d715075581 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 10:51:15 -0700 Subject: [PATCH 14/41] construct geometry for new shape types --- .../src/RenderableModelEntityItem.cpp | 144 ++++++++++-------- 1 file changed, 83 insertions(+), 61 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 43fea75eac..e1c944c8dd 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -423,7 +423,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene - bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0 + && getShapeType() == SHAPE_TYPE_COMPOUND; if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { _showCollisionHull = shouldShowCollisionHull; render::PendingChanges pendingChanges; @@ -691,7 +692,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } info.setParams(type, dimensions, _compoundShapeURL); - } else if (type == SHAPE_TYPE_STATIC_MESH) { + assert(pointCollection.size() > 0); // adebug + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + updateModelBounds(); + + // should never fall in here when model not fully loaded + assert(_model->isLoaded()); + // compute meshPart local transforms QVector localTransforms; const FBXGeometry& geometry = _model->getFBXGeometry(); @@ -716,30 +723,42 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { return; } - updateModelBounds(); - - // should never fall in here when collision model not fully loaded - assert(_model->isLoaded()); + auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); + int32_t numMeshes = (int32_t)(meshes.size()); ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); pointCollection.clear(); - - ShapeInfo::PointList points; - ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); - auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } Extents extents; int meshCount = 0; + int pointListIndex = 0; for (auto& mesh : meshes) { const gpu::BufferView& vertices = mesh->getVertexBuffer(); const gpu::BufferView& indices = mesh->getIndexBuffer(); const gpu::BufferView& parts = mesh->getPartBuffer(); + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.getNumElements()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)((gpu::Size)points.size()); + } + points.reserve(sizeToReserve); + // copy points - const glm::mat4& localTransform = localTransforms[meshCount]; uint32_t meshIndexOffset = (uint32_t)points.size(); + const glm::mat4& localTransform = localTransforms[meshCount]; gpu::BufferView::Iterator vertexItr = vertices.cbegin(); - points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements())); while (vertexItr != vertices.cend()) { glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr)); points.push_back(point); @@ -747,55 +766,57 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ++vertexItr; } - // copy triangleIndices - triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); - gpu::BufferView::Iterator partItr = parts.cbegin(); - while (partItr != parts.cend()) { - - if (partItr->_topology == model::Mesh::TRIANGLES) { - assert(partItr->_numIndices % 3 == 0); - auto indexItr = indices.cbegin() + partItr->_startIndex; - auto indexEnd = indexItr + partItr->_numIndices; - while (indexItr != indexEnd) { - triangleIndices.push_back(*indexItr + meshIndexOffset); - ++indexItr; - } - } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { - assert(partItr->_numIndices > 2); - uint32_t approxNumIndices = 3 * partItr->_numIndices; - if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { - // we underestimated the final size of triangleIndices so we pre-emptively expand it - triangleIndices.reserve(triangleIndices.size() + approxNumIndices); - } - - auto indexItr = indices.cbegin() + partItr->_startIndex; - auto indexEnd = indexItr + (partItr->_numIndices - 2); - - // first triangle uses the first three indices - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - triangleIndices.push_back(*(indexItr++) + meshIndexOffset); - - // the rest use previous and next index - uint32_t triangleCount = 1; - while (indexItr != indexEnd) { - if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { - if (triangleCount % 2 == 0) { - // even triangles use first two indices in order - triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); - triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); - } else { - // odd triangles swap order of first two indices - triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); - triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); - } + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + if (partItr->_topology == model::Mesh::TRIANGLES) { + assert(partItr->_numIndices % 3 == 0); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + partItr->_numIndices; + while (indexItr != indexEnd) { triangleIndices.push_back(*indexItr + meshIndexOffset); - ++triangleCount; + ++indexItr; + } + } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { + assert(partItr->_numIndices > 2); + uint32_t approxNumIndices = 3 * partItr->_numIndices; + if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) { + // we underestimated the final size of triangleIndices so we pre-emptively expand it + triangleIndices.reserve(triangleIndices.size() + approxNumIndices); + } + + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (partItr->_numIndices - 2); + + // first triangle uses the first three indices + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + triangleIndices.push_back(*(indexItr++) + meshIndexOffset); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + } else { + // odd triangles swap order of first two indices + triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset); + triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset); + } + triangleIndices.push_back(*indexItr + meshIndexOffset); + ++triangleCount; + } + ++indexItr; } - ++indexItr; } + ++partItr; } - ++partItr; } ++meshCount; } @@ -808,12 +829,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { scaleToFit[i] = 1.0f; } } - for (int i = 0; i < points.size(); ++i) { - points[i] = (points[i] * scaleToFit); + for (auto points : pointCollection) { + for (int i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); + } } - pointCollection.push_back(points); - info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL); + info.setParams(type, 0.5f * dimensions, _modelURL); } else { ModelEntityItem::computeShapeInfo(info); info.setParams(type, 0.5f * dimensions); From 97fc5ac3b8aab8a5b21f9762e11d69c3e141b86c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 10:51:28 -0700 Subject: [PATCH 15/41] construct Bullet shapes for new shape types --- libraries/physics/src/ShapeFactory.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 3afc170a8c..f71711eccd 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -204,7 +204,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { if (numIndices < INT16_MAX) { int16_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); for (int32_t i = 0; i < numIndices; ++i) { - indices[i] = triangleIndices[i]; + indices[i] = (int16_t)triangleIndices[i]; } } else { int32_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); @@ -257,7 +257,9 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { shape = new btCapsuleShape(radius, height); } break; - case SHAPE_TYPE_COMPOUND: { + case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_SIMPLE_COMPOUND: { const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { From 76cb80112e9672f2619eedfd6a36a578d658f5c6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 10:51:43 -0700 Subject: [PATCH 16/41] support new shape types in edit.js --- scripts/system/html/entityProperties.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 121e38c340..54c79b1d9f 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1646,6 +1646,8 @@ + + From 92af84920420a03b5591a2d6786e01e40aa0175c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 10:52:01 -0700 Subject: [PATCH 17/41] bump version for new shape types --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c74b10820d..6359ad0aff 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH; + return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e484a06502..9140cf8738 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -181,6 +181,7 @@ const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; +const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, From 6cbc566a771ad3fd8b9c1fdb8a0c0617fe188f73 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 11:53:26 -0700 Subject: [PATCH 18/41] remove debug cruft --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e1c944c8dd..840eace27e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -692,7 +692,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } info.setParams(type, dimensions, _compoundShapeURL); - assert(pointCollection.size() > 0); // adebug } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { updateModelBounds(); From 7d803b7513d4b177d92177e1e52eae7d3e914698 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 6 Jul 2016 11:54:39 -0700 Subject: [PATCH 19/41] Fix model overlays not correctly scaling --- interface/src/ui/overlays/ModelOverlay.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index adf08934f0..6b81ad7ff3 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -45,7 +45,7 @@ void ModelOverlay::update(float deltatime) { _updateModel = false; _model->setSnapModelToCenter(true); - _model->setScale(getDimensions()); + _model->setScaleToFit(true, getDimensions()); _model->setRotation(getRotation()); _model->setTranslation(getPosition()); _model->setURL(_url); @@ -100,7 +100,6 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { setDimensions(scale); } else { - _model->setScaleToFit(true, getDimensions()); _updateModel = true; } } From 5659d57ac3f6271bfae79cf395c9f815ff229ba5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 13:00:57 -0700 Subject: [PATCH 20/41] fix shape generation for SIMPLE_COMPOUND --- .../src/RenderableModelEntityItem.cpp | 10 +++++----- libraries/shared/src/ShapeInfo.cpp | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 840eace27e..7eb7d87566 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -608,7 +608,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // should never fall in here when collision model not fully loaded // hence we assert that all geometries exist and are loaded - assert(_model->isLoaded() && _model->isCollisionLoaded()); + assert(_model && _model->isLoaded() && _model->isCollisionLoaded()); const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); @@ -696,15 +696,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { updateModelBounds(); // should never fall in here when model not fully loaded - assert(_model->isLoaded()); + assert(_model && _model->isLoaded()); // compute meshPart local transforms QVector localTransforms; - const FBXGeometry& geometry = _model->getFBXGeometry(); - int numberOfMeshes = geometry.meshes.size(); + const FBXGeometry& fbxGeometry = _model->getFBXGeometry(); + int numberOfMeshes = fbxGeometry.meshes.size(); int totalNumVertices = 0; for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& mesh = geometry.meshes.at(i); + const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.clusters.size() > 0) { const FBXCluster& cluster = mesh.clusters.at(0); auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index e0f4cc18b2..424c2bfa22 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -83,12 +83,19 @@ void ShapeInfo::setOffset(const glm::vec3& offset) { } uint32_t ShapeInfo::getNumSubShapes() const { - if (_type == SHAPE_TYPE_NONE) { - return 0; - } else if (_type == SHAPE_TYPE_COMPOUND) { - return _pointCollection.size(); + switch (_type) { + case SHAPE_TYPE_NONE: + return 0; + case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_SIMPLE_COMPOUND: + return _pointCollection.size(); + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_STATIC_MESH: + assert(_pointCollection.size() == 1); + // yes fall through to default + default: + return 1; } - return 1; } int ShapeInfo::getLargestSubshapePointCount() const { From 8ef9f48221fae9a8978882140319d15e722c73b7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 6 Jul 2016 13:42:46 -0700 Subject: [PATCH 21/41] An objective test of basic performance on struggling machines. --- scripts/developer/tests/loadedMachine.js | 163 +++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 scripts/developer/tests/loadedMachine.js diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js new file mode 100644 index 0000000000..c222eb7379 --- /dev/null +++ b/scripts/developer/tests/loadedMachine.js @@ -0,0 +1,163 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*/ +// +// loadedMachine.js +// scripts/developer/tests/ +// +// Created by Howard Stearns on 6/6/16. +// Copyright 2016 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 +// +// Characterises the performance of heavily loaded or struggling machines. +// There is no point in running this on a machine that producing the target 60 or 90 hz rendering rate. +// +// The script examines several source of load, including each running script and a couple of Entity types. +// It turns all of these off, as well as LOD management, and twirls in place to get a baseline render rate. +// Then it turns each load on, one at a time, and records a new render rate. +// At the end, it reports the ordered results (in a popup), restores the original loads and LOD management, and quits. +// +// Small performance changes are not meaningful. +// If you think you find something that is significant, you should probably run again to make sure that it isn't random variation. +// You can also compare (e.g., the baseline) for different conditions such as domain, version, server settings, etc. +// This does not profile scripts as they are used -- it merely measures the effect of having the script loaded and running quietly. + +var NUMBER_OF_TWIRLS_PER_LOAD = 10; +var MILLISECONDS_PER_SECOND = 1000; +var WAIT_TIME_MILLISECONDS = MILLISECONDS_PER_SECOND; +var accumulatedRotation = 0; +var start; +var frames = 0; +var config = Render.getConfig("Stats"); +var thisPath = Script.resolvePath(''); +var scriptData = ScriptDiscoveryService.getRunning(); +var scripts = scriptData.filter(function (datum) { return datum.name !== 'defaultScripts.js'; }).map(function (script) { return script.path; }); +// If defaultScripts.js is running, we leave it running, because restarting it safely is a mess. +var otherScripts = scripts.filter(function (path) { return path !== thisPath; }); +var numberLeftRunning = scriptData.length - otherScripts.length; +print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping', otherScripts); +var typedEntities = {Light: [], ParticleEffect: []}; +var interestingTypes = Object.keys(typedEntities); +var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes); +var loadIndex = 0, nLoads = loads.length, load; +var results = []; +var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust(); +var SEARCH_RADIUS = 17000; +var DEFAULT_LOD = 32768 * 400; +LODManager.setAutomaticLODAdjust(false); +LODManager.setOctreeSizeScale(DEFAULT_LOD); + +// Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient. +var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); +print('Searching', allEntities.length, 'entities for', interestingTypes); +allEntities.forEach(function (entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'visible']); + if (properties.visible && (interestingTypes.indexOf(properties.type) >= 0)) { + typedEntities[properties.type].push(entityID); + } +}); +interestingTypes.forEach(function (type) { + print('There are', typedEntities[type].length, type, 'entities.'); +}); +function toggleVisibility(type, on) { + typedEntities[type].forEach(function (entityID) { + Entities.editEntity(entityID, {visible: on}); + }); +} +function restoreOneTest(load) { + print('restore', load); + switch (load) { + case 'baseline': + case 'ignore': // ignore is used to do a twirl to make sure everything is loaded. + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, true); + break; + default: + Script.load(load); + } +} +function undoOneTest(load) { + print('stop', load); + switch (load) { + case 'baseline': + case 'ignore': + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, false); + break; + default: + ScriptDiscoveryService.stopScript(load); + } +} +loads.forEach(undoOneTest); + +function finalReport() { + var baseline = results[0]; + results.forEach(function (result) { + result.ratio = (baseline.fps / result.fps); + }); + results.sort(function (a, b) { return b.ratio - a.ratio; }); + var report = 'Performance Report:\nBaseline: ' + baseline.fps.toFixed(1) + ' fps.'; + results.forEach(function (result) { + report += '\n' + result.ratio.toFixed(2) + ' (' + result.fps.toFixed(1) + ' fps over ' + result.elapsed + ' seconds) for ' + result.name; + }); + Window.alert(report); + LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); + loads.forEach(restoreOneTest); + Script.stop(); +} + +function onNewStats() { // Accumulates frames on signal during load test + frames++; +} +var DEGREES_PER_FULL_TWIRL = 360; +var DEGREES_PER_UPDATE = 6; +function onUpdate() { // Spins on update signal during load test + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation, 0); + accumulatedRotation += DEGREES_PER_UPDATE; + if (accumulatedRotation >= (DEGREES_PER_FULL_TWIRL * NUMBER_OF_TWIRLS_PER_LOAD)) { + tearDown(); + } +} +function startTest() { + print('start', load); + accumulatedRotation = frames = 0; + start = Date.now(); + Script.update.connect(onUpdate); + config.newStats.connect(onNewStats); +} + +function setup() { + if (loadIndex >= nLoads) { + return finalReport(); + } + load = loads[loadIndex]; + restoreOneTest(load); + Script.setTimeout(startTest, WAIT_TIME_MILLISECONDS); +} +function tearDown() { + var now = Date.now(); + var elapsed = (now - start) / MILLISECONDS_PER_SECOND; + Script.update.disconnect(onUpdate); + config.newStats.disconnect(onNewStats); + if (load !== 'ignore') { + results.push({name: load, fps: frames / elapsed, elapsed: elapsed}); + } + undoOneTest(load); + loadIndex++; + setup(); +} +function waitUntilStopped() { // Wait until all the scripts have stopped + var count = ScriptDiscoveryService.getRunning().length; + if (count === numberLeftRunning) { + return setup(); + } + print('Still', count, 'scripts running'); + Script.setTimeout(waitUntilStopped, WAIT_TIME_MILLISECONDS); +} +waitUntilStopped(); From 11ef2456713e869f462b24100d56179fba06111f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 14:28:09 -0700 Subject: [PATCH 22/41] temp hack for debugging --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7eb7d87566..9a37a8f7b7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -603,6 +603,12 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType type = getShapeType(); glm::vec3 dimensions = getDimensions(); + + // BEGIN temp HACK + int numSubMeshes = _model->getFBXGeometry().meshes.size(); + qDebug() << "HACK ModeEntity" << getName() << "numSubMeshes =" << numSubMeshes; + // END temp HACK + if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); From 301a9f4434e8b2b73af2d5d3af8aba4a343942bc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 14:31:43 -0700 Subject: [PATCH 23/41] improved hack logging --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9a37a8f7b7..87e89aa9dc 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -606,7 +606,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // BEGIN temp HACK int numSubMeshes = _model->getFBXGeometry().meshes.size(); - qDebug() << "HACK ModeEntity" << getName() << "numSubMeshes =" << numSubMeshes; + if (numSubMeshes > 1) { + qDebug() << "HACK entity name =" << getName() << " modelURL =" << " pos =" << getPosition() << " numSubMeshes =" << numSubMeshes; + } // END temp HACK if (type == SHAPE_TYPE_COMPOUND) { From 15ff7bc7319307b6cabbca3b48ec16ff3a4886c7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 14:35:57 -0700 Subject: [PATCH 24/41] final hack tweak --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 87e89aa9dc..827b1b895b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -607,7 +607,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // BEGIN temp HACK int numSubMeshes = _model->getFBXGeometry().meshes.size(); if (numSubMeshes > 1) { - qDebug() << "HACK entity name =" << getName() << " modelURL =" << " pos =" << getPosition() << " numSubMeshes =" << numSubMeshes; + qDebug() << "HACK entity name =" << getName() << " modelURL =" << getModelURL() << " pos =" << getPosition() << " numSubMeshes = " << numSubMeshes; } // END temp HACK From 110f5e9e2adaab0353cc8f3df3ecb01b5aacfe78 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 6 Jul 2016 15:08:57 -0700 Subject: [PATCH 25/41] Don't compare distance when no intersection. --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index dc6b78de8e..373f203e80 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -224,7 +224,7 @@ function entityHasActions(entityID) { function findRayIntersection(pickRay, precise, include, exclude) { var entities = Entities.findRayIntersection(pickRay, precise, include, exclude); var overlays = Overlays.findRayIntersection(pickRay); - if (!overlays.intersects || (entities.distance <= overlays.distance)) { + if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { return entities; } return overlays; From 3c4f9f8016327b8d7dc2f5dbfbdcd1fad138a3fc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 6 Jul 2016 15:51:17 -0700 Subject: [PATCH 26/41] refert debug hack --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 827b1b895b..7eb7d87566 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -603,14 +603,6 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType type = getShapeType(); glm::vec3 dimensions = getDimensions(); - - // BEGIN temp HACK - int numSubMeshes = _model->getFBXGeometry().meshes.size(); - if (numSubMeshes > 1) { - qDebug() << "HACK entity name =" << getName() << " modelURL =" << getModelURL() << " pos =" << getPosition() << " numSubMeshes = " << numSubMeshes; - } - // END temp HACK - if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); From 74ef82f800b3fe3b409e6f8c327f058c25c21312 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 6 Jul 2016 16:15:02 -0700 Subject: [PATCH 27/41] add 'dynamic' property as a load --- scripts/developer/tests/loadedMachine.js | 39 +++++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js index c222eb7379..375e3d8004 100644 --- a/scripts/developer/tests/loadedMachine.js +++ b/scripts/developer/tests/loadedMachine.js @@ -40,7 +40,9 @@ var numberLeftRunning = scriptData.length - otherScripts.length; print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping', otherScripts); var typedEntities = {Light: [], ParticleEffect: []}; var interestingTypes = Object.keys(typedEntities); -var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes); +var propertiedEntities = {dynamic: []}; +var interestingProperties = Object.keys(propertiedEntities); +var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes, interestingProperties); var loadIndex = 0, nLoads = loads.length, load; var results = []; var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust(); @@ -51,21 +53,42 @@ LODManager.setOctreeSizeScale(DEFAULT_LOD); // Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient. var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); -print('Searching', allEntities.length, 'entities for', interestingTypes); +print('Searching', allEntities.length, 'entities for', interestingTypes, 'and', interestingProperties); +var propertiesToGet = ['type', 'visible'].concat(interestingProperties); allEntities.forEach(function (entityID) { - var properties = Entities.getEntityProperties(entityID, ['type', 'visible']); - if (properties.visible && (interestingTypes.indexOf(properties.type) >= 0)) { - typedEntities[properties.type].push(entityID); + var properties = Entities.getEntityProperties(entityID, propertiesToGet); + if (properties.visible) { + if (interestingTypes.indexOf(properties.type) >= 0) { + typedEntities[properties.type].push(entityID); + } else { + interestingProperties.forEach(function (property) { + if (entityID && properties[property]) { + propertiedEntities[property].push(entityID); + entityID = false; // Put in only one bin + } + }); + } } }); +allEntities = undefined; // free them interestingTypes.forEach(function (type) { print('There are', typedEntities[type].length, type, 'entities.'); }); +interestingProperties.forEach(function (property) { + print('There are', propertiedEntities[property].length, property, 'entities.'); +}); function toggleVisibility(type, on) { typedEntities[type].forEach(function (entityID) { Entities.editEntity(entityID, {visible: on}); }); } +function toggleProperty(property, on) { + propertiedEntities[property].forEach(function (entityID) { + var properties = {}; + properties[property] = on; + Entities.editEntity(entityID, properties); + }); +} function restoreOneTest(load) { print('restore', load); switch (load) { @@ -76,6 +99,9 @@ function restoreOneTest(load) { case 'ParticleEffect': toggleVisibility(load, true); break; + case 'dynamic': + toggleProperty(load, 1); + break; default: Script.load(load); } @@ -90,6 +116,9 @@ function undoOneTest(load) { case 'ParticleEffect': toggleVisibility(load, false); break; + case 'dynamic': + toggleProperty(load, 0); + break; default: ScriptDiscoveryService.stopScript(load); } From d05a27e0e6ad9fe36dee768b9f9ed5e31718812a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Jul 2016 17:27:53 -0700 Subject: [PATCH 28/41] embed image locally --- interface/resources/images/preview.png | Bin 0 -> 112290 bytes .../display-plugins/hmd/HmdDisplayPlugin.cpp | 54 ++++++------------ .../display-plugins/hmd/HmdDisplayPlugin.h | 5 -- 3 files changed, 17 insertions(+), 42 deletions(-) create mode 100644 interface/resources/images/preview.png diff --git a/interface/resources/images/preview.png b/interface/resources/images/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..faebbfce8f89722c12643387a7bab30c7416ac05 GIT binary patch literal 112290 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�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 S6zzXCC?)tKSQ*sx)&Bt}KR+Y@ literal 0 HcmV?d00001 diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index f1f300c72b..c2497e5740 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -21,12 +21,11 @@ #include #include -#include -#include -#include #include #include +#include + #include "../Logging.h" #include "../CompositorHelper.h" @@ -65,33 +64,24 @@ bool HmdDisplayPlugin::internalActivate() { }); if (_previewTextureID == 0) { - const QUrl previewURL("https://hifi-content.s3.amazonaws.com/samuel/preview.png"); - QNetworkAccessManager& manager = NetworkAccessManager::getInstance(); - QNetworkRequest request(previewURL); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - auto rep = manager.get(request); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + QImage previewTexture(PathUtils::resourcesPath() + "images/preview.png"); + if (!previewTexture.isNull()) { + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, previewTexture.width(), previewTexture.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, previewTexture.mirrored(false, true).bits()); + using namespace oglplus; + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + _previewAspect = ((float)previewTexture.width())/((float)previewTexture.height()); + _firstPreview = true; + } } return Parent::internalActivate(); } -void HmdDisplayPlugin::downloadFinished() { - QNetworkReply* reply = static_cast(sender()); - - if (reply->error() != QNetworkReply::NetworkError::NoError) { - qDebug() << "HMDDisplayPlugin: error downloading preview image" << reply->errorString(); - return; - } - - _previewTexture.loadFromData(reply->readAll()); - - if (!_previewTexture.isNull()) { - _previewAspect = ((float)_previewTexture.width())/((float)_previewTexture.height()); - _firstPreview = true; - } -} - void HmdDisplayPlugin::internalDeactivate() { if (_previewTextureID != 0) { glDeleteTextures(1, &_previewTextureID); @@ -427,19 +417,8 @@ void HmdDisplayPlugin::internalPresent() { }); swapBuffers(); } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { - if (_firstPreview) { - glGenTextures(1, &_previewTextureID); - glBindTexture(GL_TEXTURE_2D, _previewTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _previewTexture.width(), _previewTexture.height(), 0, - GL_BGRA, GL_UNSIGNED_BYTE, _previewTexture.mirrored(false, true).bits()); - using namespace oglplus; - Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); - Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); - glBindTexture(GL_TEXTURE_2D, 0); - _firstPreview = false; - } useProgram(_previewProgram); - glEnable (GL_BLEND); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -449,6 +428,7 @@ void HmdDisplayPlugin::internalPresent() { glBindTexture(GL_TEXTURE_2D, _previewTextureID); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); swapBuffers(); + _firstPreview = false; _prevWindowSize = windowSize; _prevDevicePixelRatio = devicePixelRatio; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 61b352b17b..8e48690fd1 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -15,7 +15,6 @@ #include "../OpenGLDisplayPlugin.h" class HmdDisplayPlugin : public OpenGLDisplayPlugin { - Q_OBJECT using Parent = OpenGLDisplayPlugin; public: bool isHmd() const override final { return true; } @@ -87,9 +86,6 @@ protected: FrameInfo _currentPresentFrameInfo; FrameInfo _currentRenderFrameInfo; -public slots: - void downloadFinished(); - private: bool _enablePreview { false }; bool _monoPreview { true }; @@ -97,7 +93,6 @@ private: bool _firstPreview { true }; ProgramPtr _previewProgram; - QImage _previewTexture; float _previewAspect { 0 }; GLuint _previewTextureID { 0 }; glm::uvec2 _prevWindowSize { 0, 0 }; From 5220b52cd9de0e8ebcd957b7829c2cda7d5c64cb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 4 Jul 2016 21:20:42 -0700 Subject: [PATCH 29/41] Don't trigger entity scripts without scripting interface --- .../src/EntityTreeRenderer.cpp | 70 ++++++++++++++----- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 56f6438e70..1ec934be92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -291,7 +291,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, _currentEntitiesInside) { if (!entitiesContainingAvatar.contains(entityID)) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } } @@ -299,7 +301,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, entitiesContainingAvatar) { if (!_currentEntitiesInside.contains(entityID)) { emit enterEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + } } } _currentEntitiesInside = entitiesContainingAvatar; @@ -315,7 +319,9 @@ void EntityTreeRenderer::leaveAllEntities() { // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } _currentEntitiesInside.clear(); forceRecheckEntities(); @@ -652,11 +658,15 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { } emit mousePressOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + } _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + } } else { emit mousePressOffEntity(rayPickResult, event); } @@ -677,14 +687,18 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + } } // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickOn event if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + } } // makes it the unknown ID, we just released so we can't be clicking on anything @@ -707,8 +721,10 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + } // handle the hover logic... @@ -716,19 +732,25 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } } // If the new hover entity does not match the previous hover entity then we are entering the new one // this is true if the _currentHoverOverEntityID is known or unknown if (rayPickResult.entityID != _currentHoverOverEntityID) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + } } // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + } // remember what we're hovering over _currentHoverOverEntityID = rayPickResult.entityID; @@ -739,7 +761,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID } } @@ -748,14 +772,16 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // not yet released the hold then this is still considered a holdingClickOnEntity event if (!_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + } } _lastMouseEvent = MouseEvent(*event); _lastMouseEventValid = true; } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { - if (_tree && !_shuttingDown) { + if (_tree && !_shuttingDown && _entitiesScriptEngine) { _entitiesScriptEngine->unloadEntityScript(entityID); } @@ -801,7 +827,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); - if (entity && entity->shouldPreloadScript()) { + if (entity && entity->shouldPreloadScript() && _entitiesScriptEngine) { QString scriptUrl = entity->getScript(); scriptUrl = ResourceManager::normalizeURL(scriptUrl); ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload); @@ -910,12 +936,16 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons // And now the entity scripts if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idA, idB, collision); - _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + } } if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idB, idA, collision); - _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + } } } @@ -941,7 +971,9 @@ void EntityTreeRenderer::updateZone(const EntityItemID& id) { if (zone && zone->contains(_lastAvatarPosition)) { _currentEntitiesInside << id; emit enterEntity(id); - _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + } if (zone->getVisible()) { _bestZone = std::dynamic_pointer_cast(zone); } From d2fedee6563ff9b27b23f844a5a030e130436d79 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Jul 2016 10:22:52 -0700 Subject: [PATCH 30/41] Add missing loading images for Asset Browser --- interface/resources/images/Loading-Inner-H.png | Bin 0 -> 1551 bytes .../resources/images/Loading-Outer-Ring.png | Bin 0 -> 2638 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface/resources/images/Loading-Inner-H.png create mode 100644 interface/resources/images/Loading-Outer-Ring.png diff --git a/interface/resources/images/Loading-Inner-H.png b/interface/resources/images/Loading-Inner-H.png new file mode 100644 index 0000000000000000000000000000000000000000..cf09ea8317499d4d0e1bc9af5e162eab3daf0dd1 GIT binary patch literal 1551 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE{(JaZG%Q-e|yQz{EjrrIztFl%InM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`Gt*5rG&EE& zx70HGzDQNg6CTHe>1U13Be0{Av^NLFn^O93NU2K&qatrh_GgGWwj17%U-Hgm!TuqD& z4P8y09G#t=oL!xaEX@ra4V>IydR_99OLJ56N?>|ZGE=M^O)T9kT%3$tTulwJ>IEeT zP@q`3q!wkCrKY$Q<>xAZJ#LkW+bt%z-2%~@g2gRRf0*Lbt8b@ZqYsK+r09kT1-ZC^ znDE4Fr+`TQsd*{3N<~Wcc9V=QgfK8L)q1)(hE&{oGuziABv7Q`d8VF|uh1P;-KMO| z$6O_@D6y%Dy9u)XeHnDYr0DS~`7E)(?k1JBuG*~&1bD=g47kOegz}{7kOduA{QCouUma2hbG z9bf@-N~X!)u07$| zuh?~r;gq&TyrSJ(!#t76M?*BbEY z7Ub{NH|xmqpU_q_wfo%Xb9cW~+!eVxlZ(Ihq0L&)Gt6~WcKa_H&)emaan5+26=Q$m zr~Vt0RKhxEu>MuV?KF`ronY<2H#d&jX%$&-FOAaJ^`V z{L#?jm~m;B{04cq*sSiB_6`2s4ZAOJ=Rd6wk7-U-IpeTy+FfNnMpw6woEC4E8CU4N zX1@L+&$Hm3a^eE9yUN!h894JFDAm7fwcen`FFS=r!Sg;| zQFS^x;m0m9#>de<>$Sw3^V(m;Ji8vA9rKfC`@<@c|N0x^du}A1fJ8G=oG;jr!)3sb XyMAls+O}COpd#AS)z4*}Q$iB}Kk_vn literal 0 HcmV?d00001 diff --git a/interface/resources/images/Loading-Outer-Ring.png b/interface/resources/images/Loading-Outer-Ring.png new file mode 100644 index 0000000000000000000000000000000000000000..52613949e51e66c0a439dfb6e2efe4084e6d1abe GIT binary patch literal 2638 zcmaJ@doigsCd)Io`de>h2dG_<{z5jSqeVD7~YZz(( z0OotFVfX<6T$EQ`6#!7oi|hpe7)acLB>o6j62lU~fW|?#z>o)@wH5Y*S)AC&o3IlA zvzGG$f+RuSUSu}Hw_z!BY@~c4P#!0$RLEjSz!GQ+yp<=QU`K1~u@H|#!3NrUSNy$>akPk~(kd(hoASO#G*e|?fftaie&`VZy(4N5y6nXje|9tKp;SlBpZVLDqEtx6-2}n>~VM!j)1qu6UYQSnTUtJ9;`Fu zB;s(%ehl}owL}~)1sf)j2+25HOiYYTjI9kK+KMBPNF*Gdh$9lMMI5fRI94EGNv#E9 zvl#{kEM|*%LJ1EMKuSi|79>hS!D44D@P*%K1>&!5Vk3N}g5bfQGM24Fkkw9>> zr_)?r>8@^cBFT|J!_(boundG9#fJrw87${J*7k>3c7a@G;i*YjV z$6D||_rJzH6Y^uYE1%z%*BSc0{;+^U!HTd{=T}o0R{&Hm zc`#@J(yo^qL4rS-McR>B5N6jHar)$)ST!Oa+_Wz-sH3!^j9of2+!3e>-AGB zKHNI&_D7;z{|G6kKEzfaFvvLS%8%J|)a-R*C8wh!2K8mA>` zKU~u^u`9A;Vq(_S;+^QLo+W`lDe@c?0)=j zaM=m@Q)-;{1Q?42SF8;{?~{x0olTw()BemgeVZSy>#$DSXrXORd-y1R9$OQGsm~24 zo?O3TV7k)Ib@U$Ge65d7R!9`v6d{Vi#E#87Dzq;wa9L&K16(=+|6{eOur@wkZ|n&h zYi9ArBh}m2*Y^p2|Ktl?aS6ZuLwC-kww3zT2BY9{MLQ|f4BT}d1lt~c>`guP;6aSg zEKf(zh#l`$Mm^K<_q&~Knh8agGHp${eZx6o z92^9c(hCtbzc|Wdz0E3@cE2@QJUu%^1qzzhbVVR318%WPKI<=uraTEDw}On3U1 zoRFS*vu%hS$T5gupk%?u<_`BCvJWmys!9u2WIlWA3}{0OffsM+yu5EX#7(;6 z0lEcG#rnrKlufFD@z{*cmgsHNX6U1ayo3{e{RjwMwp-U0Uy@FHG-^xSq1^(tt^`5X z&U|nxs3Zcg&phjWBPBFF5d4<1dS|wI!oWDnVHZLA%j$yO6FQ)@_a54OgGlFf?vk5Q z%M-LL0IW8(FJ96dL_{lM9I($?LeBzF+AU^Vg)*yCEFx|7VVdHv;9S)US=sq&CDj@q z?#?IP)&?>Q2NO&Ko@;<^^STy{24ffY8nJser+~J5v4;V0Dj9!vW9|C-d8nND45m6b zH`or&v81eXeM9Fl88jox79~9SGJmqN0yn)Nog+u3FryV#j za7#-j$#^>#w1wMkf804B8n~e)+eQ>R=q# z8;@nol|IgOuf>^@GfGZdj!2?McGk(}(EG~eMSHziNMGz}pm_B8%n=U8&-Kb@cg=Zk zK5Hdu$SguC(@RAaBYtJmC`4E9y~v{Jgr)Ozz(`jp$&{N}Xt<^~K{E-LK{S5uzjp0w zvW?rdhN7yv;rN$&8tA@q+EX2Elh)YGJ8|H2gIUO}TP+`tWzYwEZo30hdShe%UeOFke{I`{N^h|2qJ-2UvXaPF)-blIbmzjmed610s_*i3hL)S-M_E_ig;H3pfP=Igc zq$2lPR_V({zT;7OO9R_7Gh0s$z;KyoX!T%B>iGrTIHx);FON|8Yu@y_Jz5mw6Q=D} zt=dJ~>4C=wFu38@Q}PEU-Bh(c{!y&f@arS=ca18`4h3?V`280JDS_PA!BfY2j~c8s zjpyyHc5LaWs#)Ri$MM@$H6C79#E}as>9!rn1OWAW^eoD+59a6Nm~m$3em=GSQ$oB` d(-0M<3fw&0pB%08ysP}-dAKnd=jow4{{g+kQvd(} literal 0 HcmV?d00001 From 09f2a1061352eb4633fc814e044e71b8024b68ee Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Jul 2016 17:02:28 -0700 Subject: [PATCH 31/41] Add rendering performance tool --- tests/render-perf/CMakeLists.txt | 13 + tests/render-perf/src/main.cpp | 650 +++++++++++++++++++++++++++++++ 2 files changed, 663 insertions(+) create mode 100644 tests/render-perf/CMakeLists.txt create mode 100644 tests/render-perf/src/main.cpp diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt new file mode 100644 index 0000000000..611cadd595 --- /dev/null +++ b/tests/render-perf/CMakeLists.txt @@ -0,0 +1,13 @@ + +set(TARGET_NAME render-perf-test) + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics) + +package_libraries_for_deployment() + +target_bullet() diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp new file mode 100644 index 0000000000..c953e51700 --- /dev/null +++ b/tests/render-perf/src/main.cpp @@ -0,0 +1,650 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// Copyright 2014 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static const QString LAST_SCENE_KEY = "lastSceneFile"; + +class ParentFinder : public SpatialParentFinder { +public: + EntityTreePointer _tree; + ParentFinder(EntityTreePointer tree) : _tree(tree) {} + + SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const override { + SpatiallyNestableWeakPointer parent; + + if (parentID.isNull()) { + success = true; + return parent; + } + + // search entities + if (entityTree) { + parent = entityTree->findByID(parentID); + } else { + parent = _tree ? _tree->findEntityByEntityItemID(parentID) : nullptr; + } + + if (!parent.expired()) { + success = true; + return parent; + } + + success = false; + return parent; + } +}; + +class Camera { +protected: + float fov { 60.0f }; + float znear { 0.1f }, zfar { 512.0f }; + float aspect { 1.0f }; + + void updateViewMatrix() { + matrices.view = glm::inverse(glm::translate(glm::mat4(), position) * glm::mat4_cast(getOrientation())); + } + + glm::quat getOrientation() const { + return glm::angleAxis(yaw, Vectors::UP); + } +public: + float yaw { 0 }; + glm::vec3 position; + + float rotationSpeed { 1.0f }; + float movementSpeed { 1.0f }; + + struct Matrices { + glm::mat4 perspective; + glm::mat4 view; + } matrices; + enum Key { + RIGHT, + LEFT, + UP, + DOWN, + BACK, + FORWARD, + KEYS_SIZE, + INVALID = -1, + }; + + std::bitset keys; + + Camera() { + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + bool moving() { + return keys.any(); + } + + void setFieldOfView(float fov) { + this->fov = fov; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setAspectRatio(const glm::vec2& size) { + setAspectRatio(size.x / size.y); + } + + void setAspectRatio(float aspect) { + this->aspect = aspect; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setPerspective(float fov, const glm::vec2& size, float znear = 0.1f, float zfar = 512.0f) { + setPerspective(fov, size.x / size.y, znear, zfar); + } + + void setPerspective(float fov, float aspect, float znear = 0.1f, float zfar = 512.0f) { + this->aspect = aspect; + this->fov = fov; + this->znear = znear; + this->zfar = zfar; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + }; + + void rotate(const float delta) { + yaw += delta; + updateViewMatrix(); + } + + void setPosition(const glm::vec3& position) { + this->position = position; + updateViewMatrix(); + } + + // Translate in the Z axis of the camera + void dolly(float delta) { + auto direction = glm::vec3(0, 0, delta); + translate(direction); + } + + // Translate in the XY plane of the camera + void translate(const glm::vec2& delta) { + auto move = glm::vec3(delta.x, delta.y, 0); + translate(move); + } + + void translate(const glm::vec3& delta) { + position += getOrientation() * delta; + updateViewMatrix(); + } + + void update(float deltaTime) { + if (moving()) { + glm::vec3 camFront = getOrientation() * Vectors::FRONT; + glm::vec3 camRight = getOrientation() * Vectors::RIGHT; + glm::vec3 camUp = getOrientation() * Vectors::UP; + float moveSpeed = deltaTime * movementSpeed; + + if (keys[FORWARD]) { + position += camFront * moveSpeed; + } + if (keys[BACK]) { + position -= camFront * moveSpeed; + } + if (keys[LEFT]) { + position -= camRight * moveSpeed; + } + if (keys[RIGHT]) { + position += camRight * moveSpeed; + } + if (keys[UP]) { + position += camUp * moveSpeed; + } + if (keys[DOWN]) { + position -= camUp * moveSpeed; + } + updateViewMatrix(); + } + } +}; + +class QWindowCamera : public Camera { + Key forKey(int key) { + switch (key) { + case Qt::Key_W: return FORWARD; + case Qt::Key_S: return BACK; + case Qt::Key_A: return LEFT; + case Qt::Key_D: return RIGHT; + case Qt::Key_E: return UP; + case Qt::Key_C: return DOWN; + default: break; + } + return INVALID; + } + + vec2 _lastMouse; +public: + void onKeyPress(QKeyEvent* event) { + Key k = forKey(event->key()); + if (k == INVALID) { + return; + } + keys.set(k); + } + + void onKeyRelease(QKeyEvent* event) { + Key k = forKey(event->key()); + if (k == INVALID) { + return; + } + keys.reset(k); + } + + void onMouseMove(QMouseEvent* event) { + vec2 mouse = toGlm(event->localPos()); + vec2 delta = mouse - _lastMouse; + auto buttons = event->buttons(); + if (buttons & Qt::RightButton) { + dolly(delta.y * 0.01f); + } else if (buttons & Qt::LeftButton) { + rotate(delta.x * -0.01f); + } else if (buttons & Qt::MiddleButton) { + delta.y *= -1.0f; + translate(delta * -0.01f); + } + + _lastMouse = mouse; + } +}; + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow, public AbstractViewStateInterface { + Q_OBJECT + +protected: + void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { + viewOut = _viewFrustum; + } + + void copyShadowViewFrustum(ViewFrustum& viewOut) const override { + viewOut = _shadowViewFrustum; + } + + QThread* getMainThread() override { + return QThread::currentThread(); + } + + PickRay computePickRay(float x, float y) const override { + return PickRay(); + } + + glm::vec3 getAvatarPosition() const override { + return vec3(); + } + + void postLambdaEvent(std::function f) override {} + qreal getDevicePixelRatio() override { + return 1.0f; + } + + render::ScenePointer getMain3DScene() override { + return _main3DScene; + } + render::EnginePointer getRenderEngine() override { + return _renderEngine; + } + + void pushPostUpdateLambda(void* key, std::function func) override {} + +public: + //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" + static void setup() { + DependencyManager::registerInheritance(); + DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + } + + QTestWindow() { + AbstractViewStateInterface::setInstance(this); + _octree = DependencyManager::set(false, this, nullptr); + _octree->init(); + DependencyManager::set(_octree->getTree()); + getEntities()->setViewFrustum(_viewFrustum); + auto nodeList = DependencyManager::get(); + NodePermissions permissions; + permissions.setAll(true); + nodeList->setPermissions(permissions); + + ResourceManager::init(); + setSurfaceType(QSurface::OpenGLSurface); + auto format = getDefaultOpenGLSurfaceFormat(); + format.setOption(QSurfaceFormat::DebugContext); + setFormat(format); + + _context.setFormat(format); + _context.create(); + + show(); + makeCurrent(); + glewInit(); + glGetError(); +#ifdef Q_OS_WIN + wglSwapIntervalEXT(0); +#endif + _camera.movementSpeed = 3.0f; + + setupDebugLogger(this); + qDebug() << (const char*)glGetString(GL_VERSION); + + // GPU library init + { + gpu::Context::init(); + _gpuContext = std::make_shared(); + } + + // Render engine library init + { + makeCurrent(); + DependencyManager::get()->init(); + _renderEngine->addJob("RenderShadowTask", _cullFunctor); + _renderEngine->addJob("RenderDeferredTask", _cullFunctor); + _renderEngine->load(); + _renderEngine->registerScene(_main3DScene); + } + + QVariant lastScene = _settings.value(LAST_SCENE_KEY); + if (lastScene.isValid()) { + auto result = QMessageBox::question(nullptr, "Question", "Load last scene " + lastScene.toString()); + if (result != QMessageBox::No) { + importScene(lastScene.toString()); + } + } + + resize(QSize(800, 600)); + _elapsed.start(); + + QTimer* timer = new QTimer(this); + timer->setInterval(0); + connect(timer, &QTimer::timeout, this, [this] { + draw(); + }); + timer->start(); + } + + virtual ~QTestWindow() { + ResourceManager::cleanup(); + } + +protected: + void keyPressEvent(QKeyEvent* event) override { + switch (event->key()) { + case Qt::Key_F1: + importScene(); + return; + + case Qt::Key_F2: + goTo(); + return; + + default: + break; + } + _camera.onKeyPress(event); + } + + void keyReleaseEvent(QKeyEvent* event) override { + _camera.onKeyRelease(event); + } + + void mouseMoveEvent(QMouseEvent* event) override { + _camera.onMouseMove(event); + } + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + +private: + + static bool cull(const RenderArgs* renderArgs, const AABox& box) { + return true; + } + + void update() { + auto now = usecTimestampNow(); + static auto last = now; + + float delta = now - last; + // Update the camera + _camera.update(delta / USECS_PER_SECOND); + + + // load the view frustum + { + _viewFrustum.setProjection(_camera.matrices.perspective); + auto view = glm::inverse(_camera.matrices.view); + _viewFrustum.setPosition(glm::vec3(view[3])); + _viewFrustum.setOrientation(glm::quat_cast(view)); + } + last = now; + } + + void draw() { + if (!isVisible()) { + return; + } + update(); + + makeCurrent(); +#define RENDER_SCENE 1 + +#if RENDER_SCENE + RenderArgs renderArgs(_gpuContext, _octree.data(), DEFAULT_OCTREE_SIZE_SCALE, + 0, RenderArgs::DEFAULT_RENDER_MODE, + RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); + + auto framebufferCache = DependencyManager::get(); + QSize windowSize = size(); + framebufferCache->setFrameBufferSize(windowSize); + // Viewport is assigned to the size of the framebuffer + renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); + + renderArgs.setViewFrustum(_viewFrustum); + + // Final framebuffer that will be handled to the display-plugin + { + auto finalFramebuffer = framebufferCache->getFramebuffer(); + renderArgs._blitFramebuffer = finalFramebuffer; + } + + render(&renderArgs); + + { + gpu::gl::GLFramebuffer* framebuffer = gpu::Backend::getGPUObject(*renderArgs._blitFramebuffer); + auto fbo = framebuffer->_id; + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + const auto& vp = renderArgs._viewport; + glBlitFramebuffer(vp.x, vp.y, vp.z, vp.w, vp.x, vp.y, vp.z, vp.w, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } +#else + glClearColor(0.0f, 0.5f, 0.8f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); +#endif + + _context.swapBuffers(this); + +#if RENDER_SCENE + framebufferCache->releaseFramebuffer(renderArgs._blitFramebuffer); + renderArgs._blitFramebuffer.reset(); + gpu::doInBatch(renderArgs._context, [&](gpu::Batch& batch) { + batch.resetStages(); + }); +#endif + fps.increment(); + static size_t _frameCount { 0 }; + ++_frameCount; + if (_elapsed.elapsed() >= 4000) { + qDebug() << "FPS " << fps.rate(); + _frameCount = 0; + _elapsed.restart(); + } + + if (0 == ++_frameCount % 100) { + } + } + + void render(RenderArgs* renderArgs) { + PROFILE_RANGE(__FUNCTION__); + PerformanceTimer perfTimer("draw"); + // The pending changes collecting the changes here + render::PendingChanges pendingChanges; + // Setup the current Zone Entity lighting + DependencyManager::get()->setGlobalLight(_sunSkyStage.getSunLight()); + { + PerformanceTimer perfTimer("SceneProcessPendingChanges"); + _main3DScene->enqueuePendingChanges(pendingChanges); + _main3DScene->processPendingChangesQueue(); + } + + // For now every frame pass the renderContext + { + PerformanceTimer perfTimer("EngineRun"); + _renderEngine->getRenderContext()->args = renderArgs; + // Before the deferred pass, let's try to use the render engine + _renderEngine->run(); + } + } + + void makeCurrent() { + _context.makeCurrent(this); + } + + void resizeWindow(const QSize& size) { + _size = size; + _camera.setAspectRatio((float)_size.width() / (float)_size.height()); + } + + void parsePath(const QString& viewpointString) { + static const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + static const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; + static const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; + static const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + + FLOAT_REGEX_STRING + "\\s*$"; + + static QRegExp orientationRegex(QUAT_REGEX_STRING); + static QRegExp positionRegex(POSITION_REGEX_STRING); + + if (positionRegex.indexIn(viewpointString) != -1) { + // we have at least a position, so emit our signal to say we need to change position + glm::vec3 newPosition(positionRegex.cap(1).toFloat(), + positionRegex.cap(2).toFloat(), + positionRegex.cap(3).toFloat()); + _camera.setPosition(newPosition); + + if (!glm::any(glm::isnan(newPosition))) { + // we may also have an orientation + if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') + && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { + //glm::vec4 v = glm::vec4( + // orientationRegex.cap(1).toFloat(), + // orientationRegex.cap(2).toFloat(), + // orientationRegex.cap(3).toFloat(), + // orientationRegex.cap(4).toFloat()); + //if (!glm::any(glm::isnan(v))) { + // _camera.setRotation(glm::normalize(glm::quat(v.w, v.x, v.y, v.z))); + //} + } + } + } + } + + void importScene(const QString& fileName) { + _settings.setValue(LAST_SCENE_KEY, fileName); + _octree->clear(); + _octree->getTree()->readFromURL(fileName); + } + + void importScene() { + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), "/home", tr("Hifi Exports (*.json *.svo)")); + if (fileName.isNull()) { + return; + } + importScene(fileName); + } + + void goTo() { + QString destination = QInputDialog::getText(nullptr, tr("Go To Location"), "Enter path"); + if (destination.isNull()) { + return; + } + parsePath(destination); + } + +private: + render::CullFunctor _cullFunctor { cull }; + gpu::ContextPointer _gpuContext; // initialized during window creation + render::EnginePointer _renderEngine { new render::Engine() }; + render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; + QOpenGLContextWrapper _context; + QSize _size; + RateCounter<> fps; + QSettings _settings; + + QWindowCamera _camera; + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + ViewFrustum _shadowViewFrustum; // current state of view frustum, perspective, orientation, etc. + model::SunSkyStage _sunSkyStage; + model::LightPointer _globalLight { std::make_shared() }; + QElapsedTimer _elapsed; + QSharedPointer _octree; + QSharedPointer getEntities() { + return _octree; + } +}; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(message.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif + std::cout << message.toLocal8Bit().constData() << std::endl; + } +} + +const char * LOG_FILTER_RULES = R"V0G0N( +hifi.gpu=true +)V0G0N"; + +int main(int argc, char** argv) { + QApplication app(argc, argv); + QCoreApplication::setApplicationName("RenderPerf"); + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setOrganizationDomain("highfidelity.com"); + + qInstallMessageHandler(messageHandler); + QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + QTestWindow::setup(); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc" From 5be238db08b7fa398db58472a5e8b443a1c338ff Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Jul 2016 20:29:35 -0700 Subject: [PATCH 32/41] Suppressing glew link warnings --- tests/render-perf/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 611cadd595..d4f90fdace 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -1,6 +1,10 @@ set(TARGET_NAME render-perf-test) +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") @@ -10,4 +14,5 @@ link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking ne package_libraries_for_deployment() + target_bullet() From c7382ea886f0c0fcc8b6499a3bbef09be88290d2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 7 Jul 2016 00:44:24 -0700 Subject: [PATCH 33/41] Optimize updateModelBounds --- .../src/RenderableModelEntityItem.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 43fea75eac..ef3c0e120b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -342,16 +342,23 @@ void RenderableModelEntityItem::updateModelBounds() { if (!hasModel() || !_model) { return; } + if (!_dimensionsInitialized || !_model->isActive()) { + return; + } + + bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); glm::vec3 dimensions = getDimensions(); - if ((movingOrAnimating || + bool success; + auto transform = getTransform(success); + + if (movingOrAnimating || _needsInitialSimulation || _needsJointSimulation || - _model->getTranslation() != getPosition() || + _model->getTranslation() != transform.getTranslation() || _model->getScaleToFitDimensions() != dimensions || - _model->getRotation() != getRotation() || - _model->getRegistrationPoint() != getRegistrationPoint()) - && _model->isActive() && _dimensionsInitialized) { + _model->getRotation() != transform.getRotation() || + _model->getRegistrationPoint() != getRegistrationPoint()) { doInitialModelSimulation(); _needsJointSimulation = false; } From 721cd79b5759f3230601e50e603f42e6b4f21988 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Jul 2016 17:55:06 -0700 Subject: [PATCH 34/41] Aggressive batch pre-allocation --- libraries/gpu/src/gpu/Batch.cpp | 50 ++++++++++++++++------------- libraries/gpu/src/gpu/Batch.h | 56 ++++++++++++++------------------- libraries/gpu/src/gpu/Context.h | 4 +-- 3 files changed, 52 insertions(+), 58 deletions(-) diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 6dc1d63ca8..8d16cd9262 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -30,24 +30,39 @@ ProfileRangeBatch::~ProfileRangeBatch() { using namespace gpu; -Batch::Batch(const CacheState& cacheState) : Batch() { - _commands.reserve(cacheState.commandsSize); - _commandOffsets.reserve(cacheState.offsetsSize); - _params.reserve(cacheState.paramsSize); - _data.reserve(cacheState.dataSize); -} +size_t Batch::_commandsMax { 128 }; +size_t Batch::_commandOffsetsMax { 128 }; +size_t Batch::_paramsMax { 128 }; +size_t Batch::_dataMax { 128 }; +size_t Batch::_objectsMax { 128 }; +size_t Batch::_drawCallInfosMax { 128 }; -Batch::CacheState Batch::getCacheState() { - return CacheState(_commands.size(), _commandOffsets.size(), _params.size(), _data.size(), - _buffers.size(), _textures.size(), _streamFormats.size(), _transforms.size(), _pipelines.size(), - _framebuffers.size(), _queries.size()); +Batch::Batch() { + _commands.reserve(_commandsMax); + _commandOffsets.reserve(_commandOffsetsMax); + _params.reserve(_paramsMax); + _data.reserve(_dataMax); + _objects.reserve(_objectsMax); + _drawCallInfos.reserve(_drawCallInfosMax); } Batch::~Batch() { - //qDebug() << "Batch::~Batch()... " << getCacheState(); + _commandsMax = std::max(_commands.size(), _commandsMax); + _commandOffsetsMax = std::max(_commandOffsets.size(), _commandOffsetsMax); + _paramsMax = std::max(_params.size(), _paramsMax); + _dataMax = std::max(_data.size(), _dataMax); + _objectsMax = std::max(_objects.size(), _objectsMax); + _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); } void Batch::clear() { + _commandsMax = std::max(_commands.size(), _commandsMax); + _commandOffsetsMax = std::max(_commandOffsets.size(), _commandOffsetsMax); + _paramsMax = std::max(_params.size(), _paramsMax); + _dataMax = std::max(_data.size(), _dataMax); + _objectsMax = std::max(_objects.size(), _objectsMax); + _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); + _commands.clear(); _commandOffsets.clear(); _params.clear(); @@ -58,6 +73,8 @@ void Batch::clear() { _transforms.clear(); _pipelines.clear(); _framebuffers.clear(); + _objects.clear(); + _drawCallInfos.clear(); } size_t Batch::cacheData(size_t size, const void* data) { @@ -458,17 +475,6 @@ void Batch::preExecute() { } } -QDebug& operator<<(QDebug& debug, const Batch::CacheState& cacheState) { - debug << "Batch::CacheState[ " - << "commandsSize:" << cacheState.commandsSize - << "offsetsSize:" << cacheState.offsetsSize - << "paramsSize:" << cacheState.paramsSize - << "dataSize:" << cacheState.dataSize - << "]"; - - return debug; -} - // Debugging void Batch::pushProfileRange(const char* name) { #if defined(NSIGHT_FOUND) diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 4e51038368..b56b5ee84b 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -87,6 +87,7 @@ public: using NamedBatchDataMap = std::map; DrawCallInfoBuffer _drawCallInfos; + static size_t _drawCallInfosMax; std::string _currentNamedCall; @@ -96,34 +97,7 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - class CacheState { - public: - size_t commandsSize; - size_t offsetsSize; - size_t paramsSize; - size_t dataSize; - - size_t buffersSize; - size_t texturesSize; - size_t streamFormatsSize; - size_t transformsSize; - size_t pipelinesSize; - size_t framebuffersSize; - size_t queriesSize; - - CacheState() : commandsSize(0), offsetsSize(0), paramsSize(0), dataSize(0), buffersSize(0), texturesSize(0), - streamFormatsSize(0), transformsSize(0), pipelinesSize(0), framebuffersSize(0), queriesSize(0) { } - - CacheState(size_t commandsSize, size_t offsetsSize, size_t paramsSize, size_t dataSize, size_t buffersSize, - size_t texturesSize, size_t streamFormatsSize, size_t transformsSize, size_t pipelinesSize, - size_t framebuffersSize, size_t queriesSize) : - commandsSize(commandsSize), offsetsSize(offsetsSize), paramsSize(paramsSize), dataSize(dataSize), - buffersSize(buffersSize), texturesSize(texturesSize), streamFormatsSize(streamFormatsSize), - transformsSize(transformsSize), pipelinesSize(pipelinesSize), framebuffersSize(framebuffersSize), queriesSize(queriesSize) { } - }; - - Batch() {} - Batch(const CacheState& cacheState); + Batch(); explicit Batch(const Batch& batch); ~Batch(); @@ -131,9 +105,6 @@ public: void preExecute(); - CacheState getCacheState(); - - // Batches may need to override the context level stereo settings // if they're performing framebuffer copy operations, like the // deferred lighting resolution mechanism @@ -401,11 +372,21 @@ public: typedef T Data; Data _data; Cache(const Data& data) : _data(data) {} + static size_t _max; class Vector { public: std::vector< Cache > _items; + Vector() { + _items.reserve(_max); + } + + ~Vector() { + _max = std::max(_items.size(), _max); + } + + size_t size() const { return _items.size(); } size_t cache(const Data& data) { size_t offset = _items.size(); @@ -449,9 +430,16 @@ public: } Commands _commands; + static size_t _commandsMax; + CommandOffsets _commandOffsets; + static size_t _commandOffsetsMax; + Params _params; + static size_t _paramsMax; + Bytes _data; + static size_t _dataMax; // SSBO class... layout MUST match the layout in Transform.slh class TransformObject { @@ -464,6 +452,7 @@ public: bool _invalidModel { true }; Transform _currentModel; TransformObjects _objects; + static size_t _objectsMax; BufferCaches _buffers; TextureCaches _textures; @@ -491,6 +480,9 @@ protected: void captureDrawCallInfoImpl(); }; +template +size_t Batch::Cache::_max = 128; + } #if defined(NSIGHT_FOUND) @@ -512,6 +504,4 @@ private: #endif -QDebug& operator<<(QDebug& debug, const gpu::Batch::CacheState& cacheState); - #endif diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 652338f911..47233b2fe3 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -231,11 +231,9 @@ typedef std::shared_ptr ContextPointer; template void doInBatch(std::shared_ptr context, F f) { - static gpu::Batch::CacheState cacheState; - gpu::Batch batch(cacheState); + gpu::Batch batch; f(batch); context->render(batch); - cacheState = batch.getCacheState(); } }; From eff1c653884cd40911ba0957158209f837d331f4 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Jul 2016 18:06:51 -0700 Subject: [PATCH 35/41] Reduce the number of temporary objects in batch commands --- libraries/gpu/src/gpu/Batch.cpp | 236 ++++++++++++++++---------------- libraries/gpu/src/gpu/Batch.h | 2 +- 2 files changed, 119 insertions(+), 119 deletions(-) diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 8d16cd9262..2c4c29cee9 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -26,7 +26,7 @@ ProfileRangeBatch::~ProfileRangeBatch() { } #endif -#define ADD_COMMAND(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); +#define ADD_COMMAND(call) _commands.emplace_back(COMMAND_##call); _commandOffsets.emplace_back(_params.size()); using namespace gpu; @@ -89,9 +89,9 @@ size_t Batch::cacheData(size_t size, const void* data) { void Batch::draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex) { ADD_COMMAND(draw); - _params.push_back(startVertex); - _params.push_back(numVertices); - _params.push_back(primitiveType); + _params.emplace_back(startVertex); + _params.emplace_back(numVertices); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -99,9 +99,9 @@ void Batch::draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 startIndex) { ADD_COMMAND(drawIndexed); - _params.push_back(startIndex); - _params.push_back(numIndices); - _params.push_back(primitiveType); + _params.emplace_back(startIndex); + _params.emplace_back(numIndices); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -109,11 +109,11 @@ void Batch::drawIndexed(Primitive primitiveType, uint32 numIndices, uint32 start void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 numVertices, uint32 startVertex, uint32 startInstance) { ADD_COMMAND(drawInstanced); - _params.push_back(startInstance); - _params.push_back(startVertex); - _params.push_back(numVertices); - _params.push_back(primitiveType); - _params.push_back(numInstances); + _params.emplace_back(startInstance); + _params.emplace_back(startVertex); + _params.emplace_back(numVertices); + _params.emplace_back(primitiveType); + _params.emplace_back(numInstances); captureDrawCallInfo(); } @@ -121,11 +121,11 @@ void Batch::drawInstanced(uint32 numInstances, Primitive primitiveType, uint32 n void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, uint32 numIndices, uint32 startIndex, uint32 startInstance) { ADD_COMMAND(drawIndexedInstanced); - _params.push_back(startInstance); - _params.push_back(startIndex); - _params.push_back(numIndices); - _params.push_back(primitiveType); - _params.push_back(numInstances); + _params.emplace_back(startInstance); + _params.emplace_back(startIndex); + _params.emplace_back(numIndices); + _params.emplace_back(primitiveType); + _params.emplace_back(numInstances); captureDrawCallInfo(); } @@ -133,16 +133,16 @@ void Batch::drawIndexedInstanced(uint32 numInstances, Primitive primitiveType, u void Batch::multiDrawIndirect(uint32 numCommands, Primitive primitiveType) { ADD_COMMAND(multiDrawIndirect); - _params.push_back(numCommands); - _params.push_back(primitiveType); + _params.emplace_back(numCommands); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } void Batch::multiDrawIndexedIndirect(uint32 nbCommands, Primitive primitiveType) { ADD_COMMAND(multiDrawIndexedIndirect); - _params.push_back(nbCommands); - _params.push_back(primitiveType); + _params.emplace_back(nbCommands); + _params.emplace_back(primitiveType); captureDrawCallInfo(); } @@ -150,16 +150,16 @@ void Batch::multiDrawIndexedIndirect(uint32 nbCommands, Primitive primitiveType) void Batch::setInputFormat(const Stream::FormatPointer& format) { ADD_COMMAND(setInputFormat); - _params.push_back(_streamFormats.cache(format)); + _params.emplace_back(_streamFormats.cache(format)); } void Batch::setInputBuffer(Slot channel, const BufferPointer& buffer, Offset offset, Offset stride) { ADD_COMMAND(setInputBuffer); - _params.push_back(stride); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(channel); + _params.emplace_back(stride); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(channel); } void Batch::setInputBuffer(Slot channel, const BufferView& view) { @@ -180,9 +180,9 @@ void Batch::setInputStream(Slot startChannel, const BufferStream& stream) { void Batch::setIndexBuffer(Type type, const BufferPointer& buffer, Offset offset) { ADD_COMMAND(setIndexBuffer); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(type); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(type); } void Batch::setIndexBuffer(const BufferView& buffer) { @@ -192,9 +192,9 @@ void Batch::setIndexBuffer(const BufferView& buffer) { void Batch::setIndirectBuffer(const BufferPointer& buffer, Offset offset, Offset stride) { ADD_COMMAND(setIndirectBuffer); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(offset); - _params.push_back(stride); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(offset); + _params.emplace_back(stride); } @@ -208,56 +208,56 @@ void Batch::setModelTransform(const Transform& model) { void Batch::setViewTransform(const Transform& view) { ADD_COMMAND(setViewTransform); - _params.push_back(_transforms.cache(view)); + _params.emplace_back(_transforms.cache(view)); } void Batch::setProjectionTransform(const Mat4& proj) { ADD_COMMAND(setProjectionTransform); - _params.push_back(cacheData(sizeof(Mat4), &proj)); + _params.emplace_back(cacheData(sizeof(Mat4), &proj)); } void Batch::setViewportTransform(const Vec4i& viewport) { ADD_COMMAND(setViewportTransform); - _params.push_back(cacheData(sizeof(Vec4i), &viewport)); + _params.emplace_back(cacheData(sizeof(Vec4i), &viewport)); } void Batch::setDepthRangeTransform(float nearDepth, float farDepth) { ADD_COMMAND(setDepthRangeTransform); - _params.push_back(farDepth); - _params.push_back(nearDepth); + _params.emplace_back(farDepth); + _params.emplace_back(nearDepth); } void Batch::setPipeline(const PipelinePointer& pipeline) { ADD_COMMAND(setPipeline); - _params.push_back(_pipelines.cache(pipeline)); + _params.emplace_back(_pipelines.cache(pipeline)); } void Batch::setStateBlendFactor(const Vec4& factor) { ADD_COMMAND(setStateBlendFactor); - _params.push_back(factor.x); - _params.push_back(factor.y); - _params.push_back(factor.z); - _params.push_back(factor.w); + _params.emplace_back(factor.x); + _params.emplace_back(factor.y); + _params.emplace_back(factor.z); + _params.emplace_back(factor.w); } void Batch::setStateScissorRect(const Vec4i& rect) { ADD_COMMAND(setStateScissorRect); - _params.push_back(cacheData(sizeof(Vec4i), &rect)); + _params.emplace_back(cacheData(sizeof(Vec4i), &rect)); } void Batch::setUniformBuffer(uint32 slot, const BufferPointer& buffer, Offset offset, Offset size) { ADD_COMMAND(setUniformBuffer); - _params.push_back(size); - _params.push_back(offset); - _params.push_back(_buffers.cache(buffer)); - _params.push_back(slot); + _params.emplace_back(size); + _params.emplace_back(offset); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(slot); } void Batch::setUniformBuffer(uint32 slot, const BufferView& view) { @@ -268,8 +268,8 @@ void Batch::setUniformBuffer(uint32 slot, const BufferView& view) { void Batch::setResourceTexture(uint32 slot, const TexturePointer& texture) { ADD_COMMAND(setResourceTexture); - _params.push_back(_textures.cache(texture)); - _params.push_back(slot); + _params.emplace_back(_textures.cache(texture)); + _params.emplace_back(slot); } void Batch::setResourceTexture(uint32 slot, const TextureView& view) { @@ -279,21 +279,21 @@ void Batch::setResourceTexture(uint32 slot, const TextureView& view) { void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { ADD_COMMAND(setFramebuffer); - _params.push_back(_framebuffers.cache(framebuffer)); + _params.emplace_back(_framebuffers.cache(framebuffer)); } void Batch::clearFramebuffer(Framebuffer::Masks targets, const Vec4& color, float depth, int stencil, bool enableScissor) { ADD_COMMAND(clearFramebuffer); - _params.push_back(enableScissor); - _params.push_back(stencil); - _params.push_back(depth); - _params.push_back(color.w); - _params.push_back(color.z); - _params.push_back(color.y); - _params.push_back(color.x); - _params.push_back(targets); + _params.emplace_back(enableScissor); + _params.emplace_back(stencil); + _params.emplace_back(depth); + _params.emplace_back(color.w); + _params.emplace_back(color.z); + _params.emplace_back(color.y); + _params.emplace_back(color.x); + _params.emplace_back(targets); } void Batch::clearColorFramebuffer(Framebuffer::Masks targets, const Vec4& color, bool enableScissor) { @@ -316,40 +316,40 @@ void Batch::blit(const FramebufferPointer& src, const Vec4i& srcViewport, const FramebufferPointer& dst, const Vec4i& dstViewport) { ADD_COMMAND(blit); - _params.push_back(_framebuffers.cache(src)); - _params.push_back(srcViewport.x); - _params.push_back(srcViewport.y); - _params.push_back(srcViewport.z); - _params.push_back(srcViewport.w); - _params.push_back(_framebuffers.cache(dst)); - _params.push_back(dstViewport.x); - _params.push_back(dstViewport.y); - _params.push_back(dstViewport.z); - _params.push_back(dstViewport.w); + _params.emplace_back(_framebuffers.cache(src)); + _params.emplace_back(srcViewport.x); + _params.emplace_back(srcViewport.y); + _params.emplace_back(srcViewport.z); + _params.emplace_back(srcViewport.w); + _params.emplace_back(_framebuffers.cache(dst)); + _params.emplace_back(dstViewport.x); + _params.emplace_back(dstViewport.y); + _params.emplace_back(dstViewport.z); + _params.emplace_back(dstViewport.w); } void Batch::generateTextureMips(const TexturePointer& texture) { ADD_COMMAND(generateTextureMips); - _params.push_back(_textures.cache(texture)); + _params.emplace_back(_textures.cache(texture)); } void Batch::beginQuery(const QueryPointer& query) { ADD_COMMAND(beginQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::endQuery(const QueryPointer& query) { ADD_COMMAND(endQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::getQuery(const QueryPointer& query) { ADD_COMMAND(getQuery); - _params.push_back(_queries.cache(query)); + _params.emplace_back(_queries.cache(query)); } void Batch::resetStages() { @@ -358,12 +358,12 @@ void Batch::resetStages() { void Batch::runLambda(std::function f) { ADD_COMMAND(runLambda); - _params.push_back(_lambdas.cache(f)); + _params.emplace_back(_lambdas.cache(f)); } void Batch::startNamedCall(const std::string& name) { ADD_COMMAND(startNamedCall); - _params.push_back(_names.cache(name)); + _params.emplace_back(_names.cache(name)); _currentNamedCall = name; } @@ -439,14 +439,14 @@ void Batch::captureDrawCallInfoImpl() { //_model.getInverseMatrix(_object._modelInverse); object._modelInverse = glm::inverse(object._model); - _objects.push_back(object); + _objects.emplace_back(object); // Flag is clean _invalidModel = false; } auto& drawCallInfos = getDrawCallInfoBuffer(); - drawCallInfos.push_back((uint16)_objects.size() - 1); + drawCallInfos.emplace_back((uint16)_objects.size() - 1); } void Batch::captureDrawCallInfo() { @@ -479,7 +479,7 @@ void Batch::preExecute() { void Batch::pushProfileRange(const char* name) { #if defined(NSIGHT_FOUND) ADD_COMMAND(pushProfileRange); - _params.push_back(_profileRanges.cache(name)); + _params.emplace_back(_profileRanges.cache(name)); #endif } @@ -496,9 +496,9 @@ void Batch::_glActiveBindTexture(uint32 unit, uint32 target, uint32 texture) { setResourceTexture(unit - GL_TEXTURE0, nullptr); ADD_COMMAND(glActiveBindTexture); - _params.push_back(texture); - _params.push_back(target); - _params.push_back(unit); + _params.emplace_back(texture); + _params.emplace_back(target); + _params.emplace_back(unit); } void Batch::_glUniform1i(int32 location, int32 v0) { @@ -506,8 +506,8 @@ void Batch::_glUniform1i(int32 location, int32 v0) { return; } ADD_COMMAND(glUniform1i); - _params.push_back(v0); - _params.push_back(location); + _params.emplace_back(v0); + _params.emplace_back(location); } void Batch::_glUniform1f(int32 location, float v0) { @@ -515,89 +515,89 @@ void Batch::_glUniform1f(int32 location, float v0) { return; } ADD_COMMAND(glUniform1f); - _params.push_back(v0); - _params.push_back(location); + _params.emplace_back(v0); + _params.emplace_back(location); } void Batch::_glUniform2f(int32 location, float v0, float v1) { ADD_COMMAND(glUniform2f); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); } void Batch::_glUniform3f(int32 location, float v0, float v1, float v2) { ADD_COMMAND(glUniform3f); - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + _params.emplace_back(v2); + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); } void Batch::_glUniform4f(int32 location, float v0, float v1, float v2, float v3) { ADD_COMMAND(glUniform4f); - _params.push_back(v3); - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + _params.emplace_back(v3); + _params.emplace_back(v2); + _params.emplace_back(v1); + _params.emplace_back(v0); + _params.emplace_back(location); } void Batch::_glUniform3fv(int32 location, int count, const float* value) { ADD_COMMAND(glUniform3fv); const int VEC3_SIZE = 3 * sizeof(float); - _params.push_back(cacheData(count * VEC3_SIZE, value)); - _params.push_back(count); - _params.push_back(location); + _params.emplace_back(cacheData(count * VEC3_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); } void Batch::_glUniform4fv(int32 location, int count, const float* value) { ADD_COMMAND(glUniform4fv); const int VEC4_SIZE = 4 * sizeof(float); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); + _params.emplace_back(cacheData(count * VEC4_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); } void Batch::_glUniform4iv(int32 location, int count, const int32* value) { ADD_COMMAND(glUniform4iv); const int VEC4_SIZE = 4 * sizeof(int); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); + _params.emplace_back(cacheData(count * VEC4_SIZE, value)); + _params.emplace_back(count); + _params.emplace_back(location); } void Batch::_glUniformMatrix3fv(int32 location, int count, uint8 transpose, const float* value) { ADD_COMMAND(glUniformMatrix3fv); const int MATRIX3_SIZE = 9 * sizeof(float); - _params.push_back(cacheData(count * MATRIX3_SIZE, value)); - _params.push_back(transpose); - _params.push_back(count); - _params.push_back(location); + _params.emplace_back(cacheData(count * MATRIX3_SIZE, value)); + _params.emplace_back(transpose); + _params.emplace_back(count); + _params.emplace_back(location); } void Batch::_glUniformMatrix4fv(int32 location, int count, uint8 transpose, const float* value) { ADD_COMMAND(glUniformMatrix4fv); const int MATRIX4_SIZE = 16 * sizeof(float); - _params.push_back(cacheData(count * MATRIX4_SIZE, value)); - _params.push_back(transpose); - _params.push_back(count); - _params.push_back(location); + _params.emplace_back(cacheData(count * MATRIX4_SIZE, value)); + _params.emplace_back(transpose); + _params.emplace_back(count); + _params.emplace_back(location); } void Batch::_glColor4f(float red, float green, float blue, float alpha) { ADD_COMMAND(glColor4f); - _params.push_back(alpha); - _params.push_back(blue); - _params.push_back(green); - _params.push_back(red); -} \ No newline at end of file + _params.emplace_back(alpha); + _params.emplace_back(blue); + _params.emplace_back(green); + _params.emplace_back(red); +} diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index b56b5ee84b..628e9e2d04 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -390,7 +390,7 @@ public: size_t size() const { return _items.size(); } size_t cache(const Data& data) { size_t offset = _items.size(); - _items.push_back(Cache(data)); + _items.emplace_back(data); return offset; } From b73fe2484813d0177dffd9970abbff0617e248bc Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 7 Jul 2016 09:56:26 -0700 Subject: [PATCH 36/41] PR feedback --- libraries/gpu/src/gpu/Batch.cpp | 12 ++++++------ libraries/gpu/src/gpu/Batch.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 2c4c29cee9..9161ee3642 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -30,12 +30,12 @@ ProfileRangeBatch::~ProfileRangeBatch() { using namespace gpu; -size_t Batch::_commandsMax { 128 }; -size_t Batch::_commandOffsetsMax { 128 }; -size_t Batch::_paramsMax { 128 }; -size_t Batch::_dataMax { 128 }; -size_t Batch::_objectsMax { 128 }; -size_t Batch::_drawCallInfosMax { 128 }; +size_t Batch::_commandsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_commandOffsetsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_paramsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_dataMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_objectsMax { BATCH_PREALLOCATE_MIN }; +size_t Batch::_drawCallInfosMax { BATCH_PREALLOCATE_MIN }; Batch::Batch() { _commands.reserve(_commandsMax); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 628e9e2d04..9cf1ca8269 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -26,7 +26,7 @@ #include "Transform.h" class QDebug; - +#define BATCH_PREALLOCATE_MIN 128 namespace gpu { enum ReservedSlot { @@ -481,7 +481,7 @@ protected: }; template -size_t Batch::Cache::_max = 128; +size_t Batch::Cache::_max = BATCH_PREALLOCATE_MIN; } From dfdc1ca1c6e2f3d93c7f827babb2040d882c0e71 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 7 Jul 2016 11:14:56 -0700 Subject: [PATCH 37/41] Ensuring render thread is highest priority --- tests/render-perf/src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index c953e51700..5367c65d94 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -319,6 +319,7 @@ public: } QTestWindow() { + QThread::currentThread()->setPriority(QThread::HighestPriority); AbstractViewStateInterface::setInstance(this); _octree = DependencyManager::set(false, this, nullptr); _octree->init(); From 23700dc90f3038de7397f9b43aa9cf59df1453b0 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 7 Jul 2016 13:16:59 -0700 Subject: [PATCH 38/41] Ensure newly shown windows are popped to the top of the zorder --- interface/resources/qml/windows/Window.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index e3e70c1e74..82bcf011e9 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -209,6 +209,9 @@ Fadable { var targetVisibility = getTargetVisibility(); if (targetVisibility === visible) { + if (force) { + window.raise(); + } return; } From e4828b03b16a7952bb96e300a44cc509c3a43cb6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 7 Jul 2016 13:32:30 -0700 Subject: [PATCH 39/41] fix crash when building shape before model loaded --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7eb7d87566..933a6ca5c6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -594,7 +594,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { // the model is still being downloaded. return false; - } else if (type == SHAPE_TYPE_STATIC_MESH) { + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { return (_model && _model->isLoaded()); } return true; From 828be940895fe4d2857a5bd7c2129f40400b8b88 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 7 Jul 2016 14:04:59 -0700 Subject: [PATCH 40/41] Fix rendering of overlay spheres with alpha --- interface/src/ui/overlays/Sphere3DOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 85530d1376..bbdd886d11 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -58,7 +58,7 @@ void Sphere3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Sphere3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withOwnPipeline(); + auto builder = render::ShapeKey::Builder(); if (getAlpha() != 1.0f) { builder.withTranslucent(); } From 99fc472d772ab942f92249ff7d40eb13fd716374 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Jul 2016 14:06:57 -0700 Subject: [PATCH 41/41] fixed errors in Browser.qml and UpdateDialog.qml --- interface/resources/qml/Browser.qml | 10 ++++--- interface/resources/qml/UpdateDialog.qml | 37 ++++++++---------------- interface/src/ui/UpdateDialog.cpp | 8 ----- interface/src/ui/UpdateDialog.h | 8 ++--- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 8c8cf05444..e9b99331c7 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -3,12 +3,14 @@ import QtQuick.Controls 1.2 import QtWebEngine 1.1 import "controls-uit" +import "styles" as HifiStyles import "styles-uit" import "windows" ScrollingWindow { id: root HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifistyles } title: "Browser" resizable: true destroyOnHidden: true @@ -46,7 +48,7 @@ ScrollingWindow { id: back; enabled: webview.canGoBack; text: hifi.glyphs.backward - color: enabled ? hifi.colors.text : hifi.colors.disabledText + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goBack() } } @@ -55,7 +57,7 @@ ScrollingWindow { id: forward; enabled: webview.canGoForward; text: hifi.glyphs.forward - color: enabled ? hifi.colors.text : hifi.colors.disabledText + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } @@ -64,7 +66,7 @@ ScrollingWindow { id: reload; enabled: webview.canGoForward; text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload - color: enabled ? hifi.colors.text : hifi.colors.disabledText + color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } @@ -105,7 +107,7 @@ ScrollingWindow { focus: true colorScheme: hifi.colorSchemes.dark placeholderText: "Enter URL" - Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { switch(event.key) { case Qt.Key_Enter: diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 91dc210eda..ca3a2da577 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -3,13 +3,16 @@ import QtQuick 2.3 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 + import "controls-uit" +import "styles" as HifiStyles import "styles-uit" import "windows" ScrollingWindow { id: root HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifistyles } objectName: "UpdateDialog" width: updateDialog.implicitWidth height: updateDialog.implicitHeight @@ -40,22 +43,6 @@ ScrollingWindow { width: updateDialog.contentWidth + updateDialog.borderWidth * 2 height: mainContent.height + updateDialog.borderWidth * 2 - updateDialog.closeMargin / 2 - - MouseArea { - width: parent.width - height: parent.height - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - drag { - target: root - minimumX: 0 - minimumY: 0 - maximumX: root.parent ? root.maximumX : 0 - maximumY: root.parent ? root.maximumY : 0 - } - } } Image { @@ -89,7 +76,7 @@ ScrollingWindow { text: "Update Available" font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.5 + pixelSize: hifistyles.fonts.pixelSize * 1.5 weight: Font.DemiBold } color: "#303030" @@ -100,10 +87,10 @@ ScrollingWindow { text: updateDialog.updateAvailableDetails font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 0.6 + pixelSize: hifistyles.fonts.pixelSize * 0.6 letterSpacing: -0.5 } - color: hifi.colors.text + color: hifistyles.colors.text anchors { top: updateAvailable.bottom } @@ -130,12 +117,12 @@ ScrollingWindow { Text { id: releaseNotes wrapMode: Text.Wrap - width: parent.width - updateDialog.closeMargin + width: parent.parent.width - updateDialog.closeMargin text: updateDialog.releaseNotes - color: hifi.colors.text + color: hifistyles.colors.text font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 0.65 + pixelSize: hifistyles.fonts.pixelSize * 0.65 } } } @@ -157,7 +144,7 @@ ScrollingWindow { color: "#0c9ab4" // Same as logo font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.2 + pixelSize: hifistyles.fonts.pixelSize * 1.2 weight: Font.DemiBold } anchors { @@ -169,7 +156,7 @@ ScrollingWindow { MouseArea { id: cancelButtonAction anchors.fill: parent - onClicked: updateDialog.closeDialog() + onClicked: root.shown = false cursorShape: "PointingHandCursor" } } @@ -185,7 +172,7 @@ ScrollingWindow { color: "#0c9ab4" // Same as logo font { family: updateDialog.fontFamily - pixelSize: hifi.fonts.pixelSize * 1.2 + pixelSize: hifistyles.fonts.pixelSize * 1.2 weight: Font.DemiBold } anchors { diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 437e173807..60acd0895b 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -48,14 +48,6 @@ const QString& UpdateDialog::releaseNotes() const { return _releaseNotes; } -void UpdateDialog::closeDialog() { - hide(); -} - -void UpdateDialog::hide() { - ((QQuickItem*)parent())->setVisible(false); -} - void UpdateDialog::triggerUpgrade() { auto applicationUpdater = DependencyManager::get(); applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey()); diff --git a/interface/src/ui/UpdateDialog.h b/interface/src/ui/UpdateDialog.h index 84d390c942..4adff90283 100644 --- a/interface/src/ui/UpdateDialog.h +++ b/interface/src/ui/UpdateDialog.h @@ -21,22 +21,20 @@ class UpdateDialog : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL - Q_PROPERTY(QString updateAvailableDetails READ updateAvailableDetails) - Q_PROPERTY(QString releaseNotes READ releaseNotes) + Q_PROPERTY(QString updateAvailableDetails READ updateAvailableDetails CONSTANT) + Q_PROPERTY(QString releaseNotes READ releaseNotes CONSTANT) public: UpdateDialog(QQuickItem* parent = nullptr); const QString& updateAvailableDetails() const; const QString& releaseNotes() const; - + private: QString _updateAvailableDetails; QString _releaseNotes; protected: - void hide(); Q_INVOKABLE void triggerUpgrade(); - Q_INVOKABLE void closeDialog(); };