From 809ff7928e6c793efd803e4d38d11a5273bcb8b3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 10:34:58 -0800 Subject: [PATCH] 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