diff --git a/interface/resources/images/Loading-Inner-H.png b/interface/resources/images/Loading-Inner-H.png new file mode 100644 index 0000000000..cf09ea8317 Binary files /dev/null and b/interface/resources/images/Loading-Inner-H.png differ diff --git a/interface/resources/images/Loading-Outer-Ring.png b/interface/resources/images/Loading-Outer-Ring.png new file mode 100644 index 0000000000..52613949e5 Binary files /dev/null and b/interface/resources/images/Loading-Outer-Ring.png differ diff --git a/interface/resources/images/preview.png b/interface/resources/images/preview.png new file mode 100644 index 0000000000..faebbfce8f Binary files /dev/null and b/interface/resources/images/preview.png differ 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/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; } 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() { 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(); }; 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; } } 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(); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 18f39cd3df..e0c87fbbed 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 @@ -612,8 +615,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 } @@ -621,9 +630,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 f1aa1edc81..c2497e5740 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -21,6 +21,11 @@ #include #include +#include +#include + +#include + #include "../Logging.h" #include "../CompositorHelper.h" @@ -58,9 +63,33 @@ bool HmdDisplayPlugin::internalActivate() { _eyeInverseProjections[eye] = glm::inverse(_eyeProjections[eye]); }); + if (_previewTextureID == 0) { + 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::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 +225,7 @@ static ProgramPtr getReprojectionProgram() { } #endif +static GLint PREVIEW_TEXTURE_LOCATION = -1; static const char * LASER_VS = R"VS(#version 410 core uniform mat4 mvp = mat4(1); @@ -227,14 +257,24 @@ void main() { 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(_laserProgram, LASER_VS, LASER_FS); - _laserGeometry = loadLaser(_laserProgram); - compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); using namespace oglplus; + if (!_enablePreview) { + const std::string version("#version 410 core\n"); + compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); + PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); + } + + compileProgram(_laserProgram, LASER_VS, LASER_FS); + _laserGeometry = loadLaser(_laserProgram); + + 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(); @@ -243,6 +283,7 @@ void HmdDisplayPlugin::customizeContext() { void HmdDisplayPlugin::uncustomizeContext() { _sphereSection.reset(); _compositeFramebuffer.reset(); + _previewProgram.reset(); _reprojectionProgram.reset(); _laserProgram.reset(); _laserGeometry.reset(); @@ -335,30 +376,32 @@ void HmdDisplayPlugin::internalPresent() { hmdPresent(); // screen preview mirroring + auto window = _container->getPrimaryWidget(); + 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) { - auto window = _container->getPrimaryWidget(); - auto windowSize = toGlm(window->size()); - 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; @@ -373,6 +416,21 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); + } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { + 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); + 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 fada15d864..8e48690fd1 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -40,6 +40,7 @@ protected: virtual void updatePresentPose(); bool internalActivate() override; + void internalDeactivate() override; void compositeScene() override; void compositeOverlay() override; void compositePointer() override; @@ -89,8 +90,17 @@ private: bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; - ShapeWrapperPtr _sphereSection; + bool _firstPreview { true }; + + ProgramPtr _previewProgram; + float _previewAspect { 0 }; + GLuint _previewTextureID { 0 }; + glm::uvec2 _prevWindowSize { 0, 0 }; + qreal _prevDevicePixelRatio { 0 }; + ProgramPtr _reprojectionProgram; + ShapeWrapperPtr _sphereSection; + ProgramPtr _laserProgram; ShapeWrapperPtr _laserGeometry; }; 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); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 43fea75eac..416a38d27c 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; } @@ -423,7 +430,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; @@ -593,7 +601,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; @@ -607,7 +615,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(); @@ -691,14 +699,19 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } info.setParams(type, dimensions, _compoundShapeURL); - } else if (type == SHAPE_TYPE_STATIC_MESH) { + } 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 && _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); @@ -716,30 +729,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 +772,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 +835,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); 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/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) { 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/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 6dc1d63ca8..9161ee3642 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -26,28 +26,43 @@ 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; -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 { 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::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) { @@ -72,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(); } @@ -82,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(); } @@ -92,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(); } @@ -104,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(); } @@ -116,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(); } @@ -133,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) { @@ -163,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) { @@ -175,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); } @@ -191,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) { @@ -251,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) { @@ -262,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) { @@ -299,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() { @@ -341,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; } @@ -422,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() { @@ -458,22 +475,11 @@ 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) ADD_COMMAND(pushProfileRange); - _params.push_back(_profileRanges.cache(name)); + _params.emplace_back(_profileRanges.cache(name)); #endif } @@ -490,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) { @@ -500,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) { @@ -509,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 4e51038368..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 { @@ -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,15 +372,25 @@ 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(); - _items.push_back(Cache(data)); + _items.emplace_back(data); return offset; } @@ -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 = BATCH_PREALLOCATE_MIN; + } #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 3c9f4aee62..2d0baa0497 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -232,11 +232,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(); } }; 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, 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) { 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 { 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 }; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 2c032f7005..6da842b7b9 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -68,18 +68,48 @@ 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 + // 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 }; + 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; diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js new file mode 100644 index 0000000000..375e3d8004 --- /dev/null +++ b/scripts/developer/tests/loadedMachine.js @@ -0,0 +1,192 @@ +"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 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(); +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, 'and', interestingProperties); +var propertiesToGet = ['type', 'visible'].concat(interestingProperties); +allEntities.forEach(function (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) { + 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; + case 'dynamic': + toggleProperty(load, 1); + 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; + case 'dynamic': + toggleProperty(load, 0); + 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(); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ecece8c4f7..373f203e80 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.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 6810bae590..4e87fcbf71 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/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 @@ + + 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); diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt new file mode 100644 index 0000000000..d4f90fdace --- /dev/null +++ b/tests/render-perf/CMakeLists.txt @@ -0,0 +1,18 @@ + +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/") + +# 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..5367c65d94 --- /dev/null +++ b/tests/render-perf/src/main.cpp @@ -0,0 +1,651 @@ +// +// 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() { + QThread::currentThread()->setPriority(QThread::HighestPriority); + 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"