From 5050a554a2b2d99fdcb171c80f9682d25f4a56aa Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 10:34:34 -0800 Subject: [PATCH 01/14] remove warning about signed/unsigned comparison --- libraries/render/src/render/IndexedContainer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render/src/render/IndexedContainer.h b/libraries/render/src/render/IndexedContainer.h index 4b51f8eb3c..2d21b5cc29 100644 --- a/libraries/render/src/render/IndexedContainer.h +++ b/libraries/render/src/render/IndexedContainer.h @@ -87,7 +87,7 @@ namespace indexed_container { if (index < (Index) _elements.size()) { _elements[index] = e; } else { - assert(index == _elements.size()); + assert(index == (Index)_elements.size()); _elements.emplace_back(e); } } @@ -159,4 +159,4 @@ namespace indexed_container { }; }; } -#endif \ No newline at end of file +#endif From 809ff7928e6c793efd803e4d38d11a5273bcb8b3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 10:34:58 -0800 Subject: [PATCH 02/14] sort and throttle UpdateRenderables --- .../src/EntityTreeRenderer.cpp | 127 ++++++++++++++++-- .../src/EntityTreeRenderer.h | 15 ++- .../src/RenderableEntityItem.cpp | 5 +- .../src/RenderableEntityItem.h | 7 +- 4 files changed, 139 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 07d8716656..527bf29cb4 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -12,6 +12,7 @@ #include "EntityTreeRenderer.h" #include +#include #include #include @@ -272,7 +273,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } } -void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction) { +void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; @@ -286,21 +287,129 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene #endif }); - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - if (!renderable) { - continue; + { + PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size()); + if (_renderablesToUpdate.empty()) { + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + if (!renderable) { + continue; + } + _renderablesToUpdate.insert({ entityId, renderable }); + } + } else { + // we weren't able to update all renderables last frame + // so we have to be more careful when processing changed renderables + std::unordered_map::iterator itr; + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + itr = _renderablesToUpdate.find(entityId); + if (itr != _renderablesToUpdate.end()) { + if (!renderable) { + _renderablesToUpdate.erase(itr); + continue; + } + _renderablesToUpdate.insert(itr, { entityId, renderable }); + } else { + _renderablesToUpdate.insert({ entityId, renderable }); + } + } } - _renderablesToUpdate.insert({ entityId, renderable }); } - if (!_renderablesToUpdate.empty()) { + float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size(); + const float MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2 * USECS_PER_MSEC; + if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) { + // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + uint64_t updateStart = usecTimestampNow(); for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; renderable->updateInScene(scene, transaction); } + size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero _renderablesToUpdate.clear(); + + // compute average per-renderable update cost + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numRenderables); + const float blend = 0.1f; + _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + } else { + // we expect the cost to updating all renderables to exceed available time budget + // so we first sort by priority and update in order until out of time + uint64_t sortStart = usecTimestampNow(); + std::priority_queue sortedRenderables; + { + PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + std::unordered_map::iterator itr = _renderablesToUpdate.begin(); + glm::vec3 viewCenter = view.getPosition(); + glm::vec3 forward = view.getDirection(); + const float OUT_OF_VIEW_PENALTY = -10.0f; + while (itr != _renderablesToUpdate.end()) { + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update + EntityItemPointer entity = itr->second->getEntity(); + glm::vec3 entityPosition = entity->getPosition(); + glm::vec3 offset = entityPosition - viewCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + + float diameter = entity->getQueryAACube().getScale(); + float apparentSize = diameter / distance; + float cosineAngle = glm::dot(offset, forward) / distance; + float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); + + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. + // These weights are pure magic tuning and should be hard coded in the relation below, + // but are currently exposed for anyone who would like to explore fine tuning: + const float APPARENT_SIZE_COEFFICIENT = 1.0f; + const float CENTER_SORT_COEFFICIENT = 0.5f; + const float AGE_SORT_COEFFICIENT = 0.25f; + float priority = APPARENT_SIZE_COEFFICIENT * apparentSize + + CENTER_SORT_COEFFICIENT * cosineAngle + + AGE_SORT_COEFFICIENT * age; + + // decrement priority of things outside keyhole + if (distance > view.getCenterRadius()) { + if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { + priority += OUT_OF_VIEW_PENALTY; + } + } + + sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); + ++itr; + } + } + { + PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + + // compute remaining time budget + uint64_t updateStart = usecTimestampNow(); + const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; + uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; + uint64_t timeForSort = updateStart - sortStart; + if (timeForSort < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { + timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - timeForSort; + } + uint64_t expiry = updateStart + timeBudget; + + std::unordered_map::iterator itr; + size_t numSorted = sortedRenderables.size(); + while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { + const EntityRendererPointer& renderer = sortedRenderables.top().renderer; + renderer->updateInScene(scene, transaction); + _renderablesToUpdate.erase(renderer->getEntity()->getID()); + sortedRenderables.pop(); + } + + // compute average per-renderable update cost + size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated + 1); + const float blend = 0.1f; + _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + } } } @@ -319,7 +428,9 @@ void EntityTreeRenderer::update(bool simulate) { if (scene) { render::Transaction transaction; addPendingEntities(scene, transaction); - updateChangedEntities(scene, transaction); + ViewFrustum view; + _viewState->copyCurrentViewFrustum(view); + updateChangedEntities(scene, view, transaction); scene->enqueueTransaction(transaction); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 1eb44f996a..e922dd83e9 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -51,6 +51,14 @@ using ModelWeakPointer = std::weak_ptr; using CalculateEntityLoadingPriority = std::function; +class SortableEntityRenderer { +public: + SortableEntityRenderer(EntityRendererPointer r, float p) : renderer(r), priority(p) {} + EntityRendererPointer renderer; + float priority; + bool operator<(const SortableEntityRenderer& other) const { return priority < other.priority; } +}; + // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeProcessor, public Dependency { Q_OBJECT @@ -144,7 +152,7 @@ protected: private: void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction); - void updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction); + void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction); EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); } render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); } @@ -235,11 +243,12 @@ private: NetworkTexturePointer _skyboxTexture; QString _ambientTextureURL; QString _skyboxTextureURL; + float _avgRenderableUpdateCost { 0.0f }; bool _pendingAmbientTexture { false }; bool _pendingSkyboxTexture { false }; - quint64 _lastZoneCheck { 0 }; - const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz + uint64_t _lastZoneCheck { 0 }; + const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz const float ZONE_CHECK_DISTANCE = 0.001f; ReadWriteLockable _changedEntitiesGuard; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 9e4d832037..24de651247 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -59,7 +59,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St const QUuid& myNodeID = nodeList->getSessionUUID(); statusGetters.push_back([entity]() -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); + uint64_t delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD @@ -71,7 +71,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St }); statusGetters.push_back([entity] () -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastBroadcast(); + uint64_t delta = usecTimestampNow() - entity->getLastBroadcast(); const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD @@ -278,6 +278,7 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans if (!isValidRenderItem()) { return; } + _updateTime = usecTimestampNow(); // FIXME is this excessive? if (!needsRenderUpdate()) { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index ed636ebf73..b134dddf8c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -52,6 +52,8 @@ public: void clearSubRenderItemIDs(); void setSubRenderItemIDs(const render::ItemIDs& ids); + const uint64_t& getUpdateTime() const { return _updateTime; } + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -100,7 +102,6 @@ protected: return result; } - signals: void requestRenderUpdate(); @@ -113,14 +114,16 @@ protected: static std::function _entitiesShouldFadeFunction; const Transform& getModelTransform() const; + Item::Bound _bound; SharedSoundPointer _collisionSound; QUuid _changeHandlerId; ItemID _renderItemID{ Item::INVALID_ITEM_ID }; ItemIDs _subRenderItemIDs; quint64 _fadeStartTime{ usecTimestampNow() }; + uint64_t _fadeStartTime{ usecTimestampNow() }; + uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; - Item::Bound _bound; bool _visible { false }; bool _moving { false }; // Only touched on the rendering thread From c6bccb3de3b9c49482b179bcd78c436c8a473612 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 18:58:25 -0800 Subject: [PATCH 03/14] avoid adding null renderables to list --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 527bf29cb4..aa4fe35bcd 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -310,7 +310,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene continue; } _renderablesToUpdate.insert(itr, { entityId, renderable }); - } else { + } else if (renderable) { _renderablesToUpdate.insert({ entityId, renderable }); } } From f47185b2f62308c42f077dac1d2b70e88e9afa2c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 10:49:54 -0800 Subject: [PATCH 04/14] remove tabs from indentation --- .../src/EntityTreeRenderer.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index aa4fe35bcd..17d8a6e9be 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -346,37 +346,37 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene glm::vec3 forward = view.getDirection(); const float OUT_OF_VIEW_PENALTY = -10.0f; while (itr != _renderablesToUpdate.end()) { - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update EntityItemPointer entity = itr->second->getEntity(); - glm::vec3 entityPosition = entity->getPosition(); - glm::vec3 offset = entityPosition - viewCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + glm::vec3 entityPosition = entity->getPosition(); + glm::vec3 offset = entityPosition - viewCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float diameter = entity->getQueryAACube().getScale(); - float apparentSize = diameter / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); + float diameter = entity->getQueryAACube().getScale(); + float apparentSize = diameter / distance; + float cosineAngle = glm::dot(offset, forward) / distance; + float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. + // These weights are pure magic tuning and should be hard coded in the relation below, + // but are currently exposed for anyone who would like to explore fine tuning: const float APPARENT_SIZE_COEFFICIENT = 1.0f; const float CENTER_SORT_COEFFICIENT = 0.5f; const float AGE_SORT_COEFFICIENT = 0.25f; - float priority = APPARENT_SIZE_COEFFICIENT * apparentSize - + CENTER_SORT_COEFFICIENT * cosineAngle - + AGE_SORT_COEFFICIENT * age; + float priority = APPARENT_SIZE_COEFFICIENT * apparentSize + + CENTER_SORT_COEFFICIENT * cosineAngle + + AGE_SORT_COEFFICIENT * age; - // decrement priority of things outside keyhole - if (distance > view.getCenterRadius()) { - if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { - priority += OUT_OF_VIEW_PENALTY; - } - } + // decrement priority of things outside keyhole + if (distance > view.getCenterRadius()) { + if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { + priority += OUT_OF_VIEW_PENALTY; + } + } sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); ++itr; From 8707c76a6a20cacb50da84512627532d9a452c10 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 16:30:12 -0800 Subject: [PATCH 05/14] templatize the ViewFrustum-relative sort algorithm --- .../src/EntityTreeRenderer.cpp | 61 ++++----- libraries/shared/src/PrioritySortUtil.h | 126 ++++++++++++++++++ 2 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 libraries/shared/src/PrioritySortUtil.h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 17d8a6e9be..1efcd863ab 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -38,6 +38,22 @@ #include "RenderableWebEntityItem.h" +// implement these methods BEFORE including PrioritySortUtil.h +namespace PrioritySortUtil { + glm::vec3 getObjectPosition(const EntityRendererPointer& object) { + return object->getEntity()->getPosition(); + } + + float getObjectRadius(const EntityRendererPointer& object) { + return 0.5f * object->getEntity()->getQueryAACube().getScale(); + } + + uint64_t getObjectAge(const EntityRendererPointer& object) { + return object->getUpdateTime(); + } +} +#include + size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -338,47 +354,16 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time uint64_t sortStart = usecTimestampNow(); - std::priority_queue sortedRenderables; + using SortableRenderer = PrioritySortUtil::Sortable; + std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + PrioritySortUtil::PriorityCalculator priorityCalculator(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); - glm::vec3 viewCenter = view.getPosition(); - glm::vec3 forward = view.getDirection(); - const float OUT_OF_VIEW_PENALTY = -10.0f; while (itr != _renderablesToUpdate.end()) { - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - EntityItemPointer entity = itr->second->getEntity(); - glm::vec3 entityPosition = entity->getPosition(); - glm::vec3 offset = entityPosition - viewCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - float diameter = entity->getQueryAACube().getScale(); - float apparentSize = diameter / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - const float APPARENT_SIZE_COEFFICIENT = 1.0f; - const float CENTER_SORT_COEFFICIENT = 0.5f; - const float AGE_SORT_COEFFICIENT = 0.25f; - float priority = APPARENT_SIZE_COEFFICIENT * apparentSize - + CENTER_SORT_COEFFICIENT * cosineAngle - + AGE_SORT_COEFFICIENT * age; - - // decrement priority of things outside keyhole - if (distance > view.getCenterRadius()) { - if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - - sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); + float priority = priorityCalculator.computePriority(itr->second); + SortableRenderer entry(itr->second, priority); + sortedRenderables.push(entry); ++itr; } } @@ -398,7 +383,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().renderer; + const EntityRendererPointer& renderer = sortedRenderables.top().getObject(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h new file mode 100644 index 0000000000..e084c6d1a5 --- /dev/null +++ b/libraries/shared/src/PrioritySortUtil.h @@ -0,0 +1,126 @@ +// +// PrioritySortUtil.h +// +// Created by Andrew Meadows on 2017-11-08 +// 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 +// +#pragma once +#ifndef hifi_PrioritySortUtil_h +#define hifi_PrioritySortUtil_h + +#include +#include "ViewFrustum.h" + +namespace PrioritySortUtil { + // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. + // To use this utility: + // + // (1) Declare and implent the following methods for your "object" type T: + // + // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); + // float PrioritySortUtil::getObjectRadius(const T&); + // uint64_t PrioritySortUtil::getObjectAge(const T&); + // + // (2) Below the implementation in (1): + / + // #include + // + // (3) Create a PriorityCalculator instance: + // + // PrioritySortUtil::PriorityCalculator calculator(viewFrustum); + // + // (4) Loop over your objects and insert the into a priority_queue: + // + // std::priority_queue< PrioritySortUtil::Sortable > sortedObjects; + // for (T obj in objects) { + // float priority = calculator.computePriority(obj); + // PrioritySortUtil::Sortable entry(obj, priority); + // sortedObjects.push(entry); + // } + + template + class Sortable { + public: + Sortable(const T& object, float sortPriority) : _object(object), _priority(sortPriority) {} + const T& getObject() const { return _object; } + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + T _object; + float _priority; + }; + + constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; + constexpr float DEFAULT_CENTER_COEF { 0.5f }; + constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + + template + class PriorityCalculator { + public: + PriorityCalculator() = delete; + + PriorityCalculator(const ViewFrustum& view) : _view(view) { + cacheView(); + } + + PriorityCalculator(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + { + cacheView(); + } + + void setView(const ViewFrustum& view) { _view = view; } + + void setWeights(float angularWeight, float centerWeight, float ageWeight) { + _angularWeight = angularWeight; + _centerWeight = centerWeight; + _ageWeight = ageWeight; + } + + float computePriority(T& object) const { + // priority = weighted linear combination of multiple values: + // (a) angular size + // (b) proximity to center of view + // (c) time since last update + // where the relative "weights" are tuned to scale the contributing values into units of "priority". + + glm::vec3 position = PrioritySortUtil::getObjectPosition(object); + glm::vec3 offset = position - _viewPosition; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + float radius = PrioritySortUtil::getObjectRadius(object); + + float priority = _angularWeight * (radius / distance) + + _centerWeight * (glm::dot(offset, _viewForward) / distance) + + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); + + // decrement priority of things outside keyhole + if (distance + radius > _viewRadius) { + if (!_view.sphereIntersectsFrustum(position, radius)) { + constexpr float OUT_OF_VIEW_PENALTY = -10.0f; + priority += OUT_OF_VIEW_PENALTY; + } + } + return priority; + } + + private: + void cacheView() { + // assuming we'll prioritize many objects: cache these values + _viewPosition = _view.getPosition(); + _viewForward = _view.getDirection(); + _viewRadius = _view.getCenterRadius(); + } + + ViewFrustum _view; + glm::vec3 _viewPosition; + glm::vec3 _viewForward; + float _viewRadius; + float _angularWeight { DEFAULT_ANGULAR_COEF }; + float _centerWeight { DEFAULT_CENTER_COEF }; + float _ageWeight { DEFAULT_AGE_COEF }; + }; +} +#endif // hifi_PrioritySortUtil_h From e93c10b5eeaa6d77fe84b776228a33602fd32f51 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 16:32:19 -0800 Subject: [PATCH 06/14] remove cruft --- libraries/entities-renderer/src/EntityTreeRenderer.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index e922dd83e9..1f5e6c8d92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -51,14 +51,6 @@ using ModelWeakPointer = std::weak_ptr; using CalculateEntityLoadingPriority = std::function; -class SortableEntityRenderer { -public: - SortableEntityRenderer(EntityRendererPointer r, float p) : renderer(r), priority(p) {} - EntityRendererPointer renderer; - float priority; - bool operator<(const SortableEntityRenderer& other) const { return priority < other.priority; } -}; - // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeProcessor, public Dependency { Q_OBJECT From 111480f6308751f81db0eb694bb5d6b1b8e69e39 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 17:16:13 -0800 Subject: [PATCH 07/14] fix bad conflict resolution --- libraries/entities-renderer/src/RenderableEntityItem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index b134dddf8c..5deb69711d 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -119,7 +119,6 @@ protected: QUuid _changeHandlerId; ItemID _renderItemID{ Item::INVALID_ITEM_ID }; ItemIDs _subRenderItemIDs; - quint64 _fadeStartTime{ usecTimestampNow() }; uint64_t _fadeStartTime{ usecTimestampNow() }; uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates bool _isFading{ _entitiesShouldFadeFunction() }; From d7b84f8a86417dd58c7dcba95044f7adcade813d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 17:18:19 -0800 Subject: [PATCH 08/14] fix typo in comment --- libraries/shared/src/PrioritySortUtil.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index e084c6d1a5..666847a3e7 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -25,7 +25,7 @@ namespace PrioritySortUtil { // uint64_t PrioritySortUtil::getObjectAge(const T&); // // (2) Below the implementation in (1): - / + // // #include // // (3) Create a PriorityCalculator instance: @@ -124,3 +124,4 @@ namespace PrioritySortUtil { }; } #endif // hifi_PrioritySortUtil_h + From 99b4283cbcbd43cdc2488f63c91b2ea1153b5330 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 9 Nov 2017 15:18:32 -0800 Subject: [PATCH 09/14] fix typo in 'out-of-view' check --- libraries/shared/src/PrioritySortUtil.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 666847a3e7..4b32b6d977 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -18,7 +18,7 @@ namespace PrioritySortUtil { // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. // To use this utility: // - // (1) Declare and implent the following methods for your "object" type T: + // (1) Declare and implement the following methods for your "object" type T: // // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); // float PrioritySortUtil::getObjectRadius(const T&); @@ -97,7 +97,7 @@ namespace PrioritySortUtil { + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); // decrement priority of things outside keyhole - if (distance + radius > _viewRadius) { + if (distance - radius > _viewRadius) { if (!_view.sphereIntersectsFrustum(position, radius)) { constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; From 55cc945c78fe2a2f9b7a63b34518741457029155 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 10 Nov 2017 10:09:41 -0800 Subject: [PATCH 10/14] organize PrioritySortUtil differently --- .../src/EntityTreeRenderer.cpp | 48 ++++--- libraries/shared/src/PrioritySortUtil.h | 118 +++++++++++------- 2 files changed, 94 insertions(+), 72 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1efcd863ab..c62eafd9f2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -19,18 +19,18 @@ #include #include -#include #include #include +#include +#include #include #include #include +#include +#include #include #include -#include -#include #include -#include #include #include "EntitiesRendererLogging.h" @@ -38,22 +38,6 @@ #include "RenderableWebEntityItem.h" -// implement these methods BEFORE including PrioritySortUtil.h -namespace PrioritySortUtil { - glm::vec3 getObjectPosition(const EntityRendererPointer& object) { - return object->getEntity()->getPosition(); - } - - float getObjectRadius(const EntityRendererPointer& object) { - return 0.5f * object->getEntity()->getQueryAACube().getScale(); - } - - uint64_t getObjectAge(const EntityRendererPointer& object) { - return object->getUpdateTime(); - } -} -#include - size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -353,17 +337,30 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } else { // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time + + // as per the instructions in PriortySortUtil we first derive from PrioritySortUtil::Prioritizable + class PrioritizableRenderer: public PrioritySortUtil::Prioritizable { + public: + PrioritizableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + glm::vec3 getPosition() const override { return _renderer->getEntity()->getPosition(); } + float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } + uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } + private: + const EntityRendererPointer& _renderer; + }; + + // prioritize and sort the renderables uint64_t sortStart = usecTimestampNow(); using SortableRenderer = PrioritySortUtil::Sortable; std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::PriorityCalculator priorityCalculator(view); + PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { - float priority = priorityCalculator.computePriority(itr->second); - SortableRenderer entry(itr->second, priority); - sortedRenderables.push(entry); + float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); + //SortableRenderer entry(itr->second, priority); + sortedRenderables.push(SortableRenderer(itr->second, priority)); ++itr; } } @@ -380,10 +377,11 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } uint64_t expiry = updateStart + timeBudget; + // process the sorted renderables std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getObject(); + const EntityRendererPointer& renderer = sortedRenderables.top().getThing(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 4b32b6d977..d03edc53ac 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -14,59 +14,82 @@ #include #include "ViewFrustum.h" -namespace PrioritySortUtil { - // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. - // To use this utility: - // - // (1) Declare and implement the following methods for your "object" type T: - // - // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); - // float PrioritySortUtil::getObjectRadius(const T&); - // uint64_t PrioritySortUtil::getObjectAge(const T&); - // - // (2) Below the implementation in (1): - // - // #include - // - // (3) Create a PriorityCalculator instance: - // - // PrioritySortUtil::PriorityCalculator calculator(viewFrustum); - // - // (4) Loop over your objects and insert the into a priority_queue: - // - // std::priority_queue< PrioritySortUtil::Sortable > sortedObjects; - // for (T obj in objects) { - // float priority = calculator.computePriority(obj); - // PrioritySortUtil::Sortable entry(obj, priority); - // sortedObjects.push(entry); - // } +/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: - template - class Sortable { - public: - Sortable(const T& object, float sortPriority) : _object(object), _priority(sortPriority) {} - const T& getObject() const { return _object; } - void setPriority(float priority) { _priority = priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } - private: - T _object; - float _priority; - }; +(1) Derive a class from pure-virtual PrioritySortUtil::Prioritizable + that wraps the Thing you want to prioritize and sort: + + class PrioritizableThing : public PrioritySortUtil::Prioritizable { + public: + PrioritizableThing(const Thing& thing) : _thing(thing) {} + glm::vec3 getPosition() const override { return _thing.getPosition(); } + float getRadius() const const override { return _thing.getBoundingRadius(); } + uint64_t getTimestamp() const override { return _thing.getLastUpdated(); } + private: + // Yes really: the data member is a const reference! + // PrioritizableThing only needs enough scope to compute a priority. + const Thing& _thing; + } + +(2) Loop over your things and insert each into a priority_queue: + + PrioritySortUtil::Prioritizer prioritizer(viewFrustum); + std::priority_queue< PrioritySortUtil::Sortable > sortedThings; + for (thing in things) { + float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing)); + sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); + } + +(4) Loop over your priority queue and do timeboxed work: + + uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; + while (!sortedThings.empty()) { + const Thing& thing = sortedThings.top(); + // ...do work on thing... + sortedThings.pop(); + if (usecTimestampNow() > cutoffTime) { + break; + } + } + +*/ + +namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; template - class PriorityCalculator { + class Sortable { public: - PriorityCalculator() = delete; + Sortable(const T& thing, float sortPriority) : _thing(thing), _priority(sortPriority) {} + const T& getThing() const { return _thing; } + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + T _thing; + float _priority; + }; - PriorityCalculator(const ViewFrustum& view) : _view(view) { + // Prioritizable isn't a template because templates can't have pure-virtual methods. + class Prioritizable { + public: + virtual glm::vec3 getPosition() const = 0; + virtual float getRadius() const = 0; + virtual uint64_t getTimestamp() const = 0; + }; + + template + class Prioritizer { + public: + Prioritizer() = delete; + + Prioritizer(const ViewFrustum& view) : _view(view) { cacheView(); } - PriorityCalculator(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + Prioritizer(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) { cacheView(); @@ -80,21 +103,21 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - float computePriority(T& object) const { + float computePriority(const Prioritizable& prioritizableThing) const { // priority = weighted linear combination of multiple values: // (a) angular size // (b) proximity to center of view // (c) time since last update // where the relative "weights" are tuned to scale the contributing values into units of "priority". - glm::vec3 position = PrioritySortUtil::getObjectPosition(object); + glm::vec3 position = prioritizableThing.getPosition(); glm::vec3 offset = position - _viewPosition; float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = PrioritySortUtil::getObjectRadius(object); + float radius = prioritizableThing.getRadius(); float priority = _angularWeight * (radius / distance) + _centerWeight * (glm::dot(offset, _viewForward) / distance) - + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); + + _ageWeight * (float)(usecTimestampNow() - prioritizableThing.getTimestamp()); // decrement priority of things outside keyhole if (distance - radius > _viewRadius) { @@ -108,7 +131,7 @@ namespace PrioritySortUtil { private: void cacheView() { - // assuming we'll prioritize many objects: cache these values + // assuming we'll prioritize many things: cache these values _viewPosition = _view.getPosition(); _viewForward = _view.getDirection(); _viewRadius = _view.getCenterRadius(); @@ -122,6 +145,7 @@ namespace PrioritySortUtil { float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; }; -} +} // namespace PrioritySortUtil + #endif // hifi_PrioritySortUtil_h From f67114a0a8c92a63aa496ff15c49de43facc78d3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 10 Nov 2017 14:45:46 -0800 Subject: [PATCH 11/14] PrioritySortUtil::Prioritizer doesn't need to be a template --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c62eafd9f2..11b9cb30bb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -355,7 +355,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::Prioritizer prioritizer(view); + PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index d03edc53ac..d5fdbad31b 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -80,7 +80,6 @@ namespace PrioritySortUtil { virtual uint64_t getTimestamp() const = 0; }; - template class Prioritizer { public: Prioritizer() = delete; From ffe16a754e088b8d7648258c5dc5731042ba10cf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 15:42:57 -0800 Subject: [PATCH 12/14] another pass through the crucible --- .../src/EntityTreeRenderer.cpp | 30 +++--- libraries/shared/src/PrioritySortUtil.h | 99 ++++++++----------- 2 files changed, 57 insertions(+), 72 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 11b9cb30bb..9e4bfe22c8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -338,42 +338,40 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time - // as per the instructions in PriortySortUtil we first derive from PrioritySortUtil::Prioritizable - class PrioritizableRenderer: public PrioritySortUtil::Prioritizable { + class SortableRenderer: public PrioritySortUtil::Sortable { public: - PrioritizableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + SortableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + glm::vec3 getPosition() const override { return _renderer->getEntity()->getPosition(); } float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } + + const EntityRendererPointer& getRenderer() const { return _renderer; } private: - const EntityRendererPointer& _renderer; + EntityRendererPointer _renderer; }; // prioritize and sort the renderables uint64_t sortStart = usecTimestampNow(); - using SortableRenderer = PrioritySortUtil::Sortable; - std::priority_queue< SortableRenderer > sortedRenderables; + PrioritySortUtil::PriorityQueue sortedRenderables(view); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { - float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); - //SortableRenderer entry(itr->second, priority); - sortedRenderables.push(SortableRenderer(itr->second, priority)); + sortedRenderables.push(SortableRenderer(itr->second)); ++itr; } } { - PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, sortedRenderables.size()); // compute remaining time budget uint64_t updateStart = usecTimestampNow(); const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; - uint64_t timeForSort = updateStart - sortStart; - if (timeForSort < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { - timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - timeForSort; + uint64_t sortCost = updateStart - sortStart; + if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { + timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - sortCost; } uint64_t expiry = updateStart + timeBudget; @@ -381,7 +379,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getThing(); + const EntityRendererPointer& renderer = sortedRenderables.top().getRenderer(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); @@ -389,7 +387,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // compute average per-renderable update cost size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero - float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated + 1); + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated); const float blend = 0.1f; _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; } diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index d5fdbad31b..6f4897c7b4 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -16,22 +16,21 @@ /* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: -(1) Derive a class from pure-virtual PrioritySortUtil::Prioritizable - that wraps the Thing you want to prioritize and sort: +(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of + the Thing you want to prioritize and sort: - class PrioritizableThing : public PrioritySortUtil::Prioritizable { - public: - PrioritizableThing(const Thing& thing) : _thing(thing) {} - glm::vec3 getPosition() const override { return _thing.getPosition(); } - float getRadius() const const override { return _thing.getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing.getLastUpdated(); } - private: - // Yes really: the data member is a const reference! - // PrioritizableThing only needs enough scope to compute a priority. - const Thing& _thing; - } + class SortableWrapper: public PrioritySortUtil::Sortable { + public: + SortableWrapper(const Thing& thing) : _thing(thing) { } + glm::vec3 getPosition() const override { return _thing->getPosition(); } + float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _thing->getLastTime(); } + const Thing& getThing() const { return _thing; } + private: + Thing _thing; + }; -(2) Loop over your things and insert each into a priority_queue: +(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: PrioritySortUtil::Prioritizer prioritizer(viewFrustum); std::priority_queue< PrioritySortUtil::Sortable > sortedThings; @@ -40,7 +39,7 @@ sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); } -(4) Loop over your priority queue and do timeboxed work: +(3) Loop over your priority queue and do timeboxed work: uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; while (!sortedThings.empty()) { @@ -51,7 +50,6 @@ break; } } - */ namespace PrioritySortUtil { @@ -60,39 +58,28 @@ namespace PrioritySortUtil { constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; - template class Sortable { - public: - Sortable(const T& thing, float sortPriority) : _thing(thing), _priority(sortPriority) {} - const T& getThing() const { return _thing; } - void setPriority(float priority) { _priority = priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } - private: - T _thing; - float _priority; - }; - - // Prioritizable isn't a template because templates can't have pure-virtual methods. - class Prioritizable { public: virtual glm::vec3 getPosition() const = 0; virtual float getRadius() const = 0; virtual uint64_t getTimestamp() const = 0; + + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + float _priority { 0.0f }; }; - class Prioritizer { + template + class PriorityQueue { public: - Prioritizer() = delete; + PriorityQueue() = delete; - Prioritizer(const ViewFrustum& view) : _view(view) { - cacheView(); - } + PriorityQueue(const ViewFrustum& view) : _view(view) { } - Prioritizer(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - { - cacheView(); - } + { } void setView(const ViewFrustum& view) { _view = view; } @@ -102,24 +89,34 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - float computePriority(const Prioritizable& prioritizableThing) const { + size_t size() const { return _queue.size(); } + void push(T thing) { + thing.setPriority(computePriority(thing)); + _queue.push(thing); + } + const T& top() const { return _queue.top(); } + void pop() { return _queue.pop(); } + bool empty() const { return _queue.empty(); } + + private: + float computePriority(const T& thing) const { // priority = weighted linear combination of multiple values: // (a) angular size // (b) proximity to center of view // (c) time since last update // where the relative "weights" are tuned to scale the contributing values into units of "priority". - glm::vec3 position = prioritizableThing.getPosition(); - glm::vec3 offset = position - _viewPosition; + glm::vec3 position = thing.getPosition(); + glm::vec3 offset = position - _view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = prioritizableThing.getRadius(); + float radius = thing.getRadius(); float priority = _angularWeight * (radius / distance) - + _centerWeight * (glm::dot(offset, _viewForward) / distance) - + _ageWeight * (float)(usecTimestampNow() - prioritizableThing.getTimestamp()); + + _centerWeight * (glm::dot(offset, _view.getDirection()) / distance) + + _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp()); // decrement priority of things outside keyhole - if (distance - radius > _viewRadius) { + if (distance - radius > _view.getCenterRadius()) { if (!_view.sphereIntersectsFrustum(position, radius)) { constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; @@ -128,18 +125,8 @@ namespace PrioritySortUtil { return priority; } - private: - void cacheView() { - // assuming we'll prioritize many things: cache these values - _viewPosition = _view.getPosition(); - _viewForward = _view.getDirection(); - _viewRadius = _view.getCenterRadius(); - } - ViewFrustum _view; - glm::vec3 _viewPosition; - glm::vec3 _viewForward; - float _viewRadius; + std::priority_queue _queue; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; From 500f38182799cc976020f3d461a00817b1ce3533 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 15:56:55 -0800 Subject: [PATCH 13/14] collect hard-coded time budgets in one spot --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 -- libraries/shared/src/PrioritySortUtil.h | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9e4bfe22c8..13e5973e27 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -318,7 +318,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size(); - const float MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2 * USECS_PER_MSEC; if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) { // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); @@ -367,7 +366,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // compute remaining time budget uint64_t updateStart = usecTimestampNow(); - const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; uint64_t sortCost = updateStart - sortStart; if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 6f4897c7b4..1d11a04265 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -133,5 +133,9 @@ namespace PrioritySortUtil { }; } // namespace PrioritySortUtil +// for now we're keeping hard-coded sorted time budgets in one spot +const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec +const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec + #endif // hifi_PrioritySortUtil_h From 2f5b7f32c6bd6ed9f6a4e4e80164bb430a6a1ee0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 17:08:12 -0800 Subject: [PATCH 14/14] only add valid renderables to _renderablesToUpdate --- .../src/EntityTreeRenderer.cpp | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 13e5973e27..35f4b2aa80 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -184,6 +184,7 @@ void EntityTreeRenderer::clear() { qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; } _entitiesInScene.clear(); + _renderablesToUpdate.clear(); // reset the zone to the default (while we load the next scene) _layeredZones.clear(); @@ -289,31 +290,12 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene { PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size()); - if (_renderablesToUpdate.empty()) { - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - if (!renderable) { - continue; - } + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + if (renderable) { + // only add valid renderables _renderablesToUpdate _renderablesToUpdate.insert({ entityId, renderable }); } - } else { - // we weren't able to update all renderables last frame - // so we have to be more careful when processing changed renderables - std::unordered_map::iterator itr; - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - itr = _renderablesToUpdate.find(entityId); - if (itr != _renderablesToUpdate.end()) { - if (!renderable) { - _renderablesToUpdate.erase(itr); - continue; - } - _renderablesToUpdate.insert(itr, { entityId, renderable }); - } else if (renderable) { - _renderablesToUpdate.insert({ entityId, renderable }); - } - } } } @@ -324,6 +306,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene uint64_t updateStart = usecTimestampNow(); for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; + assert(renderable); // only valid renderables are added to _renderablesToUpdate renderable->updateInScene(scene, transaction); } size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero @@ -357,6 +340,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { + assert(itr->second); // only valid renderables are added to _renderablesToUpdate sortedRenderables.push(SortableRenderer(itr->second)); ++itr; } @@ -377,9 +361,9 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getRenderer(); - renderer->updateInScene(scene, transaction); - _renderablesToUpdate.erase(renderer->getEntity()->getID()); + const EntityRendererPointer& renderable = sortedRenderables.top().getRenderer(); + renderable->updateInScene(scene, transaction); + _renderablesToUpdate.erase(renderable->getEntity()->getID()); sortedRenderables.pop(); } @@ -839,7 +823,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { - // If it's in the pending queue, remove it + // If it's in a pending queue, remove it + _renderablesToUpdate.erase(entityID); _entitiesToAdd.erase(entityID); auto itr = _entitiesInScene.find(entityID); @@ -847,7 +832,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { // Not in the scene, and no longer potentially in the pending queue, we're done return; } - + if (_tree && !_shuttingDown && _entitiesScriptEngine) { _entitiesScriptEngine->unloadEntityScript(entityID, true); }