Sync light state, bound shader, and various gl context states similarly to render matrices. Texture handles now refcounted, as multiple viewer textures could ref the same handle (cubemaps do this) Clean up gl extension loading a bit. Not necessary, but only look for ARB variants if not included in current core version. Removed unused extensions. Use core shader api if supported, else use ARB. (FN signatures are identical. Just doing some pointer substitution to ARB if not core.) Attempt at improving VBO update batching. Subdata updates better batched to gether per-frame. There's probably other stuff I forgot that is in this changeset, too. Todo: Fix lightstate assertion when toggling fullscreen with shaders off.
1649 lines
43 KiB
C++
1649 lines
43 KiB
C++
/**
|
|
* @file llfasttimerview.cpp
|
|
* @brief LLFastTimerView class implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2004&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2004-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llfasttimerview.h"
|
|
|
|
#include "llviewerwindow.h"
|
|
#include "llrect.h"
|
|
#include "llerror.h"
|
|
#include "llgl.h"
|
|
#include "llimagepng.h"
|
|
#include "llrender.h"
|
|
#include "llrendertarget.h"
|
|
#include "lllocalcliprect.h"
|
|
#include "llmath.h"
|
|
#include "llfontgl.h"
|
|
#include "llsdserialize.h"
|
|
#include "llbutton.h"
|
|
|
|
#include "llappviewer.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llui.h"
|
|
#include "lluictrlfactory.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llstat.h"
|
|
|
|
#include "llfasttimer.h"
|
|
#include "lltreeiterators.h"
|
|
#include "llviewerstats.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
static const S32 MAX_VISIBLE_HISTORY = 10;
|
|
static const S32 LINE_GRAPH_HEIGHT = 240;
|
|
|
|
//static const int FTV_DISPLAY_NUM = (sizeof(ft_display_table)/sizeof(ft_display_table[0]));
|
|
static S32 FTV_NUM_TIMERS;
|
|
const S32 FTV_MAX_DEPTH = 8;
|
|
|
|
std::vector<LLFastTimer::NamedTimer*> ft_display_idx; // line of table entry for display purposes (for collapse)
|
|
|
|
typedef LLTreeDFSIter<LLFastTimer::NamedTimer, LLFastTimer::NamedTimer::child_const_iter> timer_tree_iterator_t;
|
|
|
|
BOOL LLFastTimerView::sAnalyzePerformance = FALSE;
|
|
|
|
static timer_tree_iterator_t begin_timer_tree(LLFastTimer::NamedTimer& id)
|
|
{
|
|
return timer_tree_iterator_t(&id,
|
|
boost::bind(boost::mem_fn(&LLFastTimer::NamedTimer::beginChildren), _1),
|
|
boost::bind(boost::mem_fn(&LLFastTimer::NamedTimer::endChildren), _1));
|
|
}
|
|
|
|
static timer_tree_iterator_t end_timer_tree()
|
|
{
|
|
return timer_tree_iterator_t();
|
|
}
|
|
|
|
|
|
LLFastTimerView::LLFastTimerView(const std::string& name, const LLRect& rect)
|
|
: LLFloater(name, rect, std::string(), FALSE, 1, 1, FALSE, FALSE, TRUE),
|
|
mHoverTimer(NULL)
|
|
{
|
|
setVisible(FALSE);
|
|
mDisplayMode = 0;
|
|
mAvgCountTotal = 0;
|
|
mMaxCountTotal = 0;
|
|
mDisplayCenter = ALIGN_CENTER;
|
|
mDisplayCalls = 0;
|
|
mDisplayHz = 0;
|
|
mScrollIndex = 0;
|
|
mHoverID = NULL;
|
|
mHoverBarIndex = -1;
|
|
FTV_NUM_TIMERS = LLFastTimer::NamedTimer::instanceCount();
|
|
mPrintStats = -1;
|
|
mAverageCyclesPerTimer = 0;
|
|
// <FS:LO> Making the ledgend part of fast timers scrollable
|
|
mOverLegend = false;
|
|
mScrollOffset = 0;
|
|
// </FS:LO>
|
|
LLUICtrlFactory::getInstance()->buildFloater(this, "floater_fast_timers.xml");
|
|
}
|
|
|
|
void LLFastTimerView::onPause()
|
|
{
|
|
LLFastTimer::sPauseHistory = !LLFastTimer::sPauseHistory;
|
|
// reset scroll to bottom when unpausing
|
|
if (!LLFastTimer::sPauseHistory)
|
|
{
|
|
mScrollIndex = 0;
|
|
LLFastTimer::sResetHistory = true;
|
|
getChild<LLButton>("pause_btn")->setLabel(getString("pause"));
|
|
}
|
|
else
|
|
{
|
|
getChild<LLButton>("pause_btn")->setLabel(getString("run"));
|
|
}
|
|
}
|
|
|
|
void LLFastTimerView::onPauseHandler(void *data)
|
|
{
|
|
((LLFastTimerView*)data)->onPause();
|
|
}
|
|
|
|
BOOL LLFastTimerView::postBuild()
|
|
{
|
|
LLButton& pause_btn = getChildRef<LLButton>("pause_btn");
|
|
|
|
pause_btn.setClickedCallback(&LLFastTimerView::onPauseHandler,this);
|
|
//pause_btn.setCommitCallback(boost::bind(&LLFastTimerView::onPause, this));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleRightMouseDown(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (mHoverTimer )
|
|
{
|
|
// right click collapses timers
|
|
if (!mHoverTimer->getCollapsed())
|
|
{
|
|
mHoverTimer->setCollapsed(true);
|
|
}
|
|
else if (mHoverTimer->getParent())
|
|
{
|
|
mHoverTimer->getParent()->setCollapsed(true);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (mBarRect.pointInRect(x, y))
|
|
{
|
|
S32 bar_idx = MAX_VISIBLE_HISTORY - ((y - mBarRect.mBottom) * (MAX_VISIBLE_HISTORY + 2) / mBarRect.getHeight());
|
|
bar_idx = llclamp(bar_idx, 0, MAX_VISIBLE_HISTORY);
|
|
mPrintStats = LLFastTimer::NamedTimer::HISTORY_NUM - mScrollIndex - bar_idx;
|
|
return TRUE;
|
|
}
|
|
return LLFloater::handleRightMouseDown(x, y, mask);
|
|
}
|
|
|
|
LLFastTimer::NamedTimer* LLFastTimerView::getLegendID(S32 y)
|
|
{
|
|
S32 idx = (getRect().getHeight() - y) / ((S32) LLFontGL::getFontMonospace()->getLineHeight()+2) - 5;
|
|
|
|
if (idx >= 0 && idx < (S32)ft_display_idx.size())
|
|
{
|
|
return ft_display_idx[idx];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleDoubleClick(S32 x, S32 y, MASK mask)
|
|
{
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it)
|
|
{
|
|
(*it)->setCollapsed(false);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (x < mBarRect.mLeft)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = getLegendID(y);
|
|
if (idp)
|
|
{
|
|
idp->setCollapsed(!idp->getCollapsed());
|
|
}
|
|
}
|
|
else if (mHoverTimer)
|
|
{
|
|
//left click drills down by expanding timers
|
|
mHoverTimer->setCollapsed(false);
|
|
}
|
|
else if (mask & MASK_ALT)
|
|
{
|
|
if (mask & MASK_CONTROL)
|
|
{
|
|
mDisplayHz = !mDisplayHz;
|
|
}
|
|
else
|
|
{
|
|
mDisplayCalls = !mDisplayCalls;
|
|
}
|
|
}
|
|
else if (mask & MASK_SHIFT)
|
|
{
|
|
if (++mDisplayMode > 3)
|
|
mDisplayMode = 0;
|
|
}
|
|
else if (mask & MASK_CONTROL)
|
|
{
|
|
mDisplayCenter = (ChildAlignment)((mDisplayCenter + 1) % ALIGN_COUNT);
|
|
}
|
|
else if (mGraphRect.pointInRect(x, y))
|
|
{
|
|
gFocusMgr.setMouseCapture(this);
|
|
return TRUE;
|
|
}
|
|
//else
|
|
//{
|
|
// // pause/unpause
|
|
// LLFastTimer::sPauseHistory = !LLFastTimer::sPauseHistory;
|
|
// // reset scroll to bottom when unpausing
|
|
// if (!LLFastTimer::sPauseHistory)
|
|
// {
|
|
// mScrollIndex = 0;
|
|
// }
|
|
//}
|
|
return LLFloater::handleMouseDown(x, y, mask);
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleMouseUp(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (hasMouseCapture())
|
|
{
|
|
gFocusMgr.setMouseCapture(NULL);
|
|
}
|
|
return LLFloater::handleMouseUp(x, y, mask);;
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleHover(S32 x, S32 y, MASK mask)
|
|
{
|
|
if (hasMouseCapture())
|
|
{
|
|
F32 lerp = llclamp(1.f - (F32) (x - mGraphRect.mLeft) / (F32) mGraphRect.getWidth(), 0.f, 1.f);
|
|
mScrollIndex = ll_round( lerp * (F32)(LLFastTimer::NamedTimer::HISTORY_NUM - MAX_VISIBLE_HISTORY));
|
|
mScrollIndex = llclamp( mScrollIndex, 0, LLFastTimer::getLastFrameIndex());
|
|
return TRUE;
|
|
}
|
|
mHoverTimer = NULL;
|
|
mHoverID = NULL;
|
|
mOverLegend = false; // <FS:LO> Making the ledgend part of fast timers scrollable
|
|
|
|
if(LLFastTimer::sPauseHistory && mBarRect.pointInRect(x, y))
|
|
{
|
|
mHoverBarIndex = llmin(LLFastTimer::getCurFrameIndex() - 1,
|
|
MAX_VISIBLE_HISTORY - ((y - mBarRect.mBottom) * (MAX_VISIBLE_HISTORY + 2) / mBarRect.getHeight()));
|
|
if (mHoverBarIndex == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if (mHoverBarIndex == -1)
|
|
{
|
|
mHoverBarIndex = 0;
|
|
}
|
|
|
|
S32 i = 0;
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it, ++i)
|
|
{
|
|
// is mouse over bar for this timer?
|
|
if (x > mBarStart[mHoverBarIndex][i] &&
|
|
x < mBarEnd[mHoverBarIndex][i])
|
|
{
|
|
mHoverID = (*it);
|
|
if (mHoverTimer != *it)
|
|
{
|
|
// could be that existing tooltip is for a parent and is thus
|
|
// covering region for this new timer, go ahead and unblock
|
|
// so we can create a new tooltip
|
|
//LLToolTipMgr::instance().unblockToolTips();
|
|
gViewerWindow->unblockToolTips();
|
|
mHoverTimer = (*it);
|
|
}
|
|
|
|
mToolTipRect.set(mBarStart[mHoverBarIndex][i],
|
|
mBarRect.mBottom + ll_round(((F32)(MAX_VISIBLE_HISTORY - mHoverBarIndex + 1)) * ((F32)mBarRect.getHeight() / ((F32)MAX_VISIBLE_HISTORY + 2.f))),
|
|
mBarEnd[mHoverBarIndex][i],
|
|
mBarRect.mBottom + ll_round((F32)(MAX_VISIBLE_HISTORY - mHoverBarIndex) * ((F32)mBarRect.getHeight() / ((F32)MAX_VISIBLE_HISTORY + 2.f))));
|
|
}
|
|
|
|
if ((*it)->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
}
|
|
else if (x < mBarRect.mLeft)
|
|
{
|
|
LLFastTimer::NamedTimer* timer_id = getLegendID(y);
|
|
if (timer_id)
|
|
{
|
|
mHoverID = timer_id;
|
|
}
|
|
mOverLegend = true; // <FS:LO> Making the ledgend part of fast timers scrollable
|
|
}
|
|
|
|
return LLFloater::handleHover(x, y, mask);
|
|
}
|
|
|
|
|
|
BOOL LLFastTimerView::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen)
|
|
{
|
|
std::string tool_tip;
|
|
if(LLFastTimer::sPauseHistory && mBarRect.pointInRect(x, y))
|
|
{
|
|
// tooltips for timer bars
|
|
if (mHoverTimer)
|
|
{
|
|
tool_tip = mHoverTimer->getToolTip(LLFastTimer::NamedTimer::HISTORY_NUM - mScrollIndex - mHoverBarIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// tooltips for timer legend
|
|
if (x < mBarRect.mLeft)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = getLegendID(y);
|
|
if (idp)
|
|
{
|
|
tool_tip = idp->getToolTip();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!tool_tip.empty())
|
|
{
|
|
msg = tool_tip;
|
|
localPointToScreen(
|
|
0, 0,
|
|
&(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
|
|
localPointToScreen(
|
|
getRect().getWidth(), getRect().getHeight(),
|
|
&(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );
|
|
return TRUE;
|
|
}
|
|
else
|
|
return LLFloater::handleToolTip(x, y, msg, sticky_rect_screen);
|
|
}
|
|
|
|
BOOL LLFastTimerView::handleScrollWheel(S32 x, S32 y, S32 clicks)
|
|
{
|
|
//LLFastTimer::sPauseHistory = TRUE;
|
|
//mScrollIndex = llclamp( mScrollIndex + clicks,
|
|
//0,
|
|
//llmin(LLFastTimer::getLastFrameIndex(), (S32)LLFastTimer::NamedTimer::HISTORY_NUM - MAX_VISIBLE_HISTORY));
|
|
// <FS:LO> Making the ledgend part of fast timers scrollable
|
|
if(mOverLegend)
|
|
{
|
|
mScrollOffset += clicks;
|
|
S32 count = 0;
|
|
for (timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != timer_tree_iterator_t();
|
|
++it)
|
|
{
|
|
count++;
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
if (idp->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
mScrollOffset = llclamp(mScrollOffset,0,count-5);
|
|
}
|
|
else
|
|
{
|
|
LLFastTimer::sPauseHistory = TRUE;
|
|
mScrollIndex = llclamp( mScrollIndex + clicks,
|
|
0,
|
|
llmin(LLFastTimer::getLastFrameIndex(), (S32)LLFastTimer::NamedTimer::HISTORY_NUM - MAX_VISIBLE_HISTORY));
|
|
}
|
|
// </FS:LO>
|
|
return TRUE;
|
|
}
|
|
|
|
static LLTrace::BlockTimerStatHandle FTM_RENDER_TIMER("Timers", true);
|
|
|
|
static std::map<LLFastTimer::NamedTimer*, LLColor4> sTimerColors;
|
|
|
|
void LLFastTimerView::draw()
|
|
{
|
|
LL_RECORD_BLOCK_TIME(FTM_RENDER_TIMER);
|
|
|
|
std::string tdesc;
|
|
|
|
F64 clock_freq = (F64)LLFastTimer::countsPerSecond();
|
|
F64 iclock_freq = 1000.0 / clock_freq;
|
|
|
|
S32 margin = 10;
|
|
S32 height = getRect().getHeight();
|
|
S32 width = getRect().getWidth();
|
|
|
|
LLRect new_rect;
|
|
new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height);
|
|
setRect(new_rect);
|
|
|
|
S32 left, top, right, bottom;
|
|
S32 x, y, barw, barh, dx, dy;
|
|
S32 texth;
|
|
LLPointer<LLUIImage> box_imagep = LLUI::getUIImage("Rounded_Square");
|
|
|
|
// Draw the window background
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f));
|
|
|
|
S32 xleft = margin;
|
|
S32 ytop = margin;
|
|
|
|
mAverageCyclesPerTimer = LLFastTimer::sTimerCalls == 0
|
|
? 0
|
|
: ll_round(lerp((F32)mAverageCyclesPerTimer, (F32)(LLFastTimer::sTimerCycles / (U64)LLFastTimer::sTimerCalls), 0.1f));
|
|
LLFastTimer::sTimerCycles = 0;
|
|
LLFastTimer::sTimerCalls = 0;
|
|
|
|
// Draw some help
|
|
{
|
|
|
|
x = xleft;
|
|
y = height - ytop;
|
|
texth = (S32)LLFontGL::getFontMonospace()->getLineHeight();
|
|
|
|
#if TIME_FAST_TIMERS
|
|
tdesc = llformat("Cycles per timer call: %d", mAverageCyclesPerTimer);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
|
|
#else
|
|
char modedesc[][32] = {
|
|
"2 x Average ",
|
|
"Max ",
|
|
"Recent Max ",
|
|
"100 ms "
|
|
};
|
|
char centerdesc[][32] = {
|
|
"Left ",
|
|
"Centered ",
|
|
"Ordered "
|
|
};
|
|
|
|
tdesc = llformat("Full bar = %s [Click to pause/reset] [SHIFT-Click to toggle]",modedesc[mDisplayMode]);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
|
|
|
|
x = xleft, y -= (texth + 2);
|
|
tdesc = llformat("Justification = %s [CTRL-Click to toggle]",centerdesc[mDisplayCenter]);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
|
|
y -= (texth + 2);
|
|
|
|
LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected] [ALT-Click toggle counts] [ALT-SHIFT-Click sub hidden]"),
|
|
0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP);
|
|
#endif
|
|
y -= (texth + 2);
|
|
}
|
|
|
|
S32 histmax = llmin(LLFastTimer::getLastFrameIndex()+1, MAX_VISIBLE_HISTORY);
|
|
|
|
// Draw the legend
|
|
xleft = margin;
|
|
ytop = y;
|
|
|
|
y -= (texth + 2);
|
|
|
|
sTimerColors[&LLFastTimer::NamedTimer::getRootNamedTimer()] = LLColor4::grey;
|
|
|
|
F32 hue = 0.f;
|
|
// <ALCH:LL> Move color generation down to be in the next loop.
|
|
/*for (timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != timer_tree_iterator_t();
|
|
++it)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
|
|
const F32 HUE_INCREMENT = 0.23f;
|
|
hue = fmodf(hue + HUE_INCREMENT, 1.f);
|
|
// saturation increases with depth
|
|
F32 saturation = clamp_rescale((F32)idp->getDepth(), 0.f, 3.f, 0.f, 1.f);
|
|
// lightness alternates with depth
|
|
F32 lightness = idp->getDepth() % 2 ? 0.5f : 0.6f;
|
|
|
|
LLColor4 child_color;
|
|
child_color.setHSL(hue, saturation, lightness);
|
|
|
|
sTimerColors[idp] = child_color;
|
|
}*/
|
|
// </ALCH:LL>
|
|
|
|
const S32 LEGEND_WIDTH = 220;
|
|
{
|
|
LLLocalClipRect clip(LLRect(margin, y, LEGEND_WIDTH, margin));
|
|
S32 cur_line = 0;
|
|
ft_display_idx.clear();
|
|
std::map<LLFastTimer::NamedTimer*, S32> display_line;
|
|
S32 mScrollOffset_tmp = mScrollOffset; // <FS:LO> Making the ledgend part of fast timers scrollable
|
|
for (timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != timer_tree_iterator_t();
|
|
++it)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
// <ALCH:LL> Move color generation down to be in the next loop.
|
|
const F32 HUE_INCREMENT = 0.23f;
|
|
hue = fmodf(hue + HUE_INCREMENT, 1.f);
|
|
// saturation increases with depth
|
|
F32 saturation = clamp_rescale((F32)idp->getDepth(), 0.f, 3.f, 0.f, 1.f);
|
|
// lightness alternates with depth
|
|
F32 lightness = idp->getDepth() % 2 ? 0.5f : 0.6f;
|
|
|
|
LLColor4 child_color;
|
|
child_color.setHSL(hue, saturation, lightness);
|
|
|
|
sTimerColors[idp] = child_color;
|
|
// </ALCH:LL>
|
|
|
|
// <FS:LO> Making the ledgend part of fast timers scrollable
|
|
if(mScrollOffset_tmp)
|
|
{
|
|
--mScrollOffset_tmp;
|
|
if (idp->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
continue;
|
|
}
|
|
// </FS:LO>
|
|
display_line[idp] = cur_line;
|
|
ft_display_idx.push_back(idp);
|
|
cur_line++;
|
|
|
|
x = xleft;
|
|
|
|
left = x; right = x + texth;
|
|
top = y; bottom = y - texth;
|
|
S32 scale_offset = 0;
|
|
if (idp == mHoverID)
|
|
{
|
|
scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 2.f);
|
|
}
|
|
gl_rect_2d(left - scale_offset, top + scale_offset, right + scale_offset, bottom - scale_offset, sTimerColors[idp]);
|
|
|
|
F32 ms = 0;
|
|
S32 calls = 0;
|
|
if (mHoverBarIndex > 0 && mHoverID)
|
|
{
|
|
S32 hidx = LLFastTimer::NamedTimer::HISTORY_NUM - mScrollIndex - mHoverBarIndex;
|
|
U64 ticks = idp->getHistoricalCount(hidx);
|
|
ms = (F32)((F64)ticks * iclock_freq);
|
|
calls = (S32)idp->getHistoricalCalls(hidx);
|
|
}
|
|
else
|
|
{
|
|
U64 ticks = idp->getCountAverage();
|
|
ms = (F32)((F64)ticks * iclock_freq);
|
|
calls = (S32)idp->getCallAverage();
|
|
}
|
|
|
|
if (mDisplayCalls)
|
|
{
|
|
tdesc = llformat("%s (%d)",idp->getName().c_str(),calls);
|
|
}
|
|
else
|
|
{
|
|
tdesc = llformat("%s [%.1f]",idp->getName().c_str(),ms);
|
|
}
|
|
dx = (texth+4) + idp->getDepth()*8;
|
|
|
|
LLColor4 color = LLColor4::white;
|
|
if (idp->getDepth() > 0)
|
|
{
|
|
S32 line_start_y = (top + bottom) / 2;
|
|
S32 line_end_y = line_start_y + ((texth + 2) * (cur_line - display_line[idp->getParent()])) - texth;
|
|
gl_line_2d(x + dx - 8, line_start_y, x + dx, line_start_y, color);
|
|
S32 line_x = x + (texth + 4) + ((idp->getDepth() - 1) * 8);
|
|
gl_line_2d(line_x, line_start_y, line_x, line_end_y, color);
|
|
if (idp->getCollapsed() && !idp->getChildren().empty())
|
|
{
|
|
gl_line_2d(line_x+4, line_start_y-3, line_x+4, line_start_y+4, color);
|
|
}
|
|
}
|
|
|
|
x += dx;
|
|
BOOL is_child_of_hover_item = (idp == mHoverID);
|
|
LLFastTimer::NamedTimer* next_parent = idp->getParent();
|
|
while(!is_child_of_hover_item && next_parent)
|
|
{
|
|
is_child_of_hover_item = (mHoverID == next_parent);
|
|
next_parent = next_parent->getParent();
|
|
}
|
|
|
|
LLFontGL::getFontSansSerifSmall()->renderUTF8(tdesc, 0,
|
|
x, y,
|
|
color,
|
|
LLFontGL::LEFT, LLFontGL::TOP,
|
|
is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL);
|
|
|
|
y -= (texth + 2);
|
|
|
|
if (idp->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
}
|
|
|
|
xleft += LEGEND_WIDTH + 8;
|
|
// ytop = ytop;
|
|
|
|
// update rectangle that includes timer bars
|
|
mBarRect.mLeft = xleft;
|
|
mBarRect.mRight = getRect().getWidth();
|
|
mBarRect.mTop = ytop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
|
|
mBarRect.mBottom = margin + LINE_GRAPH_HEIGHT;
|
|
|
|
y = ytop;
|
|
barh = (ytop - margin - LINE_GRAPH_HEIGHT) / (MAX_VISIBLE_HISTORY + 2);
|
|
dy = barh>>2; // spacing between bars
|
|
if (dy < 1) dy = 1;
|
|
barh -= dy;
|
|
barw = width - xleft - margin;
|
|
|
|
// Draw the history bars
|
|
if (LLFastTimer::getLastFrameIndex() >= 0)
|
|
{
|
|
LLLocalClipRect clip(LLRect(xleft, ytop, getRect().getWidth() - margin, margin));
|
|
|
|
U64 totalticks;
|
|
if (!LLFastTimer::sPauseHistory)
|
|
{
|
|
U64 ticks = LLFastTimer::NamedTimer::getRootNamedTimer().getHistoricalCount(mScrollIndex);
|
|
|
|
if (LLFastTimer::getCurFrameIndex() >= 10)
|
|
{
|
|
U64 framec = LLFastTimer::getCurFrameIndex();
|
|
U64 avg = (U64)mAvgCountTotal;
|
|
mAvgCountTotal = (avg*framec + ticks) / (framec + 1);
|
|
if (ticks > mMaxCountTotal)
|
|
{
|
|
mMaxCountTotal = ticks;
|
|
}
|
|
}
|
|
|
|
if (ticks < mAvgCountTotal/100 || ticks > mAvgCountTotal*100)
|
|
{
|
|
LLFastTimer::sResetHistory = true;
|
|
}
|
|
|
|
if (LLFastTimer::getCurFrameIndex() < 10 || LLFastTimer::sResetHistory)
|
|
{
|
|
mAvgCountTotal = ticks;
|
|
mMaxCountTotal = ticks;
|
|
LLFastTimer::sResetHistory = false;
|
|
}
|
|
}
|
|
|
|
if (mDisplayMode == 0)
|
|
{
|
|
totalticks = mAvgCountTotal*2;
|
|
}
|
|
else if (mDisplayMode == 1)
|
|
{
|
|
totalticks = mMaxCountTotal;
|
|
}
|
|
else if (mDisplayMode == 2)
|
|
{
|
|
// Calculate the max total ticks for the current history
|
|
totalticks = 0;
|
|
for (S32 j=0; j<histmax; j++)
|
|
{
|
|
U64 ticks = LLFastTimer::NamedTimer::getRootNamedTimer().getHistoricalCount(j);
|
|
|
|
if (ticks > totalticks)
|
|
totalticks = ticks;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
totalticks = (U64)(clock_freq * .1); // 100 ms
|
|
}
|
|
|
|
// Draw MS ticks
|
|
{
|
|
U32 ms = (U32)((F64)totalticks * iclock_freq) ;
|
|
|
|
tdesc = llformat("%.1f ms |", (F32)ms*.25f);
|
|
x = xleft + barw/4 - LLFontGL::getFontMonospace()->getWidth(tdesc);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::TOP);
|
|
|
|
tdesc = llformat("%.1f ms |", (F32)ms*.50f);
|
|
x = xleft + barw/2 - LLFontGL::getFontMonospace()->getWidth(tdesc);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::TOP);
|
|
|
|
tdesc = llformat("%.1f ms |", (F32)ms*.75f);
|
|
x = xleft + (barw*3)/4 - LLFontGL::getFontMonospace()->getWidth(tdesc);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::TOP);
|
|
|
|
tdesc = llformat( "%d ms |", ms);
|
|
x = xleft + barw - LLFontGL::getFontMonospace()->getWidth(tdesc);
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::TOP);
|
|
}
|
|
|
|
// Draw borders
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
gGL.color4f(0.5f,0.5f,0.5f,0.5f);
|
|
|
|
S32 by = y + 2;
|
|
|
|
y -= ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4);
|
|
|
|
//heading
|
|
gl_rect_2d(xleft-5, by, getRect().getWidth()-5, y+5, FALSE);
|
|
|
|
//tree view
|
|
gl_rect_2d(5, by, xleft-10, 5, FALSE);
|
|
|
|
by = y + 5;
|
|
//average bar
|
|
gl_rect_2d(xleft-5, by, getRect().getWidth()-5, by-barh-dy-5, FALSE);
|
|
|
|
by -= barh*2+dy;
|
|
|
|
//current frame bar
|
|
gl_rect_2d(xleft-5, by, getRect().getWidth()-5, by-barh-dy-2, FALSE);
|
|
|
|
by -= barh+dy+1;
|
|
|
|
//history bars
|
|
gl_rect_2d(xleft-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-barh-dy-2, FALSE);
|
|
|
|
by = LINE_GRAPH_HEIGHT-barh-dy-7;
|
|
|
|
//line graph
|
|
mGraphRect = LLRect(xleft-5, by, getRect().getWidth()-5, 5);
|
|
|
|
gl_rect_2d(mGraphRect, FALSE);
|
|
}
|
|
|
|
mBarStart.clear();
|
|
mBarEnd.clear();
|
|
|
|
// Draw bars for each history entry
|
|
// Special: -1 = show running average
|
|
gGL.getTexUnit(0)->bind(box_imagep->getImage());
|
|
for (S32 j=-1; j<histmax && y > LINE_GRAPH_HEIGHT; j++)
|
|
{
|
|
mBarStart.push_back(std::vector<S32>());
|
|
mBarEnd.push_back(std::vector<S32>());
|
|
int sublevel_dx[FTV_MAX_DEPTH];
|
|
int sublevel_left[FTV_MAX_DEPTH];
|
|
int sublevel_right[FTV_MAX_DEPTH];
|
|
S32 tidx;
|
|
if (j >= 0)
|
|
{
|
|
tidx = LLFastTimer::NamedTimer::HISTORY_NUM - j - 1 - mScrollIndex;
|
|
}
|
|
else
|
|
{
|
|
tidx = -1;
|
|
}
|
|
|
|
x = xleft;
|
|
|
|
// draw the bars for each stat
|
|
std::vector<S32> xpos;
|
|
std::vector<S32> deltax;
|
|
xpos.push_back(xleft);
|
|
|
|
LLFastTimer::NamedTimer* prev_id = NULL;
|
|
|
|
S32 i = 0;
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it, ++i)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
F32 frac = tidx == -1
|
|
? (F32)idp->getCountAverage() / (F32)totalticks
|
|
: (F32)idp->getHistoricalCount(tidx) / (F32)totalticks;
|
|
|
|
dx = ll_round(frac * (F32)barw);
|
|
S32 prev_delta_x = deltax.empty() ? 0 : deltax.back();
|
|
deltax.push_back(dx);
|
|
|
|
int level = idp->getDepth() - 1;
|
|
|
|
while ((S32)xpos.size() > level + 1)
|
|
{
|
|
xpos.pop_back();
|
|
}
|
|
left = xpos.back();
|
|
|
|
if (level == 0)
|
|
{
|
|
sublevel_left[level] = xleft;
|
|
sublevel_dx[level] = dx;
|
|
sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
|
|
}
|
|
else if (prev_id && prev_id->getDepth() < idp->getDepth())
|
|
{
|
|
U64 sublevelticks = 0;
|
|
|
|
for (LLFastTimer::NamedTimer::child_const_iter it = prev_id->beginChildren();
|
|
it != prev_id->endChildren();
|
|
++it)
|
|
{
|
|
sublevelticks += (tidx == -1)
|
|
? (*it)->getCountAverage()
|
|
: (*it)->getHistoricalCount(tidx);
|
|
}
|
|
|
|
F32 subfrac = (F32)sublevelticks / (F32)totalticks;
|
|
sublevel_dx[level] = (int)(subfrac * (F32)barw + .5f);
|
|
|
|
if (mDisplayCenter == ALIGN_CENTER)
|
|
{
|
|
left += (prev_delta_x - sublevel_dx[level])/2;
|
|
}
|
|
else if (mDisplayCenter == ALIGN_RIGHT)
|
|
{
|
|
left += (prev_delta_x - sublevel_dx[level]);
|
|
}
|
|
|
|
sublevel_left[level] = left;
|
|
sublevel_right[level] = sublevel_left[level] + sublevel_dx[level];
|
|
}
|
|
|
|
right = left + dx;
|
|
xpos.back() = right;
|
|
xpos.push_back(left);
|
|
|
|
mBarStart.back().push_back(left);
|
|
mBarEnd.back().push_back(right);
|
|
|
|
top = y;
|
|
bottom = y - barh;
|
|
|
|
if (right > left)
|
|
{
|
|
//U32 rounded_edges = 0;
|
|
LLColor4 color = sTimerColors[idp];// *ft_display_table[i].color;
|
|
S32 scale_offset = 0;
|
|
|
|
BOOL is_child_of_hover_item = (idp == mHoverID);
|
|
LLFastTimer::NamedTimer* next_parent = idp->getParent();
|
|
while(!is_child_of_hover_item && next_parent)
|
|
{
|
|
is_child_of_hover_item = (mHoverID == next_parent);
|
|
next_parent = next_parent->getParent();
|
|
}
|
|
|
|
if (idp == mHoverID)
|
|
{
|
|
scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f);
|
|
//color = lerp(color, LLColor4::black, -0.4f);
|
|
}
|
|
else if (mHoverID != NULL && !is_child_of_hover_item)
|
|
{
|
|
color = lerp(color, LLColor4::grey, 0.8f);
|
|
}
|
|
|
|
gGL.color4fv(color.mV);
|
|
F32 start_fragment = llclamp((F32)(left - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
|
|
F32 end_fragment = llclamp((F32)(right - sublevel_left[level]) / (F32)sublevel_dx[level], 0.f, 1.f);
|
|
LLRect rect(sublevel_left[level], top - level + scale_offset, sublevel_right[level], bottom + level - scale_offset);
|
|
gl_segmented_rect_2d_fragment_tex(rect, box_imagep->getTextureWidth(), box_imagep->getTextureHeight(), 16, start_fragment, end_fragment);
|
|
|
|
}
|
|
|
|
if ((*it)->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
|
|
prev_id = idp;
|
|
}
|
|
y -= (barh + dy);
|
|
if (j < 0)
|
|
y -= barh;
|
|
}
|
|
|
|
//draw line graph history
|
|
{
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
LLLocalClipRect clip(mGraphRect);
|
|
|
|
//normalize based on last frame's maximum
|
|
static U64 last_max = 0;
|
|
static F32 alpha_interp = 0.f;
|
|
U64 max_ticks = llmax(last_max, (U64) 1);
|
|
F32 ms = (F32)((F64)max_ticks * iclock_freq);
|
|
|
|
//display y-axis range
|
|
std::string tdesc;
|
|
if (mDisplayCalls)
|
|
tdesc = llformat("%d calls", (int)max_ticks);
|
|
else if (mDisplayHz)
|
|
tdesc = llformat("%d Hz", (int)max_ticks);
|
|
else
|
|
tdesc = llformat("%4.2f ms", ms);
|
|
|
|
x = mGraphRect.mRight - LLFontGL::getFontMonospace()->getWidth(tdesc)-5;
|
|
y = mGraphRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight());
|
|
|
|
LLFontGL::getFontMonospace()->renderUTF8(tdesc, 0, x, y, LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::TOP);
|
|
|
|
//highlight visible range
|
|
{
|
|
S32 first_frame = LLFastTimer::NamedTimer::HISTORY_NUM - mScrollIndex;
|
|
S32 last_frame = first_frame - MAX_VISIBLE_HISTORY;
|
|
|
|
F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(LLFastTimer::NamedTimer::HISTORY_NUM-1);
|
|
|
|
F32 right = (F32) mGraphRect.mLeft + frame_delta*first_frame;
|
|
F32 left = (F32) mGraphRect.mLeft + frame_delta*last_frame;
|
|
|
|
gGL.color4f(0.5f,0.5f,0.5f,0.3f);
|
|
gl_rect_2d((S32) left, mGraphRect.mTop, (S32) right, mGraphRect.mBottom);
|
|
|
|
if (mHoverBarIndex >= 0)
|
|
{
|
|
S32 bar_frame = first_frame - mHoverBarIndex;
|
|
F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame;
|
|
|
|
gGL.color4f(0.5f,0.5f,0.5f,1);
|
|
|
|
gGL.begin(LLRender::LINES);
|
|
gGL.vertex2i((S32)bar, mGraphRect.mBottom);
|
|
gGL.vertex2i((S32)bar, mGraphRect.mTop);
|
|
gGL.end();
|
|
}
|
|
}
|
|
|
|
U64 cur_max = 0;
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
|
|
//fatten highlighted timer
|
|
if (mHoverID == idp)
|
|
{
|
|
gGL.flush();
|
|
gGL.setLineWidth(3);
|
|
}
|
|
|
|
const F32 * col = sTimerColors[idp].mV;// ft_display_table[idx].color->mV;
|
|
|
|
F32 alpha = 1.f;
|
|
|
|
if (mHoverID != NULL &&
|
|
idp != mHoverID)
|
|
{ //fade out non-highlighted timers
|
|
if (idp->getParent() != mHoverID)
|
|
{
|
|
alpha = alpha_interp;
|
|
}
|
|
}
|
|
|
|
gGL.color4f(col[0], col[1], col[2], alpha);
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
for (U32 j = llmax(0, LLFastTimer::NamedTimer::HISTORY_NUM - LLFastTimer::getLastFrameIndex());
|
|
j < (U32)LLFastTimer::NamedTimer::HISTORY_NUM;
|
|
j++)
|
|
{
|
|
U64 ticks = idp->getHistoricalCount(j);
|
|
|
|
if (mDisplayHz)
|
|
{
|
|
F64 tc = (F64) (ticks+1) * iclock_freq;
|
|
tc = 1000.f/tc;
|
|
ticks = llmin((U64) tc, (U64) 1024);
|
|
}
|
|
else if (mDisplayCalls)
|
|
{
|
|
ticks = (S32)idp->getHistoricalCalls(j);
|
|
}
|
|
|
|
if (alpha == 1.f)
|
|
{
|
|
//normalize to highlighted timer
|
|
cur_max = llmax(cur_max, ticks);
|
|
}
|
|
F32 x = mGraphRect.mLeft + ((F32) (mGraphRect.getWidth()))/(LLFastTimer::NamedTimer::HISTORY_NUM-1)*j;
|
|
F32 y = mGraphRect.mBottom + (F32) mGraphRect.getHeight()/max_ticks*ticks;
|
|
gGL.vertex2f(x,y);
|
|
gGL.vertex2f(x,mGraphRect.mBottom);
|
|
}
|
|
gGL.end();
|
|
|
|
if (mHoverID == idp)
|
|
{
|
|
gGL.flush();
|
|
gGL.setLineWidth(1);
|
|
}
|
|
|
|
if (idp->getCollapsed())
|
|
{
|
|
//skip hidden timers
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
|
|
//interpolate towards new maximum
|
|
last_max = (U64) lerp((F32)last_max, (F32) cur_max, LLSmoothInterpolation::getInterpolant(0.1f));
|
|
if (last_max - cur_max <= 1 || cur_max - last_max <= 1)
|
|
{
|
|
last_max = cur_max;
|
|
}
|
|
F32 alpha_target = last_max > cur_max ?
|
|
llmin((F32) last_max/ (F32) cur_max - 1.f,1.f) :
|
|
llmin((F32) cur_max/ (F32) last_max - 1.f,1.f);
|
|
alpha_interp = lerp(alpha_interp, alpha_target, LLSmoothInterpolation::getInterpolant(0.1f));
|
|
|
|
if (mHoverID != NULL)
|
|
{
|
|
x = (mGraphRect.mRight + mGraphRect.mLeft)/2;
|
|
y = mGraphRect.mBottom + 8;
|
|
|
|
LLFontGL::getFontMonospace()->renderUTF8(
|
|
mHoverID->getName(),
|
|
0,
|
|
x, y,
|
|
LLColor4::white,
|
|
LLFontGL::LEFT, LLFontGL::BOTTOM);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output stats for clicked bar to log
|
|
if (mPrintStats >= 0)
|
|
{
|
|
std::string legend_stat;
|
|
bool first = true;
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
|
|
if (!first)
|
|
{
|
|
legend_stat += ", ";
|
|
}
|
|
first = false;
|
|
legend_stat += idp->getName();
|
|
|
|
if (idp->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
LL_INFOS() << legend_stat << LL_ENDL;
|
|
|
|
std::string timer_stat;
|
|
first = true;
|
|
for(timer_tree_iterator_t it = begin_timer_tree(LLFastTimer::NamedTimer::getRootNamedTimer());
|
|
it != end_timer_tree();
|
|
++it)
|
|
{
|
|
LLFastTimer::NamedTimer* idp = (*it);
|
|
|
|
if (!first)
|
|
{
|
|
timer_stat += ", ";
|
|
}
|
|
first = false;
|
|
|
|
U64 ticks;
|
|
if (mPrintStats > 0)
|
|
{
|
|
ticks = idp->getHistoricalCount(mPrintStats);
|
|
}
|
|
else
|
|
{
|
|
ticks = idp->getCountAverage();
|
|
}
|
|
F32 ms = (F32)((F64)ticks * iclock_freq);
|
|
|
|
timer_stat += llformat("%.1f",ms);
|
|
|
|
if (idp->getCollapsed())
|
|
{
|
|
it.skipDescendants();
|
|
}
|
|
}
|
|
LL_INFOS() << timer_stat << LL_ENDL;
|
|
mPrintStats = -1;
|
|
}
|
|
|
|
mHoverID = NULL;
|
|
mHoverBarIndex = -1;
|
|
|
|
LLView::draw();
|
|
}
|
|
|
|
F64 LLFastTimerView::getTime(const std::string& name)
|
|
{
|
|
const LLFastTimer::NamedTimer* timerp = LLFastTimer::getTimerByName(name);
|
|
if (timerp)
|
|
{
|
|
return (F64)timerp->getCountAverage() / (F64)LLFastTimer::countsPerSecond();
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch)
|
|
{
|
|
//read result back into raw image
|
|
glReadPixels(0, 0, 1024, 512, GL_RGB, GL_UNSIGNED_BYTE, scratch->getData());
|
|
|
|
//write results to disk
|
|
LLPointer<LLImagePNG> result = new LLImagePNG();
|
|
result->encode(scratch, 0.f);
|
|
|
|
std::string ext = result->getExtension();
|
|
std::string filename = llformat("%s_%s.%s", label.c_str(), suffix, ext.c_str());
|
|
|
|
std::string out_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename);
|
|
result->save(out_file);
|
|
}
|
|
|
|
//static
|
|
void LLFastTimerView::exportCharts(const std::string& base, const std::string& target)
|
|
{
|
|
//allocate render target for drawing charts
|
|
LLRenderTarget buffer;
|
|
buffer.allocate(1024,512, GL_RGB, FALSE, FALSE);
|
|
|
|
|
|
LLSD cur;
|
|
|
|
LLSD base_data;
|
|
|
|
{ //read base log into memory
|
|
S32 i = 0;
|
|
std::ifstream is(base.c_str());
|
|
while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0)
|
|
{
|
|
base_data[i++] = cur;
|
|
}
|
|
is.close();
|
|
}
|
|
|
|
LLSD cur_data;
|
|
std::set<std::string> chart_names;
|
|
|
|
{ //read current log into memory
|
|
S32 i = 0;
|
|
std::ifstream is(target.c_str());
|
|
while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0)
|
|
{
|
|
cur_data[i++] = cur;
|
|
|
|
for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
|
|
{
|
|
std::string label = iter->first;
|
|
chart_names.insert(label);
|
|
}
|
|
}
|
|
is.close();
|
|
}
|
|
|
|
//get time domain
|
|
LLSD::Real cur_total_time = 0.0;
|
|
|
|
for (U32 i = 0; i < (U32)cur_data.size(); ++i)
|
|
{
|
|
cur_total_time += cur_data[i]["Total"]["Time"].asReal();
|
|
}
|
|
|
|
LLSD::Real base_total_time = 0.0;
|
|
for (U32 i = 0; i < (U32)base_data.size(); ++i)
|
|
{
|
|
base_total_time += base_data[i]["Total"]["Time"].asReal();
|
|
}
|
|
|
|
//allocate raw scratch space
|
|
LLPointer<LLImageRaw> scratch = new LLImageRaw(1024, 512, 3);
|
|
|
|
gGL.pushMatrix();
|
|
gGL.loadIdentity();
|
|
gGL.matrixMode(LLRender::MM_PROJECTION);
|
|
gGL.loadIdentity();
|
|
gGL.ortho(-0.05f, 1.05f, -0.05f, 1.05f, -1.0f, 1.0f);
|
|
|
|
//render charts
|
|
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
|
|
|
buffer.bindTarget();
|
|
|
|
for (std::set<std::string>::iterator iter = chart_names.begin(); iter != chart_names.end(); ++iter)
|
|
{
|
|
std::string label = *iter;
|
|
|
|
LLSD::Real max_time = 0.0;
|
|
LLSD::Integer max_calls = 0;
|
|
LLSD::Real max_execution = 0.0;
|
|
|
|
std::vector<LLSD::Real> cur_execution;
|
|
std::vector<LLSD::Real> cur_times;
|
|
std::vector<LLSD::Integer> cur_calls;
|
|
|
|
std::vector<LLSD::Real> base_execution;
|
|
std::vector<LLSD::Real> base_times;
|
|
std::vector<LLSD::Integer> base_calls;
|
|
|
|
for (U32 i = 0; i < (U32)cur_data.size(); ++i)
|
|
{
|
|
LLSD::Real time = cur_data[i][label]["Time"].asReal();
|
|
LLSD::Integer calls = cur_data[i][label]["Calls"].asInteger();
|
|
|
|
LLSD::Real execution = 0.0;
|
|
if (calls > 0)
|
|
{
|
|
execution = time/calls;
|
|
cur_execution.push_back(execution);
|
|
cur_times.push_back(time);
|
|
}
|
|
|
|
cur_calls.push_back(calls);
|
|
}
|
|
|
|
for (U32 i = 0; i < (U32)base_data.size(); ++i)
|
|
{
|
|
LLSD::Real time = base_data[i][label]["Time"].asReal();
|
|
LLSD::Integer calls = base_data[i][label]["Calls"].asInteger();
|
|
|
|
LLSD::Real execution = 0.0;
|
|
if (calls > 0)
|
|
{
|
|
execution = time/calls;
|
|
base_execution.push_back(execution);
|
|
base_times.push_back(time);
|
|
}
|
|
|
|
base_calls.push_back(calls);
|
|
}
|
|
|
|
std::sort(base_calls.begin(), base_calls.end());
|
|
std::sort(base_times.begin(), base_times.end());
|
|
std::sort(base_execution.begin(), base_execution.end());
|
|
|
|
std::sort(cur_calls.begin(), cur_calls.end());
|
|
std::sort(cur_times.begin(), cur_times.end());
|
|
std::sort(cur_execution.begin(), cur_execution.end());
|
|
|
|
//remove outliers
|
|
const U32 OUTLIER_CUTOFF = 512;
|
|
if (base_times.size() > OUTLIER_CUTOFF)
|
|
{
|
|
ll_remove_outliers(base_times, 1.f);
|
|
}
|
|
|
|
if (base_execution.size() > OUTLIER_CUTOFF)
|
|
{
|
|
ll_remove_outliers(base_execution, 1.f);
|
|
}
|
|
|
|
if (cur_times.size() > OUTLIER_CUTOFF)
|
|
{
|
|
ll_remove_outliers(cur_times, 1.f);
|
|
}
|
|
|
|
if (cur_execution.size() > OUTLIER_CUTOFF)
|
|
{
|
|
ll_remove_outliers(cur_execution, 1.f);
|
|
}
|
|
|
|
|
|
max_time = llmax(base_times.empty() ? 0.0 : *base_times.rbegin(), cur_times.empty() ? 0.0 : *cur_times.rbegin());
|
|
max_calls = llmax(base_calls.empty() ? 0 : *base_calls.rbegin(), cur_calls.empty() ? 0 : *cur_calls.rbegin());
|
|
max_execution = llmax(base_execution.empty() ? 0.0 : *base_execution.rbegin(), cur_execution.empty() ? 0.0 : *cur_execution.rbegin());
|
|
|
|
|
|
LLVector3 last_p;
|
|
|
|
//====================================
|
|
// basic
|
|
//====================================
|
|
buffer.clear();
|
|
|
|
last_p.clear();
|
|
|
|
LLGLDisable<GL_CULL_FACE> cull;
|
|
|
|
LLVector3 base_col(0, 0.7f, 0.f);
|
|
LLVector3 cur_col(1.f, 0.f, 0.f);
|
|
|
|
gGL.setSceneBlendType(LLRender::BT_ADD);
|
|
|
|
gGL.color3fv(base_col.mV);
|
|
for (U32 i = 0; i < base_times.size(); ++i)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
last_p.set((F32)i/(F32) base_times.size(), base_times[i]/max_time, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.end();
|
|
}
|
|
|
|
gGL.flush();
|
|
|
|
|
|
last_p.clear();
|
|
{
|
|
LLGLEnable<GL_BLEND> blend;
|
|
|
|
gGL.color3fv(cur_col.mV);
|
|
for (U32 i = 0; i < cur_times.size(); ++i)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
last_p.set((F32) i / (F32) cur_times.size(), cur_times[i]/max_time, 0.f);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.end();
|
|
}
|
|
|
|
gGL.flush();
|
|
}
|
|
|
|
saveChart(label, "time", scratch);
|
|
|
|
//======================================
|
|
// calls
|
|
//======================================
|
|
buffer.clear();
|
|
|
|
last_p.clear();
|
|
|
|
gGL.color3fv(base_col.mV);
|
|
for (U32 i = 0; i < base_calls.size(); ++i)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
last_p.set((F32) i / (F32) base_calls.size(), (F32)base_calls[i]/max_calls, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.end();
|
|
}
|
|
|
|
gGL.flush();
|
|
|
|
{
|
|
LLGLEnable<GL_BLEND> blend;
|
|
gGL.color3fv(cur_col.mV);
|
|
last_p.clear();
|
|
|
|
for (U32 i = 0; i < cur_calls.size(); ++i)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
last_p.set((F32) i / (F32) cur_calls.size(), (F32) cur_calls[i]/max_calls, 0.f);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.end();
|
|
|
|
}
|
|
|
|
gGL.flush();
|
|
}
|
|
|
|
saveChart(label, "calls", scratch);
|
|
|
|
//======================================
|
|
// execution
|
|
//======================================
|
|
buffer.clear();
|
|
|
|
|
|
gGL.color3fv(base_col.mV);
|
|
U32 count = 0;
|
|
U32 total_count = base_execution.size();
|
|
|
|
last_p.clear();
|
|
|
|
for (std::vector<LLSD::Real>::iterator iter = base_execution.begin(); iter != base_execution.end(); ++iter)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.end();
|
|
count++;
|
|
}
|
|
|
|
last_p.clear();
|
|
|
|
{
|
|
LLGLEnable<GL_BLEND> blend;
|
|
gGL.color3fv(cur_col.mV);
|
|
count = 0;
|
|
total_count = cur_execution.size();
|
|
|
|
for (std::vector<LLSD::Real>::iterator iter = cur_execution.begin(); iter != cur_execution.end(); ++iter)
|
|
{
|
|
gGL.begin(LLRender::TRIANGLE_STRIP);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f);
|
|
gGL.vertex3f(last_p.mV[0], 0.f, 0.f);
|
|
gGL.vertex3fv(last_p.mV);
|
|
gGL.end();
|
|
count++;
|
|
}
|
|
|
|
gGL.flush();
|
|
}
|
|
|
|
saveChart(label, "execution", scratch);
|
|
}
|
|
|
|
buffer.flush();
|
|
|
|
gGL.popMatrix();
|
|
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
|
gGL.popMatrix();
|
|
}
|
|
|
|
//static
|
|
LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is)
|
|
{
|
|
LLSD ret;
|
|
|
|
LLSD cur;
|
|
|
|
LLSD::Real total_time = 0.0;
|
|
LLSD::Integer total_frames = 0;
|
|
|
|
typedef std::map<std::string,LLViewerStats::StatsAccumulator> stats_map_t;
|
|
stats_map_t time_stats;
|
|
stats_map_t sample_stats;
|
|
|
|
while (!is.eof() && LLSDSerialize::fromXML(cur, is) > 0)
|
|
{
|
|
for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
|
|
{
|
|
std::string label = iter->first;
|
|
|
|
F64 time = iter->second["Time"].asReal();
|
|
|
|
// Skip the total figure
|
|
if(label.compare("Total") != 0)
|
|
{
|
|
total_time += time;
|
|
}
|
|
|
|
if (time > 0.0)
|
|
{
|
|
LLSD::Integer samples = iter->second["Calls"].asInteger();
|
|
|
|
time_stats[label].push(time);
|
|
sample_stats[label].push(samples);
|
|
}
|
|
}
|
|
total_frames++;
|
|
}
|
|
|
|
for(stats_map_t::iterator it = time_stats.begin(); it != time_stats.end(); ++it)
|
|
{
|
|
std::string label = it->first;
|
|
ret[label]["TotalTime"] = time_stats[label].mSum;
|
|
ret[label]["MeanTime"] = time_stats[label].getMean();
|
|
ret[label]["MaxTime"] = time_stats[label].getMaxValue();
|
|
ret[label]["MinTime"] = time_stats[label].getMinValue();
|
|
ret[label]["StdDevTime"] = time_stats[label].getStdDev();
|
|
|
|
ret[label]["Samples"] = sample_stats[label].mSum;
|
|
ret[label]["MaxSamples"] = sample_stats[label].getMaxValue();
|
|
ret[label]["MinSamples"] = sample_stats[label].getMinValue();
|
|
ret[label]["StdDevSamples"] = sample_stats[label].getStdDev();
|
|
|
|
ret[label]["Frames"] = (LLSD::Integer)time_stats[label].getCount();
|
|
}
|
|
|
|
ret["SessionTime"] = total_time;
|
|
ret["FrameCount"] = total_frames;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
//static
|
|
void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target, std::string output)
|
|
{
|
|
// Open baseline and current target, exit if one is inexistent
|
|
std::ifstream base_is(baseline.c_str());
|
|
std::ifstream target_is(target.c_str());
|
|
if (!base_is.is_open() || !target_is.is_open())
|
|
{
|
|
LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
|
|
base_is.close();
|
|
target_is.close();
|
|
return;
|
|
}
|
|
|
|
//analyze baseline
|
|
LLSD base = analyzePerformanceLogDefault(base_is);
|
|
base_is.close();
|
|
|
|
//analyze current
|
|
LLSD current = analyzePerformanceLogDefault(target_is);
|
|
target_is.close();
|
|
|
|
//output comparision
|
|
std::ofstream os(output.c_str());
|
|
|
|
LLSD::Real session_time = current["SessionTime"].asReal();
|
|
os <<
|
|
"Label, "
|
|
"% Change, "
|
|
"% of Session, "
|
|
"Cur Min, "
|
|
"Cur Max, "
|
|
"Cur Mean/sample, "
|
|
"Cur Mean/frame, "
|
|
"Cur StdDev/frame, "
|
|
"Cur Total, "
|
|
"Cur Frames, "
|
|
"Cur Samples, "
|
|
"Base Min, "
|
|
"Base Max, "
|
|
"Base Mean/sample, "
|
|
"Base Mean/frame, "
|
|
"Base StdDev/frame, "
|
|
"Base Total, "
|
|
"Base Frames, "
|
|
"Base Samples\n";
|
|
|
|
for (LLSD::map_iterator iter = base.beginMap(); iter != base.endMap(); ++iter)
|
|
{
|
|
LLSD::String label = iter->first;
|
|
|
|
if (current[label]["Samples"].asInteger() == 0 ||
|
|
base[label]["Samples"].asInteger() == 0)
|
|
{
|
|
//cannot compare
|
|
continue;
|
|
}
|
|
LLSD::Real a = base[label]["TotalTime"].asReal() / base[label]["Samples"].asReal();
|
|
LLSD::Real b = current[label]["TotalTime"].asReal() / current[label]["Samples"].asReal();
|
|
|
|
LLSD::Real diff = b-a;
|
|
|
|
LLSD::Real perc = diff/a * 100;
|
|
|
|
os << llformat("%s, %.2f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d\n",
|
|
label.c_str(),
|
|
(F32) perc,
|
|
(F32) (current[label]["TotalTime"].asReal()/session_time * 100.0),
|
|
|
|
(F32) current[label]["MinTime"].asReal(),
|
|
(F32) current[label]["MaxTime"].asReal(),
|
|
(F32) b,
|
|
(F32) current[label]["MeanTime"].asReal(),
|
|
(F32) current[label]["StdDevTime"].asReal(),
|
|
(F32) current[label]["TotalTime"].asReal(),
|
|
current[label]["Frames"].asInteger(),
|
|
current[label]["Samples"].asInteger(),
|
|
(F32) base[label]["MinTime"].asReal(),
|
|
(F32) base[label]["MaxTime"].asReal(),
|
|
(F32) a,
|
|
(F32) base[label]["MeanTime"].asReal(),
|
|
(F32) base[label]["StdDevTime"].asReal(),
|
|
(F32) base[label]["TotalTime"].asReal(),
|
|
base[label]["Frames"].asInteger(),
|
|
base[label]["Samples"].asInteger());
|
|
}
|
|
|
|
exportCharts(baseline, target);
|
|
|
|
os.flush();
|
|
os.close();
|
|
}
|
|
|
|
//static
|
|
void LLFastTimerView::outputAllMetrics()
|
|
{
|
|
/*if (LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
|
|
{
|
|
for (LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin();
|
|
iter != LLMetricPerformanceTesterBasic::sTesterMap.end(); ++iter)
|
|
{
|
|
LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)iter->second);
|
|
tester->outputTestResults();
|
|
}
|
|
}*/
|
|
}
|
|
|
|
//static
|
|
void LLFastTimerView::doAnalysis(std::string baseline, std::string target, std::string output)
|
|
{
|
|
if(LLFastTimer::sLog)
|
|
{
|
|
doAnalysisDefault(baseline, target, output) ;
|
|
return ;
|
|
}
|
|
|
|
/*if(LLFastTimer::sMetricLog)
|
|
{
|
|
LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ;
|
|
return ;
|
|
}*/
|
|
}
|
|
void LLFastTimerView::onClose(bool app_quitting)
|
|
{
|
|
if(app_quitting)
|
|
{
|
|
LLFloater::close(app_quitting);
|
|
}
|
|
else
|
|
{
|
|
setVisible(false);
|
|
}
|
|
}
|
|
|