sort and throttle UpdateRenderables

This commit is contained in:
Andrew Meadows 2017-11-07 10:34:58 -08:00
parent 5050a554a2
commit 809ff7928e
4 changed files with 139 additions and 15 deletions

View file

@ -12,6 +12,7 @@
#include "EntityTreeRenderer.h"
#include <glm/gtx/quaternion.hpp>
#include <queue>
#include <QEventLoop>
#include <QScriptSyntaxCheckResult>
@ -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<EntityItemID> 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<EntityItemID, EntityRendererPointer>::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<SortableEntityRenderer> sortedRenderables;
{
PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
std::unordered_map<EntityItemID, EntityRendererPointer>::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<EntityItemID, EntityRendererPointer>::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);
}
}

View file

@ -51,6 +51,14 @@ using ModelWeakPointer = std::weak_ptr<Model>;
using CalculateEntityLoadingPriority = std::function<float(const EntityItem& item)>;
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;

View file

@ -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()) {

View file

@ -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<bool()> _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