515 lines
19 KiB
C++
515 lines
19 KiB
C++
/**
|
|
* @file llavatarrendernotifier.cpp
|
|
* @author andreykproductengine
|
|
* @date 2015-08-05
|
|
* @brief
|
|
*
|
|
* $LicenseInfo:firstyear=2013&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2013, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
// Pre-compiled headers
|
|
#include "llviewerprecompiledheaders.h"
|
|
// STL headers
|
|
// std headers
|
|
// external library headers
|
|
// other Linden headers
|
|
#include "llagentwearables.h"
|
|
#include "llappearancemgr.h"
|
|
#include "llattachmentsmgr.h"
|
|
#include "llnotifications.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llnotificationtemplate.h"
|
|
#include "llslurl.h"
|
|
#include "lltimer.h"
|
|
#include "llvoavatarself.h"
|
|
#include "llviewercontrol.h"
|
|
#include "lltrans.h"
|
|
#include "llagentcamera.h"
|
|
// associated header
|
|
#include "llavatarrendernotifier.h"
|
|
#include "llresmgr.h"
|
|
|
|
// when change exceeds this ration, notification is shown
|
|
static const F32 RENDER_ALLOWED_CHANGE_PCT = 0.1f;
|
|
// wait seconds before processing over limit updates after last complexity change
|
|
static const U32 OVER_LIMIT_UPDATE_DELAY = 70;
|
|
|
|
static const U32 WARN_HUD_OBJECTS_LIMIT = 1000;
|
|
static const U32 WARN_HUD_TEXTURES_LIMIT = 200;
|
|
static const U32 WARN_HUD_OVERSIZED_TEXTURES_LIMIT = 6;
|
|
static const U32 WARN_HUD_TEXTURE_MEMORY_LIMIT = 32000000; // in bytes
|
|
|
|
|
|
LLAvatarRenderNotifier::LLAvatarRenderNotifier() :
|
|
mAgentsCount(0),
|
|
mOverLimitAgents(0),
|
|
mAgentComplexity(0),
|
|
mOverLimitPct(0.0f),
|
|
mLatestAgentsCount(0),
|
|
mLatestOverLimitAgents(0),
|
|
mLatestAgentComplexity(0),
|
|
mLatestOverLimitPct(0.0f),
|
|
mShowOverLimitAgents(false),
|
|
mNotifyOutfitLoading(false),
|
|
mLastCofVersion(LLViewerInventoryCategory::VERSION_UNKNOWN),
|
|
mLastOutfitRezStatus(-1),
|
|
mLastSkeletonSerialNum(-1)
|
|
{
|
|
mPopUpDelayTimer.resetWithExpiry(OVER_LIMIT_UPDATE_DELAY);
|
|
}
|
|
|
|
std::string LLAvatarRenderNotifier::overLimitMessage()
|
|
{
|
|
static const char* everyone_now = "av_render_everyone_now";
|
|
static const char* not_everyone = "av_render_not_everyone";
|
|
static const char* over_half = "av_render_over_half";
|
|
static const char* most = "av_render_most_of";
|
|
static const char* anyone = "av_render_anyone";
|
|
|
|
std::string message;
|
|
if ( mLatestOverLimitPct >= 99.0 )
|
|
{
|
|
message = anyone;
|
|
}
|
|
else if ( mLatestOverLimitPct >= 75.0 )
|
|
{
|
|
message = most;
|
|
}
|
|
else if ( mLatestOverLimitPct >= 50.0 )
|
|
{
|
|
message = over_half;
|
|
}
|
|
else if ( mLatestOverLimitPct > 10.0 )
|
|
{
|
|
message = not_everyone;
|
|
}
|
|
else
|
|
{
|
|
// Will be shown only after overlimit was > 0
|
|
message = everyone_now;
|
|
}
|
|
return LLTrans::getString(message);
|
|
}
|
|
|
|
void LLAvatarRenderNotifier::displayNotification(bool show_over_limit)
|
|
{
|
|
mAgentComplexity = mLatestAgentComplexity;
|
|
mShowOverLimitAgents = show_over_limit;
|
|
static LLCachedControl<U32> expire_delay(gSavedSettings, "ShowMyComplexityChanges", 20);
|
|
|
|
LLDate expire_date(LLDate::now().secondsSinceEpoch() + expire_delay);
|
|
LLSD args;
|
|
// <FS:Ansariel> FIRE-19958: Add digit group separators to avatar complexity notification
|
|
//args["AGENT_COMPLEXITY"] = LLSD::Integer(mLatestAgentComplexity);
|
|
{
|
|
LLLocale locale("");
|
|
std::string complexity_string;
|
|
LLResMgr::getInstance()->getIntegerString(complexity_string, mLatestAgentComplexity);
|
|
args["AGENT_COMPLEXITY"] = complexity_string;
|
|
}
|
|
// </FS:Ansariel>
|
|
std::string notification_name;
|
|
if (mShowOverLimitAgents)
|
|
{
|
|
notification_name = "AgentComplexityWithVisibility";
|
|
args["OVERLIMIT_MSG"] = overLimitMessage();
|
|
|
|
// remember what the situation was so that we only notify when it has changed
|
|
mAgentsCount = mLatestAgentsCount;
|
|
mOverLimitAgents = mLatestOverLimitAgents;
|
|
mOverLimitPct = mLatestOverLimitPct;
|
|
}
|
|
else
|
|
{
|
|
// no change in visibility, just update complexity
|
|
notification_name = "AgentComplexity";
|
|
}
|
|
|
|
if (mNotificationPtr != NULL && mNotificationPtr->getName() != notification_name)
|
|
{
|
|
// since unique tag works only for same notification,
|
|
// old notification needs to be canceled manually
|
|
LLNotifications::instance().cancel(mNotificationPtr);
|
|
}
|
|
|
|
// log unconditionally
|
|
LL_DEBUGS("AvatarRenderInfo") << notification_name << " " << args << LL_ENDL;
|
|
|
|
if ( expire_delay // expiration of zero means do not show the notices
|
|
&& gAgentCamera.getLastCameraMode() != CAMERA_MODE_MOUSELOOK // don't display notices in Mouselook
|
|
)
|
|
{
|
|
mNotificationPtr = LLNotifications::instance().add(LLNotification::Params()
|
|
.name(notification_name)
|
|
.expiry(expire_date)
|
|
.substitutions(args));
|
|
}
|
|
}
|
|
|
|
bool LLAvatarRenderNotifier::isNotificationVisible()
|
|
{
|
|
return mNotificationPtr != NULL && mNotificationPtr->isActive();
|
|
}
|
|
|
|
void LLAvatarRenderNotifier::updateNotificationRegion(U32 agentcount, U32 overLimit)
|
|
{
|
|
if (agentcount == 0)
|
|
{
|
|
// Data not ready
|
|
return;
|
|
}
|
|
|
|
// save current values for later use
|
|
mLatestAgentsCount = agentcount > overLimit ? agentcount - 1 : agentcount; // subtract self
|
|
mLatestOverLimitAgents = overLimit;
|
|
mLatestOverLimitPct = mLatestAgentsCount != 0 ? ((F32)overLimit / (F32)mLatestAgentsCount) * 100.0 : 0;
|
|
|
|
if (mAgentsCount == mLatestAgentsCount
|
|
&& mOverLimitAgents == mLatestOverLimitAgents)
|
|
{
|
|
// no changes since last notification
|
|
return;
|
|
}
|
|
|
|
if ((mPopUpDelayTimer.hasExpired() || (isNotificationVisible() && mShowOverLimitAgents))
|
|
&& (mOverLimitPct > 0 || mLatestOverLimitPct > 0)
|
|
&& std::abs(mOverLimitPct - mLatestOverLimitPct) > mLatestOverLimitPct * RENDER_ALLOWED_CHANGE_PCT
|
|
)
|
|
{
|
|
// display in case of drop to/from zero and in case of significant (RENDER_ALLOWED_CHANGE_PCT) changes
|
|
displayNotification(true);
|
|
|
|
// default timeout before next notification
|
|
static LLCachedControl<U32> pop_up_delay(gSavedSettings, "ComplexityChangesPopUpDelay", 300);
|
|
mPopUpDelayTimer.resetWithExpiry(pop_up_delay);
|
|
}
|
|
}
|
|
|
|
void LLAvatarRenderNotifier::updateNotificationState()
|
|
{
|
|
if (!isAgentAvatarValid())
|
|
{
|
|
// data not ready, nothing to show.
|
|
return;
|
|
}
|
|
|
|
// Don't use first provided COF and Sceleton versions - let them load anf 'form' first
|
|
if (mLastCofVersion < 0
|
|
&& gAgentWearables.areWearablesLoaded()
|
|
&& LLAttachmentsMgr::getInstance()->isAttachmentStateComplete())
|
|
{
|
|
// cof formed
|
|
mLastCofVersion = LLAppearanceMgr::instance().getCOFVersion();
|
|
mLastSkeletonSerialNum = gAgentAvatarp->mLastSkeletonSerialNum;
|
|
}
|
|
else if (mLastCofVersion >= 0
|
|
&& (mLastCofVersion != LLAppearanceMgr::instance().getCOFVersion()
|
|
|| mLastSkeletonSerialNum != gAgentAvatarp->mLastSkeletonSerialNum))
|
|
{
|
|
// version mismatch in comparison to previous outfit - outfit changed
|
|
mNotifyOutfitLoading = true;
|
|
mLastCofVersion = LLAppearanceMgr::instance().getCOFVersion();
|
|
mLastSkeletonSerialNum = gAgentAvatarp->mLastSkeletonSerialNum;
|
|
}
|
|
|
|
if (gAgentAvatarp->mLastRezzedStatus < mLastOutfitRezStatus)
|
|
{
|
|
// rez status decreased - outfit related action was initiated
|
|
mNotifyOutfitLoading = true;
|
|
}
|
|
|
|
mLastOutfitRezStatus = gAgentAvatarp->mLastRezzedStatus;
|
|
}
|
|
void LLAvatarRenderNotifier::updateNotificationAgent(U32 agentComplexity)
|
|
{
|
|
// save the value for use in following messages
|
|
mLatestAgentComplexity = agentComplexity;
|
|
|
|
static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20);
|
|
if (!show_my_complexity_changes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!isAgentAvatarValid() || !gAgentWearables.areWearablesLoaded())
|
|
{
|
|
// data not ready, nothing to show.
|
|
return;
|
|
}
|
|
|
|
if (!mNotifyOutfitLoading)
|
|
{
|
|
// We should not notify about initial outfit and it's load process without reason
|
|
updateNotificationState();
|
|
|
|
if (mLatestOverLimitAgents > 0)
|
|
{
|
|
// Some users can't see agent already, notify user about complexity growth
|
|
mNotifyOutfitLoading = true;
|
|
}
|
|
|
|
if (!mNotifyOutfitLoading)
|
|
{
|
|
// avatar or outfit not ready
|
|
mAgentComplexity = mLatestAgentComplexity;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mAgentComplexity != mLatestAgentComplexity)
|
|
{
|
|
// if we have an agent complexity change, we always display it and hide 'over limit'
|
|
displayNotification(false);
|
|
|
|
// next 'over limit' update should be displayed after delay to make sure information got updated at server side
|
|
mPopUpDelayTimer.resetWithExpiry(OVER_LIMIT_UPDATE_DELAY);
|
|
}
|
|
}
|
|
|
|
// LLHUDRenderNotifier
|
|
|
|
static const char* e_hud_messages[] =
|
|
{
|
|
"hud_render_textures_warning",
|
|
"hud_render_cramped_warning",
|
|
"hud_render_heavy_textures_warning",
|
|
"hud_render_cost_warning",
|
|
"hud_render_memory_warning",
|
|
};
|
|
|
|
LLHUDRenderNotifier::LLHUDRenderNotifier() :
|
|
mReportedHUDWarning(WARN_NONE),
|
|
mHUDsCount(0)
|
|
{
|
|
}
|
|
|
|
LLHUDRenderNotifier::~LLHUDRenderNotifier()
|
|
{
|
|
}
|
|
|
|
void LLHUDRenderNotifier::updateNotificationHUD(hud_complexity_list_t complexity)
|
|
{
|
|
if (!isAgentAvatarValid() || !gAgentWearables.areWearablesLoaded())
|
|
{
|
|
// data not ready.
|
|
return;
|
|
}
|
|
|
|
mHUDComplexityList = complexity;
|
|
mHUDsCount = static_cast<S32>(mHUDComplexityList.size());
|
|
|
|
static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20);
|
|
if (!show_my_complexity_changes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO:
|
|
// Find a way to show message with list of issues, but without making it too large
|
|
// and intrusive.
|
|
|
|
LLHUDComplexity new_total_complexity;
|
|
LLHUDComplexity report_complexity;
|
|
|
|
hud_complexity_list_t::iterator iter = complexity.begin();
|
|
hud_complexity_list_t::iterator end = complexity.end();
|
|
EWarnLevel warning_level = WARN_NONE;
|
|
for (; iter != end; ++iter)
|
|
{
|
|
LLHUDComplexity object_complexity = *iter;
|
|
EWarnLevel object_level = getWarningType(object_complexity, report_complexity);
|
|
if (object_level >= 0)
|
|
{
|
|
warning_level = object_level;
|
|
report_complexity = object_complexity;
|
|
}
|
|
new_total_complexity.objectsCost += object_complexity.objectsCost;
|
|
new_total_complexity.objectsCount += object_complexity.objectsCount;
|
|
new_total_complexity.texturesCost += object_complexity.texturesCost;
|
|
new_total_complexity.texturesCount += object_complexity.texturesCount;
|
|
new_total_complexity.largeTexturesCount += object_complexity.largeTexturesCount;
|
|
new_total_complexity.texturesMemoryTotal += object_complexity.texturesMemoryTotal;
|
|
}
|
|
|
|
if (mHUDPopUpDelayTimer.hasExpired() || isNotificationVisible())
|
|
{
|
|
if (warning_level >= 0)
|
|
{
|
|
// Display info about most complex HUD object
|
|
// make sure it shown only once unless object's complexity or object itself changed
|
|
if (mReportedHUDComplexity.objectId != report_complexity.objectId
|
|
|| mReportedHUDWarning != warning_level)
|
|
{
|
|
displayHUDNotification(warning_level, report_complexity.objectId, report_complexity.objectName, report_complexity.jointName);
|
|
mReportedHUDComplexity = report_complexity;
|
|
mReportedHUDWarning = warning_level;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if total complexity is above threshold and above previous warning
|
|
// Show warning with highest importance (5m delay between warnings by default)
|
|
if (!mReportedHUDComplexity.objectId.isNull())
|
|
{
|
|
mReportedHUDComplexity.reset();
|
|
mReportedHUDWarning = WARN_NONE;
|
|
}
|
|
|
|
warning_level = getWarningType(new_total_complexity, mReportedHUDComplexity);
|
|
if (warning_level >= 0 && mReportedHUDWarning != warning_level)
|
|
{
|
|
displayHUDNotification(warning_level);
|
|
}
|
|
mReportedHUDComplexity = new_total_complexity;
|
|
mReportedHUDWarning = warning_level;
|
|
}
|
|
}
|
|
else if (warning_level >= 0)
|
|
{
|
|
LL_DEBUGS("HUDdetail") << "HUD individual warning postponed" << LL_ENDL;
|
|
}
|
|
|
|
if (mLatestHUDComplexity.objectsCost != new_total_complexity.objectsCost
|
|
|| mLatestHUDComplexity.objectsCount != new_total_complexity.objectsCount
|
|
|| mLatestHUDComplexity.texturesCost != new_total_complexity.texturesCost
|
|
|| mLatestHUDComplexity.texturesCount != new_total_complexity.texturesCount
|
|
|| mLatestHUDComplexity.largeTexturesCount != new_total_complexity.largeTexturesCount
|
|
|| mLatestHUDComplexity.texturesMemoryTotal != new_total_complexity.texturesMemoryTotal)
|
|
{
|
|
LL_INFOS("HUDdetail") << "HUD textures count: " << new_total_complexity.texturesCount
|
|
<< " HUD textures cost: " << new_total_complexity.texturesCost
|
|
<< " Large textures: " << new_total_complexity.largeTexturesCount
|
|
<< " HUD objects cost: " << new_total_complexity.objectsCost
|
|
<< " HUD objects count: " << new_total_complexity.objectsCount << LL_ENDL;
|
|
|
|
mLatestHUDComplexity = new_total_complexity;
|
|
}
|
|
}
|
|
|
|
bool LLHUDRenderNotifier::isNotificationVisible()
|
|
{
|
|
return mHUDNotificationPtr != NULL && mHUDNotificationPtr->isActive();
|
|
}
|
|
|
|
// private static
|
|
LLHUDRenderNotifier::EWarnLevel LLHUDRenderNotifier::getWarningType(LLHUDComplexity object_complexity, LLHUDComplexity cmp_complexity)
|
|
{
|
|
static LLCachedControl<U32> max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0U); // ties max HUD cost to avatar cost
|
|
static LLCachedControl<U32> max_objects_count(gSavedSettings, "RenderHUDObjectsWarning", WARN_HUD_OBJECTS_LIMIT);
|
|
static LLCachedControl<U32> max_textures_count(gSavedSettings, "RenderHUDTexturesWarning", WARN_HUD_TEXTURES_LIMIT);
|
|
static LLCachedControl<U32> max_oversized_count(gSavedSettings, "RenderHUDOversizedTexturesWarning", WARN_HUD_OVERSIZED_TEXTURES_LIMIT);
|
|
static LLCachedControl<U32> max_texture_memory(gSavedSettings, "RenderHUDTexturesMemoryWarning", WARN_HUD_TEXTURE_MEMORY_LIMIT);
|
|
|
|
if (cmp_complexity.texturesMemoryTotal < object_complexity.texturesMemoryTotal
|
|
&& object_complexity.texturesMemoryTotal > (F64Bytes)max_texture_memory)
|
|
{
|
|
// Note: Memory might not be accurate since texture is still loading or discard level changes
|
|
|
|
LL_DEBUGS("HUDdetail") << "HUD " << object_complexity.objectName << " memory usage over limit, "
|
|
<< " was " << cmp_complexity.texturesMemoryTotal
|
|
<< " is " << object_complexity.texturesMemoryTotal << LL_ENDL;
|
|
|
|
return WARN_MEMORY;
|
|
}
|
|
else if ((cmp_complexity.objectsCost < object_complexity.objectsCost
|
|
|| cmp_complexity.texturesCost < object_complexity.texturesCost)
|
|
&& max_render_cost > 0
|
|
&& object_complexity.objectsCost + object_complexity.texturesCost > max_render_cost)
|
|
{
|
|
LL_DEBUGS("HUDdetail") << "HUD " << object_complexity.objectName << " complexity over limit,"
|
|
<< " HUD textures cost: " << object_complexity.texturesCost
|
|
<< " HUD objects cost: " << object_complexity.objectsCost << LL_ENDL;
|
|
|
|
return WARN_COST;
|
|
}
|
|
else if (cmp_complexity.largeTexturesCount < object_complexity.largeTexturesCount
|
|
&& object_complexity.largeTexturesCount > max_oversized_count)
|
|
{
|
|
LL_DEBUGS("HUDdetail") << "HUD " << object_complexity.objectName << " contains to many large textures: "
|
|
<< object_complexity.largeTexturesCount << LL_ENDL;
|
|
|
|
return WARN_HEAVY;
|
|
}
|
|
else if (cmp_complexity.texturesCount < object_complexity.texturesCount
|
|
&& object_complexity.texturesCount > max_textures_count)
|
|
{
|
|
LL_DEBUGS("HUDdetail") << "HUD " << object_complexity.objectName << " contains too many textures: "
|
|
<< object_complexity.texturesCount << LL_ENDL;
|
|
|
|
return WARN_CRAMPED;
|
|
}
|
|
else if (cmp_complexity.objectsCount < object_complexity.objectsCount
|
|
&& object_complexity.objectsCount > max_objects_count)
|
|
{
|
|
LL_DEBUGS("HUDdetail") << "HUD " << object_complexity.objectName << " contains too many objects: "
|
|
<< object_complexity.objectsCount << LL_ENDL;
|
|
|
|
return WARN_TEXTURES;
|
|
}
|
|
return WARN_NONE;
|
|
}
|
|
|
|
void LLHUDRenderNotifier::displayHUDNotification(EWarnLevel warn_type, LLUUID obj_id, std::string obj_name, std::string joint_name)
|
|
{
|
|
static LLCachedControl<U32> pop_up_delay(gSavedSettings, "ComplexityChangesPopUpDelay", 300);
|
|
static LLCachedControl<U32> expire_delay(gSavedSettings, "ShowMyComplexityChanges", 20);
|
|
LLDate expire_date(LLDate::now().secondsSinceEpoch() + expire_delay);
|
|
|
|
// Since we need working "ignoretext" there is no other way but to
|
|
// use single notification while constructing it from multiple pieces
|
|
LLSD reason_args;
|
|
if (obj_id.isNull())
|
|
{
|
|
reason_args["HUD_DETAILS"] = LLTrans::getString("hud_description_total");
|
|
}
|
|
else
|
|
{
|
|
if (obj_name.empty())
|
|
{
|
|
LL_WARNS("HUDdetail") << "Object name not assigned" << LL_ENDL;
|
|
}
|
|
if (joint_name.empty())
|
|
{
|
|
std::string verb = "select?name=" + LLURI::escape(obj_name);
|
|
reason_args["HUD_DETAILS"] = LLSLURL("inventory", obj_id, verb.c_str()).getSLURLString();
|
|
}
|
|
else
|
|
{
|
|
LLSD object_args;
|
|
std::string verb = "select?name=" + LLURI::escape(obj_name);
|
|
object_args["OBJ_NAME"] = LLSLURL("inventory", obj_id, verb.c_str()).getSLURLString();
|
|
object_args["JNT_NAME"] = LLTrans::getString(joint_name);
|
|
reason_args["HUD_DETAILS"] = LLTrans::getString("hud_name_with_joint", object_args);
|
|
}
|
|
}
|
|
|
|
LLSD msg_args;
|
|
msg_args["HUD_REASON"] = LLTrans::getString(e_hud_messages[warn_type], reason_args);
|
|
|
|
mHUDNotificationPtr = LLNotifications::instance().add(LLNotification::Params()
|
|
.name("HUDComplexityWarning")
|
|
.expiry(expire_date)
|
|
.substitutions(msg_args));
|
|
mHUDPopUpDelayTimer.resetWithExpiry(pop_up_delay);
|
|
}
|
|
|