From ac180758ad87dab53efd4747c237f99f0fe58fba Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 24 Jan 2017 10:36:42 -0800 Subject: [PATCH] sort, optimize, and timebox process avatar updates --- interface/src/avatar/Avatar.cpp | 15 ++-- interface/src/avatar/AvatarManager.cpp | 112 ++++++++++++++----------- interface/src/avatar/SkeletonModel.cpp | 18 ++-- 3 files changed, 83 insertions(+), 62 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 208d265dcd..cdf9323ace 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -323,10 +323,13 @@ void Avatar::simulate(float deltaTime, bool inView) { PerformanceTimer perfTimer("simulate"); { PROFILE_RANGE(simulation, "updateJoints"); - uint64_t start = usecTimestampNow(); - if (inView) { + if (inView && _hasNewJointData) { + uint64_t start = usecTimestampNow(); _skeletonModel->getRig()->copyJointsFromJointData(_jointData); - _skeletonModel->simulate(deltaTime, _hasNewJointData); + _skeletonModel->simulate(deltaTime, true); + timeProcessingJoints += usecTimestampNow() - start; + numJointsProcessed += _jointData.size(); + locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointData = false; @@ -337,15 +340,11 @@ void Avatar::simulate(float deltaTime, bool inView) { Head* head = getHead(); head->setPosition(headPosition); head->setScale(getUniformScale()); - const bool useBillboard = false; // HACK - head->simulate(deltaTime, false, useBillboard); + head->simulate(deltaTime, false); } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. - getHead()->setPosition(getPosition()); _skeletonModel->simulate(deltaTime, false); } - timeProcessingJoints += usecTimestampNow() - start; - numJointsProcessed += _jointData.size(); } // update animation for display name fade in/out diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fa2ee2190c..73f3fb0f38 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -149,14 +149,11 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } lock.unlock(); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); - PerformanceTimer perfTimer("otherAvatars"); + uint64_t startTime = usecTimestampNow(); auto avatarMap = getHashCopy(); QList avatarList = avatarMap.values(); - uint64_t now = usecTimestampNow(); ViewFrustum cameraView; qApp->copyDisplayViewFrustum(cameraView); glm::vec3 frustumCenter = cameraView.getPosition(); @@ -164,47 +161,56 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const float OUT_OF_VIEW_PENALTY = -10.0; std::priority_queue sortedAvatars; - for (int32_t i = 0; i < avatarList.size(); ++i) { - const auto& avatar = std::static_pointer_cast(avatarList.at(i)); - if (avatar == _myAvatar || !avatar->isInitialized()) { - // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. - // DO NOT update or fade out uninitialized Avatars - continue; - } - if (avatar->shouldDie()) { - removeAvatar(avatar->getID()); - continue; - } - if (avatar->isDead()) { - continue; - } - - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view, and - // (c) time since last update - glm::vec3 avatarPosition = avatar->getPosition(); - glm::vec3 offset = avatarPosition - frustumCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = avatar->getBoundingRadius(); - const glm::vec3& forward = cameraView.getDirection(); - float apparentSize = radius / distance; - float cosineAngle = glm::length(offset - glm::dot(offset, forward) * forward) / distance; - float age = (float)(now - avatar->getLastRenderUpdateTime()) / (float)(USECS_PER_SECOND); - float priority = 2.0f * apparentSize + 0.25f * cosineAngle + age; - - // decrement priority of avatars outside keyhole - if (distance > cameraView.getCenterRadius()) { - if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { - priority += OUT_OF_VIEW_PENALTY; + { + PROFILE_RANGE(simulation, "sort"); + for (int32_t i = 0; i < avatarList.size(); ++i) { + const auto& avatar = std::static_pointer_cast(avatarList.at(i)); + if (avatar == _myAvatar || !avatar->isInitialized()) { + // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. + // DO NOT update or fade out uninitialized Avatars + continue; } + if (avatar->shouldDie()) { + removeAvatar(avatar->getID()); + continue; + } + if (avatar->isDead()) { + continue; + } + + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update + // (d) TIME_PENALTY to help recently updated entries sort toward back + glm::vec3 avatarPosition = avatar->getPosition(); + glm::vec3 offset = avatarPosition - frustumCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + float radius = avatar->getBoundingRadius(); + const glm::vec3& forward = cameraView.getDirection(); + float apparentSize = radius / distance; + float cosineAngle = glm::length(offset - glm::dot(offset, forward) * forward) / distance; + const float TIME_PENALTY = 0.080f; // seconds + float age = (float)(startTime - avatar->getLastRenderUpdateTime()) / (float)(USECS_PER_SECOND) - TIME_PENALTY; + float priority = apparentSize + 0.25f * cosineAngle + age; + + // decrement priority of avatars outside keyhole + if (distance > cameraView.getCenterRadius()) { + if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { + priority += OUT_OF_VIEW_PENALTY; + } + } + sortedAvatars.push(AvatarPriority(avatar, priority)); } - sortedAvatars.push(AvatarPriority(avatar, priority)); } render::PendingChanges pendingChanges; - const uint64_t AVATAR_RENDER_UPDATE_TIME_BUDGET = 2 * USECS_PER_MSEC; - uint64_t expiry = now + AVATAR_RENDER_UPDATE_TIME_BUDGET; + // NOTE: the copy of hash and sort above takes about 300 usec + const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec + const uint64_t MAX_UPDATE_BUDGET = 2000; // usec + uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET; + uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET; + size_t numAvatarsProcessed = sortedAvatars.size(); while (!sortedAvatars.empty()) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); @@ -225,12 +231,23 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } avatar->animateScaleChanges(deltaTime); - // for avatars in view... - const bool inView = sortData.priority > 0.5f * OUT_OF_VIEW_PENALTY; - avatar->simulate(deltaTime, inView); - if (expiry > usecTimestampNow()) { + uint64_t now = usecTimestampNow(); + if (now < renderExpiry) { + // we're within budget + bool inView = sortData.priority > 0.5f * OUT_OF_VIEW_PENALTY; + avatar->simulate(deltaTime, inView); avatar->updateRenderItem(pendingChanges); - avatar->setLastRenderUpdateTime(now); + avatar->setLastRenderUpdateTime(startTime); + } else if (now < maxExpiry) { + // we've spent most of our time budget, but we still simulate() the avatar as it if were out of view + // --> some avatars may freeze until their priority trickles up + const bool inView = false; + avatar->simulate(deltaTime, inView); + } else { + // we've spent ALL of our time budget --> bail on the rest of the avatar updates + // --> some scale or fade animations may glitch + // --> some avatar velocity measurements may be a little off + break; } sortedAvatars.pop(); } @@ -238,8 +255,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { simulateAvatarFades(deltaTime); - //PROFILE_COUNTER(simulation_avatar, "NumAvatarsPerSec", - // { { "NumAvatarsPerSec", (float)((size() - sortedAvatars.size()) * USECS_PER_SECOND) / (float)(usecTimestampNow() - now) } }); + numAvatarsProcessed -= sortedAvatars.size(); + float numAvatarsPerSec = (float)(numAvatarsProcessed * USECS_PER_SECOND) / (float)(usecTimestampNow() - startTime); + PROFILE_COUNTER(simulation_avatar, "NumAvatarsPerSec", { { "NumAvatarsPerSec", numAvatarsPerSec } }); PROFILE_COUNTER(simulation_avatar, "NumJointsPerSec", { { "NumJointsPerSec", Avatar::getNumJointsProcessedPerSecond() } }); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 54f6682191..4b77323bba 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -220,15 +220,19 @@ void SkeletonModel::updateAttitude() { // Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed), // but just before head has been simulated. void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { - updateAttitude(); - setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); + if (fullUpdate) { + updateAttitude(); + setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); - Model::simulate(deltaTime, fullUpdate); + Model::simulate(deltaTime, fullUpdate); - // let rig compute the model offset - glm::vec3 registrationPoint; - if (_rig->getModelRegistrationPoint(registrationPoint)) { - setOffset(registrationPoint); + // let rig compute the model offset + glm::vec3 registrationPoint; + if (_rig->getModelRegistrationPoint(registrationPoint)) { + setOffset(registrationPoint); + } + } else { + Model::simulate(deltaTime, fullUpdate); } if (!isActive() || !_owningAvatar->isMyAvatar()) {