/**
 * @file llscenemonitor.cpp
 * @brief monitor the scene loading process.
 *
 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, 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$
 */

#include "llviewerprecompiledheaders.h"
#include "llrendertarget.h"
#include "llscenemonitor.h"
#include "llviewerwindow.h"
#include "llviewerdisplay.h"
#include "llviewercontrol.h"
#include "llviewershadermgr.h"
#include "llui.h"
#include "llstartup.h"
#include "llappviewer.h"
#include "llwindow.h"
#include "llpointer.h"
#include "llspatialpartition.h"
#include "llagent.h"
#include "pipeline.h"
#include "llviewerparcelmgr.h"
#include "llviewerpartsim.h"

LLSceneMonitorView* gSceneMonitorView = NULL;

//
//The procedures of monitoring when the scene finishes loading visually,
//i.e., no pixel differences among frames, are:
//1, freeze all dynamic objects and avatars;
//2, (?) disable all sky and water;
//3, capture frames periodically, by calling "capture()";
//4, compute pixel differences between two latest captured frames, by calling "compare()", results are stored at mDiff;
//5, compute the number of pixels in mDiff above some tolerance threshold in GPU, by calling "calcDiffAggregate()";
//6, use gl occlusion query to fetch the result from GPU, by calling "fetchQueryResult()";
//END.
//

LLSceneMonitor::LLSceneMonitor() :
    mEnabled(false),
    mDiff(NULL),
    mDiffResult(0.f),
    mDiffTolerance(0.1f),
    mDiffState(WAITING_FOR_NEXT_DIFF),
    mDebugViewerVisible(false),
    mQueryObject(0),
    mDiffPixelRatio(0.5f)
{
    mFrames[0] = NULL;
    mFrames[1] = NULL;
}

LLSceneMonitor::~LLSceneMonitor()
{
    mDiffState = VIEWER_QUITTING;
    reset();

    mDitheringTexture = NULL;
}

void LLSceneMonitor::reset()
{
    delete mFrames[0];
    delete mFrames[1];
    delete mDiff;

    mFrames[0] = NULL;
    mFrames[1] = NULL;
    mDiff = NULL;

    mMonitorRecording.reset();
    mSceneLoadRecording.reset();
    mRecordingTimer.reset();

    unfreezeScene();

    if(mQueryObject > 0)
    {
        LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(mQueryObject);
        mQueryObject = 0;
    }
}

void LLSceneMonitor::generateDitheringTexture(S32 width, S32 height)
{
#if 1
    //4 * 4 matrix
    mDitherMatrixWidth = 4;
    S32 dither_matrix[4][4] =
    {
        {1, 9, 3, 11},
        {13, 5, 15, 7},
        {4, 12, 2, 10},
        {16, 8, 14, 6}
    };

    mDitherScale = 255.f / 17;
#else
    //8 * 8 matrix
    mDitherMatrixWidth = 16;
    S32 dither_matrix[16][16] =
    {
        {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64},
        {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32},
        {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56},
        {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24},
        {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62},
        {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30},
        {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54},
        {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22},
        {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64},
        {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32},
        {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56},
        {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24},
        {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62},
        {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30},
        {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54},
        {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22}
    };

    mDitherScale = 255.f / 65;
#endif

    LLPointer<LLImageRaw> image_raw = new LLImageRaw(mDitherMatrixWidth, mDitherMatrixWidth, 3);
    U8* data = image_raw->getData();
    for (S32 i = 0; i < mDitherMatrixWidth; i++)
    {
        for (S32 j = 0; j < mDitherMatrixWidth; j++)
        {
            U8 val = dither_matrix[i][j];
            *data++ = val;
            *data++ = val;
            *data++ = val;
        }
    }

    mDitheringTexture = LLViewerTextureManager::getLocalTexture(image_raw.get(), false) ;
    mDitheringTexture->setAddressMode(LLTexUnit::TAM_WRAP);
    mDitheringTexture->setFilteringOption(LLTexUnit::TFO_POINT);

    mDitherScaleS = (F32)width / mDitherMatrixWidth;
    mDitherScaleT = (F32)height / mDitherMatrixWidth;
}

void LLSceneMonitor::setDebugViewerVisible(bool visible)
{
    mDebugViewerVisible = visible;
}

LLRenderTarget& LLSceneMonitor::getCaptureTarget()
{
    LLRenderTarget* cur_target = NULL;

    S32 width = gViewerWindow->getWorldViewWidthRaw();
    S32 height = gViewerWindow->getWorldViewHeightRaw();

    if(!mFrames[0])
    {
        mFrames[0] = new LLRenderTarget();
        mFrames[0]->allocate(width, height, GL_RGB);
        gGL.getTexUnit(0)->bind(mFrames[0]);
        gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);

        cur_target = mFrames[0];
    }
    else if(!mFrames[1])
    {
        mFrames[1] = new LLRenderTarget();
        mFrames[1]->allocate(width, height, GL_RGB);
        gGL.getTexUnit(0)->bind(mFrames[1]);
        gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);

        cur_target = mFrames[1];
    }
    else //swap
    {
        cur_target = mFrames[0];
        mFrames[0] = mFrames[1];
        mFrames[1] = cur_target;
    }

    if(cur_target->getWidth() != width || cur_target->getHeight() != height) //size changed
    {
        cur_target->resize(width, height);
    }

    // we're promising the target exists
    return *cur_target;
}

void LLSceneMonitor::freezeAvatar(LLCharacter* avatarp)
{
    if(mEnabled)
    {
        mAvatarPauseHandles.push_back(avatarp->requestPause());
    }
}

void LLSceneMonitor::freezeScene()
{
    if(!mEnabled)
    {
        return;
    }

    // freeze all avatars
    for (LLCharacter* character : LLCharacter::sInstances)
    {
        freezeAvatar((LLCharacter*)character);
    }

    // freeze everything else
    gSavedSettings.setBOOL("FreezeTime", true);

    //disable sky, water and clouds
    gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY,
        LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES);

    //disable particle system
    LLViewerPartSim::getInstance()->enable(false);
}

void LLSceneMonitor::unfreezeScene()
{
    //thaw all avatars
    mAvatarPauseHandles.clear();

    if(mDiffState == VIEWER_QUITTING)
    {
        return;
    }

    // thaw everything else
    gSavedSettings.setBOOL("FreezeTime", false);

    //enable sky, water and clouds
    gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY,
        LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES);

    //enable particle system
    LLViewerPartSim::getInstance()->enable(true);
}

void LLSceneMonitor::capture()
{
    static U32 last_capture_frame = 0;
    static LLCachedControl<bool> monitor_enabled(gSavedSettings, "SceneLoadingMonitorEnabled");
    static LLCachedControl<F32>  scene_load_sample_time(gSavedSettings, "SceneLoadingMonitorSampleTime");
    static bool force_capture = true;

    bool enabled = monitor_enabled || mDebugViewerVisible;
    if(mEnabled != enabled)
    {
        if(mEnabled)
        {
            mEnabled = enabled;
            unfreezeScene();
            reset();
            force_capture = true;
        }
        else
        {
            mEnabled = enabled;
            reset();
            freezeScene();
        }
    }

    if (mEnabled
        && (mMonitorRecording.getSum(*LLViewerCamera::getVelocityStat()) > 0.1f
            || mMonitorRecording.getSum(*LLViewerCamera::getAngularVelocityStat()) > 0.05f))
    {
        reset();
        freezeScene();
        force_capture = true;
    }

    if(mEnabled
        && (mRecordingTimer.getElapsedTimeF32() > scene_load_sample_time()
            || force_capture)
        && last_capture_frame != gFrameCount)
    {
        force_capture = false;

        mSceneLoadRecording.resume();
        mMonitorRecording.resume();

        last_capture_frame = gFrameCount;

        LLRenderTarget& cur_target = getCaptureTarget();

        U32 old_FBO = LLRenderTarget::sCurFBO;

        gGL.getTexUnit(0)->bind(&cur_target);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); //point to the main frame buffer.

        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, cur_target.getWidth(), cur_target.getHeight()); //copy the content

        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, old_FBO);

        mDiffState = NEED_DIFF;
    }
}

bool LLSceneMonitor::needsUpdate() const
{
    return mDiffState == NEED_DIFF;
}

static LLStaticHashedString sDitherScale("dither_scale");
static LLStaticHashedString sDitherScaleS("dither_scale_s");
static LLStaticHashedString sDitherScaleT("dither_scale_t");

void LLSceneMonitor::compare()
{
#ifdef LL_WINDOWS
    if(mDiffState != NEED_DIFF)
    {
        return;
    }

    if(!mFrames[0] || !mFrames[1])
    {
        return;
    }
    if(mFrames[0]->getWidth() != mFrames[1]->getWidth() || mFrames[0]->getHeight() != mFrames[1]->getHeight())
    {   //size does not match
        return;
    }

    mDiffState = EXECUTE_DIFF;

    S32 width = gViewerWindow->getWindowWidthRaw();
    S32 height = gViewerWindow->getWindowHeightRaw();
    if(!mDiff)
    {
        mDiff = new LLRenderTarget();
        mDiff->allocate(width, height, GL_RGBA);

        generateDitheringTexture(width, height);
    }
    else if(mDiff->getWidth() != width || mDiff->getHeight() != height)
    {
        mDiff->resize(width, height);
        generateDitheringTexture(width, height);
    }

    mDiff->bindTarget();
    mDiff->clear();

    gTwoTextureCompareProgram.bind();

    gTwoTextureCompareProgram.uniform1f(sDitherScale, mDitherScale);
    gTwoTextureCompareProgram.uniform1f(sDitherScaleS, mDitherScaleS);
    gTwoTextureCompareProgram.uniform1f(sDitherScaleT, mDitherScaleT);

    gGL.getTexUnit(0)->activate();
    gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE);
    gGL.getTexUnit(0)->bind(mFrames[0]);
    gGL.getTexUnit(0)->activate();

    gGL.getTexUnit(1)->activate();
    gGL.getTexUnit(1)->enable(LLTexUnit::TT_TEXTURE);
    gGL.getTexUnit(1)->bind(mFrames[1]);
    gGL.getTexUnit(1)->activate();

    gGL.getTexUnit(2)->activate();
    gGL.getTexUnit(2)->enable(LLTexUnit::TT_TEXTURE);
    gGL.getTexUnit(2)->bind(mDitheringTexture);
    gGL.getTexUnit(2)->activate();

    gl_rect_2d_simple_tex(width, height);

    mDiff->flush();

    gTwoTextureCompareProgram.unbind();

    gGL.getTexUnit(0)->disable();
    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
    gGL.getTexUnit(1)->disable();
    gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE);
    gGL.getTexUnit(2)->disable();
    gGL.getTexUnit(2)->unbind(LLTexUnit::TT_TEXTURE);

    if (!mDebugViewerVisible)
    {
        calcDiffAggregate();
    }
#endif
}

static LLStaticHashedString sTolerance("tolerance");

//calculate Diff aggregate information in GPU, and enable gl occlusion query to capture it.
void LLSceneMonitor::calcDiffAggregate()
{
#ifdef LL_WINDOWS

    if(mDiffState != EXECUTE_DIFF && !mDebugViewerVisible)
    {
        return;
    }

    if(!mQueryObject)
    {
        mQueryObject = LLOcclusionCullingGroup::getNewOcclusionQueryObjectName();
    }

    LLGLDepthTest depth(true, false, GL_ALWAYS);
    if(!mDebugViewerVisible)
    {
        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    }

    LLGLSLShader* cur_shader = NULL;

    cur_shader = LLGLSLShader::sCurBoundShaderPtr;
    gOneTextureFilterProgram.bind();
    gOneTextureFilterProgram.uniform1f(sTolerance, mDiffTolerance);

    if(mDiffState == EXECUTE_DIFF)
    {
        glBeginQuery(GL_SAMPLES_PASSED, mQueryObject);
    }

    gl_draw_scaled_target(0, 0, S32(mDiff->getWidth() * mDiffPixelRatio), S32(mDiff->getHeight() * mDiffPixelRatio), mDiff);

    if(mDiffState == EXECUTE_DIFF)
    {
        glEndQuery(GL_SAMPLES_PASSED);
        mDiffState = WAIT_ON_RESULT;
    }

    gOneTextureFilterProgram.unbind();

    if(cur_shader != NULL)
    {
        cur_shader->bind();
    }

    if(!mDebugViewerVisible)
    {
        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    }
#endif
}

static LLTrace::EventStatHandle<> sFramePixelDiff("FramePixelDifference");
void LLSceneMonitor::fetchQueryResult()
{
    // also throttle timing here, to avoid going below sample time due to phasing with frame capture
    static LLCachedControl<F32>  scene_load_sample_time_control(gSavedSettings, "SceneLoadingMonitorSampleTime");
    F32Seconds scene_load_sample_time = (F32Seconds)scene_load_sample_time_control();

    if(mDiffState == WAIT_ON_RESULT
        && !LLAppViewer::instance()->quitRequested())
    {
        mDiffState = WAITING_FOR_NEXT_DIFF;

        GLuint available = 0;
        glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT_AVAILABLE, &available);
        if(available)
        {
            GLuint count = 0;
            glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT, &count);

            mDiffResult = sqrtf(count * 0.5f / (mDiff->getWidth() * mDiff->getHeight() * mDiffPixelRatio * mDiffPixelRatio)); //0.5 -> (front face + back face)

            LL_DEBUGS("SceneMonitor") << "Frame difference: " << mDiffResult << LL_ENDL;
            record(sFramePixelDiff, mDiffResult);

            static LLCachedControl<F32> diff_threshold(gSavedSettings,"SceneLoadingMonitorPixelDiffThreshold");
            F32Seconds elapsed_time = mRecordingTimer.getElapsedTimeF32();

            if (elapsed_time > scene_load_sample_time)
            {
                if(mDiffResult > diff_threshold())
                {
                    mSceneLoadRecording.extend();
                    llassert_always(mSceneLoadRecording.getResults().getLastRecording().getDuration() > scene_load_sample_time);
                }
                else
                {
                    mSceneLoadRecording.nextPeriod();
                }
                mRecordingTimer.reset();
            }
        }
    }
}

//dump results to a file _scene_xmonitor_results.csv
void LLSceneMonitor::dumpToFile(const std::string &file_name)
{
    if (!hasResults()) return;

    LL_INFOS("SceneMonitor") << "Saving scene load stats to " << file_name << LL_ENDL;
    try
    {
        llofstream os(file_name.c_str());

        os << std::setprecision(10);

        LLTrace::PeriodicRecording& scene_load_recording = mSceneLoadRecording.getResults();
        const auto frame_count = scene_load_recording.getNumRecordedPeriods();

        F64Seconds frame_time;

        os << "Stat";
        for (S32 frame = 1; frame <= frame_count; frame++)
        {
            frame_time += scene_load_recording.getPrevRecording(frame_count - frame).getDuration();
            os << ", " << frame_time.value();
        }
        os << '\n';

        os << "Sample period(s)";
        for (S32 frame = 1; frame <= frame_count; frame++)
        {
            frame_time = scene_load_recording.getPrevRecording(frame_count - frame).getDuration();
            os << ", " << frame_time.value();
        }
        os << '\n';


        typedef LLTrace::StatType<LLTrace::CountAccumulator> trace_count;
        for (auto& it : trace_count::instance_snapshot())
        {
            std::ostringstream row;
            row << std::setprecision(10);

            row << it.getName();

            const char* unit_label = it.getUnitLabel();
            if (unit_label[0])
            {
                row << "(" << unit_label << ")";
            }

            S32 samples = 0;

            for (S32 frame = 1; frame <= frame_count; frame++)
            {
                LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
                samples += recording.getSampleCount(it);
                row << ", " << recording.getSum(it);
            }

            row << '\n';

            if (samples > 0)
            {
                os << row.str();
            }
        }

        typedef LLTrace::StatType<LLTrace::EventAccumulator> trace_event;

        for (auto& it : trace_event::instance_snapshot())
        {
            std::ostringstream row;
            row << std::setprecision(10);
            row << it.getName();

            const char* unit_label = it.getUnitLabel();
            if (unit_label[0])
            {
                row << "(" << unit_label << ")";
            }

            S32 samples = 0;

            for (S32 frame = 1; frame <= frame_count; frame++)
            {
                LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
                samples += recording.getSampleCount(it);
                F64 mean = recording.getMean(it);
                if (llisnan(mean))
                {
                    row << ", n/a";
                }
                else
                {
                    row << ", " << mean;
                }
            }

            row << '\n';

            if (samples > 0)
            {
                os << row.str();
            }
        }

        typedef LLTrace::StatType<LLTrace::SampleAccumulator> trace_sample;

        for (auto& it : trace_sample::instance_snapshot())
        {
            std::ostringstream row;
            row << std::setprecision(10);
            row << it.getName();

            const char* unit_label = it.getUnitLabel();
            if (unit_label[0])
            {
                row << "(" << unit_label << ")";
            }

            S32 samples = 0;

            for (S32 frame = 1; frame <= frame_count; frame++)
            {
                LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
                samples += recording.getSampleCount(it);
                F64 mean = recording.getMean(it);
                if (llisnan(mean))
                {
                    row << ", n/a";
                }
                else
                {
                    row << ", " << mean;
                }
            }

            row << '\n';

            if (samples > 0)
            {
                os << row.str();
            }
        }

        os.flush();
        os.close();
    }
    catch (const std::ios_base::failure &e)
    {
        LL_WARNS() << "Unable to dump scene monitor results: " << e.what() << LL_ENDL;
    }
}

//-------------------------------------------------------------------------------------------------------------
//definition of class LLSceneMonitorView
//-------------------------------------------------------------------------------------------------------------
LLSceneMonitorView::LLSceneMonitorView(const LLRect& rect)
    :   LLFloater(LLSD())
{
    setRect(rect);
    setVisible(false);

    setCanMinimize(false);
    setCanClose(true);

    sTeleportFinishConnection = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLSceneMonitorView::onTeleportFinished, this));
}

LLSceneMonitorView::~LLSceneMonitorView()
{
    sTeleportFinishConnection.disconnect();
}

void LLSceneMonitorView::onClose(bool app_quitting)
{
    setVisible(false);
}

void LLSceneMonitorView::onClickCloseBtn(bool app_quitting)
{
    setVisible(false);
}

void LLSceneMonitorView::onTeleportFinished()
{
    if(isInVisibleChain())
    {
        LLSceneMonitor::getInstance()->reset();
    }
}

void LLSceneMonitorView::onVisibilityChange(bool visible)
{
    LLSceneMonitor::getInstance()->setDebugViewerVisible(visible);
}

void LLSceneMonitorView::draw()
{
    const LLRenderTarget* target = LLSceneMonitor::getInstance()->getDiffTarget();
    if(!target)
    {
        return;
    }

    F32 ratio = LLSceneMonitor::getInstance()->getDiffPixelRatio();
    S32 height = (S32)(target->getHeight() * ratio);
    S32 width = (S32)(target->getWidth() * ratio);

    LLRect new_rect;
    new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height);
    setRect(new_rect);

    //draw 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));

    LLSceneMonitor::getInstance()->calcDiffAggregate();

    //show some texts
    LLColor4 color = LLColor4::white;
    S32 line_height = LLFontGL::getFontMonospace()->getLineHeight();

    S32 lines = 0;
    std::string num_str = llformat("Frame difference: %.6f", LLSceneMonitor::getInstance()->getDiffResult());
    LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP);
    lines++;

    num_str = llformat("Pixel tolerance: (R+G+B) < %.4f", LLSceneMonitor::getInstance()->getDiffTolerance());
    LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP);
    lines++;

    num_str = llformat("Sampling time: %.3f seconds", gSavedSettings.getF32("SceneLoadingMonitorSampleTime"));
    LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP);
    lines++;

    num_str = llformat("Scene Loading time: %.3f seconds", (F32)LLSceneMonitor::getInstance()->getRecording()->getResults().getDuration().value());
    LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP);
    lines++;

    LLView::draw();
}