diff --git a/.gitignore b/.gitignore index f085b676e4..c1eef3817f 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ gvr-interface/libs/* # ignore files for various dev environments TAGS *.sw[po] +*.qmlc # ignore QML compilation output *.qmlc diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt index a30396c6fd..e3ba36a440 100644 --- a/cmake/externals/hifiAudioCodec/CMakeLists.txt +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -if (NOT ANDROID) - - if (WIN32 OR APPLE) - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip - URL_MD5 23ec3fe51eaa155ea159a4971856fc13 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - else () - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip - URL_MD5 7d37914a18aa4de971d2f45dd3043bde - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - endif() - - # Hide this external target (for ide users) - set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - - ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) - - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) - - if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) - elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - elseif(NOT ANDROID) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - endif() - +if (WIN32) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip) + set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67) +elseif (APPLE) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip) + set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba) +elseif (ANDROID) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip) + set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683) +elseif (UNIX) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip) + set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481) +else() + return() +endif() + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) +else() + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) endif() diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 47d28486a9..3debc8b9e7 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -11,8 +11,11 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs + import "../../styles-uit" import "../../controls-uit" +import "../dialogs" Rectangle { id: newModelDialog @@ -25,6 +28,15 @@ Rectangle { property bool punctuationMode: false property bool keyboardRasied: false + function errorMessageBox(message) { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } + Item { id: column1 anchors.rightMargin: 10 @@ -98,7 +110,6 @@ Rectangle { CheckBox { id: dynamic text: qsTr("Dynamic") - } Row { @@ -117,6 +128,7 @@ Rectangle { Text { id: text2 width: 160 + x: dynamic.width / 2 color: "#ffffff" text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") wrapMode: Text.WordWrap @@ -139,15 +151,40 @@ Rectangle { ComboBox { id: collisionType + + property int priorIndex: 0 + property string staticMeshCollisionText: "Exact - All polygons" + property var collisionArray: ["No Collision", + "Basic - Whole model", + "Good - Sub-meshes", + staticMeshCollisionText, + "Box", + "Sphere"] + width: 200 z: 100 transformOrigin: Item.Center - model: ["No Collision", - "Basic - Whole model", - "Good - Sub-meshes", - "Exact - All polygons", - "Box", - "Sphere"] + model: collisionArray + + onCurrentIndexChanged: { + if (collisionArray[currentIndex] === staticMeshCollisionText) { + + if (dynamic.checked) { + currentIndex = priorIndex; + + errorMessageBox("Models with Automatic Collisions set to \"" + + staticMeshCollisionText + "\" cannot be dynamic."); + //--EARLY EXIT--( Can't have a static mesh model that's dynamic ) + return; + } + + dynamic.enabled = false; + } else { + dynamic.enabled = true; + } + + priorIndex = currentIndex; + } } Row { @@ -155,10 +192,10 @@ Rectangle { width: 200 height: 400 spacing: 5 - - anchors { - rightMargin: 15 - } + + anchors.horizontalCenter: column3.horizontalCenter + anchors.horizontalCenterOffset: -20 + Button { id: button1 text: qsTr("Add") diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index d3c8746e16..01ccbd0d9a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -54,7 +54,9 @@ void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float delta float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET; float maxTime = glm::max(renderTime, engineRunTime); const float BLEND_TIMESCALE = 0.3f; // sec - float blend = BLEND_TIMESCALE / deltaTimeSec; + const float MIN_DELTA_TIME = 0.001f; + const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME); + float blend = BLEND_TIMESCALE / safeDeltaTime; if (blend > 1.0f) { blend = 1.0f; } diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 808396c901..1adf5650dd 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -71,6 +71,12 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis return false; } +bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) { + _selectedItemsListMap.insert(listName, GameplayObjects()); + emit selectedItemsListChanged(listName); + return true; +} + template bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) { GameplayObjects currentList = _selectedItemsListMap.value(listName); currentList.addToGameplayObjects(idToAdd); diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index d1a372c5c4..d9003c2c32 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -56,11 +56,45 @@ public: GameplayObjects getList(const QString& listName); + /**jsdoc + * Prints out the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.printList + * @param listName {string} name of the selection + */ Q_INVOKABLE void printList(const QString& listName); + /**jsdoc + * Removes a named selection from the list of selections. + * @function Selection.removeListFromMap + * @param listName {string} name of the selection + * @returns {bool} true if the selection existed and was successfully removed. + */ Q_INVOKABLE bool removeListFromMap(const QString& listName); + /**jsdoc + * Add an item in a selection. + * @function Selection.addToSelectedItemsList + * @param listName {string} name of the selection + * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") + * @param id {EntityID} the Id of the item to add to the selection + * @returns {bool} true if the item was successfully added. + */ Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc + * Remove an item from a selection. + * @function Selection.removeFromSelectedItemsList + * @param listName {string} name of the selection + * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") + * @param id {EntityID} the Id of the item to remove + * @returns {bool} true if the item was successfully removed. + */ Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc + * Remove all items from a selection. + * @function Selection.clearSelectedItemsList + * @param listName {string} name of the selection + * @returns {bool} true if the item was successfully cleared. + */ + Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); signals: void selectedItemsListChanged(const QString& listName); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index b5af529f2b..d40c0972e9 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -38,8 +38,6 @@ ContextOverlayInterface::ContextOverlayInterface() { _tabletScriptingInterface = DependencyManager::get(); _selectionScriptingInterface = DependencyManager::get(); - _selectionToSceneHandler.initialize("contextOverlayHighlightList"); - _entityPropertyFlags += PROP_POSITION; _entityPropertyFlags += PROP_ROTATION; _entityPropertyFlags += PROP_MARKETPLACE_ID; @@ -66,12 +64,20 @@ ContextOverlayInterface::ContextOverlayInterface() { } }); connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity); - connect(&qApp->getOverlays(), &Overlays::mousePressOnOverlay, this, &ContextOverlayInterface::contextOverlays_mousePressOnOverlay); connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay); connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); - connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged); + { + render::Transaction transaction; + initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction); + for (auto i = 1; i < MAX_SELECTION_COUNT; i++) { + auto selectionName = QString("highlightList") + QString::number(i); + initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction); + } + const render::ScenePointer& scene = qApp->getMain3DScene(); + scene->enqueueTransaction(transaction); + } auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); @@ -79,6 +85,12 @@ ContextOverlayInterface::ContextOverlayInterface() { _challengeOwnershipTimeoutTimer.setSingleShot(true); } +void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) { + handler.initialize(selectionName); + connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged); + transaction.resetSelectionHighlight(selectionName.toStdString()); +} + static const uint32_t MOUSE_HW_ID = 0; static const uint32_t LEFT_HAND_HW_ID = 1; static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 }; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 8f0a40ef8e..81e398e15d 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -47,6 +47,7 @@ class ContextOverlayInterface : public QObject, public Dependency { OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID }; std::shared_ptr _contextOverlay { nullptr }; public: + ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } @@ -75,6 +76,11 @@ private slots: void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: + + enum { + MAX_SELECTION_COUNT = 16 + }; + bool _verboseLogging { true }; bool _enabled { true }; EntityItemID _currentEntityWithContextOverlay{}; @@ -90,8 +96,9 @@ private: void disableEntityHighlight(const EntityItemID& entityItemID); void deletingEntity(const EntityItemID& entityItemID); + void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction); - SelectionToSceneHandler _selectionToSceneHandler; + SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT]; Q_INVOKABLE void startChallengeOwnershipTimer(); QTimer _challengeOwnershipTimeoutTimer; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3dad4e3fb6..f06156874b 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -39,7 +39,12 @@ static std::mutex rigRegistryMutex; static bool isEqual(const glm::vec3& u, const glm::vec3& v) { const float EPSILON = 0.0001f; - return glm::length(u - v) / glm::length(u) <= EPSILON; + float uLen = glm::length(u); + if (uLen == 0.0f) { + return glm::length(v) <= EPSILON; + } else { + return (glm::length(u - v) / uLen) <= EPSILON; + } } static bool isEqual(const glm::quat& p, const glm::quat& q) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d2dc116e15..49d2431098 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -453,7 +453,9 @@ void Avatar::applyPositionDelta(const glm::vec3& delta) { void Avatar::measureMotionDerivatives(float deltaTime) { PerformanceTimer perfTimer("derivatives"); // linear - float invDeltaTime = 1.0f / deltaTime; + const float MIN_DELTA_TIME = 0.001f; + const float safeDeltaTime = glm::max(deltaTime, MIN_DELTA_TIME); + float invDeltaTime = 1.0f / safeDeltaTime; // Floating point error prevents us from computing velocity in a naive way // (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator. glm::vec3 velocity = _positionDeltaAccumulator * invDeltaTime; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fcfeaf7741..4dabbb3ff5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -15,21 +15,7 @@ #include #include #include - -/* VS2010 defines stdint.h, but not inttypes.h */ -#if defined(_MSC_VER) -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef signed long long int64_t; -typedef unsigned long long quint64; -#define PRId64 "I64d" -#else #include -#endif #include #include diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index 1b898e5c22..a1cf27afa6 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -110,6 +110,7 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { GL_SHORT, GL_UNSIGNED_SHORT, GL_BYTE, + GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 192a82dafc..528a2b524b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -212,6 +212,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NUINT8: result = GL_RGBA8; break; + case gpu::NUINT2: + result = GL_RGBA2; + break; case gpu::NINT8: result = GL_RGBA8_SNORM; break; @@ -498,6 +501,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -548,6 +552,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -660,6 +665,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.format = GL_RGBA; texel.internalFormat = GL_RGBA8_SNORM; break; + case gpu::NUINT2: + texel.format = GL_RGBA; + texel.internalFormat = GL_RGBA2; + break; case gpu::NUINT32: case gpu::NINT32: case gpu::COMPRESSED: diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 7efe4d3ed6..3b153097cf 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -19,6 +19,8 @@ const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA }; const Element Element::COLOR_BGRA_32{ VEC4, NUINT8, BGRA }; const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA }; +const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA }; + const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 0654b23581..9d5d2fc49d 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -38,6 +38,7 @@ enum Type : uint8_t { NUINT16, NINT8, NUINT8, + NUINT2, COMPRESSED, @@ -309,6 +310,7 @@ public: static const Element COLOR_SRGBA_32; static const Element COLOR_BGRA_32; static const Element COLOR_SBGRA_32; + static const Element COLOR_RGBA_2; static const Element COLOR_R11G11B10; static const Element COLOR_RGB9E5; static const Element COLOR_COMPRESSED_RED; diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index f1257e7c83..4fcc9ab0f9 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -220,7 +220,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const { } } -bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { +bool Framebuffer::assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { if (isSwapchain()) { return false; } @@ -244,20 +244,59 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For // assign the new one _depthStencilBuffer = TextureView(texture, subresource, format); - _bufferMask = ( _bufferMask & ~BUFFER_DEPTHSTENCIL); - if (texture) { - if (format.getSemantic() == gpu::DEPTH) { - _bufferMask |= BUFFER_DEPTH; - } else if (format.getSemantic() == gpu::STENCIL) { - _bufferMask |= BUFFER_STENCIL; - } else if (format.getSemantic() == gpu::DEPTH_STENCIL) { - _bufferMask |= BUFFER_DEPTHSTENCIL; - } - } - return true; } +bool Framebuffer::setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::DEPTH || format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_DEPTH; + } else { + return false; + } + } + + return true; + } + return false; +} + +bool Framebuffer::setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::STENCIL || format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_STENCIL; + } else { + return false; + } + } + + return true; + } + return false; +} + +bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::DEPTH) { + _bufferMask |= BUFFER_DEPTH; + } else if (format.getSemantic() == gpu::STENCIL) { + _bufferMask |= BUFFER_STENCIL; + } else if (format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_DEPTHSTENCIL; + } + } + + return true; + } + return false; +} + TexturePointer Framebuffer::getDepthStencilBuffer() const { if (isSwapchain()) { return TexturePointer(); diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index b3a500d68f..b3cf0fbba3 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -107,6 +107,8 @@ public: TexturePointer getRenderBuffer(uint32 slot) const; uint32 getRenderBufferSubresource(uint32 slot) const; + bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); + bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); TexturePointer getDepthStencilBuffer() const; uint32 getDepthStencilBufferSubresource() const; @@ -168,6 +170,7 @@ protected: uint16 _numSamples = 0; void updateSize(const TexturePointer& texture); + bool assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource); // Non exposed Framebuffer(const Framebuffer& framebuffer) = delete; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 9feca4a3c9..b9b8544601 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -193,13 +193,17 @@ TransformObject getTransformObject() { } <@endfunc@> -<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> - { // transformModelToClipPos +<@func transformModelToMonoClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> + { // transformModelToMonoClipPos vec4 eyeWAPos; <$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$> - <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; - + } +<@endfunc@> + +<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> + { // transformModelToClipPos + <$transformModelToMonoClipPos($cameraTransform$, $objectTransform$, $modelPos$, $clipPos$)$> <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> } <@endfunc@> diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 81a63a696c..fc9ea525e6 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -12,21 +12,7 @@ #ifndef hifi_OctreeQuery_h #define hifi_OctreeQuery_h -/* VS2010 defines stdint.h, but not inttypes.h */ -#if defined(_MSC_VER) -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef signed long long int64_t; -typedef unsigned long long quint64; -#define PRId64 "I64d" -#else #include -#endif - #include #include diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index cd0fba848a..f484f32fdf 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -16,6 +16,40 @@ #include "ShapeFactory.h" #include "BulletUtil.h" + +class StaticMeshShape : public btBvhTriangleMeshShape { +public: + StaticMeshShape() = delete; + + StaticMeshShape(btTriangleIndexVertexArray* dataArray) + : btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { + assert(_dataArray); + } + + ~StaticMeshShape() { + assert(_dataArray); + IndexedMeshArray& meshes = _dataArray->getIndexedMeshArray(); + for (int32_t i = 0; i < meshes.size(); ++i) { + btIndexedMesh mesh = meshes[i]; + mesh.m_numTriangles = 0; + delete [] mesh.m_triangleIndexBase; + mesh.m_triangleIndexBase = nullptr; + mesh.m_numVertices = 0; + delete [] mesh.m_vertexBase; + mesh.m_vertexBase = nullptr; + } + meshes.clear(); + delete _dataArray; + _dataArray = nullptr; + } + +private: + // the StaticMeshShape owns its vertex/index data + btTriangleIndexVertexArray* _dataArray; +}; + +// the dataArray must be created before we create the StaticMeshShape + // These are the same normalized directions used by the btShapeHull class. // 12 points for the face centers of a dodecahedron plus another 30 points // for the midpoints the edges, for a total of 42. @@ -230,23 +264,6 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { return dataArray; } -// util method -void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { - assert(dataArray); - IndexedMeshArray& meshes = dataArray->getIndexedMeshArray(); - for (int32_t i = 0; i < meshes.size(); ++i) { - btIndexedMesh mesh = meshes[i]; - mesh.m_numTriangles = 0; - delete [] mesh.m_triangleIndexBase; - mesh.m_triangleIndexBase = nullptr; - mesh.m_numVertices = 0; - delete [] mesh.m_vertexBase; - mesh.m_vertexBase = nullptr; - } - meshes.clear(); - delete dataArray; -} - const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); @@ -431,7 +448,6 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) { assert(shape); // ShapeFactory is responsible for deleting all shapes, even the const ones that are stored // in the ShapeManager, so we must cast to non-const here when deleting. - // so we cast to non-const here when deleting memory. btCollisionShape* nonConstShape = const_cast(shape); if (nonConstShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) { btCompoundShape* compoundShape = static_cast(nonConstShape); @@ -448,14 +464,3 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) { } delete nonConstShape; } - -// the dataArray must be created before we create the StaticMeshShape -ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray) -: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { - assert(dataArray); -} - -ShapeFactory::StaticMeshShape::~StaticMeshShape() { - deleteStaticMeshArray(_dataArray); - _dataArray = nullptr; -} diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index 2bf79f390c..704a7804b3 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -17,25 +17,11 @@ #include -// translates between ShapeInfo and btShape +// The ShapeFactory assembles and correctly disassembles btCollisionShapes. namespace ShapeFactory { const btCollisionShape* createShapeFromInfo(const ShapeInfo& info); void deleteShape(const btCollisionShape* shape); - - //btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info); - //void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray); - - class StaticMeshShape : public btBvhTriangleMeshShape { - public: - StaticMeshShape() = delete; - StaticMeshShape(btTriangleIndexVertexArray* dataArray); - ~StaticMeshShape(); - - private: - // the StaticMeshShape owns its vertex/index data - btTriangleIndexVertexArray* _dataArray; - }; }; #endif // hifi_ShapeFactory_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 77716f671b..97b9e5dab1 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -32,7 +32,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return nullptr; } - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { shapeRef->refCount++; @@ -50,7 +50,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { } // private helper method -bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) { +bool ShapeManager::releaseShapeByKey(const HashKey& key) { ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { if (shapeRef->refCount > 0) { @@ -88,7 +88,7 @@ bool ShapeManager::releaseShape(const btCollisionShape* shape) { void ShapeManager::collectGarbage() { int numShapes = _pendingGarbage.size(); for (int i = 0; i < numShapes; ++i) { - DoubleHashKey& key = _pendingGarbage[i]; + HashKey& key = _pendingGarbage[i]; ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { ShapeFactory::deleteShape(shapeRef->shape); @@ -99,7 +99,7 @@ void ShapeManager::collectGarbage() { } int ShapeManager::getNumReferences(const ShapeInfo& info) const { - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { return shapeRef->refCount; diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index ed81b5e8f8..d75bb1dc4a 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -17,7 +17,29 @@ #include -#include "DoubleHashKey.h" +#include "HashKey.h" + +// The ShapeManager handles the ref-counting on shared shapes: +// +// Each object added to the physics simulation gets a corresponding btRigidBody. +// The body has a btCollisionShape that represents the contours of its collision +// surface. Multiple bodies may have the same shape. Rather than create a unique +// btCollisionShape instance for every body with a particular shape we can instead +// use a single shape instance for all of the bodies. This is called "shape +// sharing". +// +// When body needs a new shape a description of ths shape (ShapeInfo) is assembled +// and a request is sent to the ShapeManager for a corresponding btCollisionShape +// pointer. The ShapeManager will compute a hash of the ShapeInfo's data and use +// that to find the shape in its map. If it finds one it increments the ref-count +// and returns the pointer. If not it asks the ShapeFactory to create it, adds an +// entry in the map with a ref-count of 1, and returns the pointer. +// +// When a body stops using a shape the ShapeManager must be informed so it can +// decrement its ref-count. When a ref-count drops to zero the ShapeManager +// doesn't delete it right away. Instead it puts the shape's key on a list delete +// later. When that list grows big enough the ShapeManager will remove any matching +// entries that still have zero ref-count. class ShapeManager { public: @@ -41,18 +63,19 @@ public: bool hasShape(const btCollisionShape* shape) const; private: - bool releaseShapeByKey(const DoubleHashKey& key); + bool releaseShapeByKey(const HashKey& key); class ShapeReference { public: int refCount; const btCollisionShape* shape; - DoubleHashKey key; + HashKey key; ShapeReference() : refCount(0), shape(nullptr) {} }; - btHashMap _shapeMap; - btAlignedObjectArray _pendingGarbage; + // btHashMap is required because it supports memory alignment of the btCollisionShapes + btHashMap _shapeMap; + btAlignedObjectArray _pendingGarbage; }; #endif // hifi_ShapeManager_h diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 2ea3683c4a..2d2c0ed150 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -14,6 +14,7 @@ #include std::string BackgroundStage::_stageName { "BACKGROUND_STAGE"}; +const BackgroundStage::Index BackgroundStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; BackgroundStage::Index BackgroundStage::findBackground(const BackgroundPointer& background) const { auto found = _backgroundMap.find(background); diff --git a/libraries/render-utils/src/BackgroundStage.h b/libraries/render-utils/src/BackgroundStage.h index eab7c94f0d..4e0e09db5b 100644 --- a/libraries/render-utils/src/BackgroundStage.h +++ b/libraries/render-utils/src/BackgroundStage.h @@ -27,7 +27,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using BackgroundPointer = model::SunSkyStagePointer; diff --git a/libraries/render-utils/src/HazeStage.cpp b/libraries/render-utils/src/HazeStage.cpp index 7a12ee3c8a..016282d16f 100644 --- a/libraries/render-utils/src/HazeStage.cpp +++ b/libraries/render-utils/src/HazeStage.cpp @@ -14,6 +14,7 @@ #include std::string HazeStage::_stageName { "HAZE_STAGE"}; +const HazeStage::Index HazeStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; FetchHazeStage::FetchHazeStage() { _haze = std::make_shared(); diff --git a/libraries/render-utils/src/HazeStage.h b/libraries/render-utils/src/HazeStage.h index 102f299d8f..c355f06644 100644 --- a/libraries/render-utils/src/HazeStage.h +++ b/libraries/render-utils/src/HazeStage.h @@ -28,7 +28,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using HazePointer = model::HazePointer; diff --git a/libraries/render-utils/src/Outline.slf b/libraries/render-utils/src/Highlight.slf similarity index 72% rename from libraries/render-utils/src/Outline.slf rename to libraries/render-utils/src/Highlight.slf index 68ef870cba..bf65f92613 100644 --- a/libraries/render-utils/src/Outline.slf +++ b/libraries/render-utils/src/Highlight.slf @@ -1,5 +1,5 @@ -// Outline.slf -// Add outline effect based on two zbuffers : one containing the total scene z and another +// Highlight.slf +// Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version without the fill effect inside the silhouette. // @@ -9,5 +9,5 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include Outline.slh@> +<@include Highlight.slh@> <$main(0)$> diff --git a/libraries/render-utils/src/Outline.slh b/libraries/render-utils/src/Highlight.slh similarity index 67% rename from libraries/render-utils/src/Outline.slh rename to libraries/render-utils/src/Highlight.slh index ac56e4c95c..2faa10682e 100644 --- a/libraries/render-utils/src/Outline.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> <$declareDeferredFrameTransform()$> -<@include Outline_shared.slh@> +<@include Highlight_shared.slh@> -uniform outlineParamsBuffer { - OutlineParameters params; +uniform highlightParamsBuffer { + HighlightParameters params; }; uniform sampler2D sceneDepthMap; -uniform sampler2D outlinedDepthMap; +uniform sampler2D highlightedDepthMap; in vec2 varTexCoord0; out vec4 outFragColor; @@ -35,30 +35,26 @@ void main(void) { // We offset by half a texel to be centered on the depth sample. If we don't do this // the blur will have a different width between the left / right sides and top / bottom // sides of the silhouette - vec2 halfTexel = getInvWidthHeight() / 2; - vec2 texCoord0 = varTexCoord0+halfTexel; - float outlinedDepth = texture(outlinedDepthMap, texCoord0).x; + float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; float intensity = 0.0; - if (outlinedDepth < FAR_Z) { - // We're not on the far plane so we are on the outlined object, thus no outline to do! + if (highlightedDepth < FAR_Z) { + // We're not on the far plane so we are on the highlighted object, thus no outline to do! <@if IS_FILLED@> // But we need to fill the interior - float sceneDepth = texture(sceneDepthMap, texCoord0).x; + float sceneDepth = texture(sceneDepthMap, varTexCoord0).x; // Transform to linear depth for better precision - outlinedDepth = -evalZeyeFromZdb(outlinedDepth); + highlightedDepth = -evalZeyeFromZdb(highlightedDepth); sceneDepth = -evalZeyeFromZdb(sceneDepth); // Are we occluded? - if (sceneDepth < (outlinedDepth-LINEAR_DEPTH_BIAS)) { - intensity = params._fillOpacityOccluded; - } else { - intensity = params._fillOpacityUnoccluded; - } + intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity; <@else@> discard; <@endif@> } else { + vec2 halfTexel = getInvWidthHeight() / 2; + vec2 texCoord0 = varTexCoord0+halfTexel; float weight = 0.0; vec2 deltaUv = params._size / params._blurKernelSize; vec2 lineStartUv = texCoord0 - params._size / 2.0; @@ -74,9 +70,9 @@ void main(void) { for (x=0 ; x=0.0 && uv.x<=1.0) { - outlinedDepth = texture(outlinedDepthMap, uv).x; - intensity += (outlinedDepth < FAR_Z) ? 1.0 : 0.0; - weight += 1.f; + highlightedDepth = texture(highlightedDepthMap, uv).x; + intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0; + weight += 1.0; } uv.x += deltaUv.x; } diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp new file mode 100644 index 0000000000..7c58e5ba66 --- /dev/null +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -0,0 +1,562 @@ +// +// HighlightEffect.cpp +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 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 "HighlightEffect.h" + +#include "GeometryCache.h" + +#include "CubeProjectedPolygon.h" + +#include +#include + +#include "gpu/Context.h" +#include "gpu/StandardShaderLib.h" + +#include + +#include "surfaceGeometry_copyDepth_frag.h" +#include "debug_deferred_buffer_vert.h" +#include "debug_deferred_buffer_frag.h" +#include "Highlight_frag.h" +#include "Highlight_filled_frag.h" +#include "Highlight_aabox_vert.h" +#include "nop_frag.h" + +using namespace render; + +#define OUTLINE_STENCIL_MASK 1 + +HighlightRessources::HighlightRessources() { +} + +void HighlightRessources::update(const gpu::FramebufferPointer& primaryFrameBuffer) { + auto newFrameSize = glm::ivec2(primaryFrameBuffer->getSize()); + + // If the buffer size changed, we need to delete our FBOs and recreate them at the + // new correct dimensions. + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + allocateDepthBuffer(primaryFrameBuffer); + allocateColorBuffer(primaryFrameBuffer); + } else { + if (!_depthFrameBuffer) { + allocateDepthBuffer(primaryFrameBuffer); + } + if (!_colorFrameBuffer) { + allocateColorBuffer(primaryFrameBuffer); + } + } +} + +void HighlightRessources::allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { + _colorFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithStencil")); + _colorFrameBuffer->setRenderBuffer(0, primaryFrameBuffer->getRenderBuffer(0)); + _colorFrameBuffer->setStencilBuffer(_depthStencilTexture, _depthStencilTexture->getTexelFormat()); +} + +void HighlightRessources::allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); + _depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, _frameSize.x, _frameSize.y)); + _depthFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("highlightDepth")); + _depthFrameBuffer->setDepthStencilBuffer(_depthStencilTexture, depthFormat); +} + +gpu::FramebufferPointer HighlightRessources::getDepthFramebuffer() { + assert(_depthFrameBuffer); + return _depthFrameBuffer; +} + +gpu::TexturePointer HighlightRessources::getDepthTexture() { + return _depthStencilTexture; +} + +gpu::FramebufferPointer HighlightRessources::getColorFramebuffer() { + assert(_colorFrameBuffer); + return _colorFrameBuffer; +} + +HighlightSharedParameters::HighlightSharedParameters() { + _highlightIds.fill(render::HighlightStage::INVALID_INDEX); +} + +float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) { + return ceilf(style.outlineWidth * frameBufferHeight / 400.0f); +} + +PrepareDrawHighlight::PrepareDrawHighlight() { + _ressources = std::make_shared(); +} + +void PrepareDrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + auto destinationFrameBuffer = inputs; + + _ressources->update(destinationFrameBuffer); + outputs = _ressources; +} + +gpu::PipelinePointer DrawHighlightMask::_stencilMaskPipeline; +gpu::PipelinePointer DrawHighlightMask::_stencilMaskFillPipeline; + +DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex, + render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters) : + _highlightPassIndex{ highlightIndex }, + _shapePlumber { shapePlumber }, + _sharedParameters{ parameters } { +} + +void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + auto& inShapes = inputs.get0(); + + if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO, gpu::State::STENCIL_OP_REPLACE)); + state->setColorWriteMask(false, false, false, false); + state->setCullMode(gpu::State::CULL_FRONT); + + gpu::StatePointer fillState = gpu::StatePointer(new gpu::State()); + fillState->setDepthTest(false, false, gpu::LESS_EQUAL); + fillState->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); + fillState->setColorWriteMask(false, false, false, false); + fillState->setCullMode(gpu::State::CULL_FRONT); + + auto vs = gpu::Shader::createVertex(std::string(Highlight_aabox_vert)); + auto ps = gpu::Shader::createPixel(std::string(nop_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _stencilMaskPipeline = gpu::Pipeline::create(program, state); + _stencilMaskFillPipeline = gpu::Pipeline::create(program, fillState); + } + + if (!_boundsBuffer) { + _boundsBuffer = std::make_shared(sizeof(render::ItemBound)); + } + + auto highlightStage = renderContext->_scene->getStage(render::HighlightStage::getName()); + auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex]; + + if (!inShapes.empty() && !render::HighlightStage::isIndexInvalid(highlightId)) { + auto ressources = inputs.get1(); + auto& highlight = highlightStage->getHighlight(highlightId); + + RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + + // Render full screen + outputs = args->_viewport; + + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(ressources->getDepthFramebuffer()); + batch.clearDepthStencilFramebuffer(1.0f, 0); + }); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + render::ItemBounds itemBounds; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + std::vector skinnedShapeKeys{}; + + // Iterate through all inShapes and render the unskinned + args->_shapePipeline = maskPipeline; + batch.setPipeline(maskPipeline->pipeline); + for (const auto& items : inShapes) { + itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end()); + if (items.first.isSkinned()) { + skinnedShapeKeys.push_back(items.first); + } else { + renderItems(renderContext, items.second); + } + } + + // Reiterate to render the skinned + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + + args->_shapePipeline = nullptr; + args->_batch = nullptr; + }); + + _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + // Draw stencil mask with object bounding boxes + const auto highlightWidthLoc = _stencilMaskPipeline->getProgram()->getUniforms().findLocation("outlineWidth"); + const auto securityMargin = 2.0f; + const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w); + const auto framebufferSize = ressources->getSourceFrameSize(); + + auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline; + batch.setPipeline(stencilPipeline); + batch.setResourceBuffer(0, _boundsBuffer); + batch._glUniform2f(highlightWidthLoc, blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y); + static const int NUM_VERTICES_PER_CUBE = 36; + batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_CUBE * (gpu::uint32) itemBounds.size(), 0); + }); + } else { + // Highlight rect should be null as there are no highlighted shapes + outputs = glm::ivec4(0, 0, 0, 0); + } +} + +gpu::PipelinePointer DrawHighlight::_pipeline; +gpu::PipelinePointer DrawHighlight::_pipelineFilled; + +DrawHighlight::DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters) : + _highlightPassIndex{ highlightIndex }, + _sharedParameters{ parameters } { +} + +void DrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + auto highlightFrameBuffer = inputs.get1(); + auto highlightRect = inputs.get3(); + + if (highlightFrameBuffer && highlightRect.z>0 && highlightRect.w>0) { + auto sceneDepthBuffer = inputs.get2(); + const auto frameTransform = inputs.get0(); + auto highlightedDepthTexture = highlightFrameBuffer->getDepthTexture(); + auto destinationFrameBuffer = highlightFrameBuffer->getColorFramebuffer(); + auto framebufferSize = glm::ivec2(highlightedDepthTexture->getDimensions()); + + if (sceneDepthBuffer) { + auto args = renderContext->args; + + auto highlightStage = renderContext->_scene->getStage(render::HighlightStage::getName()); + auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex]; + if (!render::HighlightStage::isIndexInvalid(highlightId)) { + auto& highlight = highlightStage->getHighlight(highlightId); + auto pipeline = getPipeline(highlight._style); + { + auto& shaderParameters = _configuration.edit(); + + shaderParameters._color = highlight._style.color; + shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity; + shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity; + shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f; + shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f))); + // Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400. + auto size = highlight._style.outlineWidth / 400.0f; + shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x; + shaderParameters._size.y = size; + } + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(destinationFrameBuffer); + + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); + batch.setPipeline(pipeline); + + batch.setUniformBuffer(HIGHLIGHT_PARAMS_SLOT, _configuration); + batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); + batch.setResourceTexture(SCENE_DEPTH_MAP_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(HIGHLIGHTED_DEPTH_MAP_SLOT, highlightedDepthTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } + } + } +} + +const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightStyle& style) { + if (!_pipeline) { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(Highlight_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("highlightParamsBuffer", HIGHLIGHT_PARAMS_SLOT)); + slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); + slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_MAP_SLOT)); + slotBindings.insert(gpu::Shader::Binding("highlightedDepthMap", HIGHLIGHTED_DEPTH_MAP_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); + + _pipeline = gpu::Pipeline::create(program, state); + + ps = gpu::Shader::createPixel(std::string(Highlight_filled_frag)); + program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram(*program, slotBindings); + _pipelineFilled = gpu::Pipeline::create(program, state); + } + return style.isFilled() ? _pipelineFilled : _pipeline; +} + +DebugHighlight::DebugHighlight() { + _geometryDepthId = DependencyManager::get()->allocateID(); +} + +DebugHighlight::~DebugHighlight() { + auto geometryCache = DependencyManager::get(); + if (geometryCache) { + geometryCache->releaseID(_geometryDepthId); + } +} + +void DebugHighlight::configure(const Config& config) { + _isDisplayEnabled = config.viewMask; +} + +void DebugHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& input) { + const auto highlightRessources = input.get0(); + const auto highlightRect = input.get1(); + + if (_isDisplayEnabled && highlightRessources && highlightRect.z>0 && highlightRect.w>0) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.setViewportTransform(args->_viewport); + batch.setFramebuffer(highlightRessources->getColorFramebuffer()); + + const auto geometryBuffer = DependencyManager::get(); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); + batch.setModelTransform(Transform()); + + const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); + + batch.setPipeline(getDepthPipeline()); + batch.setResourceTexture(0, highlightRessources->getDepthTexture()); + const glm::vec2 bottomLeft(-1.0f, -1.0f); + const glm::vec2 topRight(1.0f, 1.0f); + geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId); + + batch.setResourceTexture(0, nullptr); + }); + } +} + +void DebugHighlight::initializePipelines() { + static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; + static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; + static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; + static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); + Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, + "Could not find source placeholder"); + + auto state = std::make_shared(); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + + const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); + + // Depth shader + { + static const std::string DEPTH_SHADER{ + "vec4 getFragmentColor() {" + " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" + " Zdb = 1.0-(1.0-Zdb)*100;" + " return vec4(Zdb, Zdb, Zdb, 1.0); " + "}" + }; + + auto fragmentShader = FRAGMENT_SHADER; + fragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEPTH_SHADER); + + const auto ps = gpu::Shader::createPixel(fragmentShader); + const auto program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + _depthPipeline = gpu::Pipeline::create(program, state); + } +} + +const gpu::PipelinePointer& DebugHighlight::getDepthPipeline() { + if (!_depthPipeline) { + initializePipelines(); + } + + return _depthPipeline; +} + +void SelectionToHighlight::run(const render::RenderContextPointer& renderContext, Outputs& outputs) { + auto scene = renderContext->_scene; + auto highlightStage = scene->getStage(render::HighlightStage::getName()); + + outputs.clear(); + _sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX); + + for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { + std::ostringstream stream; + if (i > 0) { + stream << "highlightList" << i; + } else { + stream << "contextOverlayHighlightList"; + } + auto selectionName = stream.str(); + if (!scene->isSelectionEmpty(selectionName)) { + auto highlightId = highlightStage->getHighlightIdBySelection(selectionName); + if (!render::HighlightStage::isIndexInvalid(highlightId)) { + _sharedParameters->_highlightIds[outputs.size()] = highlightId; + outputs.emplace_back(selectionName); + } + } + } +} + +void ExtractSelectionName::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + if (_highlightPassIndex < inputs.size()) { + outputs = inputs[_highlightPassIndex]; + } else { + outputs.clear(); + } +} + +DrawHighlightTask::DrawHighlightTask() { + +} + +void DrawHighlightTask::configure(const Config& config) { + +} + +void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { + const auto items = inputs.getN(0).get(); + const auto sceneFrameBuffer = inputs.getN(1); + const auto primaryFramebuffer = inputs.getN(2); + const auto deferredFrameTransform = inputs.getN(3); + + // Prepare the ShapePipeline + auto shapePlumber = std::make_shared(); + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setColorWriteMask(false, false, false, false); + + initMaskPipelines(*shapePlumber, state); + } + auto sharedParameters = std::make_shared(); + + const auto highlightSelectionNames = task.addJob("SelectionToHighlight", sharedParameters); + + // Prepare for highlight group rendering. + const auto highlightRessources = task.addJob("PrepareHighlight", primaryFramebuffer); + render::Varying highlight0Rect; + + for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { + const auto selectionName = task.addJob("ExtractSelectionName", highlightSelectionNames, i); + const auto groupItems = addSelectItemJobs(task, selectionName, items); + const auto highlightedItemIDs = task.addJob("HighlightMetaToSubItemIDs", groupItems); + const auto highlightedItems = task.addJob("HighlightMetaToSubItems", highlightedItemIDs); + + // Sort + const auto sortedPipelines = task.addJob("HighlightPipelineSort", highlightedItems); + const auto sortedBounds = task.addJob("HighlightDepthSort", sortedPipelines); + + // Draw depth of highlighted objects in separate buffer + std::string name; + { + std::ostringstream stream; + stream << "HighlightMask" << i; + name = stream.str(); + } + const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightRessources).asVarying(); + const auto highlightedRect = task.addJob(name, drawMaskInputs, i, shapePlumber, sharedParameters); + if (i == 0) { + highlight0Rect = highlightedRect; + } + + // Draw highlight + { + std::ostringstream stream; + stream << "HighlightEffect" << i; + name = stream.str(); + } + const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect).asVarying(); + task.addJob(name, drawHighlightInputs, i, sharedParameters); + } + + // Debug highlight + const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast(highlight0Rect)).asVarying(); + task.addJob("HighlightDebug", debugInputs); +} + +const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const render::Varying& selectionName, + const RenderFetchCullSortTask::BucketList& items) { + const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& metas = items[RenderFetchCullSortTask::META]; + + const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), selectionName).asVarying(); + const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, selectionName).asVarying(); + const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); + return task.addJob("TransparentSelection", selectItemInput); +} + +#include "model_shadow_vert.h" +#include "skin_model_shadow_vert.h" + +#include "model_shadow_frag.h" + +void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { + auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); + auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutSkinned(), + modelProgram, state); + + auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); + gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, modelPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withSkinned(), + skinProgram, state); +} diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h new file mode 100644 index 0000000000..90a8e730ce --- /dev/null +++ b/libraries/render-utils/src/HighlightEffect.h @@ -0,0 +1,226 @@ +// +// HighlightEffect.h +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_render_utils_HighlightEffect_h +#define hifi_render_utils_HighlightEffect_h + +#include +#include +#include + +#include "DeferredFramebuffer.h" +#include "DeferredFrameTransform.h" + +class HighlightRessources { +public: + HighlightRessources(); + + gpu::FramebufferPointer getDepthFramebuffer(); + gpu::TexturePointer getDepthTexture(); + + gpu::FramebufferPointer getColorFramebuffer(); + + // Update the source framebuffer size which will drive the allocation of all the other resources. + void update(const gpu::FramebufferPointer& primaryFrameBuffer); + const glm::ivec2& getSourceFrameSize() const { return _frameSize; } + +protected: + + gpu::FramebufferPointer _depthFrameBuffer; + gpu::FramebufferPointer _colorFrameBuffer; + gpu::TexturePointer _depthStencilTexture; + + glm::ivec2 _frameSize; + + void allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer); + void allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer); +}; + +using HighlightRessourcesPointer = std::shared_ptr; + +class HighlightSharedParameters { +public: + + enum { + MAX_PASS_COUNT = 8 + }; + + HighlightSharedParameters(); + + std::array _highlightIds; + + static float getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight); +}; + +using HighlightSharedParametersPointer = std::shared_ptr; + +class PrepareDrawHighlight { +public: + using Inputs = gpu::FramebufferPointer; + using Outputs = HighlightRessourcesPointer; + using JobModel = render::Job::ModelIO; + + PrepareDrawHighlight(); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + + HighlightRessourcesPointer _ressources; + +}; + +class SelectionToHighlight { +public: + + using Outputs = std::vector; + using JobModel = render::Job::ModelO; + + SelectionToHighlight(HighlightSharedParametersPointer parameters) : _sharedParameters{ parameters } {} + + void run(const render::RenderContextPointer& renderContext, Outputs& outputs); + +private: + + HighlightSharedParametersPointer _sharedParameters; +}; + +class ExtractSelectionName { +public: + + using Inputs = SelectionToHighlight::Outputs; + using Outputs = std::string; + using JobModel = render::Job::ModelIO; + + ExtractSelectionName(unsigned int highlightIndex) : _highlightPassIndex{ highlightIndex } {} + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + + unsigned int _highlightPassIndex; + +}; + +class DrawHighlightMask { +public: + + using Inputs = render::VaryingSet2; + using Outputs = glm::ivec4; + using JobModel = render::Job::ModelIO; + + DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +protected: + + unsigned int _highlightPassIndex; + render::ShapePlumberPointer _shapePlumber; + HighlightSharedParametersPointer _sharedParameters; + gpu::BufferPointer _boundsBuffer; + + static gpu::PipelinePointer _stencilMaskPipeline; + static gpu::PipelinePointer _stencilMaskFillPipeline; +}; + +class DrawHighlight { +public: + + using Inputs = render::VaryingSet4; + using Config = render::Job::Config; + using JobModel = render::Job::ModelI; + + DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + +#include "Highlight_shared.slh" + + enum { + SCENE_DEPTH_MAP_SLOT = 0, + HIGHLIGHTED_DEPTH_MAP_SLOT, + + HIGHLIGHT_PARAMS_SLOT = 0, + FRAME_TRANSFORM_SLOT, + }; + + using HighlightConfigurationBuffer = gpu::StructBuffer; + + static const gpu::PipelinePointer& getPipeline(const render::HighlightStyle& style); + + static gpu::PipelinePointer _pipeline; + static gpu::PipelinePointer _pipelineFilled; + + unsigned int _highlightPassIndex; + HighlightParameters _parameters; + HighlightSharedParametersPointer _sharedParameters; + HighlightConfigurationBuffer _configuration; +}; + +class DebugHighlightConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool viewMask MEMBER viewMask NOTIFY dirty) + +public: + + bool viewMask{ false }; + +signals: + void dirty(); +}; + +class DebugHighlight { +public: + using Inputs = render::VaryingSet2; + using Config = DebugHighlightConfig; + using JobModel = render::Job::ModelI; + + DebugHighlight(); + ~DebugHighlight(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + + gpu::PipelinePointer _depthPipeline; + int _geometryDepthId{ 0 }; + bool _isDisplayEnabled{ false }; + + const gpu::PipelinePointer& getDepthPipeline(); + void initializePipelines(); +}; + +class DrawHighlightTask { +public: + + using Inputs = render::VaryingSet4; + using Config = render::Task::Config; + using JobModel = render::Task::ModelI; + + DrawHighlightTask(); + + void configure(const Config& config); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + +private: + + static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); + static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); + +}; + +#endif // hifi_render_utils_HighlightEffect_h + + diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv new file mode 100644 index 0000000000..4927db9610 --- /dev/null +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -0,0 +1,104 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Draw and transform the fed vertex position with the standard MVP stack +// and offset the vertices by a certain amount in the vertex direction +// +// Created by Olivier Prat on 11/02/2017 +// Copyright 2017 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 gpu/Transform.slh@> + +<$declareStandardTransform()$> + +struct ItemBound { + vec4 id_boundPos; + vec4 boundDim_s; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer ssbo0Buffer; +ItemBound getItemBound(int i) { + int offset = 2 * i; + ItemBound bound; + bound.id_boundPos = texelFetch(ssbo0Buffer, offset); + bound.boundDim_s = texelFetch(ssbo0Buffer, offset + 1); + return bound; +} +#else +layout(std140) buffer ssbo0Buffer { + ItemBound bounds[]; +}; +ItemBound getItemBound(int i) { + ItemBound bound = bounds[i]; + return bound; +} +#endif + +uniform vec2 outlineWidth; + +void main(void) { + const vec3 UNIT_BOX_VERTICES[8] = vec3[8]( + vec3(0.0, 1.0, 0.0), + vec3(1.0, 1.0, 0.0), + vec3(1.0, 0.0, 0.0), + vec3(0.0, 0.0, 0.0), + vec3(0.0, 1.0, 1.0), + vec3(1.0, 1.0, 1.0), + vec3(1.0, 0.0, 1.0), + vec3(0.0, 0.0, 1.0) + ); + const vec3 UNIT_BOX_NORMALS[8] = vec3[8]( + vec3(-1.0, 1.0, -1.0), + vec3(1.0, 1.0, -1.0), + vec3(1.0, -1.0, -1.0), + vec3(-1.0, -1.0, -1.0), + vec3(-1.0, 1.0, 1.0), + vec3(1.0, 1.0, 1.0), + vec3(1.0, -1.0, 1.0), + vec3(-1.0, -1.0, 1.0) + ); + const int NUM_VERTICES_PER_CUBE = 36; + const int UNIT_BOX_TRIANGLE_INDICES[NUM_VERTICES_PER_CUBE] = int[NUM_VERTICES_PER_CUBE]( + 0, 1, 2, + 0, 2, 3, + 3, 2, 6, + 3, 6, 7, + 7, 6, 5, + 7, 5, 4, + 4, 5, 1, + 4, 1, 0, + 1, 5, 6, + 1, 6, 2, + 4, 0, 3, + 4, 3, 7 + ); + + int boundID = gl_VertexID / NUM_VERTICES_PER_CUBE; + int vertexID = gl_VertexID - boundID * NUM_VERTICES_PER_CUBE; + int triangleIndex = UNIT_BOX_TRIANGLE_INDICES[vertexID]; + vec3 cubeVec = UNIT_BOX_VERTICES[triangleIndex]; + + ItemBound bound = getItemBound(boundID); + vec3 boundPos = bound.id_boundPos.yzw; + vec3 boundDim = bound.boundDim_s.xyz; + + vec4 pos = vec4(boundPos + boundDim * cubeVec.xyz, 1.0); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToMonoClipPos(cam, obj, pos, gl_Position)$> + + // Offset the vertex to take into account the outline width + pos.xyz += UNIT_BOX_NORMALS[triangleIndex]; + vec4 offsetPosition; + <$transformModelToMonoClipPos(cam, obj, pos, offsetPosition)$> + gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * outlineWidth * gl_Position.w; + <$transformStereoClipsSpace(cam, gl_Position)$> +} diff --git a/libraries/render-utils/src/Outline_filled.slf b/libraries/render-utils/src/Highlight_filled.slf similarity index 71% rename from libraries/render-utils/src/Outline_filled.slf rename to libraries/render-utils/src/Highlight_filled.slf index aaa3396bac..53530746f0 100644 --- a/libraries/render-utils/src/Outline_filled.slf +++ b/libraries/render-utils/src/Highlight_filled.slf @@ -1,5 +1,5 @@ -// Outline_filled.slf -// Add outline effect based on two zbuffers : one containing the total scene z and another +// Highlight_filled.slf +// Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version with the fill effect inside the silhouette. // @@ -9,5 +9,5 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include Outline.slh@> +<@include Highlight.slh@> <$main(1)$> diff --git a/libraries/render-utils/src/Highlight_shared.slh b/libraries/render-utils/src/Highlight_shared.slh new file mode 100644 index 0000000000..5efbde4d52 --- /dev/null +++ b/libraries/render-utils/src/Highlight_shared.slh @@ -0,0 +1,30 @@ +// glsl / C++ compatible source as interface for highlight +#ifdef __cplusplus +# define TVEC2 glm::vec2 +# define TVEC3 glm::vec3 +# define TVEC4 glm::vec4 +#else +# define TVEC2 vec2 +# define TVEC3 vec3 +# define TVEC4 vec4 +#endif + +struct HighlightParameters +{ + TVEC3 _color; + float _intensity; + + TVEC2 _size; + float _unoccludedFillOpacity; + float _occludedFillOpacity; + + float _threshold; + int _blurKernelSize; + float padding2; + float padding3; +}; + +// <@if 1@> +// Trigger Scribe include +// <@endif@> +// diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 411f179d49..160fa2deea 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -14,6 +14,7 @@ #include "LightStage.h" std::string LightStage::_stageName { "LIGHT_STAGE"}; +const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { } diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 3a2a77055f..052c8dd222 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -32,7 +32,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using LightPointer = model::LightPointer; diff --git a/libraries/render-utils/src/OutlineEffect.cpp b/libraries/render-utils/src/OutlineEffect.cpp deleted file mode 100644 index d5b3b1c3bb..0000000000 --- a/libraries/render-utils/src/OutlineEffect.cpp +++ /dev/null @@ -1,371 +0,0 @@ -// -// OutlineEffect.cpp -// render-utils/src/ -// -// Created by Olivier Prat on 08/08/17. -// Copyright 2017 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 "OutlineEffect.h" - -#include "GeometryCache.h" - -#include -#include - -#include "gpu/Context.h" -#include "gpu/StandardShaderLib.h" - - -#include "surfaceGeometry_copyDepth_frag.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" -#include "Outline_frag.h" -#include "Outline_filled_frag.h" - -using namespace render; - -extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); - -OutlineFramebuffer::OutlineFramebuffer() { -} - -void OutlineFramebuffer::update(const gpu::TexturePointer& colorBuffer) { - // If the depth buffer or size changed, we need to delete our FBOs and recreate them at the - // new correct dimensions. - if (_depthTexture) { - auto newFrameSize = glm::ivec2(colorBuffer->getDimensions()); - if (_frameSize != newFrameSize) { - _frameSize = newFrameSize; - clear(); - } - } -} - -void OutlineFramebuffer::clear() { - _depthFramebuffer.reset(); - _depthTexture.reset(); -} - -void OutlineFramebuffer::allocate() { - - auto width = _frameSize.x; - auto height = _frameSize.y; - auto format = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); - - _depthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height)); - _depthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("outlineDepth")); - _depthFramebuffer->setDepthStencilBuffer(_depthTexture, format); -} - -gpu::FramebufferPointer OutlineFramebuffer::getDepthFramebuffer() { - if (!_depthFramebuffer) { - allocate(); - } - return _depthFramebuffer; -} - -gpu::TexturePointer OutlineFramebuffer::getDepthTexture() { - if (!_depthTexture) { - allocate(); - } - return _depthTexture; -} - -void DrawOutlineDepth::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - auto& inShapes = inputs.get0(); - auto& deferredFrameBuffer = inputs.get1(); - - if (!inShapes.empty()) { - RenderArgs* args = renderContext->args; - ShapeKey::Builder defaultKeyBuilder; - - if (!_outlineFramebuffer) { - _outlineFramebuffer = std::make_shared(); - } - _outlineFramebuffer->update(deferredFrameBuffer->getDeferredColorTexture()); - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.setFramebuffer(_outlineFramebuffer->getDepthFramebuffer()); - // Clear it - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, false); - - // Setup camera, projection and viewport for all items - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); - auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); - - std::vector skinnedShapeKeys{}; - - // Iterate through all inShapes and render the unskinned - args->_shapePipeline = shadowPipeline; - batch.setPipeline(shadowPipeline->pipeline); - for (auto items : inShapes) { - if (items.first.isSkinned()) { - skinnedShapeKeys.push_back(items.first); - } - else { - renderItems(renderContext, items.second); - } - } - - // Reiterate to render the skinned - args->_shapePipeline = shadowSkinnedPipeline; - batch.setPipeline(shadowSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); - } - - args->_shapePipeline = nullptr; - args->_batch = nullptr; - }); - - output = _outlineFramebuffer; - } else { - output = nullptr; - } -} - -DrawOutline::DrawOutline() { -} - -void DrawOutline::configure(const Config& config) { - _color = config.color; - _blurKernelSize = std::min(10, std::max(2, (int)floorf(config.width*2 + 0.5f))); - // Size is in normalized screen height. We decide that for outline width = 1, this is equal to 1/400. - _size = config.width / 400.f; - _fillOpacityUnoccluded = config.fillOpacityUnoccluded; - _fillOpacityOccluded = config.fillOpacityOccluded; - _threshold = config.glow ? 1.f : 1e-3f; - _intensity = config.intensity * (config.glow ? 2.f : 1.f); -} - -void DrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { - auto outlineFrameBuffer = inputs.get1(); - - if (outlineFrameBuffer) { - auto sceneDepthBuffer = inputs.get2(); - const auto frameTransform = inputs.get0(); - auto outlinedDepthTexture = outlineFrameBuffer->getDepthTexture(); - auto destinationFrameBuffer = inputs.get3(); - auto framebufferSize = glm::ivec2(outlinedDepthTexture->getDimensions()); - - if (!_primaryWithoutDepthBuffer || framebufferSize!=_frameBufferSize) { - // Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac - _primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth")); - _primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0)); - _frameBufferSize = framebufferSize; - } - - if (sceneDepthBuffer) { - const auto OPACITY_EPSILON = 5e-3f; - auto pipeline = getPipeline(_fillOpacityUnoccluded>OPACITY_EPSILON || _fillOpacityOccluded>OPACITY_EPSILON); - auto args = renderContext->args; - { - auto& configuration = _configuration.edit(); - configuration._color = _color; - configuration._intensity = _intensity; - configuration._fillOpacityUnoccluded = _fillOpacityUnoccluded; - configuration._fillOpacityOccluded = _fillOpacityOccluded; - configuration._threshold = _threshold; - configuration._blurKernelSize = _blurKernelSize; - configuration._size.x = _size * _frameBufferSize.y / _frameBufferSize.x; - configuration._size.y = _size; - } - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_primaryWithoutDepthBuffer); - - batch.setViewportTransform(args->_viewport); - batch.setProjectionTransform(glm::mat4()); - batch.resetViewTransform(); - batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_frameBufferSize, args->_viewport)); - batch.setPipeline(pipeline); - - batch.setUniformBuffer(OUTLINE_PARAMS_SLOT, _configuration); - batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); - batch.setResourceTexture(SCENE_DEPTH_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); - batch.setResourceTexture(OUTLINED_DEPTH_SLOT, outlinedDepthTexture); - batch.draw(gpu::TRIANGLE_STRIP, 4); - - // Restore previous frame buffer - batch.setFramebuffer(destinationFrameBuffer); - }); - } - } -} - -const gpu::PipelinePointer& DrawOutline::getPipeline(bool isFilled) { - if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(Outline_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("outlineParamsBuffer", OUTLINE_PARAMS_SLOT)); - slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); - slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_SLOT)); - slotBindings.insert(gpu::Shader::Binding("outlinedDepthMap", OUTLINED_DEPTH_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false, false)); - state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _pipeline = gpu::Pipeline::create(program, state); - - ps = gpu::Shader::createPixel(std::string(Outline_filled_frag)); - program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program, slotBindings); - _pipelineFilled = gpu::Pipeline::create(program, state); - } - return isFilled ? _pipelineFilled : _pipeline; -} - -DebugOutline::DebugOutline() { - _geometryId = DependencyManager::get()->allocateID(); -} - -DebugOutline::~DebugOutline() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -void DebugOutline::configure(const Config& config) { - _isDisplayDepthEnabled = config.viewOutlinedDepth; -} - -void DebugOutline::run(const render::RenderContextPointer& renderContext, const Inputs& input) { - const auto outlineFramebuffer = input; - - if (_isDisplayDepthEnabled && outlineFramebuffer) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - - const auto geometryBuffer = DependencyManager::get(); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); - batch.setModelTransform(Transform()); - - batch.setPipeline(getDebugPipeline()); - batch.setResourceTexture(0, outlineFramebuffer->getDepthTexture()); - - const glm::vec4 color(1.0f, 0.5f, 0.2f, 1.0f); - const glm::vec2 bottomLeft(-1.0f, -1.0f); - const glm::vec2 topRight(1.0f, 1.0f); - geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId); - - batch.setResourceTexture(0, nullptr); - }); - } -} - -const gpu::PipelinePointer& DebugOutline::getDebugPipeline() { - if (!_debugPipeline) { - static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; - static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; - static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); - Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, - "Could not find source placeholder"); - static const std::string DEFAULT_DEPTH_SHADER{ - "vec4 getFragmentColor() {" - " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" - " Zdb = 1.0-(1.0-Zdb)*100;" - " return vec4(Zdb, Zdb, Zdb, 1.0);" - " }" - }; - - auto bakedFragmentShader = FRAGMENT_SHADER; - bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEFAULT_DEPTH_SHADER); - - static const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); - const auto ps = gpu::Shader::createPixel(bakedFragmentShader); - const auto program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - - auto state = std::make_shared(); - state->setDepthTest(gpu::State::DepthTest(false)); - _debugPipeline = gpu::Pipeline::create(program, state); - } - - return _debugPipeline; -} - -DrawOutlineTask::DrawOutlineTask() { - -} - -void DrawOutlineTask::configure(const Config& config) { - -} - -void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { - const auto input = inputs.get(); - const auto selectedMetas = inputs.getN(0); - const auto shapePlumber = input.get1(); - const auto sceneFrameBuffer = inputs.getN(2); - const auto primaryFramebuffer = inputs.getN(3); - const auto deferredFrameTransform = inputs.getN(4); - - // Prepare the ShapePipeline - ShapePlumberPointer shapePlumberZPass = std::make_shared(); - { - auto state = std::make_shared(); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setColorWriteMask(false, false, false, false); - - initZPassPipelines(*shapePlumberZPass, state); - } - - const auto outlinedItemIDs = task.addJob("OutlineMetaToSubItemIDs", selectedMetas); - const auto outlinedItems = task.addJob("OutlineMetaToSubItems", outlinedItemIDs, true); - - // Sort - const auto sortedPipelines = task.addJob("OutlinePipelineSort", outlinedItems); - const auto sortedShapes = task.addJob("OutlineDepthSort", sortedPipelines); - - // Draw depth of outlined objects in separate buffer - const auto drawOutlineDepthInputs = DrawOutlineDepth::Inputs(sortedShapes, sceneFrameBuffer).asVarying(); - const auto outlinedFrameBuffer = task.addJob("OutlineDepth", drawOutlineDepthInputs, shapePlumberZPass); - - // Draw outline - const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying(); - task.addJob("OutlineEffect", drawOutlineInputs); - - // Debug outline - task.addJob("OutlineDebug", outlinedFrameBuffer); -} diff --git a/libraries/render-utils/src/OutlineEffect.h b/libraries/render-utils/src/OutlineEffect.h deleted file mode 100644 index f88092429f..0000000000 --- a/libraries/render-utils/src/OutlineEffect.h +++ /dev/null @@ -1,183 +0,0 @@ -// -// OutlineEffect.h -// render-utils/src/ -// -// Created by Olivier Prat on 08/08/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_render_utils_OutlineEffect_h -#define hifi_render_utils_OutlineEffect_h - -#include -#include "DeferredFramebuffer.h" -#include "DeferredFrameTransform.h" - -class OutlineFramebuffer { -public: - OutlineFramebuffer(); - - gpu::FramebufferPointer getDepthFramebuffer(); - gpu::TexturePointer getDepthTexture(); - - // Update the source framebuffer size which will drive the allocation of all the other resources. - void update(const gpu::TexturePointer& colorBuffer); - const glm::ivec2& getSourceFrameSize() const { return _frameSize; } - -protected: - - void clear(); - void allocate(); - - gpu::FramebufferPointer _depthFramebuffer; - gpu::TexturePointer _depthTexture; - - glm::ivec2 _frameSize; -}; - -using OutlineFramebufferPointer = std::shared_ptr; - -class DrawOutlineDepth { -public: - - using Inputs = render::VaryingSet2; - // Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer - using Outputs = OutlineFramebufferPointer; - using JobModel = render::Job::ModelIO; - - DrawOutlineDepth(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} - - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); - -protected: - - render::ShapePlumberPointer _shapePlumber; - OutlineFramebufferPointer _outlineFramebuffer; -}; - -class DrawOutlineConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(bool glow MEMBER glow NOTIFY dirty) - Q_PROPERTY(float width MEMBER width NOTIFY dirty) - Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty) - Q_PROPERTY(float colorR READ getColorR WRITE setColorR NOTIFY dirty) - Q_PROPERTY(float colorG READ getColorG WRITE setColorG NOTIFY dirty) - Q_PROPERTY(float colorB READ getColorB WRITE setColorB NOTIFY dirty) - Q_PROPERTY(float fillOpacityUnoccluded MEMBER fillOpacityUnoccluded NOTIFY dirty) - Q_PROPERTY(float fillOpacityOccluded MEMBER fillOpacityOccluded NOTIFY dirty) - -public: - - void setColorR(float value) { color.r = value; emit dirty(); } - float getColorR() const { return color.r; } - - void setColorG(float value) { color.g = value; emit dirty(); } - float getColorG() const { return color.g; } - - void setColorB(float value) { color.b = value; emit dirty(); } - float getColorB() const { return color.b; } - - glm::vec3 color{ 1.f, 0.7f, 0.2f }; - float width{ 2.0f }; - float intensity{ 0.9f }; - float fillOpacityUnoccluded{ 0.0f }; - float fillOpacityOccluded{ 0.0f }; - bool glow{ false }; - -signals: - void dirty(); -}; - -class DrawOutline { -public: - using Inputs = render::VaryingSet4; - using Config = DrawOutlineConfig; - using JobModel = render::Job::ModelI; - - DrawOutline(); - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -private: - - enum { - SCENE_DEPTH_SLOT = 0, - OUTLINED_DEPTH_SLOT, - - OUTLINE_PARAMS_SLOT = 0, - FRAME_TRANSFORM_SLOT - }; - -#include "Outline_shared.slh" - - using OutlineConfigurationBuffer = gpu::StructBuffer; - - const gpu::PipelinePointer& getPipeline(bool isFilled); - - gpu::FramebufferPointer _primaryWithoutDepthBuffer; - glm::ivec2 _frameBufferSize {0, 0}; - gpu::PipelinePointer _pipeline; - gpu::PipelinePointer _pipelineFilled; - OutlineConfigurationBuffer _configuration; - glm::vec3 _color; - float _size; - int _blurKernelSize; - float _intensity; - float _fillOpacityUnoccluded; - float _fillOpacityOccluded; - float _threshold; -}; - -class DebugOutlineConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(bool viewOutlinedDepth MEMBER viewOutlinedDepth NOTIFY dirty) - -public: - - bool viewOutlinedDepth{ false }; - -signals: - void dirty(); -}; - -class DebugOutline { -public: - using Inputs = OutlineFramebufferPointer; - using Config = DebugOutlineConfig; - using JobModel = render::Job::ModelI; - - DebugOutline(); - ~DebugOutline(); - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -private: - - const gpu::PipelinePointer& getDebugPipeline(); - - gpu::PipelinePointer _debugPipeline; - int _geometryId{ 0 }; - bool _isDisplayDepthEnabled{ false }; -}; - -class DrawOutlineTask { -public: - using Inputs = render::VaryingSet5; - using Config = render::Task::Config; - using JobModel = render::Task::ModelI; - - DrawOutlineTask(); - - void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); - -}; - -#endif // hifi_render_utils_OutlineEffect_h - - diff --git a/libraries/render-utils/src/Outline_shared.slh b/libraries/render-utils/src/Outline_shared.slh deleted file mode 100644 index 902bbd20ad..0000000000 --- a/libraries/render-utils/src/Outline_shared.slh +++ /dev/null @@ -1,28 +0,0 @@ -// glsl / C++ compatible source as interface for Outline -#ifdef __cplusplus -# define VEC2 glm::vec2 -# define VEC3 glm::vec3 -#else -# define VEC2 vec2 -# define VEC3 vec3 -#endif - -struct OutlineParameters -{ - VEC3 _color; - float _intensity; - - VEC2 _size; - float _fillOpacityUnoccluded; - float _fillOpacityOccluded; - - float _threshold; - int _blurKernelSize; - float padding2; - float padding3; -}; - -// <@if 1@> -// Trigger Scribe include -// <@endif@> -// diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 75af8506a2..15d307d2e4 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -35,16 +35,18 @@ #include "TextureCache.h" #include "ZoneRenderer.h" #include "FadeEffect.h" +#include "RenderUtilsLogging.h" #include "AmbientOcclusionEffect.h" #include "AntialiasingEffect.h" #include "ToneMappingEffect.h" #include "SubsurfaceScattering.h" #include "DrawHaze.h" -#include "OutlineEffect.h" +#include "HighlightEffect.h" #include +#include using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); @@ -58,6 +60,18 @@ void RenderDeferredTask::configure(const Config& config) { } +const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName, + const render::Varying& metas, + const render::Varying& opaques, + const render::Varying& transparents) { + const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); + const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput, selectionName); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, std::string()).asVarying(); + const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput, selectionName); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, std::string()).asVarying(); + return task.addJob("TransparentSelection", selectItemInput, selectionName); +} + void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { const auto& items = input.get(); auto fadeEffect = DependencyManager::get(); @@ -95,15 +109,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", primaryFramebuffer); - // Select items that need to be outlined - const auto selectionName = "contextOverlayHighlightList"; - const auto selectMetaInput = SelectItems::Inputs(metas, Varying()).asVarying(); - const auto selectedMetas = task.addJob("PassTestMetaSelection", selectMetaInput, selectionName); - const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas).asVarying(); - const auto selectedMetasAndOpaques = task.addJob("PassTestOpaqueSelection", selectMetaAndOpaqueInput, selectionName); - const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques).asVarying(); - const auto selectedItems = task.addJob("PassTestTransparentSelection", selectItemInput, selectionName); - // Render opaque objects in DeferredBuffer const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); @@ -178,10 +183,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying(); task.addJob("ToneMapping", toneMappingInputs); - const auto outlineRangeTimer = task.addJob("BeginOutlineRangeTimer", "Outline"); - const auto outlineInputs = DrawOutlineTask::Inputs(selectedItems, shapePlumber, deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); - task.addJob("DrawOutline", outlineInputs); - task.addJob("EndOutlineRangeTimer", outlineRangeTimer); + const auto outlineRangeTimer = task.addJob("BeginHighlightRangeTimer", "Highlight"); + // Select items that need to be outlined + const auto selectionBaseName = "contextOverlayHighlightList"; + const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); + + const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); + task.addJob("DrawHighlight", outlineInputs); + + task.addJob("HighlightRangeTimer", outlineRangeTimer); { // DEbug the bounds of the rendered items, still look at the zbuffer task.addJob("DrawMetaBounds", metas); @@ -191,6 +201,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawLightBounds", lights); task.addJob("DrawZones", zones); task.addJob("DrawFrustums"); + + // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true + task.addJob("DrawSelectionBounds", selectedItems); } // Layered Overlays @@ -237,9 +250,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren } task.addJob("DrawZoneStack", deferredFrameTransform); - - // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true - task.addJob("DrawSelectionBounds", selectedItems); } // AA job to be revisited @@ -455,6 +465,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer auto blitFbo = renderArgs->_blitFramebuffer; if (!blitFbo) { + qCWarning(renderutils) << "Blit::run - no blit frame buffer."; return; } diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index e8dd22359d..bf457d7fcb 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -232,6 +232,10 @@ public: void configure(const Config& config); void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); +private: + + static const render::Varying addSelectItemJobs(JobModel& task, const char* selectionName, + const render::Varying& metas, const render::Varying& opaques, const render::Varying& transparents); }; #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/UpdateSceneTask.cpp b/libraries/render-utils/src/UpdateSceneTask.cpp index 06e02907f2..e05f28ef0d 100644 --- a/libraries/render-utils/src/UpdateSceneTask.cpp +++ b/libraries/render-utils/src/UpdateSceneTask.cpp @@ -15,6 +15,7 @@ #include "BackgroundStage.h" #include "HazeStage.h" #include +#include #include "DeferredLightingEffect.h" void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { @@ -22,6 +23,7 @@ void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render task.addJob("BackgroundStageSetup"); task.addJob("HazeStageSetup"); task.addJob("TransitionStageSetup"); + task.addJob("HighlightStageSetup"); task.addJob("DefaultLightingSetup"); diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index 49a9ada91e..20d29f3e5d 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -57,7 +57,12 @@ void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds } void SelectItems::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { - auto selection = renderContext->_scene->getSelection(_name); + auto selectionName{ _name }; + if (!inputs.get2().empty()) { + selectionName = inputs.get2(); + } + + auto selection = renderContext->_scene->getSelection(selectionName); const auto& selectedItems = selection.getItems(); const auto& inItems = inputs.get0(); const auto itemsToAppend = inputs[1]; @@ -118,8 +123,9 @@ void MetaToSubItems::run(const RenderContextPointer& renderContext, const ItemBo for (auto idBound : inItems) { auto& item = scene->getItem(idBound.id); - - item.fetchMetaSubItems(outItems); + if (item.exist()) { + item.fetchMetaSubItems(outItems); + } } } @@ -132,8 +138,9 @@ void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs& if (!_disableAABBs) { for (auto id : inItems) { auto& item = scene->getItem(id); - - outItems.emplace_back(ItemBound{ id, item.getBound() }); + if (item.exist()) { + outItems.emplace_back(ItemBound{ id, item.getBound() }); + } } } else { for (auto id : inItems) { diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index a7180b6cde..9b40728b00 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -113,12 +113,13 @@ namespace render { // Keep items belonging to the job selection class SelectItems { public: - using Inputs = VaryingSet2; + using Inputs = VaryingSet3; using JobModel = Job::ModelIO; std::string _name; + SelectItems() {} SelectItems(const Selection::Name& name) : _name(name) {} - + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; diff --git a/libraries/render/src/render/HighlightStage.cpp b/libraries/render/src/render/HighlightStage.cpp new file mode 100644 index 0000000000..ade3844321 --- /dev/null +++ b/libraries/render/src/render/HighlightStage.cpp @@ -0,0 +1,130 @@ +#include "HighlightStage.h" + +using namespace render; + +std::string HighlightStage::_name("Highlight"); +const HighlightStage::Index HighlightStage::INVALID_INDEX{ render::indexed_container::INVALID_INDEX }; + +HighlightStage::Index HighlightStage::addHighlight(const std::string& selectionName, const HighlightStyle& style) { + Highlight outline{ selectionName, style }; + Index id; + + id = _highlights.newElement(outline); + _activeHighlightIds.push_back(id); + + return id; +} + +void HighlightStage::removeHighlight(Index index) { + HighlightIdList::iterator idIterator = std::find(_activeHighlightIds.begin(), _activeHighlightIds.end(), index); + if (idIterator != _activeHighlightIds.end()) { + _activeHighlightIds.erase(idIterator); + } + if (!_highlights.isElementFreed(index)) { + _highlights.freeElement(index); + } +} + +Index HighlightStage::getHighlightIdBySelection(const std::string& selectionName) const { + for (auto outlineId : _activeHighlightIds) { + const auto& outline = _highlights.get(outlineId); + if (outline._selectionName == selectionName) { + return outlineId; + } + } + return INVALID_INDEX; +} + +const HighlightStyle& HighlightStageConfig::getStyle() const { + auto styleIterator = _styles.find(_selectionName); + if (styleIterator != _styles.end()) { + return styleIterator->second; + } else { + auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} }); + return insertion.first->second; + } +} + +HighlightStyle& HighlightStageConfig::editStyle() { + auto styleIterator = _styles.find(_selectionName); + if (styleIterator != _styles.end()) { + return styleIterator->second; + } else { + auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} }); + return insertion.first->second; + } +} + +void HighlightStageConfig::setSelectionName(const QString& name) { + _selectionName = name.toStdString(); + emit dirty(); +} + +void HighlightStageConfig::setOutlineSmooth(bool isSmooth) { + editStyle().isOutlineSmooth = isSmooth; + emit dirty(); +} + +void HighlightStageConfig::setColorRed(float value) { + editStyle().color.r = value; + emit dirty(); +} + +void HighlightStageConfig::setColorGreen(float value) { + editStyle().color.g = value; + emit dirty(); +} + +void HighlightStageConfig::setColorBlue(float value) { + editStyle().color.b = value; + emit dirty(); +} + +void HighlightStageConfig::setOutlineWidth(float value) { + editStyle().outlineWidth = value; + emit dirty(); +} + +void HighlightStageConfig::setOutlineIntensity(float value) { + editStyle().outlineIntensity = value; + emit dirty(); +} + +void HighlightStageConfig::setUnoccludedFillOpacity(float value) { + editStyle().unoccludedFillOpacity = value; + emit dirty(); +} + +void HighlightStageConfig::setOccludedFillOpacity(float value) { + editStyle().occludedFillOpacity = value; + emit dirty(); +} + +HighlightStageSetup::HighlightStageSetup() { +} + +void HighlightStageSetup::configure(const Config& config) { + // Copy the styles here but update the stage with the new styles in run to be sure everything is + // thread safe... + _styles = config._styles; +} + +void HighlightStageSetup::run(const render::RenderContextPointer& renderContext) { + auto stage = renderContext->_scene->getStage(HighlightStage::getName()); + if (!stage) { + stage = std::make_shared(); + renderContext->_scene->resetStage(HighlightStage::getName(), stage); + } + + if (!_styles.empty()) { + render::Transaction transaction; + for (const auto& selection : _styles) { + auto& selectionName = selection.first; + auto& selectionStyle = selection.second; + transaction.resetSelectionHighlight(selectionName, selectionStyle); + } + renderContext->_scene->enqueueTransaction(transaction); + _styles.clear(); + } +} + diff --git a/libraries/render/src/render/HighlightStage.h b/libraries/render/src/render/HighlightStage.h new file mode 100644 index 0000000000..b35fff654c --- /dev/null +++ b/libraries/render/src/render/HighlightStage.h @@ -0,0 +1,135 @@ +// +// HighlightStage.h + +// Created by Olivier Prat on 07/07/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_render_utils_HighlightStage_h +#define hifi_render_utils_HighlightStage_h + +#include "Stage.h" +#include "Engine.h" +#include "IndexedContainer.h" +#include "HighlightStyle.h" + +namespace render { + + // Highlight stage to set up HighlightStyle-related effects + class HighlightStage : public Stage { + public: + + class Highlight { + public: + + Highlight(const std::string& selectionName, const HighlightStyle& style) : _selectionName{ selectionName }, _style{ style } { } + + std::string _selectionName; + HighlightStyle _style; + + }; + + static const std::string& getName() { return _name; } + + using Index = render::indexed_container::Index; + static const Index INVALID_INDEX; + using HighlightIdList = render::indexed_container::Indices; + + static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } + + bool checkHighlightId(Index index) const { return _highlights.checkIndex(index); } + + const Highlight& getHighlight(Index index) const { return _highlights.get(index); } + Highlight& editHighlight(Index index) { return _highlights.edit(index); } + + Index addHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle()); + Index getHighlightIdBySelection(const std::string& selectionName) const; + void removeHighlight(Index index); + + HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); } + HighlightIdList::iterator end() { return _activeHighlightIds.end(); } + + private: + + using Highlights = render::indexed_container::IndexedVector; + + static std::string _name; + + Highlights _highlights; + HighlightIdList _activeHighlightIds; + }; + using HighlightStagePointer = std::shared_ptr; + + class HighlightStageConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(QString selectionName READ getSelectionName WRITE setSelectionName NOTIFY dirty) + Q_PROPERTY(bool isOutlineSmooth READ isOutlineSmooth WRITE setOutlineSmooth NOTIFY dirty) + Q_PROPERTY(float colorR READ getColorRed WRITE setColorRed NOTIFY dirty) + Q_PROPERTY(float colorG READ getColorGreen WRITE setColorGreen NOTIFY dirty) + Q_PROPERTY(float colorB READ getColorBlue WRITE setColorBlue NOTIFY dirty) + Q_PROPERTY(float outlineWidth READ getOutlineWidth WRITE setOutlineWidth NOTIFY dirty) + Q_PROPERTY(float outlineIntensity READ getOutlineIntensity WRITE setOutlineIntensity NOTIFY dirty) + Q_PROPERTY(float unoccludedFillOpacity READ getUnoccludedFillOpacity WRITE setUnoccludedFillOpacity NOTIFY dirty) + Q_PROPERTY(float occludedFillOpacity READ getOccludedFillOpacity WRITE setOccludedFillOpacity NOTIFY dirty) + + public: + + using SelectionStyles = std::map; + + QString getSelectionName() const { return QString(_selectionName.c_str()); } + void setSelectionName(const QString& name); + + bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; } + void setOutlineSmooth(bool isSmooth); + + float getColorRed() const { return getStyle().color.r; } + void setColorRed(float value); + + float getColorGreen() const { return getStyle().color.g; } + void setColorGreen(float value); + + float getColorBlue() const { return getStyle().color.b; } + void setColorBlue(float value); + + float getOutlineWidth() const { return getStyle().outlineWidth; } + void setOutlineWidth(float value); + + float getOutlineIntensity() const { return getStyle().outlineIntensity; } + void setOutlineIntensity(float value); + + float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; } + void setUnoccludedFillOpacity(float value); + + float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; } + void setOccludedFillOpacity(float value); + + std::string _selectionName{ "contextOverlayHighlightList" }; + mutable SelectionStyles _styles; + + const HighlightStyle& getStyle() const; + HighlightStyle& editStyle(); + + signals: + void dirty(); + }; + + class HighlightStageSetup { + public: + using Config = HighlightStageConfig; + using JobModel = render::Job::Model; + + HighlightStageSetup(); + + void configure(const Config& config); + void run(const RenderContextPointer& renderContext); + + protected: + + HighlightStageConfig::SelectionStyles _styles; + }; + +} +#endif // hifi_render_utils_HighlightStage_h diff --git a/libraries/render/src/render/HighlightStyle.h b/libraries/render/src/render/HighlightStyle.h new file mode 100644 index 0000000000..6e7373c78b --- /dev/null +++ b/libraries/render/src/render/HighlightStyle.h @@ -0,0 +1,38 @@ +// +// HighlightStyle.h + +// Created by Olivier Prat on 11/06/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_render_utils_HighlightStyle_h +#define hifi_render_utils_HighlightStyle_h + +#include + +#include + +namespace render { + + // This holds the configuration for a particular outline style + class HighlightStyle { + public: + + bool isFilled() const { + return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f; + } + + glm::vec3 color{ 1.f, 0.7f, 0.2f }; + float outlineWidth{ 2.0f }; + float outlineIntensity{ 0.9f }; + float unoccludedFillOpacity{ 0.0f }; + float occludedFillOpacity{ 0.0f }; + bool isOutlineSmooth{ false }; + }; + +} + +#endif // hifi_render_utils_HighlightStyle_h \ No newline at end of file diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 714be2a8c6..88e25b6d27 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -14,6 +14,7 @@ #include #include "Logging.h" #include "TransitionStage.h" +#include "HighlightStage.h" using namespace render; @@ -54,6 +55,18 @@ void Transaction::resetSelection(const Selection& selection) { _resetSelections.emplace_back(selection); } +void Transaction::resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style) { + _highlightResets.emplace_back(HighlightReset{ selectionName, style }); +} + +void Transaction::removeHighlightFromSelection(const std::string& selectionName) { + _highlightRemoves.emplace_back(selectionName); +} + +void Transaction::querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func) { + _highlightQueries.emplace_back(HighlightQuery{ selectionName, func }); +} + void Transaction::merge(const Transaction& transaction) { _resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end()); _removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end()); @@ -62,6 +75,9 @@ void Transaction::merge(const Transaction& transaction) { _addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end()); _queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end()); _reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end()); + _highlightResets.insert(_highlightResets.end(), transaction._highlightResets.begin(), transaction._highlightResets.end()); + _highlightRemoves.insert(_highlightRemoves.end(), transaction._highlightRemoves.begin(), transaction._highlightRemoves.end()); + _highlightQueries.insert(_highlightQueries.end(), transaction._highlightQueries.begin(), transaction._highlightQueries.end()); } @@ -176,6 +192,10 @@ void Scene::processTransactionFrame(const Transaction& transaction) { // resets and potential NEW items resetSelections(transaction._resetSelections); } + + resetHighlights(transaction._highlightResets); + removeHighlights(transaction._highlightRemoves); + queryHighlights(transaction._highlightQueries); } void Scene::resetItems(const Transaction::Resets& transactions) { @@ -316,6 +336,50 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti } } +void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& newStyle = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->addHighlight(selectionName, newStyle); + } else { + outlineStage->editHighlight(outlineId)._style = newStyle; + } + } +} + +void Scene::removeHighlights(const Transaction::HighlightRemoves& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& selectionName : transactions) { + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (!HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->removeHighlight(outlineId); + } + } +} + +void Scene::queryHighlights(const Transaction::HighlightQueries& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& func = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (!HighlightStage::isIndexInvalid(outlineId)) { + func(&outlineStage->editHighlight(outlineId)._style); + } else { + func(nullptr); + } + } +} + void Scene::collectSubItems(ItemID parentId, ItemIDs& subItems) const { // Access the true item auto& item = _items[parentId]; @@ -362,7 +426,7 @@ void Scene::resetItemTransition(ItemID itemId) { } } -// THis fucntion is thread safe +// This function is thread safe Selection Scene::getSelection(const Selection::Name& name) const { std::unique_lock lock(_selectionsMutex); auto found = _selections.find(name); @@ -373,6 +437,17 @@ Selection Scene::getSelection(const Selection::Name& name) const { } } +// This function is thread safe +bool Scene::isSelectionEmpty(const Selection::Name& name) const { + std::unique_lock lock(_selectionsMutex); + auto found = _selections.find(name); + if (found == _selections.end()) { + return false; + } else { + return (*found).second.isEmpty(); + } +} + void Scene::resetSelections(const Transaction::SelectionResets& transactions) { for (auto selection : transactions) { auto found = _selections.find(selection.getName()); diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index fef2077897..af6204acb4 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -17,6 +17,7 @@ #include "Stage.h" #include "Selection.h" #include "Transition.h" +#include "HighlightStyle.h" namespace render { @@ -37,6 +38,7 @@ class Transaction { public: typedef std::function TransitionQueryFunc; + typedef std::function SelectionHighlightQueryFunc; Transaction() {} ~Transaction() {} @@ -61,6 +63,10 @@ public: // Selection transactions void resetSelection(const Selection& selection); + void resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle()); + void removeHighlightFromSelection(const std::string& selectionName); + void querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func); + void merge(const Transaction& transaction); // Checkers if there is work to do when processing the transaction @@ -75,6 +81,9 @@ protected: using TransitionQuery = std::tuple; using TransitionReApply = ItemID; using SelectionReset = Selection; + using HighlightReset = std::tuple; + using HighlightRemove = std::string; + using HighlightQuery = std::tuple; using Resets = std::vector; using Removes = std::vector; @@ -83,6 +92,9 @@ protected: using TransitionQueries = std::vector; using TransitionReApplies = std::vector; using SelectionResets = std::vector; + using HighlightResets = std::vector; + using HighlightRemoves = std::vector; + using HighlightQueries = std::vector; Resets _resetItems; Removes _removedItems; @@ -91,6 +103,9 @@ protected: TransitionQueries _queriedTransitions; TransitionReApplies _reAppliedTransitions; SelectionResets _resetSelections; + HighlightResets _highlightResets; + HighlightRemoves _highlightRemoves; + HighlightQueries _highlightQueries; }; typedef std::queue TransactionQueue; @@ -102,6 +117,7 @@ typedef std::queue TransactionQueue; // Items are notified accordingly on any update message happening class Scene { public: + Scene(glm::vec3 origin, float size); ~Scene(); @@ -127,6 +143,10 @@ public: // Thread safe Selection getSelection(const Selection::Name& name) const; + // Check if a particular selection is empty (returns true if doesn't exist) + // Thread safe + bool isSelectionEmpty(const Selection::Name& name) const; + // This next call are NOT threadsafe, you have to call them from the correct thread to avoid any potential issues // Access a particular item form its ID @@ -187,6 +207,9 @@ protected: void transitionItems(const Transaction::TransitionAdds& transactions); void reApplyTransitions(const Transaction::TransitionReApplies& transactions); void queryTransitionItems(const Transaction::TransitionQueries& transactions); + void resetHighlights(const Transaction::HighlightResets& transactions); + void removeHighlights(const Transaction::HighlightRemoves& transactions); + void queryHighlights(const Transaction::HighlightQueries& transactions); void collectSubItems(ItemID parentId, ItemIDs& subItems) const; diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index 987b25358a..a97d795736 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -76,17 +76,25 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron } // Finally once sorted result to a list of itemID + // Finally once sorted result to a list of itemID and keep uniques + render::ItemID previousID = Item::INVALID_ITEM_ID; if (!bounds) { for (auto& item : itemBoundSorts) { - outItems.emplace_back(ItemBound(item._id, item._bounds)); + if (item._id != previousID) { + outItems.emplace_back(ItemBound(item._id, item._bounds)); + previousID = item._id; + } } } else if (!itemBoundSorts.empty()) { if (bounds->isNull()) { *bounds = itemBoundSorts.front()._bounds; } for (auto& item : itemBoundSorts) { - *bounds += item._bounds; - outItems.emplace_back(ItemBound(item._id, item._bounds)); + if (item._id != previousID) { + outItems.emplace_back(ItemBound(item._id, item._bounds)); + previousID = item._id; + *bounds += item._bounds; + } } } } diff --git a/libraries/render/src/render/TransitionStage.cpp b/libraries/render/src/render/TransitionStage.cpp index 33ef829c64..9ddc72f4db 100644 --- a/libraries/render/src/render/TransitionStage.cpp +++ b/libraries/render/src/render/TransitionStage.cpp @@ -5,6 +5,7 @@ using namespace render; std::string TransitionStage::_name("Transition"); +const TransitionStage::Index TransitionStage::INVALID_INDEX{ indexed_container::INVALID_INDEX }; TransitionStage::Index TransitionStage::addTransition(ItemID itemId, Transition::Type type, ItemID boundId) { Transition transition; diff --git a/libraries/render/src/render/TransitionStage.h b/libraries/render/src/render/TransitionStage.h index 226d531d8b..1d10a5dedb 100644 --- a/libraries/render/src/render/TransitionStage.h +++ b/libraries/render/src/render/TransitionStage.h @@ -25,7 +25,7 @@ namespace render { static const std::string& getName() { return _name; } using Index = indexed_container::Index; - static const Index INVALID_INDEX{ indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; using TransitionIdList = indexed_container::Indices; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } diff --git a/libraries/shared/src/DoubleHashKey.cpp b/libraries/shared/src/DoubleHashKey.cpp deleted file mode 100644 index ded2f073eb..0000000000 --- a/libraries/shared/src/DoubleHashKey.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// DoubleHashKey.cpp -// libraries/shared/src -// -// Created by Andrew Meadows 2014.11.02 -// 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 "DoubleHashKey.h" - -const uint32_t NUM_PRIMES = 64; -const uint32_t PRIMES[] = { - 4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U, - 4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U, - 4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U, - 4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U, - 4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U, - 4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U, - 4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U, - 4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U -}; - -uint32_t DoubleHashKey::hashFunction(uint32_t value, uint32_t primeIndex) { - uint32_t hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U); - hash += ~(hash << 15); - hash ^= (hash >> 10); - hash += (hash << 3); - hash ^= (hash >> 6); - hash += ~(hash << 11); - return hash ^ (hash >> 16); -} - -uint32_t DoubleHashKey::hashFunction2(uint32_t value) { - uint32_t hash = 0x811c9dc5U; - for (uint32_t i = 0; i < 4; i++ ) { - uint32_t byte = (value << (i * 8)) >> (24 - i * 8); - hash = ( hash ^ byte ) * 0x01000193U; - } - return hash; -} - -void DoubleHashKey::computeHash(uint32_t value, uint32_t primeIndex) { - _hash = DoubleHashKey::hashFunction(value, primeIndex); - _hash2 = DoubleHashKey::hashFunction2(value); -} diff --git a/libraries/shared/src/DoubleHashKey.h b/libraries/shared/src/DoubleHashKey.h deleted file mode 100644 index ca92a7197f..0000000000 --- a/libraries/shared/src/DoubleHashKey.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// DoubleHashKey.h -// libraries/shared/src -// -// Created by Andrew Meadows 2014.11.02 -// 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 -// - -#ifndef hifi_DoubleHashKey_h -#define hifi_DoubleHashKey_h - -#include - -// DoubleHashKey for use with btHashMap -class DoubleHashKey { -public: - static uint32_t hashFunction(uint32_t value, uint32_t primeIndex); - static uint32_t hashFunction2(uint32_t value); - - DoubleHashKey() : _hash(0), _hash2(0) { } - - DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) : - _hash(hashFunction(value, primeIndex)), - _hash2(hashFunction2(value)) { - } - - void clear() { _hash = 0; _hash2 = 0; } - bool isNull() const { return _hash == 0 && _hash2 == 0; } - - bool equals(const DoubleHashKey& other) const { - return _hash == other._hash && _hash2 == other._hash2; - } - - void computeHash(uint32_t value, uint32_t primeIndex = 0); - uint32_t getHash() const { return _hash; } - uint32_t getHash2() const { return _hash2; } - - void setHash(uint32_t hash) { _hash = hash; } - void setHash2(uint32_t hash2) { _hash2 = hash2; } - -private: - uint32_t _hash; - uint32_t _hash2; -}; - -#endif // hifi_DoubleHashKey_h diff --git a/libraries/shared/src/HashKey.cpp b/libraries/shared/src/HashKey.cpp new file mode 100644 index 0000000000..488eccb1bf --- /dev/null +++ b/libraries/shared/src/HashKey.cpp @@ -0,0 +1,67 @@ +// +// HashKey.cpp +// libraries/shared/src +// +// Created by Andrew Meadows 2017.10.25 +// Copyright 2017 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 "HashKey.h" + +#include "NumericalConstants.h" + + +const uint8_t NUM_PRIMES = 64; +const uint64_t PRIMES[] = { + 4194301UL, 4194287UL, 4194277UL, 4194271UL, 4194247UL, 4194217UL, 4194199UL, 4194191UL, + 4194187UL, 4194181UL, 4194173UL, 4194167UL, 4194143UL, 4194137UL, 4194131UL, 4194107UL, + 4194103UL, 4194023UL, 4194011UL, 4194007UL, 4193977UL, 4193971UL, 4193963UL, 4193957UL, + 4193939UL, 4193929UL, 4193909UL, 4193869UL, 4193807UL, 4193803UL, 4193801UL, 4193789UL, + 4193759UL, 4193753UL, 4193743UL, 4193701UL, 4193663UL, 4193633UL, 4193573UL, 4193569UL, + 4193551UL, 4193549UL, 4193531UL, 4193513UL, 4193507UL, 4193459UL, 4193447UL, 4193443UL, + 4193417UL, 4193411UL, 4193393UL, 4193389UL, 4193381UL, 4193377UL, 4193369UL, 4193359UL, + 4193353UL, 4193327UL, 4193309UL, 4193303UL, 4193297UL, 4193279UL, 4193269UL, 4193263UL +}; + + +// this hash function inspired by Squirrel Eiserloh's GDC2017 talk: "Noise-Based RNG" +uint64_t squirrel3_64(uint64_t data, uint8_t primeIndex) { + constexpr uint64_t BIT_NOISE1 = 2760725261486592643UL; + constexpr uint64_t BIT_NOISE2 = 6774464464027632833UL; + constexpr uint64_t BIT_NOISE3 = 5545331650366059883UL; + + // blend prime numbers into the hash to prevent dupes + // when hashing the same set of numbers in a different order + uint64_t hash = PRIMES[primeIndex % NUM_PRIMES] * data; + hash *= BIT_NOISE1; + hash ^= (hash >> 16); + hash += BIT_NOISE2; + hash ^= (hash << 16); + hash *= BIT_NOISE3; + return hash ^ (hash >> 16); +} + +constexpr float QUANTIZED_VALUES_PER_METER = 250.0f; + +// static +float HashKey::getNumQuantizedValuesPerMeter() { + return QUANTIZED_VALUES_PER_METER; +} + +void HashKey::hashUint64(uint64_t data) { + _hash += squirrel3_64(data, ++_hashCount); +} + +void HashKey::hashFloat(float data) { + _hash += squirrel3_64((uint64_t)((int64_t)(data * QUANTIZED_VALUES_PER_METER)), ++_hashCount); +} + +void HashKey::hashVec3(const glm::vec3& data) { + _hash += squirrel3_64((uint64_t)((int64_t)(data[0] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); + _hash *= squirrel3_64((uint64_t)((int64_t)(data[1] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); + _hash ^= squirrel3_64((uint64_t)((int64_t)(data[2] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); +} + diff --git a/libraries/shared/src/HashKey.h b/libraries/shared/src/HashKey.h new file mode 100644 index 0000000000..5fce182084 --- /dev/null +++ b/libraries/shared/src/HashKey.h @@ -0,0 +1,52 @@ +// +// HashKey.h +// libraries/shared/src +// +// Created by Andrew Meadows 2017.10.25 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_HashKey_h +#define hifi_HashKey_h + +#include +#include + +#include + + +// HashKey for use with btHashMap which requires a particular API for its keys. In particular it +// requires its Key to implement these methods: +// +// bool Key::equals() +// int32_t Key::getHash() +// +// The important thing about the HashKey implementation is that while getHash() returns 32-bits, +// internally HashKey stores a 64-bit hash which is used for the equals() comparison. This allows +// btHashMap to insert "dupe" 32-bit keys to different "values". + +class HashKey { +public: + static float getNumQuantizedValuesPerMeter(); + + // These two methods are required by btHashMap. + bool equals(const HashKey& other) const { return _hash == other._hash; } + int32_t getHash() const { return (int32_t)((uint32_t)_hash); } + + void clear() { _hash = _hashCount = 0; } + bool isNull() const { return _hash == 0 && _hashCount == 0; } + void hashUint64(uint64_t data); + void hashFloat(float data); + void hashVec3(const glm::vec3& data); + + uint64_t getHash64() const { return _hash; } // for debug/test purposes + +private: + uint64_t _hash { 0 }; + uint8_t _hashCount { 0 }; +}; + +#endif // hifi_HashKey_h diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 580baf7317..782b0dc523 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -59,8 +59,12 @@ public: _max = other._max; } double totalSamples = _samples + other._samples; - _average = _average * ((double)_samples / totalSamples) - + other._average * ((double)other._samples / totalSamples); + if (totalSamples > 0) { + _average = _average * ((double)_samples / totalSamples) + + other._average * ((double)other._samples / totalSamples); + } else { + _average = 0.0f; + } _samples += other._samples; } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 36ce38335a..8cdc4bcf14 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -53,7 +53,7 @@ void ShapeInfo::clear() { _triangleIndices.clear(); _halfExtents = glm::vec3(0.0f); _offset = glm::vec3(0.0f); - _doubleHashKey.clear(); + _hashKey.clear(); _type = SHAPE_TYPE_NONE; } @@ -87,14 +87,14 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString default: break; } - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; setHalfExtents(halfExtents); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setSphere(float radius) { @@ -102,12 +102,12 @@ void ShapeInfo::setSphere(float radius) { _type = SHAPE_TYPE_SPHERE; radius = glm::max(radius, MIN_HALF_EXTENT); _halfExtents = glm::vec3(radius); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { _pointCollection = pointCollection; - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setCapsuleY(float radius, float halfHeight) { @@ -116,12 +116,12 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) { radius = glm::max(radius, MIN_HALF_EXTENT); halfHeight = glm::max(halfHeight, 0.0f); _halfExtents = glm::vec3(radius, halfHeight, radius); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setOffset(const glm::vec3& offset) { _offset = offset; - _doubleHashKey.clear(); + _hashKey.clear(); } uint32_t ShapeInfo::getNumSubShapes() const { @@ -256,119 +256,46 @@ bool ShapeInfo::contains(const glm::vec3& point) const { } } -const DoubleHashKey& ShapeInfo::getHash() const { +const HashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. - if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) { - bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET; + if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) { // The key is not yet cached therefore we must compute it. - // compute hash1 - // TODO?: provide lookup table for hash/hash2 of _type rather than recompute? - uint32_t primeIndex = 0; - _doubleHashKey.computeHash((uint32_t)_type, primeIndex++); - + _hashKey.hashUint64((uint64_t)_type); if (_type != SHAPE_TYPE_SIMPLE_HULL) { - // compute hash1 - uint32_t hash = _doubleHashKey.getHash(); - for (int j = 0; j < 3; ++j) { - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), - primeIndex++); - if (useOffset) { - hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), - primeIndex++); - } - } - _doubleHashKey.setHash(hash); - - // compute hash2 - hash = _doubleHashKey.getHash2(); - for (int j = 0; j < 3; ++j) { - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - uint32_t floatHash = DoubleHashKey::hashFunction2( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f)); - if (useOffset) { - floatHash ^= DoubleHashKey::hashFunction2( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f)); - } - hash += ~(floatHash << 17); - hash ^= (floatHash >> 11); - hash += (floatHash << 4); - hash ^= (floatHash >> 7); - hash += ~(floatHash << 10); - hash = (hash << 16) | (hash >> 16); - } - _doubleHashKey.setHash2(hash); + _hashKey.hashVec3(_halfExtents); + _hashKey.hashVec3(_offset); } else { - + // TODO: we could avoid hashing all of these points if we were to supply the ShapeInfo with a unique + // descriptive string. Shapes that are uniquely described by their type and URL could just put their + // url in the description. assert(_pointCollection.size() == (size_t)1); const PointList & points = _pointCollection.back(); const int numPoints = (int)points.size(); - uint32_t hash = _doubleHashKey.getHash(); - uint32_t hash2 = _doubleHashKey.getHash2(); - for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex) { - // compute hash1 & 2 - const glm::vec3 &curPoint = points[pointIndex]; - for (int vecCompIndex = 0; vecCompIndex < 3; ++vecCompIndex) { - - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - uint32_t valueToHash = (uint32_t)(curPoint[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, curPoint[vecCompIndex]) * 0.49f); - - hash ^= DoubleHashKey::hashFunction(valueToHash, primeIndex++); - uint32_t floatHash = DoubleHashKey::hashFunction2(valueToHash); - - if (useOffset) { - - const uint32_t offsetValToHash = (uint32_t)(_offset[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[vecCompIndex])* 0.49f); - - hash ^= DoubleHashKey::hashFunction(offsetValToHash, primeIndex++); - floatHash ^= DoubleHashKey::hashFunction2(offsetValToHash); - } - - hash2 += ~(floatHash << 17); - hash2 ^= (floatHash >> 11); - hash2 += (floatHash << 4); - hash2 ^= (floatHash >> 7); - hash2 += ~(floatHash << 10); - hash2 = (hash2 << 16) | (hash2 >> 16); - } + for (int i = 0; i < numPoints; ++i) { + _hashKey.hashVec3(points[i]); } - - _doubleHashKey.setHash(hash); - _doubleHashKey.setHash2(hash2); } QString url = _url.toString(); if (!url.isEmpty()) { - // fold the urlHash into both parts QByteArray baUrl = url.toLocal8Bit(); uint32_t urlHash = qChecksum(baUrl.data(), baUrl.size()); - _doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash); - _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash); + _hashKey.hashUint64((uint64_t)urlHash); } - uint32_t numHulls = 0; if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) { - numHulls = (uint32_t)_pointCollection.size(); + uint64_t numHulls = (uint64_t)_pointCollection.size(); + _hashKey.hashUint64(numHulls); } else if (_type == SHAPE_TYPE_SIMPLE_HULL) { - numHulls = 1; - } - if (numHulls > 0) { - uint32_t hash = DoubleHashKey::hashFunction(numHulls, primeIndex++); - _doubleHashKey.setHash(_doubleHashKey.getHash() ^ hash); - hash = DoubleHashKey::hashFunction2(numHulls); - _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ hash); + _hashKey.hashUint64(1); } } - return _doubleHashKey; + return _hashKey; } void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) { _halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT)); + _hashKey.clear(); } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index d658b936a3..069241e29d 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -18,7 +18,7 @@ #include #include -#include "DoubleHashKey.h" +#include "HashKey.h" const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored @@ -89,7 +89,7 @@ public: /// For compound shapes it will only return whether it is inside the bounding box bool contains(const glm::vec3& point) const; - const DoubleHashKey& getHash() const; + const HashKey& getHash() const; protected: void setHalfExtents(const glm::vec3& halfExtents); @@ -99,7 +99,7 @@ protected: TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); - mutable DoubleHashKey _doubleHashKey; + mutable HashKey _hashKey; ShapeType _type = SHAPE_TYPE_NONE; }; diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 978221e167..dcbfd83ec7 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -504,16 +505,17 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERT {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left }; -CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { +template +CubeProjectedPolygon ViewFrustum::computeProjectedPolygon(const TBOX& box) const { const glm::vec3& bottomNearRight = box.getCorner(); glm::vec3 topFarLeft = box.calcTopFarLeft(); - int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit - + ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to - + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera - + ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to - + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining - + ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes + int lookUp = ((_position.x < bottomNearRight.x)) // 1 = right | compute 6-bit + + ((_position.x > topFarLeft.x) << 1) // 2 = left | code to + + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera + + ((_position.y > topFarLeft.y) << 3) // 8 = top | with respect to + + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining + + ((_position.z > topFarLeft.z) << 5); // 32 = back/far | planes int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices @@ -524,8 +526,8 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { bool anyPointsInView = false; // assume the worst! if (vertexCount) { allPointsInView = true; // assume the best! - for(int i = 0; i < vertexCount; i++) { - int vertexNum = hullVertexLookup[lookUp][i+1]; + for (int i = 0; i < vertexCount; i++) { + int vertexNum = hullVertexLookup[lookUp][i + 1]; glm::vec3 point = box.getVertex((BoxVertex)vertexNum); glm::vec2 projectedPoint = projectPoint(point, pointInView); allPointsInView = allPointsInView && pointInView; @@ -538,24 +540,24 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { // NOTE: This clipping does not improve our overall performance. It basically causes more polygons to // end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes() if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { + (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { - CoverageRegion::_clippedPolygons++; + CoverageRegion::_clippedPolygons++; - glm::vec2* clippedVertices; - int clippedVertexCount; - PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); + glm::vec2* clippedVertices; + int clippedVertexCount; + PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); - // Now reset the vertices of our projectedPolygon object - projectedPolygon.setVertexCount(clippedVertexCount); - for(int i = 0; i < clippedVertexCount; i++) { - projectedPolygon.setVertex(i, clippedVertices[i]); - } - delete[] clippedVertices; + // Now reset the vertices of our projectedPolygon object + projectedPolygon.setVertexCount(clippedVertexCount); + for(int i = 0; i < clippedVertexCount; i++) { + projectedPolygon.setVertex(i, clippedVertices[i]); + } + delete[] clippedVertices; - lookUp += PROJECTION_CLIPPED; + lookUp += PROJECTION_CLIPPED; } ***/ } @@ -568,6 +570,97 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { return projectedPolygon; } +CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { + return computeProjectedPolygon(box); +} + +CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { + return computeProjectedPolygon(box); +} + +bool ViewFrustum::getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const { + using Edge = std::pair; + + const int VERTEX_COUNT = 8; + const int EDGE_COUNT = 12; + // In theory, after clipping a box with a plane, only 4 new vertices at max + // should be created but due to potential imprecisions (edge almost parallel to + // near plane for instance) there might be more + const int MAX_VERTEX_COUNT = VERTEX_COUNT + 4 + 2; + + std::array vertices; + std::array boxEdges{ { + Edge{BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR}, + Edge{TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, + Edge{BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, + Edge{TOP_LEFT_FAR, TOP_RIGHT_FAR}, + Edge{BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, + Edge{BOTTOM_LEFT_FAR, TOP_LEFT_FAR}, + Edge{BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR}, + Edge{BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR}, + Edge{BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR}, + Edge{TOP_LEFT_NEAR, TOP_LEFT_FAR}, + Edge{BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR}, + Edge{TOP_RIGHT_NEAR, TOP_RIGHT_FAR} + } }; + std::array distancesToNearPlane; + std::bitset areVerticesInside; + int vertexCount = VERTEX_COUNT; + int i; + + // Clip the hull with the near plane. + const auto& nearPlane = _planes[NEAR_PLANE]; + + for (i = 0; i < VERTEX_COUNT; i++) { + vertices[i] = box.getVertex(static_cast(i)); + distancesToNearPlane[i] = nearPlane.distance(vertices[i]); + } + + for (i = 0; i < EDGE_COUNT; i++) { + const auto& edgeVertexIndices = boxEdges[i]; + const auto& startVertex = vertices[edgeVertexIndices.first]; + const auto& endVertex = vertices[edgeVertexIndices.second]; + float startVertexDistance = distancesToNearPlane[edgeVertexIndices.first]; + float endVertexDistance = distancesToNearPlane[edgeVertexIndices.second]; + bool isStartPointInside = startVertexDistance >= 0.0f; + bool isEndPointInside = endVertexDistance >= 0.0f; + + areVerticesInside.set(edgeVertexIndices.first, isStartPointInside); + areVerticesInside.set(edgeVertexIndices.second, isEndPointInside); + + if (isStartPointInside != isEndPointInside) { + // One of the two vertices is behind the near plane so add a new clipped vertex + // add tag it as projectable. + vertices[vertexCount] = startVertex + (endVertex - startVertex) * (startVertexDistance / (startVertexDistance - endVertexDistance)); + areVerticesInside.set(vertexCount); + vertexCount++; + } + } + + // Project points that are inside + bottomLeft.x = std::numeric_limits::max(); + bottomLeft.y = std::numeric_limits::max(); + topRight.x = -std::numeric_limits::max(); + topRight.y = -std::numeric_limits::max(); + for (i = 0; i < vertexCount; i++) { + if (areVerticesInside[i]) { + bool isPointInside; + auto projectedPoint = projectPoint(vertices[i], isPointInside); + bottomLeft.x = std::min(bottomLeft.x, projectedPoint.x); + bottomLeft.y = std::min(bottomLeft.y, projectedPoint.y); + topRight.x = std::max(topRight.x, projectedPoint.x); + topRight.y = std::max(topRight.y, projectedPoint.y); + } + } + + bottomLeft.x = glm::clamp(bottomLeft.x, -1.0f, 1.0f); + bottomLeft.y = glm::clamp(bottomLeft.y, -1.0f, 1.0f); + topRight.x = glm::clamp(topRight.x, -1.0f, 1.0f); + topRight.y = glm::clamp(topRight.y, -1.0f, 1.0f); + + return areVerticesInside.any(); +} + // Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the // axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for // squares and square-roots. Just compares. diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index d1b88fb2a5..98f666d666 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -119,6 +119,8 @@ public: glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; CubeProjectedPolygon getProjectedPolygon(const AACube& box) const; + CubeProjectedPolygon getProjectedPolygon(const AABox& box) const; + bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; float distanceToCamera(const glm::vec3& point) const; @@ -169,6 +171,10 @@ private: // Used to project points glm::mat4 _ourModelViewProjectionMatrix; + + template + CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const; + }; using ViewFrustumPointer = std::shared_ptr; diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index b16739b2b8..068efb351b 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -10,13 +10,24 @@ (function() { // BEGIN LOCAL_SCOPE +//check if script already running. +var scriptData = ScriptDiscoveryService.getRunning(); +var scripts = scriptData.filter(function (datum) { return datum.name === 'debugWindow.js'; }); +if (scripts.length >= 2) { + //2nd instance of the script is too much + ScriptDiscoveryService.stopScript(scripts[1].url); + return; +} + // Set up the qml ui var qml = Script.resolvePath('debugWindow.qml'); + var window = new OverlayWindow({ title: 'Debug Window', source: qml, width: 400, height: 900, }); + window.setPosition(25, 50); window.closed.connect(function() { Script.stop(); }); diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js new file mode 100644 index 0000000000..5175761978 --- /dev/null +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -0,0 +1,160 @@ +// +// debugHighlight.js +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 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 +// + +// Set up the qml ui +var qml = Script.resolvePath('highlight.qml'); +var window = new OverlayWindow({ + title: 'Highlight', + source: qml, + width: 400, + height: 400, +}); +window.closed.connect(function() { Script.stop(); }); + +"use strict"; + +// Created by Sam Gondelman on 9/7/2017 +// Copyright 2017 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 + +(function() { // BEGIN LOCAL_SCOPE + +var END_DIMENSIONS = { + x: 0.15, + y: 0.15, + z: 0.15 +}; +var COLOR = {red: 97, green: 247, blue: 255}; +var end = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR, + ignoreRayIntersection: true, + alpha: 1.0, + visible: true +} + +var COLOR2 = {red: 247, green: 97, blue: 255}; +var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true, + alpha: 1.0, + visible: true +} + +var highlightGroupIndex = 0 +var isSelectionAddEnabled = false +var isSelectionEnabled = false +var renderStates = [{name: "test", end: end}]; +var defaultRenderStates = [{name: "test", distance: 20.0, end: end2}]; +var time = 0 + +var ray = LaserPointers.createLaserPointer({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_AVATARS | RayPick.PICK_INVISIBLE | RayPick.PICK_NONCOLLIDABLE, + renderStates: renderStates, + defaultRenderStates: defaultRenderStates, + enabled: false +}); + +function getSelectionName() { + var selectionName = "contextOverlayHighlightList" + + if (highlightGroupIndex>0) { + selectionName += highlightGroupIndex + } + return selectionName +} + +function fromQml(message) { + tokens = message.split(' ') + print("Received '"+message+"' from hightlight.qml") + if (tokens[0]=="highlight") { + highlightGroupIndex = parseInt(tokens[1]) + print("Switching to highlight group "+highlightGroupIndex) + } else if (tokens[0]=="pick") { + isSelectionEnabled = tokens[1]=='true' + print("Ray picking set to "+isSelectionEnabled.toString()) + if (isSelectionEnabled) { + LaserPointers.enableLaserPointer(ray) + } else { + LaserPointers.disableLaserPointer(ray) + } + time = 0 + } else if (tokens[0]=="add") { + isSelectionAddEnabled = tokens[1]=='true' + print("Add to selection set to "+isSelectionAddEnabled.toString()) + if (!isSelectionAddEnabled) { + Selection.clearSelectedItemsList(getSelectionName()) + } + } +} + +window.fromQml.connect(fromQml); + +function cleanup() { + LaserPointers.removeLaserPointer(ray); +} +Script.scriptEnding.connect(cleanup); + +var prevID = 0 +var prevType = "" +var selectedID = 0 +var selectedType = "" +function update(deltaTime) { + + // you have to do this repeatedly because there's a bug but I'll fix it + LaserPointers.setRenderState(ray, "test"); + + var result = LaserPointers.getPrevRayPickResult(ray); + var selectionName = getSelectionName() + + if (isSelectionEnabled && result.type != RayPick.INTERSECTED_NONE) { + time += deltaTime + if (result.objectID != prevID) { + var typeName = "" + if (result.type == RayPick.INTERSECTED_ENTITY) { + typeName = "entity" + } else if (result.type == RayPick.INTERSECTED_OVERLAY) { + typeName = "overlay" + } else if (result.type == RayPick.INTERSECTED_AVATAR) { + typeName = "avatar" + } + + prevID = result.objectID; + prevType = typeName; + time = 0 + } else if (time>1.0 && prevID!=selectedID) { + if (prevID != 0 && !isSelectionAddEnabled) { + Selection.removeFromSelectedItemsList(selectionName, selectedType, selectedID) + } + selectedID = prevID + selectedType = prevType + Selection.addToSelectedItemsList(selectionName, selectedType, selectedID) + print("HIGHLIGHT " + highlightGroupIndex + " picked type: " + result.type + ", id: " + result.objectID); + } + } else { + if (prevID != 0 && !isSelectionAddEnabled) { + Selection.removeFromSelectedItemsList(selectionName, prevType, prevID) + } + prevID = 0 + selectedID = 0 + time = 0 + } +} + +Script.update.connect(update); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/developer/utilities/render/debugOutline.js b/scripts/developer/utilities/render/debugOutline.js deleted file mode 100644 index e333ab5869..0000000000 --- a/scripts/developer/utilities/render/debugOutline.js +++ /dev/null @@ -1,20 +0,0 @@ -// -// debugOutline.js -// developer/utilities/render -// -// Olivier Prat, created on 08/08/2017. -// Copyright 2017 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 -// - -// Set up the qml ui -var qml = Script.resolvePath('outline.qml'); -var window = new OverlayWindow({ - title: 'Outline', - source: qml, - width: 285, - height: 370, -}); -window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml new file mode 100644 index 0000000000..6be74fcf40 --- /dev/null +++ b/scripts/developer/utilities/render/highlight.qml @@ -0,0 +1,177 @@ +// +// highlight.qml +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "configSlider" + +Rectangle { + id: root + HifiConstants { id: hifi;} + color: hifi.colors.baseGray; + anchors.margins: hifi.dimensions.contentMargin.x + + property var debugConfig: Render.getConfig("RenderMainView.HighlightDebug") + property var highlightConfig: Render.getConfig("UpdateScene.HighlightStageSetup") + + signal sendToScript(var message); + + Column { + id: col + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Row { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + + HifiControls.CheckBox { + id: debug + text: "View Mask" + checked: root.debugConfig["viewMask"] + onCheckedChanged: { + root.debugConfig["viewMask"] = checked; + } + } + HifiControls.CheckBox { + text: "Hover select" + checked: false + onCheckedChanged: { + sendToScript("pick "+checked.toString()) + } + } + HifiControls.CheckBox { + text: "Add to selection" + checked: false + onCheckedChanged: { + sendToScript("add "+checked.toString()) + } + } + } + + HifiControls.ComboBox { + id: box + width: 350 + z: 999 + editable: true + colorScheme: hifi.colorSchemes.dark + model: [ + "contextOverlayHighlightList", + "highlightList1", + "highlightList2", + "highlightList3", + "highlightList4"] + label: "" + + Timer { + id: postpone + interval: 100; running: false; repeat: false + onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets } + } + onCurrentIndexChanged: { + root.highlightConfig["selectionName"] = model[currentIndex]; + sendToScript("highlight "+currentIndex) + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 100ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 100 + postpone.start() + } + } + + Loader { + id: paramWidgetLoader + sourceComponent: paramWidgets + width: 350 + } + + Component { + id: paramWidgets + + Column { + spacing: 10 + anchors.margins: hifi.dimensions.contentMargin.x + + HifiControls.Label { + text: "Outline" + } + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + text: "Smooth" + checked: root.highlightConfig["isOutlineSmooth"] + onCheckedChanged: { + root.highlightConfig["isOutlineSmooth"] = checked; + } + } + Repeater { + model: ["Width:outlineWidth:5.0:0.0", + "Intensity:outlineIntensity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + } + + Separator {} + HifiControls.Label { + text: "Color" + } + Repeater { + model: ["Red:colorR:1.0:0.0", + "Green:colorG:1.0:0.0", + "Blue:colorB:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + + Separator {} + HifiControls.Label { + text: "Fill Opacity" + } + Repeater { + model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", + "Occluded:occludedFillOpacity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + } + } + } +} diff --git a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml new file mode 100644 index 0000000000..5669f90628 --- /dev/null +++ b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml @@ -0,0 +1,116 @@ +// +// highlightPage.qml +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import "../configSlider" +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +Rectangle { + id: root + property var highlightIndex: 0 + property var drawConfig: Render.getConfig("RenderMainView.HighlightEffect"+highlightIndex) + + HifiConstants { id: hifi;} + anchors.margins: hifi.dimensions.contentMargin.x + + Column { + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + HifiControls.CheckBox { + id: glow + text: "Glow" + checked: root.drawConfig["glow"] + onCheckedChanged: { + root.drawConfig["glow"] = checked; + } + } + Repeater { + model: ["Width:width:5.0:0.0", + "Intensity:intensity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + + GroupBox { + title: "Color" + anchors.left: parent.left + anchors.right: parent.right + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Repeater { + model: ["Red:colorR:1.0:0.0", + "Green:colorG:1.0:0.0", + "Blue:colorB:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + + GroupBox { + title: "Fill Opacity" + anchors.left: parent.left + anchors.right: parent.right + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Repeater { + model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", + "Occluded:occludedFillOpacity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + } +} diff --git a/scripts/developer/utilities/render/highlightPage/qmldir b/scripts/developer/utilities/render/highlightPage/qmldir new file mode 100644 index 0000000000..bb3de24b84 --- /dev/null +++ b/scripts/developer/utilities/render/highlightPage/qmldir @@ -0,0 +1 @@ +HighlightPage 1.0 HighlightPage.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/outline.qml b/scripts/developer/utilities/render/outline.qml deleted file mode 100644 index e17f7c1f1c..0000000000 --- a/scripts/developer/utilities/render/outline.qml +++ /dev/null @@ -1,119 +0,0 @@ -// -// outline.qml -// developer/utilities/render -// -// Olivier Prat, created on 08/08/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "configSlider" - -Item { - id: root - property var debugConfig: Render.getConfig("RenderMainView.OutlineDebug") - property var drawConfig: Render.getConfig("RenderMainView.OutlineEffect") - - Column { - spacing: 8 - - CheckBox { - text: "View Outlined Depth" - checked: root.debugConfig["viewOutlinedDepth"] - onCheckedChanged: { - root.debugConfig["viewOutlinedDepth"] = checked; - } - } - CheckBox { - text: "Glow" - checked: root.drawConfig["glow"] - onCheckedChanged: { - root.drawConfig["glow"] = checked; - } - } - ConfigSlider { - label: "Width" - integral: false - config: root.drawConfig - property: "width" - max: 15.0 - min: 0.0 - width: 280 - } - ConfigSlider { - label: "Intensity" - integral: false - config: root.drawConfig - property: "intensity" - max: 1.0 - min: 0.0 - width: 280 - } - - GroupBox { - title: "Color" - width: 280 - Column { - spacing: 8 - - ConfigSlider { - label: "Red" - integral: false - config: root.drawConfig - property: "colorR" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Green" - integral: false - config: root.drawConfig - property: "colorG" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Blue" - integral: false - config: root.drawConfig - property: "colorB" - max: 1.0 - min: 0.0 - width: 270 - } - } - } - - GroupBox { - title: "Fill Opacity" - width: 280 - Column { - spacing: 8 - - ConfigSlider { - label: "Unoccluded" - integral: false - config: root.drawConfig - property: "fillOpacityUnoccluded" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Occluded" - integral: false - config: root.drawConfig - property: "fillOpacityOccluded" - max: 1.0 - min: 0.0 - width: 270 - } - } - } - } -} diff --git a/scripts/developer/utilities/render/statsGPU.qml b/scripts/developer/utilities/render/statsGPU.qml index 6b80f00af3..8d284c11ca 100644 --- a/scripts/developer/utilities/render/statsGPU.qml +++ b/scripts/developer/utilities/render/statsGPU.qml @@ -65,6 +65,13 @@ Item { label: "tone and post", color: "#FF0000" } + , + { + object: Render.getConfig("RenderMainView.OutlineRangeTimer"), + prop: "gpuRunTime", + label: "outline", + color: "#FFFF00" + } ] } PlotPerf { @@ -105,6 +112,13 @@ Item { label: "tone and post", color: "#FF0000" } + , + { + object: Render.getConfig("RenderMainView.OutlineRangeTimer"), + prop: "batchRunTime", + label: "outline", + color: "#FFFF00" + } ] } } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index e7d4aed3fb..9453b476ee 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -555,20 +555,21 @@
-
+
+ + +
-
-
-
-
-
-
-
+
+
+
+
+
@@ -582,6 +583,18 @@
+
+
+
+
+ + + + +
+
+
+
@@ -600,20 +613,21 @@
-
-
-
-
-
- - +
+
+ + + +
+
+
+
+ + +
-
- - -
diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp index c6a19084a2..79d0092dc3 100644 --- a/tests/physics/src/ShapeInfoTests.cpp +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -23,108 +23,78 @@ QTEST_MAIN(ShapeInfoTests) +// Enable this to manually run testHashCollisions +// (NOT a regular unit test; takes ~40 secs to run on an i7) +//#define MANUAL_TEST true + void ShapeInfoTests::testHashFunctions() { #if MANUAL_TEST int maxTests = 10000000; ShapeInfo info; - btHashMap hashes; + btHashMap hashes; - uint32_t bits[32]; - uint32_t masks[32]; - for (int i = 0; i < 32; ++i) { + const int32_t NUM_HASH_BITS = 32; + uint32_t bits[NUM_HASH_BITS]; + uint32_t masks[NUM_HASH_BITS]; + for (int i = 0; i < NUM_HASH_BITS; ++i) { bits[i] = 0; - masks[i] = 1U << i; + masks[i] = 1UL << i; } - float deltaLength = 0.002f; - float endLength = 100.0f; + float deltaLength = 1.0f / (HashKey::getNumQuantizedValuesPerMeter() - 3.0f); + float endLength = 2000.0f * deltaLength; int numSteps = (int)(endLength / deltaLength); int testCount = 0; int numCollisions = 0; btClock timer; - for (int x = 1; x < numSteps && testCount < maxTests; ++x) { - float radiusX = (float)x * deltaLength; + for (int i = 1; i < numSteps && testCount < maxTests; ++i) { + float radiusX = (float)i * deltaLength; + int32_t* hashPtr; // test sphere info.setSphere(radiusX); ++testCount; - DoubleHashKey key = info.getHash(); - uint32_t* hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; + HashKey key = info.getHash(); + hashPtr = hashes.find(key); + if (hashPtr) { + std::cout << testCount << " hash collision sphere radius = " << radiusX + << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr + << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key.getHash(), key.getHash2()); + hashes.insert(key, key.getHash()); } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { - ++bits[k]; + // track bit distribution counts to evaluate hash function randomness + for (int j = 0; j < NUM_HASH_BITS; ++j) { + if (masks[j] & key.getHash()) { + ++bits[j]; } } for (int y = 1; y < numSteps && testCount < maxTests; ++y) { float radiusY = (float)y * deltaLength; - /* TODO: reimplement Cylinder and Capsule shapes - // test cylinder and capsule - int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE }; - for (int i = 0; i < 2; ++i) { - switch(types[i]) { - case CYLINDER_SHAPE_PROXYTYPE: { - info.setCylinder(radiusX, radiusY); - break; - } - case CAPSULE_SHAPE_PROXYTYPE: { - info.setCapsuleY(radiusX, radiusY); - break; - } - } - - ++testCount; - key = info.getHash(); - hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; - ++numCollisions; - assert(false); - } else { - hashes.insert(key.getHash(), key.getHash2()); - } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { - ++bits[k]; - } - } - } - */ - for (int z = 1; z < numSteps && testCount < maxTests; ++z) { float radiusZ = (float)z * deltaLength; // test box info.setBox(glm::vec3(radiusX, radiusY, radiusZ)); ++testCount; - DoubleHashKey key = info.getHash(); - hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX - << " radiusY = " << radiusY << " radiusZ = " << radiusZ - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; + HashKey key = info.getHash(); + hashPtr = hashes.find(key); + if (hashPtr) { + std::cout << testCount << " hash collision box dimensions = < " << radiusX + << ", " << radiusY << ", " << radiusZ << " >" + << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr << " : 0x" << key.getHash64() + << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key.getHash(), key.getHash2()); + hashes.insert(key, key.getHash()); } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { + // track bit distribution counts to evaluate hash function randomness + for (int k = 0; k < NUM_HASH_BITS; ++k) { + if (masks[k] & key.getHash()) { ++bits[k]; } } @@ -135,7 +105,8 @@ void ShapeInfoTests::testHashFunctions() { std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl; // print out distribution of bits - for (int i = 0; i < 32; ++i) { + // ideally the numbers in each bin will be about the same + for (int i = 0; i < NUM_HASH_BITS; ++i) { std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl; } QCOMPARE(numCollisions, 0); @@ -146,15 +117,14 @@ void ShapeInfoTests::testBoxShape() { ShapeInfo info; glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); info.setBox(halfExtents); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; } @@ -163,15 +133,14 @@ void ShapeInfoTests::testSphereShape() { ShapeInfo info; float radius = 1.23f; info.setSphere(radius); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; } @@ -182,15 +151,14 @@ void ShapeInfoTests::testCylinderShape() { float radius = 1.23f; float height = 4.56f; info.setCylinder(radius, height); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; */ @@ -202,15 +170,14 @@ void ShapeInfoTests::testCapsuleShape() { float radius = 1.23f; float height = 4.56f; info.setCapsule(radius, height); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; */ diff --git a/tests/physics/src/ShapeInfoTests.h b/tests/physics/src/ShapeInfoTests.h index fbd89a13a8..1f6054dd1a 100644 --- a/tests/physics/src/ShapeInfoTests.h +++ b/tests/physics/src/ShapeInfoTests.h @@ -18,10 +18,6 @@ //#include "BulletTestUtils.h" //#include "../QTestExtensions.h" -// Enable this to manually run testHashCollisions -// (NOT a regular unit test; takes ~17 secs to run on an i7) -#define MANUAL_TEST false - class ShapeInfoTests : public QObject { Q_OBJECT private slots: