mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 02:16:51 +02:00
sort and throttle UpdateRenderables
This commit is contained in:
parent
5050a554a2
commit
809ff7928e
4 changed files with 139 additions and 15 deletions
|
@ -12,6 +12,7 @@
|
||||||
#include "EntityTreeRenderer.h"
|
#include "EntityTreeRenderer.h"
|
||||||
|
|
||||||
#include <glm/gtx/quaternion.hpp>
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QScriptSyntaxCheckResult>
|
#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());
|
PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size());
|
||||||
PerformanceTimer pt("change");
|
PerformanceTimer pt("change");
|
||||||
std::unordered_set<EntityItemID> changedEntities;
|
std::unordered_set<EntityItemID> changedEntities;
|
||||||
|
@ -286,21 +287,129 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const auto& entityId : changedEntities) {
|
{
|
||||||
auto renderable = renderableForEntityId(entityId);
|
PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size());
|
||||||
if (!renderable) {
|
if (_renderablesToUpdate.empty()) {
|
||||||
continue;
|
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());
|
PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
|
||||||
|
uint64_t updateStart = usecTimestampNow();
|
||||||
for (const auto& entry : _renderablesToUpdate) {
|
for (const auto& entry : _renderablesToUpdate) {
|
||||||
const auto& renderable = entry.second;
|
const auto& renderable = entry.second;
|
||||||
renderable->updateInScene(scene, transaction);
|
renderable->updateInScene(scene, transaction);
|
||||||
}
|
}
|
||||||
|
size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero
|
||||||
_renderablesToUpdate.clear();
|
_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) {
|
if (scene) {
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
addPendingEntities(scene, transaction);
|
addPendingEntities(scene, transaction);
|
||||||
updateChangedEntities(scene, transaction);
|
ViewFrustum view;
|
||||||
|
_viewState->copyCurrentViewFrustum(view);
|
||||||
|
updateChangedEntities(scene, view, transaction);
|
||||||
scene->enqueueTransaction(transaction);
|
scene->enqueueTransaction(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,14 @@ using ModelWeakPointer = std::weak_ptr<Model>;
|
||||||
|
|
||||||
using CalculateEntityLoadingPriority = std::function<float(const EntityItem& item)>;
|
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.
|
// Generic client side Octree renderer class.
|
||||||
class EntityTreeRenderer : public OctreeProcessor, public Dependency {
|
class EntityTreeRenderer : public OctreeProcessor, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -144,7 +152,7 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction);
|
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()); }
|
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
|
||||||
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
||||||
|
|
||||||
|
@ -235,11 +243,12 @@ private:
|
||||||
NetworkTexturePointer _skyboxTexture;
|
NetworkTexturePointer _skyboxTexture;
|
||||||
QString _ambientTextureURL;
|
QString _ambientTextureURL;
|
||||||
QString _skyboxTextureURL;
|
QString _skyboxTextureURL;
|
||||||
|
float _avgRenderableUpdateCost { 0.0f };
|
||||||
bool _pendingAmbientTexture { false };
|
bool _pendingAmbientTexture { false };
|
||||||
bool _pendingSkyboxTexture { false };
|
bool _pendingSkyboxTexture { false };
|
||||||
|
|
||||||
quint64 _lastZoneCheck { 0 };
|
uint64_t _lastZoneCheck { 0 };
|
||||||
const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||||
const float ZONE_CHECK_DISTANCE = 0.001f;
|
const float ZONE_CHECK_DISTANCE = 0.001f;
|
||||||
|
|
||||||
ReadWriteLockable _changedEntitiesGuard;
|
ReadWriteLockable _changedEntitiesGuard;
|
||||||
|
|
|
@ -59,7 +59,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St
|
||||||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||||
|
|
||||||
statusGetters.push_back([entity]() -> render::Item::Status::Value {
|
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);
|
const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND);
|
||||||
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
||||||
// Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD
|
// 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 {
|
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);
|
const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND);
|
||||||
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
||||||
// Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD
|
// 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()) {
|
if (!isValidRenderItem()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_updateTime = usecTimestampNow();
|
||||||
|
|
||||||
// FIXME is this excessive?
|
// FIXME is this excessive?
|
||||||
if (!needsRenderUpdate()) {
|
if (!needsRenderUpdate()) {
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
void clearSubRenderItemIDs();
|
void clearSubRenderItemIDs();
|
||||||
void setSubRenderItemIDs(const render::ItemIDs& ids);
|
void setSubRenderItemIDs(const render::ItemIDs& ids);
|
||||||
|
|
||||||
|
const uint64_t& getUpdateTime() const { return _updateTime; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
|
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
|
||||||
virtual void onAddToScene(const EntityItemPointer& entity);
|
virtual void onAddToScene(const EntityItemPointer& entity);
|
||||||
|
@ -100,7 +102,6 @@ protected:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void requestRenderUpdate();
|
void requestRenderUpdate();
|
||||||
|
|
||||||
|
@ -113,14 +114,16 @@ protected:
|
||||||
static std::function<bool()> _entitiesShouldFadeFunction;
|
static std::function<bool()> _entitiesShouldFadeFunction;
|
||||||
const Transform& getModelTransform() const;
|
const Transform& getModelTransform() const;
|
||||||
|
|
||||||
|
Item::Bound _bound;
|
||||||
SharedSoundPointer _collisionSound;
|
SharedSoundPointer _collisionSound;
|
||||||
QUuid _changeHandlerId;
|
QUuid _changeHandlerId;
|
||||||
ItemID _renderItemID{ Item::INVALID_ITEM_ID };
|
ItemID _renderItemID{ Item::INVALID_ITEM_ID };
|
||||||
ItemIDs _subRenderItemIDs;
|
ItemIDs _subRenderItemIDs;
|
||||||
quint64 _fadeStartTime{ usecTimestampNow() };
|
quint64 _fadeStartTime{ usecTimestampNow() };
|
||||||
|
uint64_t _fadeStartTime{ usecTimestampNow() };
|
||||||
|
uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates
|
||||||
bool _isFading{ _entitiesShouldFadeFunction() };
|
bool _isFading{ _entitiesShouldFadeFunction() };
|
||||||
bool _prevIsTransparent { false };
|
bool _prevIsTransparent { false };
|
||||||
Item::Bound _bound;
|
|
||||||
bool _visible { false };
|
bool _visible { false };
|
||||||
bool _moving { false };
|
bool _moving { false };
|
||||||
// Only touched on the rendering thread
|
// Only touched on the rendering thread
|
||||||
|
|
Loading…
Reference in a new issue