diff options
24 files changed, 11142 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cdac1177..2ac94bc9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,7 @@ set(INI_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/externals/inih")  include_directories(${INI_PREFIX})  add_subdirectory(${INI_PREFIX}) +include_directories(externals/microprofile)  include_directories(externals/nihstro/include)  if (MSVC) diff --git a/externals/microprofile/README.md b/externals/microprofile/README.md new file mode 100644 index 000000000..0a58d1c5a --- /dev/null +++ b/externals/microprofile/README.md @@ -0,0 +1,7 @@ +# microprofile + +MicroProfile is a embeddable profiler in a single file, written in C++ + +It can display profile information in the application, or by generating captures via a minimal built in webserver. + +For more information see the project webpage at https://bitbucket.org/jonasmeyer/microprofile diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h new file mode 100644 index 000000000..d1ae0c1c2 --- /dev/null +++ b/externals/microprofile/microprofile.h @@ -0,0 +1,3571 @@ +#pragma once +// This is free and unencumbered software released into the public domain. +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// For more information, please refer to <http://unlicense.org/> +// +// *********************************************************************** +// +// +// +// +// Howto: +// Call these functions from your code: +//  MicroProfileOnThreadCreate +//  MicroProfileMouseButton +//  MicroProfileMousePosition +//  MicroProfileModKey +//  MicroProfileFlip                <-- Call this once per frame +//  MicroProfileDraw                <-- Call this once per frame +//  MicroProfileToggleDisplayMode   <-- Bind to a key to toggle profiling +//  MicroProfileTogglePause         <-- Bind to a key to toggle pause +// +// Use these macros in your code in blocks you want to time: +// +//  MICROPROFILE_DECLARE +//  MICROPROFILE_DEFINE +//  MICROPROFILE_DECLARE_GPU +//  MICROPROFILE_DEFINE_GPU +//  MICROPROFILE_SCOPE +//  MICROPROFILE_SCOPEI +//  MICROPROFILE_SCOPEGPU +//  MICROPROFILE_SCOPEGPUI +//  MICROPROFILE_META +// +// +//  Usage: +// +//  { +//      MICROPROFILE_SCOPEI("GroupName", "TimerName", nColorRgb): +//      ..Code to be timed.. +//  } +// +//  MICROPROFILE_DECLARE / MICROPROFILE_DEFINE allows defining groups in a shared place, to ensure sorting of the timers +// +//  (in global scope) +//  MICROPROFILE_DEFINE(g_ProfileFisk, "Fisk", "Skalle", nSomeColorRgb); +// +//  (in some other file) +//  MICROPROFILE_DECLARE(g_ProfileFisk); +// +//  void foo(){ +//      MICROPROFILE_SCOPE(g_ProfileFisk); +//  } +// +//  Once code is instrumented the gui is activeted by calling MicroProfileToggleDisplayMode or by clicking in the upper left corner of +//  the screen +// +// The following functions must be implemented before the profiler is usable +//  debug render: +//      void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText, uint32_t nNumCharacters); +//      void MicroProfileDrawBox(int nX, int nY, int nX1, int nY1, uint32_t nColor, MicroProfileBoxType = MicroProfileBoxTypeFlat); +//      void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices, uint32_t nColor); +//  Gpu time stamps: (See below for d3d/opengl helper) +//      uint32_t MicroProfileGpuInsertTimeStamp(); +//      uint64_t MicroProfileGpuGetTimeStamp(uint32_t nKey); +//      uint64_t MicroProfileTicksPerSecondGpu(); +//  threading: +//      const char* MicroProfileGetThreadName(); Threadnames in detailed view +// +// Default implementations of Gpu timestamp functions: +//      Opengl: +//          in .c file where MICROPROFILE_IMPL is defined: +//          #define MICROPROFILE_GPU_TIMERS_GL +//          call MicroProfileGpuInitGL() on startup +//      D3D11: +//          in .c file where MICROPROFILE_IMPL is defined: +//          #define MICROPROFILE_GPU_TIMERS_D3D11 +//          call MICROPROFILE_GPU_TIMERS_D3D11(). Pass Device & ImmediateContext +// +// Limitations: +//  GPU timestamps can only be inserted from one thread. + + + +#ifndef MICROPROFILE_ENABLED +#define MICROPROFILE_ENABLED 1 +#endif + +#include <stdint.h> +typedef uint64_t MicroProfileToken; +typedef uint16_t MicroProfileGroupId; + +#if 0 == MICROPROFILE_ENABLED + +#define MICROPROFILE_DECLARE(var) +#define MICROPROFILE_DEFINE(var, group, name, color) +#define MICROPROFILE_REGISTER_GROUP(group, color, category) +#define MICROPROFILE_DECLARE_GPU(var) +#define MICROPROFILE_DEFINE_GPU(var, name, color) +#define MICROPROFILE_SCOPE(var) do{}while(0) +#define MICROPROFILE_SCOPEI(group, name, color) do{}while(0) +#define MICROPROFILE_SCOPEGPU(var) do{}while(0) +#define MICROPROFILE_SCOPEGPUI( name, color) do{}while(0) +#define MICROPROFILE_META_CPU(name, count) +#define MICROPROFILE_META_GPU(name, count) +#define MICROPROFILE_FORCEENABLECPUGROUP(s) do{} while(0) +#define MICROPROFILE_FORCEDISABLECPUGROUP(s) do{} while(0) +#define MICROPROFILE_FORCEENABLEGPUGROUP(s) do{} while(0) +#define MICROPROFILE_FORCEDISABLEGPUGROUP(s) do{} while(0) +#define MICROPROFILE_SCOPE_TOKEN(token) + +#define MicroProfileGetTime(group, name) 0.f +#define MicroProfileOnThreadCreate(foo) do{}while(0) +#define MicroProfileFlip() do{}while(0) +#define MicroProfileSetAggregateFrames(a) do{}while(0) +#define MicroProfileGetAggregateFrames() 0 +#define MicroProfileGetCurrentAggregateFrames() 0 +#define MicroProfileTogglePause() do{}while(0) +#define MicroProfileToggleAllGroups() do{} while(0) +#define MicroProfileDumpTimers() do{}while(0) +#define MicroProfileShutdown() do{}while(0) +#define MicroProfileSetForceEnable(a) do{} while(0) +#define MicroProfileGetForceEnable() false +#define MicroProfileSetEnableAllGroups(a) do{} while(0) +#define MicroProfileEnableCategory(a) do{} while(0) +#define MicroProfileDisableCategory(a) do{} while(0) +#define MicroProfileGetEnableAllGroups() false +#define MicroProfileSetForceMetaCounters(a) +#define MicroProfileGetForceMetaCounters() 0 +#define MicroProfileEnableMetaCounter(c) do{}while(0) +#define MicroProfileDisableMetaCounter(c) do{}while(0) +#define MicroProfileDumpFile(html,csv) do{} while(0) +#define MicroProfileWebServerPort() ((uint32_t)-1) + +#else + +#include <stdint.h> +#include <string.h> +#include <thread> +#include <mutex> +#include <atomic> + +#ifndef MICROPROFILE_API +#define MICROPROFILE_API +#endif + +MICROPROFILE_API int64_t MicroProfileTicksPerSecondCpu(); + + +#if defined(__APPLE__) +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <unistd.h> +#include <libkern/OSAtomic.h> +#include <TargetConditionals.h> +#if TARGET_OS_IPHONE +#define MICROPROFILE_IOS +#endif + +#define MP_TICK() mach_absolute_time() +inline int64_t MicroProfileTicksPerSecondCpu() +{ +    static int64_t nTicksPerSecond = 0; +    if(nTicksPerSecond == 0) +    { +        mach_timebase_info_data_t sTimebaseInfo; +        mach_timebase_info(&sTimebaseInfo); +        nTicksPerSecond = 1000000000ll * sTimebaseInfo.denom / sTimebaseInfo.numer; +    } +    return nTicksPerSecond; +} +inline uint64_t MicroProfileGetCurrentThreadId() +{ +    uint64_t tid; +    pthread_threadid_np(pthread_self(), &tid); +    return tid; +} + +#define MP_BREAK() __builtin_trap() +#define MP_THREAD_LOCAL __thread +#define MP_STRCASECMP strcasecmp +#define MP_GETCURRENTTHREADID() MicroProfileGetCurrentThreadId() +typedef uint64_t ThreadIdType; +#elif defined(_WIN32) +int64_t MicroProfileGetTick(); +#define MP_TICK() MicroProfileGetTick() +#define MP_BREAK() __debugbreak() +#define MP_THREAD_LOCAL __declspec(thread) +#define MP_STRCASECMP _stricmp +#define MP_GETCURRENTTHREADID() GetCurrentThreadId() +typedef uint32_t ThreadIdType; + +#elif defined(__linux__) +#include <unistd.h> +#include <time.h> +inline int64_t MicroProfileTicksPerSecondCpu() +{ +    return 1000000000ll; +} + +inline int64_t MicroProfileGetTick() +{ +    timespec ts; +    clock_gettime(CLOCK_REALTIME, &ts); +    return 1000000000ll * ts.tv_sec + ts.tv_nsec; +} +#define MP_TICK() MicroProfileGetTick() +#define MP_BREAK() __builtin_trap() +#define MP_THREAD_LOCAL __thread +#define MP_STRCASECMP strcasecmp +#define MP_GETCURRENTTHREADID() (uint64_t)pthread_self() +typedef uint64_t ThreadIdType; +#endif + + +#ifndef MP_GETCURRENTTHREADID +#define MP_GETCURRENTTHREADID() 0 +typedef uint32_t ThreadIdType; +#endif + + +#define MP_ASSERT(a) do{if(!(a)){MP_BREAK();} }while(0) +#define MICROPROFILE_DECLARE(var) extern MicroProfileToken g_mp_##var +#define MICROPROFILE_DEFINE(var, group, name, color) MicroProfileToken g_mp_##var = MicroProfileGetToken(group, name, color, MicroProfileTokenTypeCpu) +#define MICROPROFILE_REGISTER_GROUP(group, category, color) MicroProfileRegisterGroup(group, category, color) +#define MICROPROFILE_DECLARE_GPU(var) extern MicroProfileToken g_mp_##var +#define MICROPROFILE_DEFINE_GPU(var, name, color) MicroProfileToken g_mp_##var = MicroProfileGetToken("GPU", name, color, MicroProfileTokenTypeGpu) +#define MICROPROFILE_TOKEN_PASTE0(a, b) a ## b +#define MICROPROFILE_TOKEN_PASTE(a, b)  MICROPROFILE_TOKEN_PASTE0(a,b) +#define MICROPROFILE_SCOPE(var) MicroProfileScopeHandler MICROPROFILE_TOKEN_PASTE(foo, __LINE__)(g_mp_##var) +#define MICROPROFILE_SCOPE_TOKEN(token) MicroProfileScopeHandler MICROPROFILE_TOKEN_PASTE(foo, __LINE__)(token) +#define MICROPROFILE_SCOPEI(group, name, color) static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__) = MicroProfileGetToken(group, name, color, MicroProfileTokenTypeCpu); MicroProfileScopeHandler MICROPROFILE_TOKEN_PASTE(foo,__LINE__)( MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__)) +#define MICROPROFILE_SCOPEGPU(var) MicroProfileScopeGpuHandler MICROPROFILE_TOKEN_PASTE(foo, __LINE__)(g_mp_##var) +#define MICROPROFILE_SCOPEGPUI(name, color) static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__) = MicroProfileGetToken("GPU", name, color,  MicroProfileTokenTypeGpu); MicroProfileScopeGpuHandler MICROPROFILE_TOKEN_PASTE(foo,__LINE__)( MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__)) +#define MICROPROFILE_META_CPU(name, count) static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__) = MicroProfileGetMetaToken(name); MicroProfileMetaUpdate(MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__), count, MicroProfileTokenTypeCpu) +#define MICROPROFILE_META_GPU(name, count) static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__) = MicroProfileGetMetaToken(name); MicroProfileMetaUpdate(MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__), count, MicroProfileTokenTypeGpu) + + +#ifndef MICROPROFILE_USE_THREAD_NAME_CALLBACK +#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 0 +#endif + +#ifndef MICROPROFILE_PER_THREAD_BUFFER_SIZE +#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (2048<<10) +#endif + +#ifndef MICROPROFILE_MAX_FRAME_HISTORY +#define MICROPROFILE_MAX_FRAME_HISTORY 512 +#endif + +#ifndef MICROPROFILE_PRINTF +#define MICROPROFILE_PRINTF printf +#endif + +#ifndef MICROPROFILE_META_MAX +#define MICROPROFILE_META_MAX 8 +#endif + +#ifndef MICROPROFILE_WEBSERVER_PORT +#define MICROPROFILE_WEBSERVER_PORT 1338 +#endif + +#ifndef MICROPROFILE_WEBSERVER +#define MICROPROFILE_WEBSERVER 1 +#endif + +#ifndef MICROPROFILE_WEBSERVER_MAXFRAMES +#define MICROPROFILE_WEBSERVER_MAXFRAMES 30 +#endif + +#ifndef MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE +#define MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE (16<<10) +#endif + +#ifndef MICROPROFILE_GPU_TIMERS +#define MICROPROFILE_GPU_TIMERS 1 +#endif + +#ifndef MICROPROFILE_GPU_FRAME_DELAY +#define MICROPROFILE_GPU_FRAME_DELAY 3 //must be > 0 +#endif + + +#ifndef MICROPROFILE_NAME_MAX_LEN +#define MICROPROFILE_NAME_MAX_LEN 64 +#endif + +#define MICROPROFILE_FORCEENABLECPUGROUP(s) MicroProfileForceEnableGroup(s, MicroProfileTokenTypeCpu) +#define MICROPROFILE_FORCEDISABLECPUGROUP(s) MicroProfileForceDisableGroup(s, MicroProfileTokenTypeCpu) +#define MICROPROFILE_FORCEENABLEGPUGROUP(s) MicroProfileForceEnableGroup(s, MicroProfileTokenTypeGpu) +#define MICROPROFILE_FORCEDISABLEGPUGROUP(s) MicroProfileForceDisableGroup(s, MicroProfileTokenTypeGpu) + +#define MICROPROFILE_INVALID_TICK ((uint64_t)-1) +#define MICROPROFILE_GROUP_MASK_ALL 0xffffffffffff + + +#define MICROPROFILE_INVALID_TOKEN (uint64_t)-1 + +enum MicroProfileTokenType +{ +    MicroProfileTokenTypeCpu, +    MicroProfileTokenTypeGpu, +}; + +enum MicroProfileBoxType +{ +    MicroProfileBoxTypeBar, +    MicroProfileBoxTypeFlat, +}; + + + +struct MicroProfile; + +MICROPROFILE_API void MicroProfileInit(); +MICROPROFILE_API void MicroProfileShutdown(); +MICROPROFILE_API MicroProfileToken MicroProfileFindToken(const char* sGroup, const char* sName); +MICROPROFILE_API MicroProfileToken MicroProfileGetToken(const char* sGroup, const char* sName, uint32_t nColor, MicroProfileTokenType Token = MicroProfileTokenTypeCpu); +MICROPROFILE_API MicroProfileToken MicroProfileGetMetaToken(const char* pName); +MICROPROFILE_API void MicroProfileMetaUpdate(MicroProfileToken, int nCount, MicroProfileTokenType eTokenType); +MICROPROFILE_API uint64_t MicroProfileEnter(MicroProfileToken nToken); +MICROPROFILE_API void MicroProfileLeave(MicroProfileToken nToken, uint64_t nTick); +MICROPROFILE_API uint64_t MicroProfileGpuEnter(MicroProfileToken nToken); +MICROPROFILE_API void MicroProfileGpuLeave(MicroProfileToken nToken, uint64_t nTick); +inline uint16_t MicroProfileGetTimerIndex(MicroProfileToken t){ return (t&0xffff); } +inline uint64_t MicroProfileGetGroupMask(MicroProfileToken t){ return ((t>>16)&MICROPROFILE_GROUP_MASK_ALL);} +inline MicroProfileToken MicroProfileMakeToken(uint64_t nGroupMask, uint16_t nTimer){ return (nGroupMask<<16) | nTimer;} + +MICROPROFILE_API void MicroProfileFlip(); //! call once per frame. +MICROPROFILE_API void MicroProfileTogglePause(); +MICROPROFILE_API void MicroProfileForceEnableGroup(const char* pGroup, MicroProfileTokenType Type); +MICROPROFILE_API void MicroProfileForceDisableGroup(const char* pGroup, MicroProfileTokenType Type); +MICROPROFILE_API float MicroProfileGetTime(const char* pGroup, const char* pName); +MICROPROFILE_API void MicroProfileContextSwitchSearch(uint32_t* pContextSwitchStart, uint32_t* pContextSwitchEnd, uint64_t nBaseTicksCpu, uint64_t nBaseTicksEndCpu); +MICROPROFILE_API void MicroProfileOnThreadCreate(const char* pThreadName); //should be called from newly created threads +MICROPROFILE_API void MicroProfileOnThreadExit(); //call on exit to reuse log +MICROPROFILE_API void MicroProfileInitThreadLog(); +MICROPROFILE_API void MicroProfileSetForceEnable(bool bForceEnable); +MICROPROFILE_API bool MicroProfileGetForceEnable(); +MICROPROFILE_API void MicroProfileSetEnableAllGroups(bool bEnable); +MICROPROFILE_API void MicroProfileEnableCategory(const char* pCategory); +MICROPROFILE_API void MicroProfileDisableCategory(const char* pCategory); +MICROPROFILE_API bool MicroProfileGetEnableAllGroups(); +MICROPROFILE_API void MicroProfileSetForceMetaCounters(bool bEnable); +MICROPROFILE_API bool MicroProfileGetForceMetaCounters(); +MICROPROFILE_API void MicroProfileEnableMetaCounter(const char* pMet); +MICROPROFILE_API void MicroProfileDisableMetaCounter(const char* pMet); +MICROPROFILE_API void MicroProfileSetAggregateFrames(int frames); +MICROPROFILE_API int MicroProfileGetAggregateFrames(); +MICROPROFILE_API int MicroProfileGetCurrentAggregateFrames(); +MICROPROFILE_API MicroProfile* MicroProfileGet(); +MICROPROFILE_API void MicroProfileGetRange(uint32_t nPut, uint32_t nGet, uint32_t nRange[2][2]); +MICROPROFILE_API std::recursive_mutex& MicroProfileGetMutex(); +MICROPROFILE_API void MicroProfileStartContextSwitchTrace(); +MICROPROFILE_API void MicroProfileStopContextSwitchTrace(); +MICROPROFILE_API bool MicroProfileIsLocalThread(uint32_t nThreadId); + + +#if MICROPROFILE_WEBSERVER +MICROPROFILE_API void MicroProfileDumpFile(const char* pHtml, const char* pCsv); +MICROPROFILE_API uint32_t MicroProfileWebServerPort(); +#else +#define MicroProfileDumpFile(c) do{} while(0) +#define MicroProfileWebServerPort() ((uint32_t)-1) +#endif + + + + +#if MICROPROFILE_GPU_TIMERS +MICROPROFILE_API uint32_t MicroProfileGpuInsertTimeStamp(); +MICROPROFILE_API uint64_t MicroProfileGpuGetTimeStamp(uint32_t nKey); +MICROPROFILE_API uint64_t MicroProfileTicksPerSecondGpu(); +MICROPROFILE_API int MicroProfileGetGpuTickReference(int64_t* pOutCPU, int64_t* pOutGpu); +#else +#define MicroProfileGpuInsertTimeStamp() 1 +#define MicroProfileGpuGetTimeStamp(a) 0 +#define MicroProfileTicksPerSecondGpu() 1 +#define MicroProfileGetGpuTickReference(a,b) 0 +#endif + +#if MICROPROFILE_GPU_TIMERS_D3D11 +#define MICROPROFILE_D3D_MAX_QUERIES (8<<10) +MICROPROFILE_API void MicroProfileGpuInitD3D11(void* pDevice, void* pDeviceContext); +#endif + +#if MICROPROFILE_GPU_TIMERS_GL +#define MICROPROFILE_GL_MAX_QUERIES (8<<10) +MICROPROFILE_API void MicroProfileGpuInitGL(); +#endif + + + +#if MICROPROFILE_USE_THREAD_NAME_CALLBACK +MICROPROFILE_API const char* MicroProfileGetThreadName(); +#else +#define MicroProfileGetThreadName() "<implement MicroProfileGetThreadName to get threadnames>" +#endif + +#if !defined(MICROPROFILE_THREAD_NAME_FROM_ID) +#define MICROPROFILE_THREAD_NAME_FROM_ID(a) "" +#endif + + +struct MicroProfileScopeHandler +{ +    MicroProfileToken nToken; +    uint64_t nTick; +    MicroProfileScopeHandler(MicroProfileToken Token):nToken(Token) +    { +        nTick = MicroProfileEnter(nToken); +    } +    ~MicroProfileScopeHandler() +    { +        MicroProfileLeave(nToken, nTick); +    } +}; + +struct MicroProfileScopeGpuHandler +{ +    MicroProfileToken nToken; +    uint64_t nTick; +    MicroProfileScopeGpuHandler(MicroProfileToken Token):nToken(Token) +    { +        nTick = MicroProfileGpuEnter(nToken); +    } +    ~MicroProfileScopeGpuHandler() +    { +        MicroProfileGpuLeave(nToken, nTick); +    } +}; + + + +#define MICROPROFILE_MAX_TIMERS 1024 +#define MICROPROFILE_MAX_GROUPS 48 //dont bump! no. of bits used it bitmask +#define MICROPROFILE_MAX_CATEGORIES 16 +#define MICROPROFILE_MAX_GRAPHS 5 +#define MICROPROFILE_GRAPH_HISTORY 128 +#define MICROPROFILE_BUFFER_SIZE ((MICROPROFILE_PER_THREAD_BUFFER_SIZE)/sizeof(MicroProfileLogEntry)) +#define MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS 256 +#define MICROPROFILE_STACK_MAX 32 +//#define MICROPROFILE_MAX_PRESETS 5 +#define MICROPROFILE_ANIM_DELAY_PRC 0.5f +#define MICROPROFILE_GAP_TIME 50 //extra ms to fetch to close timers from earlier frames + + +#ifndef MICROPROFILE_MAX_THREADS +#define MICROPROFILE_MAX_THREADS 32 +#endif + +#ifndef MICROPROFILE_UNPACK_RED +#define MICROPROFILE_UNPACK_RED(c) ((c)>>16) +#endif + +#ifndef MICROPROFILE_UNPACK_GREEN +#define MICROPROFILE_UNPACK_GREEN(c) ((c)>>8) +#endif + +#ifndef MICROPROFILE_UNPACK_BLUE +#define MICROPROFILE_UNPACK_BLUE(c) ((c)) +#endif + +#ifndef MICROPROFILE_DEFAULT_PRESET +#define MICROPROFILE_DEFAULT_PRESET "Default" +#endif + + +#ifndef MICROPROFILE_CONTEXT_SWITCH_TRACE +#if defined(_WIN32) +#define MICROPROFILE_CONTEXT_SWITCH_TRACE 1 +#elif defined(__APPLE__) +#define MICROPROFILE_CONTEXT_SWITCH_TRACE 0 //disabled until dtrace script is working. +#else +#define MICROPROFILE_CONTEXT_SWITCH_TRACE 0 +#endif +#endif + +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +#define MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE (128*1024) //2mb with 16 byte entry size +#else +#define MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE (1) +#endif + +#ifndef MICROPROFILE_MINIZ +#define MICROPROFILE_MINIZ 0 +#endif + +#ifdef _WIN32 +#include <basetsd.h> +typedef UINT_PTR MpSocket; +#else +typedef int MpSocket; +#endif + + +#if defined(__APPLE__) || defined(__linux__) +typedef pthread_t MicroProfileThread; +#elif defined(_WIN32) +typedef HANDLE MicroProfileThread; +#else +typedef std::thread* MicroProfileThread; +#endif + + + +enum MicroProfileDrawMask +{ +    MP_DRAW_OFF         = 0x0, +    MP_DRAW_BARS        = 0x1, +    MP_DRAW_DETAILED    = 0x2, +    MP_DRAW_HIDDEN      = 0x3, +}; + +enum MicroProfileDrawBarsMask +{ +    MP_DRAW_TIMERS              = 0x1, +    MP_DRAW_AVERAGE             = 0x2, +    MP_DRAW_MAX                 = 0x4, +    MP_DRAW_CALL_COUNT          = 0x8, +    MP_DRAW_TIMERS_EXCLUSIVE    = 0x10, +    MP_DRAW_AVERAGE_EXCLUSIVE   = 0x20, +    MP_DRAW_MAX_EXCLUSIVE       = 0x40, +    MP_DRAW_META_FIRST          = 0x80, +    MP_DRAW_ALL                 = 0xffffffff, + +}; + +typedef uint64_t MicroProfileLogEntry; + +struct MicroProfileTimer +{ +    uint64_t nTicks; +    uint32_t nCount; +}; + +struct MicroProfileCategory +{ +    char pName[MICROPROFILE_NAME_MAX_LEN]; +    uint64_t nGroupMask; +}; + +struct MicroProfileGroupInfo +{ +    char pName[MICROPROFILE_NAME_MAX_LEN]; +    uint32_t nNameLen; +    uint32_t nGroupIndex; +    uint32_t nNumTimers; +    uint32_t nMaxTimerNameLen; +    uint32_t nColor; +    uint32_t nCategory; +    MicroProfileTokenType Type; +}; + +struct MicroProfileTimerInfo +{ +    MicroProfileToken nToken; +    uint32_t nTimerIndex; +    uint32_t nGroupIndex; +    char pName[MICROPROFILE_NAME_MAX_LEN]; +    uint32_t nNameLen; +    uint32_t nColor; +    bool bGraph; +}; + +struct MicroProfileGraphState +{ +    int64_t nHistory[MICROPROFILE_GRAPH_HISTORY]; +    MicroProfileToken nToken; +    int32_t nKey; +}; + +struct MicroProfileContextSwitch +{ +    ThreadIdType nThreadOut; +    ThreadIdType nThreadIn; +    int64_t nCpu : 8; +    int64_t nTicks : 56; +}; + + +struct MicroProfileFrameState +{ +    int64_t nFrameStartCpu; +    int64_t nFrameStartGpu; +    uint32_t nLogStart[MICROPROFILE_MAX_THREADS]; +}; + +struct MicroProfileThreadLog +{ +    MicroProfileLogEntry    Log[MICROPROFILE_BUFFER_SIZE]; + +    std::atomic<uint32_t>   nPut; +    std::atomic<uint32_t>   nGet; +    uint32_t                nActive; +    uint32_t                nGpu; +    ThreadIdType            nThreadId; + +    uint32_t                nStack[MICROPROFILE_STACK_MAX]; +    int64_t                 nChildTickStack[MICROPROFILE_STACK_MAX]; +    uint32_t                nStackPos; + + +    uint8_t                 nGroupStackPos[MICROPROFILE_MAX_GROUPS]; +    int64_t                 nGroupTicks[MICROPROFILE_MAX_GROUPS]; +    int64_t                 nAggregateGroupTicks[MICROPROFILE_MAX_GROUPS]; +    enum +    { +        THREAD_MAX_LEN = 64, +    }; +    char                    ThreadName[64]; +    int                     nFreeListNext; +}; + +#if MICROPROFILE_GPU_TIMERS_D3D11 +struct MicroProfileD3D11Frame +{ +    uint32_t m_nQueryStart; +    uint32_t m_nQueryCount; +    uint32_t m_nRateQueryStarted; +    void* m_pRateQuery; +}; + +struct MicroProfileGpuTimerState +{ +    uint32_t bInitialized; +    void* m_pDevice; +    void* m_pDeviceContext; +    void* m_pQueries[MICROPROFILE_D3D_MAX_QUERIES]; +    int64_t m_nQueryResults[MICROPROFILE_D3D_MAX_QUERIES]; +    uint32_t m_nQueryPut; +    uint32_t m_nQueryGet; +    uint32_t m_nQueryFrame; +    int64_t m_nQueryFrequency; +    MicroProfileD3D11Frame m_QueryFrames[MICROPROFILE_GPU_FRAME_DELAY]; +}; +#elif MICROPROFILE_GPU_TIMERS_GL +struct MicroProfileGpuTimerState +{ +    uint32_t GLTimers[MICROPROFILE_GL_MAX_QUERIES]; +    uint32_t GLTimerPos; +}; +#else +struct MicroProfileGpuTimerState{}; +#endif + +struct MicroProfile +{ +    uint32_t nTotalTimers; +    uint32_t nGroupCount; +    uint32_t nCategoryCount; +    uint32_t nAggregateClear; +    uint32_t nAggregateFlip; +    uint32_t nAggregateFlipCount; +    uint32_t nAggregateFrames; + +    uint64_t nAggregateFlipTick; + +    uint32_t nDisplay; +    uint32_t nBars; +    uint64_t nActiveGroup; +    uint32_t nActiveBars; + +    uint64_t nForceGroup; +    uint32_t nForceEnable; +    uint32_t nForceMetaCounters; + +    uint64_t nForceGroupUI; +    uint64_t nActiveGroupWanted; +    uint32_t nAllGroupsWanted; +    uint32_t nAllThreadsWanted; + +    uint32_t nOverflow; + +    uint64_t nGroupMask; +    uint32_t nRunning; +    uint32_t nToggleRunning; +    uint32_t nMaxGroupSize; +    uint32_t nDumpFileNextFrame; +    uint32_t nAutoClearFrames; +    char HtmlDumpPath[512]; +    char CsvDumpPath[512]; + +    int64_t nPauseTicks; + +    float fReferenceTime; +    float fRcpReferenceTime; + +    MicroProfileCategory    CategoryInfo[MICROPROFILE_MAX_CATEGORIES]; +    MicroProfileGroupInfo   GroupInfo[MICROPROFILE_MAX_GROUPS]; +    MicroProfileTimerInfo   TimerInfo[MICROPROFILE_MAX_TIMERS]; +    uint8_t                 TimerToGroup[MICROPROFILE_MAX_TIMERS]; + +    MicroProfileTimer       AccumTimers[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AccumMaxTimers[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AccumTimersExclusive[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AccumMaxTimersExclusive[MICROPROFILE_MAX_TIMERS]; + +    MicroProfileTimer       Frame[MICROPROFILE_MAX_TIMERS]; +    uint64_t                FrameExclusive[MICROPROFILE_MAX_TIMERS]; + +    MicroProfileTimer       Aggregate[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AggregateMax[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AggregateExclusive[MICROPROFILE_MAX_TIMERS]; +    uint64_t                AggregateMaxExclusive[MICROPROFILE_MAX_TIMERS]; + + +    uint64_t                FrameGroup[MICROPROFILE_MAX_GROUPS]; +    uint64_t                AccumGroup[MICROPROFILE_MAX_GROUPS]; +    uint64_t                AccumGroupMax[MICROPROFILE_MAX_GROUPS]; + +    uint64_t                AggregateGroup[MICROPROFILE_MAX_GROUPS]; +    uint64_t                AggregateGroupMax[MICROPROFILE_MAX_GROUPS]; + + +    struct +    { +        uint64_t nCounters[MICROPROFILE_MAX_TIMERS]; + +        uint64_t nAccum[MICROPROFILE_MAX_TIMERS]; +        uint64_t nAccumMax[MICROPROFILE_MAX_TIMERS]; + +        uint64_t nAggregate[MICROPROFILE_MAX_TIMERS]; +        uint64_t nAggregateMax[MICROPROFILE_MAX_TIMERS]; + +        uint64_t nSum; +        uint64_t nSumAccum; +        uint64_t nSumAccumMax; +        uint64_t nSumAggregate; +        uint64_t nSumAggregateMax; + +        const char* pName; +    } MetaCounters[MICROPROFILE_META_MAX]; + +    MicroProfileGraphState  Graph[MICROPROFILE_MAX_GRAPHS]; +    uint32_t                nGraphPut; + +    uint32_t                nThreadActive[MICROPROFILE_MAX_THREADS]; +    MicroProfileThreadLog*  Pool[MICROPROFILE_MAX_THREADS]; +    uint32_t                nNumLogs; +    uint32_t                nMemUsage; +    int                     nFreeListHead; + +    uint32_t                nFrameCurrent; +    uint32_t                nFrameCurrentIndex; +    uint32_t                nFramePut; +    uint64_t                nFramePutIndex; + +    MicroProfileFrameState Frames[MICROPROFILE_MAX_FRAME_HISTORY]; + +    uint64_t                nFlipTicks; +    uint64_t                nFlipAggregate; +    uint64_t                nFlipMax; +    uint64_t                nFlipAggregateDisplay; +    uint64_t                nFlipMaxDisplay; + +    MicroProfileThread          ContextSwitchThread; +    bool                        bContextSwitchRunning; +    bool                        bContextSwitchStop; +    bool                        bContextSwitchAllThreads; +    bool                        bContextSwitchNoBars; +    uint32_t                    nContextSwitchUsage; +    uint32_t                    nContextSwitchLastPut; + +    int64_t                     nContextSwitchHoverTickIn; +    int64_t                     nContextSwitchHoverTickOut; +    uint32_t                    nContextSwitchHoverThread; +    uint32_t                    nContextSwitchHoverThreadBefore; +    uint32_t                    nContextSwitchHoverThreadAfter; +    uint8_t                     nContextSwitchHoverCpu; +    uint8_t                     nContextSwitchHoverCpuNext; + +    uint32_t                    nContextSwitchPut; +    MicroProfileContextSwitch   ContextSwitch[MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE]; + + +    MpSocket                    ListenerSocket; +    uint32_t                    nWebServerPort; + +    char                        WebServerBuffer[MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE]; +    uint32_t                    WebServerPut; + +    uint64_t                    nWebServerDataSent; + +    MicroProfileGpuTimerState   GPU; + + +}; + +#define MP_LOG_TICK_MASK  0x0000ffffffffffff +#define MP_LOG_INDEX_MASK 0x3fff000000000000 +#define MP_LOG_BEGIN_MASK 0xc000000000000000 +#define MP_LOG_GPU_EXTRA 0x3 +#define MP_LOG_META 0x2 +#define MP_LOG_ENTER 0x1 +#define MP_LOG_LEAVE 0x0 + + +inline int MicroProfileLogType(MicroProfileLogEntry Index) +{ +    return ((MP_LOG_BEGIN_MASK & Index)>>62) & 0x3; +} + +inline uint64_t MicroProfileLogTimerIndex(MicroProfileLogEntry Index) +{ +    return (0x3fff&(Index>>48)); +} + +inline MicroProfileLogEntry MicroProfileMakeLogIndex(uint64_t nBegin, MicroProfileToken nToken, int64_t nTick) +{ +    MicroProfileLogEntry Entry =  (nBegin<<62) | ((0x3fff&nToken)<<48) | (MP_LOG_TICK_MASK&nTick); +    int t = MicroProfileLogType(Entry); +    uint64_t nTimerIndex = MicroProfileLogTimerIndex(Entry); +    MP_ASSERT(t == nBegin); +    MP_ASSERT(nTimerIndex == (nToken&0x3fff)); +    return Entry; + +} + +inline int64_t MicroProfileLogTickDifference(MicroProfileLogEntry Start, MicroProfileLogEntry End) +{ +    uint64_t nStart = Start; +    uint64_t nEnd = End; +    int64_t nDifference = ((nEnd<<16) - (nStart<<16)); +    return nDifference >> 16; +} + +inline int64_t MicroProfileLogGetTick(MicroProfileLogEntry e) +{ +    return MP_LOG_TICK_MASK & e; +} + +inline int64_t MicroProfileLogSetTick(MicroProfileLogEntry e, int64_t nTick) +{ +    return (MP_LOG_TICK_MASK & nTick) | (e & ~MP_LOG_TICK_MASK); +} + +template<typename T> +T MicroProfileMin(T a, T b) +{ return a < b ? a : b; } + +template<typename T> +T MicroProfileMax(T a, T b) +{ return a > b ? a : b; } + +inline int64_t MicroProfileMsToTick(float fMs, int64_t nTicksPerSecond) +{ +    return (int64_t)(fMs*0.001f*nTicksPerSecond); +} + +inline float MicroProfileTickToMsMultiplier(int64_t nTicksPerSecond) +{ +    return 1000.f / nTicksPerSecond; +} + +inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t) +{ +    return (uint16_t)MicroProfileGet()->TimerToGroup[MicroProfileGetTimerIndex(t)]; +} + + + +#ifdef MICROPROFILE_IMPL + +#ifdef _WIN32 +#include <windows.h> +#define snprintf _snprintf + +#pragma warning(push) +#pragma warning(disable: 4244) +int64_t MicroProfileTicksPerSecondCpu() +{ +    static int64_t nTicksPerSecond = 0; +    if(nTicksPerSecond == 0) +    { +        QueryPerformanceFrequency((LARGE_INTEGER*)&nTicksPerSecond); +    } +    return nTicksPerSecond; +} +int64_t MicroProfileGetTick() +{ +    int64_t ticks; +    QueryPerformanceCounter((LARGE_INTEGER*)&ticks); +    return ticks; +} + +#endif + +#if defined(MICROPROFILE_WEBSERVER) || defined(MICROPROFILE_CONTEXT_SWITCH_TRACE) + + +typedef void* (*MicroProfileThreadFunc)(void*); + +#if defined(__APPLE__) || defined(__linux__) +typedef pthread_t MicroProfileThread; +void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func) +{ +    pthread_attr_t Attr; +    int r  = pthread_attr_init(&Attr); +    MP_ASSERT(r == 0); +    pthread_create(pThread, &Attr, Func, 0); +} +void MicroProfileThreadJoin(MicroProfileThread* pThread) +{ +    int r = pthread_join(*pThread, 0); +    MP_ASSERT(r == 0); +} +#elif defined(_WIN32) +typedef HANDLE MicroProfileThread; +DWORD _stdcall ThreadTrampoline(void* pFunc) +{ +    MicroProfileThreadFunc F = (MicroProfileThreadFunc)pFunc; +    return (uint32_t)F(0); +} + +void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func) +{ +    *pThread = CreateThread(0, 0, ThreadTrampoline, Func, 0, 0); +} +void MicroProfileThreadJoin(MicroProfileThread* pThread) +{ +    WaitForSingleObject(*pThread, INFINITE); +    CloseHandle(*pThread); +} +#else +#include <thread> +typedef std::thread* MicroProfileThread; +inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func) +{ +    *pThread = new std::thread(Func, nullptr); +} +inline void MicroProfileThreadJoin(MicroProfileThread* pThread) +{ +    (*pThread)->join(); +    delete *pThread; +} +#endif +#endif + +#if MICROPROFILE_WEBSERVER + +#ifdef _WIN32 +#define MP_INVALID_SOCKET(f) (f == INVALID_SOCKET) +#endif + +#if defined(__APPLE__) +#include <sys/socket.h> +#include <netinet/in.h> +#include <fcntl.h> +#define MP_INVALID_SOCKET(f) (f < 0) +#endif + + +void MicroProfileWebServerStart(); +void MicroProfileWebServerStop(); +bool MicroProfileWebServerUpdate(); +void MicroProfileDumpToFile(); + +#else + +#define MicroProfileWebServerStart() do{}while(0) +#define MicroProfileWebServerStop() do{}while(0) +#define MicroProfileWebServerUpdate() false +#define MicroProfileDumpToFile() do{} while(0) +#endif + + +#if MICROPROFILE_GPU_TIMERS_D3D11 +void MicroProfileGpuFlip(); +void MicroProfileGpuShutdown(); +#else +#define MicroProfileGpuFlip() do{}while(0) +#define MicroProfileGpuShutdown() do{}while(0) +#endif + + + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <algorithm> + + +#ifndef MICROPROFILE_DEBUG +#define MICROPROFILE_DEBUG 0 +#endif + + +#define S g_MicroProfile + +MicroProfile g_MicroProfile; +MicroProfileThreadLog*          g_MicroProfileGpuLog = 0; +#ifdef MICROPROFILE_IOS +// iOS doesn't support __thread +static pthread_key_t g_MicroProfileThreadLogKey; +static pthread_once_t g_MicroProfileThreadLogKeyOnce = PTHREAD_ONCE_INIT; +static void MicroProfileCreateThreadLogKey() +{ +    pthread_key_create(&g_MicroProfileThreadLogKey, NULL); +} +#else +MP_THREAD_LOCAL MicroProfileThreadLog* g_MicroProfileThreadLog = 0; +#endif +static bool g_bUseLock = false; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled) + + +MICROPROFILE_DEFINE(g_MicroProfileFlip, "MicroProfile", "MicroProfileFlip", 0x3355ee); +MICROPROFILE_DEFINE(g_MicroProfileThreadLoop, "MicroProfile", "ThreadLoop", 0x3355ee); +MICROPROFILE_DEFINE(g_MicroProfileClear, "MicroProfile", "Clear", 0x3355ee); +MICROPROFILE_DEFINE(g_MicroProfileAccumulate, "MicroProfile", "Accumulate", 0x3355ee); +MICROPROFILE_DEFINE(g_MicroProfileContextSwitchSearch,"MicroProfile", "ContextSwitchSearch", 0xDD7300); + +inline std::recursive_mutex& MicroProfileMutex() +{ +    static std::recursive_mutex Mutex; +    return Mutex; +} +std::recursive_mutex& MicroProfileGetMutex() +{ +    return MicroProfileMutex(); +} + +MICROPROFILE_API MicroProfile* MicroProfileGet() +{ +    return &g_MicroProfile; +} + + +MicroProfileThreadLog* MicroProfileCreateThreadLog(const char* pName); + + +void MicroProfileInit() +{ +    std::recursive_mutex& mutex = MicroProfileMutex(); +    bool bUseLock = g_bUseLock; +    if(bUseLock) +        mutex.lock(); +    static bool bOnce = true; +    if(bOnce) +    { +        S.nMemUsage += sizeof(S); +        bOnce = false; +        memset(&S, 0, sizeof(S)); +        for(int i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +        { +            S.GroupInfo[i].pName[0] = '\0'; +        } +        for(int i = 0; i < MICROPROFILE_MAX_CATEGORIES; ++i) +        { +            S.CategoryInfo[i].pName[0] = '\0'; +            S.CategoryInfo[i].nGroupMask = 0; +        } +        strcpy(&S.CategoryInfo[0].pName[0], "default"); +        S.nCategoryCount = 1; +        for(int i = 0; i < MICROPROFILE_MAX_TIMERS; ++i) +        { +            S.TimerInfo[i].pName[0] = '\0'; +        } +        S.nGroupCount = 0; +        S.nAggregateFlipTick = MP_TICK(); +        S.nActiveGroup = 0; +        S.nActiveBars = 0; +        S.nForceGroup = 0; +        S.nAllGroupsWanted = 0; +        S.nActiveGroupWanted = 0; +        S.nAllThreadsWanted = 1; +        S.nAggregateFlip = 0; +        S.nTotalTimers = 0; +        for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +        { +            S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +        } +        S.nRunning = 1; +        S.fReferenceTime = 33.33f; +        S.fRcpReferenceTime = 1.f / S.fReferenceTime; +        S.nFreeListHead = -1; +        int64_t nTick = MP_TICK(); +        for(int i = 0; i < MICROPROFILE_MAX_FRAME_HISTORY; ++i) +        { +            S.Frames[i].nFrameStartCpu = nTick; +            S.Frames[i].nFrameStartGpu = -1; +        } + +        MicroProfileThreadLog* pGpu = MicroProfileCreateThreadLog("GPU"); +        g_MicroProfileGpuLog = pGpu; +        MP_ASSERT(S.Pool[0] == pGpu); +        pGpu->nGpu = 1; +        pGpu->nThreadId = 0; + +        S.nWebServerDataSent = (uint64_t)-1; +    } +    if(bUseLock) +        mutex.unlock(); +} + +void MicroProfileShutdown() +{ +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    MicroProfileWebServerStop(); +    MicroProfileStopContextSwitchTrace(); +    MicroProfileGpuShutdown(); +} + +#ifdef MICROPROFILE_IOS +inline MicroProfileThreadLog* MicroProfileGetThreadLog() +{ +    pthread_once(&g_MicroProfileThreadLogKeyOnce, MicroProfileCreateThreadLogKey); +    return (MicroProfileThreadLog*)pthread_getspecific(g_MicroProfileThreadLogKey); +} + +inline void MicroProfileSetThreadLog(MicroProfileThreadLog* pLog) +{ +    pthread_once(&g_MicroProfileThreadLogKeyOnce, MicroProfileCreateThreadLogKey); +    pthread_setspecific(g_MicroProfileThreadLogKey, pLog); +} +#else +MicroProfileThreadLog* MicroProfileGetThreadLog() +{ +    return g_MicroProfileThreadLog; +} +inline void MicroProfileSetThreadLog(MicroProfileThreadLog* pLog) +{ +    g_MicroProfileThreadLog = pLog; +} +#endif + + +MicroProfileThreadLog* MicroProfileCreateThreadLog(const char* pName) +{ +    MicroProfileThreadLog* pLog = 0; +    if(S.nFreeListHead != -1) +    { +        pLog = S.Pool[S.nFreeListHead]; +        MP_ASSERT(pLog->nPut.load() == 0); +        MP_ASSERT(pLog->nGet.load() == 0); +        S.nFreeListHead = S.Pool[S.nFreeListHead]->nFreeListNext; +    } +    else +    { +        pLog = new MicroProfileThreadLog; +        S.nMemUsage += sizeof(MicroProfileThreadLog); +        S.Pool[S.nNumLogs++] = pLog; +    } +    memset(pLog, 0, sizeof(*pLog)); +    int len = (int)strlen(pName); +    int maxlen = sizeof(pLog->ThreadName)-1; +    len = len < maxlen ? len : maxlen; +    memcpy(&pLog->ThreadName[0], pName, len); +    pLog->ThreadName[len] = '\0'; +    pLog->nThreadId = MP_GETCURRENTTHREADID(); +    pLog->nFreeListNext = -1; +    pLog->nActive = 1; +    return pLog; +} + +void MicroProfileOnThreadCreate(const char* pThreadName) +{ +    g_bUseLock = true; +    MicroProfileInit(); +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    MP_ASSERT(MicroProfileGetThreadLog() == 0); +    MicroProfileThreadLog* pLog = MicroProfileCreateThreadLog(pThreadName ? pThreadName : MicroProfileGetThreadName()); +    MP_ASSERT(pLog); +    MicroProfileSetThreadLog(pLog); +} + +void MicroProfileOnThreadExit() +{ +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    MicroProfileThreadLog* pLog = MicroProfileGetThreadLog(); +    if(pLog) +    { +        int32_t nLogIndex = -1; +        for(int i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +        { +            if(pLog == S.Pool[i]) +            { +                nLogIndex = i; +                break; +            } +        } +        MP_ASSERT(nLogIndex < MICROPROFILE_MAX_THREADS && nLogIndex > 0); +        pLog->nFreeListNext = S.nFreeListHead; +        pLog->nActive = 0; +        pLog->nPut.store(0); +        pLog->nGet.store(0); +        S.nFreeListHead = nLogIndex; +        for(int i = 0; i < MICROPROFILE_MAX_FRAME_HISTORY; ++i) +        { +            S.Frames[i].nLogStart[nLogIndex] = 0; +        } +        memset(pLog->nGroupStackPos, 0, sizeof(pLog->nGroupStackPos)); +        memset(pLog->nGroupTicks, 0, sizeof(pLog->nGroupTicks)); +    } +} + +void MicroProfileInitThreadLog() +{ +    MicroProfileOnThreadCreate(nullptr); +} + + +struct MicroProfileScopeLock +{ +    bool bUseLock; +    std::recursive_mutex& m; +    MicroProfileScopeLock(std::recursive_mutex& m) : bUseLock(g_bUseLock), m(m) +    { +        if(bUseLock) +            m.lock(); +    } +    ~MicroProfileScopeLock() +    { +        if(bUseLock) +            m.unlock(); +    } +}; + +MicroProfileToken MicroProfileFindToken(const char* pGroup, const char* pName) +{ +    MicroProfileInit(); +    MicroProfileScopeLock L(MicroProfileMutex()); +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        if(!MP_STRCASECMP(pName, S.TimerInfo[i].pName) && !MP_STRCASECMP(pGroup, S.GroupInfo[S.TimerToGroup[i]].pName)) +        { +            return S.TimerInfo[i].nToken; +        } +    } +    return MICROPROFILE_INVALID_TOKEN; +} + +uint16_t MicroProfileGetGroup(const char* pGroup, MicroProfileTokenType Type) +{ +    for(uint32_t i = 0; i < S.nGroupCount; ++i) +    { +        if(!MP_STRCASECMP(pGroup, S.GroupInfo[i].pName)) +        { +            return i; +        } +    } +    uint16_t nGroupIndex = 0xffff; +    uint32_t nLen = (uint32_t)strlen(pGroup); +    if(nLen > MICROPROFILE_NAME_MAX_LEN-1) +        nLen = MICROPROFILE_NAME_MAX_LEN-1; +    memcpy(&S.GroupInfo[S.nGroupCount].pName[0], pGroup, nLen); +    S.GroupInfo[S.nGroupCount].pName[nLen] = '\0'; +    S.GroupInfo[S.nGroupCount].nNameLen = nLen; +    S.GroupInfo[S.nGroupCount].nNumTimers = 0; +    S.GroupInfo[S.nGroupCount].nGroupIndex = S.nGroupCount; +    S.GroupInfo[S.nGroupCount].Type = Type; +    S.GroupInfo[S.nGroupCount].nMaxTimerNameLen = 0; +    S.GroupInfo[S.nGroupCount].nColor = 0x88888888; +    S.GroupInfo[S.nGroupCount].nCategory = 0; +    S.CategoryInfo[0].nGroupMask |= (1ll << (uint64_t)S.nGroupCount); +    nGroupIndex = S.nGroupCount++; +    S.nGroupMask = (S.nGroupMask<<1)|1; +    MP_ASSERT(nGroupIndex < MICROPROFILE_MAX_GROUPS); +    return nGroupIndex; +} + +void MicroProfileRegisterGroup(const char* pGroup, const char* pCategory, uint32_t nColor) +{ +    int nCategoryIndex = -1; +    for(uint32_t i = 0; i < S.nCategoryCount; ++i) +    { +        if(!MP_STRCASECMP(pCategory, S.CategoryInfo[i].pName)) +        { +            nCategoryIndex = (int)i; +            break; +        } +    } +    if(-1 == nCategoryIndex && S.nCategoryCount < MICROPROFILE_MAX_CATEGORIES) +    { +        MP_ASSERT(S.CategoryInfo[S.nCategoryCount].pName[0] == '\0'); +        nCategoryIndex = (int)S.nCategoryCount++; +        uint32_t nLen = (uint32_t)strlen(pCategory); +        if(nLen > MICROPROFILE_NAME_MAX_LEN-1) +            nLen = MICROPROFILE_NAME_MAX_LEN-1; +        memcpy(&S.CategoryInfo[nCategoryIndex].pName[0], pCategory, nLen); +        S.CategoryInfo[nCategoryIndex].pName[nLen] = '\0'; +    } +    uint16_t nGroup = MicroProfileGetGroup(pGroup, 0 != MP_STRCASECMP(pGroup, "gpu")?MicroProfileTokenTypeCpu : MicroProfileTokenTypeGpu); +    S.GroupInfo[nGroup].nColor = nColor; +    if(nCategoryIndex >= 0) +    { +        uint64_t nBit = 1ll << nGroup; +        uint32_t nOldCategory = S.GroupInfo[nGroup].nCategory; +        S.CategoryInfo[nOldCategory].nGroupMask &= ~nBit; +        S.CategoryInfo[nCategoryIndex].nGroupMask |= nBit; +        S.GroupInfo[nGroup].nCategory = nCategoryIndex; +    } +} + +MicroProfileToken MicroProfileGetToken(const char* pGroup, const char* pName, uint32_t nColor, MicroProfileTokenType Type) +{ +    MicroProfileInit(); +    MicroProfileScopeLock L(MicroProfileMutex()); +    MicroProfileToken ret = MicroProfileFindToken(pGroup, pName); +    if(ret != MICROPROFILE_INVALID_TOKEN) +        return ret; +    uint16_t nGroupIndex = MicroProfileGetGroup(pGroup, Type); +    uint16_t nTimerIndex = (uint16_t)(S.nTotalTimers++); +    uint64_t nGroupMask = 1ll << nGroupIndex; +    MicroProfileToken nToken = MicroProfileMakeToken(nGroupMask, nTimerIndex); +    S.GroupInfo[nGroupIndex].nNumTimers++; +    S.GroupInfo[nGroupIndex].nMaxTimerNameLen = MicroProfileMax(S.GroupInfo[nGroupIndex].nMaxTimerNameLen, (uint32_t)strlen(pName)); +    MP_ASSERT(S.GroupInfo[nGroupIndex].Type == Type); //dont mix cpu & gpu timers in the same group +    S.nMaxGroupSize = MicroProfileMax(S.nMaxGroupSize, S.GroupInfo[nGroupIndex].nNumTimers); +    S.TimerInfo[nTimerIndex].nToken = nToken; +    uint32_t nLen = (uint32_t)strlen(pName); +    if(nLen > MICROPROFILE_NAME_MAX_LEN-1) +        nLen = MICROPROFILE_NAME_MAX_LEN-1; +    memcpy(&S.TimerInfo[nTimerIndex].pName, pName, nLen); +    S.TimerInfo[nTimerIndex].pName[nLen] = '\0'; +    S.TimerInfo[nTimerIndex].nNameLen = nLen; +    S.TimerInfo[nTimerIndex].nColor = nColor&0xffffff; +    S.TimerInfo[nTimerIndex].nGroupIndex = nGroupIndex; +    S.TimerInfo[nTimerIndex].nTimerIndex = nTimerIndex; +    S.TimerToGroup[nTimerIndex] = nGroupIndex; +    return nToken; +} + +MicroProfileToken MicroProfileGetMetaToken(const char* pName) +{ +    MicroProfileInit(); +    MicroProfileScopeLock L(MicroProfileMutex()); +    for(uint32_t i = 0; i < MICROPROFILE_META_MAX; ++i) +    { +        if(!S.MetaCounters[i].pName) +        { +            S.MetaCounters[i].pName = pName; +            return i; +        } +        else if(!MP_STRCASECMP(pName, S.MetaCounters[i].pName)) +        { +            return i; +        } +    } +    MP_ASSERT(0);//out of slots, increase MICROPROFILE_META_MAX +    return (MicroProfileToken)-1; +} + + +inline void MicroProfileLogPut(MicroProfileToken nToken_, uint64_t nTick, uint64_t nBegin, MicroProfileThreadLog* pLog) +{ +    MP_ASSERT(pLog != 0); //this assert is hit if MicroProfileOnCreateThread is not called +    MP_ASSERT(pLog->nActive); +    uint32_t nPos = pLog->nPut.load(std::memory_order_relaxed); +    uint32_t nNextPos = (nPos+1) % MICROPROFILE_BUFFER_SIZE; +    if(nNextPos == pLog->nGet.load(std::memory_order_relaxed)) +    { +        S.nOverflow = 100; +    } +    else +    { +        pLog->Log[nPos] = MicroProfileMakeLogIndex(nBegin, nToken_, nTick); +        pLog->nPut.store(nNextPos, std::memory_order_release); +    } +} + +uint64_t MicroProfileEnter(MicroProfileToken nToken_) +{ +    if(MicroProfileGetGroupMask(nToken_) & S.nActiveGroup) +    { +        if(!MicroProfileGetThreadLog()) +        { +            MicroProfileInitThreadLog(); +        } +        uint64_t nTick = MP_TICK(); +        MicroProfileLogPut(nToken_, nTick, MP_LOG_ENTER, MicroProfileGetThreadLog()); +        return nTick; +    } +    return MICROPROFILE_INVALID_TICK; +} + +void MicroProfileMetaUpdate(MicroProfileToken nToken, int nCount, MicroProfileTokenType eTokenType) +{ +    if((MP_DRAW_META_FIRST<<nToken) & S.nActiveBars) +    { +        MicroProfileThreadLog* pLog = MicroProfileTokenTypeCpu == eTokenType ? MicroProfileGetThreadLog() : g_MicroProfileGpuLog; +        if(pLog) +        { +            MP_ASSERT(nToken < MICROPROFILE_META_MAX); +            MicroProfileLogPut(nToken, nCount, MP_LOG_META, pLog); +        } +    } +} + + +void MicroProfileLeave(MicroProfileToken nToken_, uint64_t nTickStart) +{ +    if(MICROPROFILE_INVALID_TICK != nTickStart) +    { +        if(!MicroProfileGetThreadLog()) +        { +            MicroProfileInitThreadLog(); +        } +        uint64_t nTick = MP_TICK(); +        MicroProfileThreadLog* pLog = MicroProfileGetThreadLog(); +        MicroProfileLogPut(nToken_, nTick, MP_LOG_LEAVE, pLog); +    } +} + + +uint64_t MicroProfileGpuEnter(MicroProfileToken nToken_) +{ +    if(MicroProfileGetGroupMask(nToken_) & S.nActiveGroup) +    { +        uint64_t nTimer = MicroProfileGpuInsertTimeStamp(); +        MicroProfileLogPut(nToken_, nTimer, MP_LOG_ENTER, g_MicroProfileGpuLog); +        MicroProfileLogPut(nToken_, MP_TICK(), MP_LOG_GPU_EXTRA, g_MicroProfileGpuLog); +        return 1; +    } +    return 0; +} + +void MicroProfileGpuLeave(MicroProfileToken nToken_, uint64_t nTickStart) +{ +    if(nTickStart) +    { +        uint64_t nTimer = MicroProfileGpuInsertTimeStamp(); +        MicroProfileLogPut(nToken_, nTimer, MP_LOG_LEAVE, g_MicroProfileGpuLog); +        MicroProfileLogPut(nToken_, MP_TICK(), MP_LOG_GPU_EXTRA, g_MicroProfileGpuLog); +    } +} + +void MicroProfileContextSwitchPut(MicroProfileContextSwitch* pContextSwitch) +{ +    if(S.nRunning || pContextSwitch->nTicks <= S.nPauseTicks) +    { +        uint32_t nPut = S.nContextSwitchPut; +        S.ContextSwitch[nPut] = *pContextSwitch; +        S.nContextSwitchPut = (S.nContextSwitchPut+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE; +    } +} + + +void MicroProfileGetRange(uint32_t nPut, uint32_t nGet, uint32_t nRange[2][2]) +{ +    if(nPut > nGet) +    { +        nRange[0][0] = nGet; +        nRange[0][1] = nPut; +        nRange[1][0] = nRange[1][1] = 0; +    } +    else if(nPut != nGet) +    { +        MP_ASSERT(nGet != MICROPROFILE_BUFFER_SIZE); +        uint32_t nCountEnd = MICROPROFILE_BUFFER_SIZE - nGet; +        nRange[0][0] = nGet; +        nRange[0][1] = nGet + nCountEnd; +        nRange[1][0] = 0; +        nRange[1][1] = nPut; +    } +} + +void MicroProfileFlip() +{ +    #if 0 +    //verify LogEntry wraps correctly +    MicroProfileLogEntry c = MP_LOG_TICK_MASK-5000; +    for(int i = 0; i < 10000; ++i, c += 1) +    { +        MicroProfileLogEntry l2 = (c+2500) & MP_LOG_TICK_MASK; +        MP_ASSERT(2500 == MicroProfileLogTickDifference(c, l2)); +    } +    #endif +    MICROPROFILE_SCOPE(g_MicroProfileFlip); +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); + + +    MicroProfileGpuFlip(); + +    if(S.nToggleRunning) +    { +        S.nRunning = !S.nRunning; +        if(!S.nRunning) +            S.nPauseTicks = MP_TICK(); +        S.nToggleRunning = 0; +        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +        { +            MicroProfileThreadLog* pLog = S.Pool[i]; +            if(pLog) +            { +                pLog->nStackPos = 0; +            } +        } +    } +    uint32_t nAggregateClear = S.nAggregateClear || S.nAutoClearFrames, nAggregateFlip = 0; +    if(S.nDumpFileNextFrame) +    { +        MicroProfileDumpToFile(); +        S.nDumpFileNextFrame = 0; +        S.nAutoClearFrames = MICROPROFILE_GPU_FRAME_DELAY + 3; //hide spike from dumping webpage +    } +    if(S.nWebServerDataSent == (uint64_t)-1) +    { +        MicroProfileWebServerStart(); +        S.nWebServerDataSent = 0; +    } + +    if(MicroProfileWebServerUpdate()) +    { +        S.nAutoClearFrames = MICROPROFILE_GPU_FRAME_DELAY + 3; //hide spike from dumping webpage +    } + +    if(S.nAutoClearFrames) +    { +        nAggregateClear = 1; +        nAggregateFlip = 1; +        S.nAutoClearFrames -= 1; +    } + + +    if(S.nRunning || S.nForceEnable) +    { +        S.nFramePutIndex++; +        S.nFramePut = (S.nFramePut+1) % MICROPROFILE_MAX_FRAME_HISTORY; +        MP_ASSERT((S.nFramePutIndex % MICROPROFILE_MAX_FRAME_HISTORY) == S.nFramePut); +        S.nFrameCurrent = (S.nFramePut + MICROPROFILE_MAX_FRAME_HISTORY - MICROPROFILE_GPU_FRAME_DELAY - 1) % MICROPROFILE_MAX_FRAME_HISTORY; +        S.nFrameCurrentIndex++; +        uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY; + +        uint32_t nContextSwitchPut = S.nContextSwitchPut; +        if(S.nContextSwitchLastPut < nContextSwitchPut) +        { +            S.nContextSwitchUsage = (nContextSwitchPut - S.nContextSwitchLastPut); +        } +        else +        { +            S.nContextSwitchUsage = MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE - S.nContextSwitchLastPut + nContextSwitchPut; +        } +        S.nContextSwitchLastPut = nContextSwitchPut; + +        MicroProfileFrameState* pFramePut = &S.Frames[S.nFramePut]; +        MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent]; +        MicroProfileFrameState* pFrameNext = &S.Frames[nFrameNext]; + +        pFramePut->nFrameStartCpu = MP_TICK(); +        pFramePut->nFrameStartGpu = (uint32_t)MicroProfileGpuInsertTimeStamp(); +        if(pFrameNext->nFrameStartGpu != (uint64_t)-1) +            pFrameNext->nFrameStartGpu = MicroProfileGpuGetTimeStamp((uint32_t)pFrameNext->nFrameStartGpu); + +        if(pFrameCurrent->nFrameStartGpu == (uint64_t)-1) +            pFrameCurrent->nFrameStartGpu = pFrameNext->nFrameStartGpu + 1; + +        uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu; +        uint64_t nFrameEndCpu = pFrameNext->nFrameStartCpu; + +        { +            uint64_t nTick = nFrameEndCpu - nFrameStartCpu; +            S.nFlipTicks = nTick; +            S.nFlipAggregate += nTick; +            S.nFlipMax = MicroProfileMax(S.nFlipMax, nTick); +        } + +        uint8_t* pTimerToGroup = &S.TimerToGroup[0]; +        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +        { +            MicroProfileThreadLog* pLog = S.Pool[i]; +            if(!pLog) +            { +                pFramePut->nLogStart[i] = 0; +            } +            else +            { +                uint32_t nPut = pLog->nPut.load(std::memory_order_acquire); +                pFramePut->nLogStart[i] = nPut; +                MP_ASSERT(nPut< MICROPROFILE_BUFFER_SIZE); +                //need to keep last frame around to close timers. timers more than 1 frame old is ditched. +                pLog->nGet.store(nPut, std::memory_order_relaxed); +            } +        } + +        if(S.nRunning) +        { +            uint64_t* pFrameGroup = &S.FrameGroup[0]; +            { +                MICROPROFILE_SCOPE(g_MicroProfileClear); +                for(uint32_t i = 0; i < S.nTotalTimers; ++i) +                { +                    S.Frame[i].nTicks = 0; +                    S.Frame[i].nCount = 0; +                    S.FrameExclusive[i] = 0; +                } +                for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +                { +                    pFrameGroup[i] = 0; +                } +                for(uint32_t j = 0; j < MICROPROFILE_META_MAX; ++j) +                { +                    if(S.MetaCounters[j].pName && 0 != (S.nActiveBars & (MP_DRAW_META_FIRST<<j))) +                    { +                        auto& Meta = S.MetaCounters[j]; +                        for(uint32_t i = 0; i < S.nTotalTimers; ++i) +                        { +                            Meta.nCounters[i] = 0; +                        } +                    } +                } + +            } +            { +                MICROPROFILE_SCOPE(g_MicroProfileThreadLoop); +                for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +                { +                    MicroProfileThreadLog* pLog = S.Pool[i]; +                    if(!pLog) +                        continue; + +                    uint8_t* pGroupStackPos = &pLog->nGroupStackPos[0]; +                    int64_t nGroupTicks[MICROPROFILE_MAX_GROUPS] = {0}; + + +                    uint32_t nPut = pFrameNext->nLogStart[i]; +                    uint32_t nGet = pFrameCurrent->nLogStart[i]; +                    uint32_t nRange[2][2] = { {0, 0}, {0, 0}, }; +                    MicroProfileGetRange(nPut, nGet, nRange); + + +                    //fetch gpu results. +                    if(pLog->nGpu) +                    { +                        for(uint32_t j = 0; j < 2; ++j) +                        { +                            uint32_t nStart = nRange[j][0]; +                            uint32_t nEnd = nRange[j][1]; +                            for(uint32_t k = nStart; k < nEnd; ++k) +                            { +                                MicroProfileLogEntry L = pLog->Log[k]; +                                if(MicroProfileLogType(L) < MP_LOG_META) +                                { +                                    pLog->Log[k] = MicroProfileLogSetTick(L, MicroProfileGpuGetTimeStamp((uint32_t)MicroProfileLogGetTick(L))); +                                } +                            } +                        } +                    } + + +                    uint32_t* pStack = &pLog->nStack[0]; +                    int64_t* pChildTickStack = &pLog->nChildTickStack[0]; +                    uint32_t nStackPos = pLog->nStackPos; + +                    for(uint32_t j = 0; j < 2; ++j) +                    { +                        uint32_t nStart = nRange[j][0]; +                        uint32_t nEnd = nRange[j][1]; +                        for(uint32_t k = nStart; k < nEnd; ++k) +                        { +                            MicroProfileLogEntry LE = pLog->Log[k]; +                            int nType = MicroProfileLogType(LE); + +                            if(MP_LOG_ENTER == nType) +                            { +                                int nTimer = MicroProfileLogTimerIndex(LE); +                                uint8_t nGroup = pTimerToGroup[nTimer]; +                                MP_ASSERT(nStackPos < MICROPROFILE_STACK_MAX); +                                MP_ASSERT(nGroup < MICROPROFILE_MAX_GROUPS); +                                pGroupStackPos[nGroup]++; +                                pStack[nStackPos++] = k; +                                pChildTickStack[nStackPos] = 0; + +                            } +                            else if(MP_LOG_META == nType) +                            { +                                if(nStackPos) +                                { +                                    int64_t nMetaIndex = MicroProfileLogTimerIndex(LE); +                                    int64_t nMetaCount = MicroProfileLogGetTick(LE); +                                    MP_ASSERT(nMetaIndex < MICROPROFILE_META_MAX); +                                    int64_t nCounter = MicroProfileLogTimerIndex(pLog->Log[pStack[nStackPos-1]]); +                                    S.MetaCounters[nMetaIndex].nCounters[nCounter] += nMetaCount; +                                } +                            } +                            else if(MP_LOG_LEAVE == nType) +                            { +                                int nTimer = MicroProfileLogTimerIndex(LE); +                                uint8_t nGroup = pTimerToGroup[nTimer]; +                                MP_ASSERT(nGroup < MICROPROFILE_MAX_GROUPS); +                                if(nStackPos) +                                { +                                    int64_t nTickStart = pLog->Log[pStack[nStackPos-1]]; +                                    int64_t nTicks = MicroProfileLogTickDifference(nTickStart, LE); +                                    int64_t nChildTicks = pChildTickStack[nStackPos]; +                                    nStackPos--; +                                    pChildTickStack[nStackPos] += nTicks; + +                                    uint32_t nTimerIndex = MicroProfileLogTimerIndex(LE); +                                    S.Frame[nTimerIndex].nTicks += nTicks; +                                    S.FrameExclusive[nTimerIndex] += (nTicks-nChildTicks); +                                    S.Frame[nTimerIndex].nCount += 1; + +                                    MP_ASSERT(nGroup < MICROPROFILE_MAX_GROUPS); +                                    uint8_t nGroupStackPos = pGroupStackPos[nGroup]; +                                    if(nGroupStackPos) +                                    { +                                        nGroupStackPos--; +                                        if(0 == nGroupStackPos) +                                        { +                                            nGroupTicks[nGroup] += nTicks; +                                        } +                                        pGroupStackPos[nGroup] = nGroupStackPos; +                                    } +                                } +                            } +                        } +                    } +                    for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +                    { +                        pLog->nGroupTicks[i] += nGroupTicks[i]; +                        pFrameGroup[i] += nGroupTicks[i]; +                    } +                    pLog->nStackPos = nStackPos; +                } +            } +            { +                MICROPROFILE_SCOPE(g_MicroProfileAccumulate); +                for(uint32_t i = 0; i < S.nTotalTimers; ++i) +                { +                    S.AccumTimers[i].nTicks += S.Frame[i].nTicks; +                    S.AccumTimers[i].nCount += S.Frame[i].nCount; +                    S.AccumMaxTimers[i] = MicroProfileMax(S.AccumMaxTimers[i], S.Frame[i].nTicks); +                    S.AccumTimersExclusive[i] += S.FrameExclusive[i]; +                    S.AccumMaxTimersExclusive[i] = MicroProfileMax(S.AccumMaxTimersExclusive[i], S.FrameExclusive[i]); +                } + +                for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +                { +                    S.AccumGroup[i] += pFrameGroup[i]; +                    S.AccumGroupMax[i] = MicroProfileMax(S.AccumGroupMax[i], pFrameGroup[i]); +                } + +                for(uint32_t j = 0; j < MICROPROFILE_META_MAX; ++j) +                { +                    if(S.MetaCounters[j].pName && 0 != (S.nActiveBars & (MP_DRAW_META_FIRST<<j))) +                    { +                        auto& Meta = S.MetaCounters[j]; +                        uint64_t nSum = 0;; +                        for(uint32_t i = 0; i < S.nTotalTimers; ++i) +                        { +                            uint64_t nCounter = Meta.nCounters[i]; +                            Meta.nAccumMax[i] = MicroProfileMax(Meta.nAccumMax[i], nCounter); +                            Meta.nAccum[i] += nCounter; +                            nSum += nCounter; +                        } +                        Meta.nSumAccum += nSum; +                        Meta.nSumAccumMax = MicroProfileMax(Meta.nSumAccumMax, nSum); +                    } +                } +            } +            for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +            { +                if(S.Graph[i].nToken != MICROPROFILE_INVALID_TOKEN) +                { +                    MicroProfileToken nToken = S.Graph[i].nToken; +                    S.Graph[i].nHistory[S.nGraphPut] = S.Frame[MicroProfileGetTimerIndex(nToken)].nTicks; +                } +            } +            S.nGraphPut = (S.nGraphPut+1) % MICROPROFILE_GRAPH_HISTORY; + +        } + + +        if(S.nRunning && S.nAggregateFlip <= ++S.nAggregateFlipCount) +        { +            nAggregateFlip = 1; +            if(S.nAggregateFlip) // if 0 accumulate indefinitely +            { +                nAggregateClear = 1; +            } +        } +    } +    if(nAggregateFlip) +    { +        memcpy(&S.Aggregate[0], &S.AccumTimers[0], sizeof(S.Aggregate[0]) * S.nTotalTimers); +        memcpy(&S.AggregateMax[0], &S.AccumMaxTimers[0], sizeof(S.AggregateMax[0]) * S.nTotalTimers); +        memcpy(&S.AggregateExclusive[0], &S.AccumTimersExclusive[0], sizeof(S.AggregateExclusive[0]) * S.nTotalTimers); +        memcpy(&S.AggregateMaxExclusive[0], &S.AccumMaxTimersExclusive[0], sizeof(S.AggregateMaxExclusive[0]) * S.nTotalTimers); + +        memcpy(&S.AggregateGroup[0], &S.AccumGroup[0], sizeof(S.AggregateGroup)); +        memcpy(&S.AggregateGroupMax[0], &S.AccumGroupMax[0], sizeof(S.AggregateGroup)); + +        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +        { +            MicroProfileThreadLog* pLog = S.Pool[i]; +            if(!pLog) +                continue; + +            memcpy(&pLog->nAggregateGroupTicks[0], &pLog->nGroupTicks[0], sizeof(pLog->nAggregateGroupTicks)); + +            if(nAggregateClear) +            { +                memset(&pLog->nGroupTicks[0], 0, sizeof(pLog->nGroupTicks)); +            } +        } + +        for(uint32_t j = 0; j < MICROPROFILE_META_MAX; ++j) +        { +            if(S.MetaCounters[j].pName && 0 != (S.nActiveBars & (MP_DRAW_META_FIRST<<j))) +            { +                auto& Meta = S.MetaCounters[j]; +                memcpy(&Meta.nAggregateMax[0], &Meta.nAccumMax[0], sizeof(Meta.nAggregateMax[0]) * S.nTotalTimers); +                memcpy(&Meta.nAggregate[0], &Meta.nAccum[0], sizeof(Meta.nAggregate[0]) * S.nTotalTimers); +                Meta.nSumAggregate = Meta.nSumAccum; +                Meta.nSumAggregateMax = Meta.nSumAccumMax; +                if(nAggregateClear) +                { +                    memset(&Meta.nAccumMax[0], 0, sizeof(Meta.nAccumMax[0]) * S.nTotalTimers); +                    memset(&Meta.nAccum[0], 0, sizeof(Meta.nAccum[0]) * S.nTotalTimers); +                    Meta.nSumAccum = 0; +                    Meta.nSumAccumMax = 0; +                } +            } +        } + + + + + +        S.nAggregateFrames = S.nAggregateFlipCount; +        S.nFlipAggregateDisplay = S.nFlipAggregate; +        S.nFlipMaxDisplay = S.nFlipMax; +        if(nAggregateClear) +        { +            memset(&S.AccumTimers[0], 0, sizeof(S.Aggregate[0]) * S.nTotalTimers); +            memset(&S.AccumMaxTimers[0], 0, sizeof(S.AccumMaxTimers[0]) * S.nTotalTimers); +            memset(&S.AccumTimersExclusive[0], 0, sizeof(S.AggregateExclusive[0]) * S.nTotalTimers); +            memset(&S.AccumMaxTimersExclusive[0], 0, sizeof(S.AccumMaxTimersExclusive[0]) * S.nTotalTimers); +            memset(&S.AccumGroup[0], 0, sizeof(S.AggregateGroup)); +            memset(&S.AccumGroupMax[0], 0, sizeof(S.AggregateGroup)); + +            S.nAggregateFlipCount = 0; +            S.nFlipAggregate = 0; +            S.nFlipMax = 0; + +            S.nAggregateFlipTick = MP_TICK(); +        } +    } +    S.nAggregateClear = 0; + +    uint64_t nNewActiveGroup = 0; +    if(S.nForceEnable || (S.nDisplay && S.nRunning)) +        nNewActiveGroup = S.nAllGroupsWanted ? S.nGroupMask : S.nActiveGroupWanted; +    nNewActiveGroup |= S.nForceGroup; +    nNewActiveGroup |= S.nForceGroupUI; +    if(S.nActiveGroup != nNewActiveGroup) +        S.nActiveGroup = nNewActiveGroup; +    uint32_t nNewActiveBars = 0; +    if(S.nDisplay && S.nRunning) +        nNewActiveBars = S.nBars; +    if(S.nForceMetaCounters) +    { +        for(int i = 0; i < MICROPROFILE_META_MAX; ++i) +        { +            if(S.MetaCounters[i].pName) +            { +                nNewActiveBars |= (MP_DRAW_META_FIRST<<i); +            } +        } +    } +    if(nNewActiveBars != S.nActiveBars) +        S.nActiveBars = nNewActiveBars; +} + +void MicroProfileSetForceEnable(bool bEnable) +{ +    S.nForceEnable = bEnable ? 1 : 0; +} +bool MicroProfileGetForceEnable() +{ +    return S.nForceEnable != 0; +} + +void MicroProfileSetEnableAllGroups(bool bEnableAllGroups) +{ +    S.nAllGroupsWanted = bEnableAllGroups ? 1 : 0; +} + +void MicroProfileEnableCategory(const char* pCategory, bool bEnabled) +{ +    int nCategoryIndex = -1; +    for(uint32_t i = 0; i < S.nCategoryCount; ++i) +    { +        if(!MP_STRCASECMP(pCategory, S.CategoryInfo[i].pName)) +        { +            nCategoryIndex = (int)i; +            break; +        } +    } +    if(nCategoryIndex >= 0) +    { +        if(bEnabled) +        { +            S.nActiveGroupWanted |= S.CategoryInfo[nCategoryIndex].nGroupMask; +        } +        else +        { +            S.nActiveGroupWanted &= ~S.CategoryInfo[nCategoryIndex].nGroupMask; +        } +    } +} + + +void MicroProfileEnableCategory(const char* pCategory) +{ +    MicroProfileEnableCategory(pCategory, true); +} +void MicroProfileDisableCategory(const char* pCategory) +{ +    MicroProfileEnableCategory(pCategory, false); +} + +bool MicroProfileGetEnableAllGroups() +{ +    return 0 != S.nAllGroupsWanted; +} + +void MicroProfileSetForceMetaCounters(bool bForce) +{ +    S.nForceMetaCounters = bForce ? 1 : 0; +} + +bool MicroProfileGetForceMetaCounters() +{ +    return 0 != S.nForceMetaCounters; +} + +void MicroProfileEnableMetaCounter(const char* pMeta) +{ +    for(uint32_t i = 0; i < MICROPROFILE_META_MAX; ++i) +    { +        if(S.MetaCounters[i].pName && 0 == MP_STRCASECMP(S.MetaCounters[i].pName, pMeta)) +        { +            S.nBars |= (MP_DRAW_META_FIRST<<i); +            return; +        } +    } +} +void MicroProfileDisableMetaCounter(const char* pMeta) +{ +    for(uint32_t i = 0; i < MICROPROFILE_META_MAX; ++i) +    { +        if(S.MetaCounters[i].pName && 0 == MP_STRCASECMP(S.MetaCounters[i].pName, pMeta)) +        { +            S.nBars &= ~(MP_DRAW_META_FIRST<<i); +            return; +        } +    } +} + + +void MicroProfileSetAggregateFrames(int nFrames) +{ +    S.nAggregateFlip = (uint32_t)nFrames; +    if(0 == nFrames) +    { +        S.nAggregateClear = 1; +    } +} + +int MicroProfileGetAggregateFrames() +{ +    return S.nAggregateFlip; +} + +int MicroProfileGetCurrentAggregateFrames() +{ +    return int(S.nAggregateFlip ? S.nAggregateFlip : S.nAggregateFlipCount); +} + + +void MicroProfileForceEnableGroup(const char* pGroup, MicroProfileTokenType Type) +{ +    MicroProfileInit(); +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    uint16_t nGroup = MicroProfileGetGroup(pGroup, Type); +    S.nForceGroup |= (1ll << nGroup); +} + +void MicroProfileForceDisableGroup(const char* pGroup, MicroProfileTokenType Type) +{ +    MicroProfileInit(); +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    uint16_t nGroup = MicroProfileGetGroup(pGroup, Type); +    S.nForceGroup &= ~(1ll << nGroup); +} + + +void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, float* pTotal, uint32_t nSize) +{ +    for(uint32_t i = 0; i < S.nTotalTimers && i < nSize; ++i) +    { +        const uint32_t nGroupId = S.TimerInfo[i].nGroupIndex; +        const float fToMs = MicroProfileTickToMsMultiplier(S.GroupInfo[nGroupId].Type == MicroProfileTokenTypeGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +        uint32_t nTimer = i; +        uint32_t nIdx = i * 2; +        uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +        uint32_t nAggregateCount = S.Aggregate[nTimer].nCount ? S.Aggregate[nTimer].nCount : 1; +        float fToPrc = S.fRcpReferenceTime; +        float fMs = fToMs * (S.Frame[nTimer].nTicks); +        float fPrc = MicroProfileMin(fMs * fToPrc, 1.f); +        float fAverageMs = fToMs * (S.Aggregate[nTimer].nTicks / nAggregateFrames); +        float fAveragePrc = MicroProfileMin(fAverageMs * fToPrc, 1.f); +        float fMaxMs = fToMs * (S.AggregateMax[nTimer]); +        float fMaxPrc = MicroProfileMin(fMaxMs * fToPrc, 1.f); +        float fCallAverageMs = fToMs * (S.Aggregate[nTimer].nTicks / nAggregateCount); +        float fCallAveragePrc = MicroProfileMin(fCallAverageMs * fToPrc, 1.f); +        float fMsExclusive = fToMs * (S.FrameExclusive[nTimer]); +        float fPrcExclusive = MicroProfileMin(fMsExclusive * fToPrc, 1.f); +        float fAverageMsExclusive = fToMs * (S.AggregateExclusive[nTimer] / nAggregateFrames); +        float fAveragePrcExclusive = MicroProfileMin(fAverageMsExclusive * fToPrc, 1.f); +        float fMaxMsExclusive = fToMs * (S.AggregateMaxExclusive[nTimer]); +        float fMaxPrcExclusive = MicroProfileMin(fMaxMsExclusive * fToPrc, 1.f); +        float fTotalMs = fToMs * S.Aggregate[nTimer].nTicks; +        pTimers[nIdx] = fMs; +        pTimers[nIdx+1] = fPrc; +        pAverage[nIdx] = fAverageMs; +        pAverage[nIdx+1] = fAveragePrc; +        pMax[nIdx] = fMaxMs; +        pMax[nIdx+1] = fMaxPrc; +        pCallAverage[nIdx] = fCallAverageMs; +        pCallAverage[nIdx+1] = fCallAveragePrc; +        pExclusive[nIdx] = fMsExclusive; +        pExclusive[nIdx+1] = fPrcExclusive; +        pAverageExclusive[nIdx] = fAverageMsExclusive; +        pAverageExclusive[nIdx+1] = fAveragePrcExclusive; +        pMaxExclusive[nIdx] = fMaxMsExclusive; +        pMaxExclusive[nIdx+1] = fMaxPrcExclusive; +        pTotal[nIdx] = fTotalMs; +        pTotal[nIdx+1] = 0.f; +    } +} + +void MicroProfileTogglePause() +{ +    S.nToggleRunning = 1; +} + +float MicroProfileGetTime(const char* pGroup, const char* pName) +{ +    MicroProfileToken nToken = MicroProfileFindToken(pGroup, pName); +    if(nToken == MICROPROFILE_INVALID_TOKEN) +    { +        return 0.f; +    } +    uint32_t nTimerIndex = MicroProfileGetTimerIndex(nToken); +    uint32_t nGroupIndex = MicroProfileGetGroupIndex(nToken); +    float fToMs = MicroProfileTickToMsMultiplier(S.GroupInfo[nGroupIndex].Type == MicroProfileTokenTypeGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +    return S.Frame[nTimerIndex].nTicks * fToMs; +} + + +void MicroProfileContextSwitchSearch(uint32_t* pContextSwitchStart, uint32_t* pContextSwitchEnd, uint64_t nBaseTicksCpu, uint64_t nBaseTicksEndCpu) +{ +    MICROPROFILE_SCOPE(g_MicroProfileContextSwitchSearch); +    uint32_t nContextSwitchPut = S.nContextSwitchPut; +    uint64_t nContextSwitchStart, nContextSwitchEnd; +    nContextSwitchStart = nContextSwitchEnd = (nContextSwitchPut + MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE - 1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE; +    int64_t nSearchEnd = nBaseTicksEndCpu + MicroProfileMsToTick(30.f, MicroProfileTicksPerSecondCpu()); +    int64_t nSearchBegin = nBaseTicksCpu - MicroProfileMsToTick(30.f, MicroProfileTicksPerSecondCpu()); +    for(uint32_t i = 0; i < MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE; ++i) +    { +        uint32_t nIndex = (nContextSwitchPut + MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE - (i+1)) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE; +        MicroProfileContextSwitch& CS = S.ContextSwitch[nIndex]; +        if(CS.nTicks > nSearchEnd) +        { +            nContextSwitchEnd = nIndex; +        } +        if(CS.nTicks > nSearchBegin) +        { +            nContextSwitchStart = nIndex; +        } +    } +    *pContextSwitchStart = nContextSwitchStart; +    *pContextSwitchEnd = nContextSwitchEnd; +} + + + +#if MICROPROFILE_WEBSERVER + +#define MICROPROFILE_EMBED_HTML + +extern const char* g_MicroProfileHtml_begin[]; +extern size_t g_MicroProfileHtml_begin_sizes[]; +extern size_t g_MicroProfileHtml_begin_count; +extern const char* g_MicroProfileHtml_end[]; +extern size_t g_MicroProfileHtml_end_sizes[]; +extern size_t g_MicroProfileHtml_end_count; + +typedef void MicroProfileWriteCallback(void* Handle, size_t size, const char* pData); + +uint32_t MicroProfileWebServerPort() +{ +    return S.nWebServerPort; +} + +void MicroProfileDumpFile(const char* pHtml, const char* pCsv) +{ +    S.nDumpFileNextFrame = 0; +    if(pHtml) +    { +        uint32_t nLen = strlen(pHtml); +        if(nLen > sizeof(S.HtmlDumpPath)-1) +        { +            return; +        } +        memcpy(S.HtmlDumpPath, pHtml, nLen+1); +        S.nDumpFileNextFrame |= 1; +    } +    if(pCsv) +    { +        uint32_t nLen = strlen(pCsv); +        if(nLen > sizeof(S.CsvDumpPath)-1) +        { +            return; +        } +        memcpy(S.CsvDumpPath, pCsv, nLen+1); +        S.nDumpFileNextFrame |= 2; +    } +} + +void MicroProfilePrintf(MicroProfileWriteCallback CB, void* Handle, const char* pFmt, ...) +{ +    char buffer[32*1024]; +    va_list args; +    va_start (args, pFmt); +#ifdef _WIN32 +    size_t size = vsprintf_s(buffer, pFmt, args); +#else +    size_t size = vsnprintf(buffer, sizeof(buffer)-1,  pFmt, args); +#endif +    CB(Handle, size, &buffer[0]); +    va_end (args); +} + +#define printf(...) MicroProfilePrintf(CB, Handle, __VA_ARGS__) +void MicroProfileDumpCsv(MicroProfileWriteCallback CB, void* Handle, int nMaxFrames) +{ +    uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +    float fToMsCPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    float fToMsGPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondGpu()); + +    printf("frames,%d\n", nAggregateFrames); +    printf("group,name,average,max,callaverage\n"); + +    uint32_t nNumTimers = S.nTotalTimers; +    uint32_t nBlockSize = 2 * nNumTimers; +    float* pTimers = (float*)alloca(nBlockSize * 8 * sizeof(float)); +    float* pAverage = pTimers + nBlockSize; +    float* pMax = pTimers + 2 * nBlockSize; +    float* pCallAverage = pTimers + 3 * nBlockSize; +    float* pTimersExclusive = pTimers + 4 * nBlockSize; +    float* pAverageExclusive = pTimers + 5 * nBlockSize; +    float* pMaxExclusive = pTimers + 6 * nBlockSize; +    float* pTotal = pTimers + 7 * nBlockSize; + +    MicroProfileCalcAllTimers(pTimers, pAverage, pMax, pCallAverage, pTimersExclusive, pAverageExclusive, pMaxExclusive, pTotal, nNumTimers); + +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        uint32_t nIdx = i * 2; +        printf("\"%s\",\"%s\",%f,%f,%f\n", S.TimerInfo[i].pName, S.GroupInfo[S.TimerInfo[i].nGroupIndex].pName, pAverage[nIdx], pMax[nIdx], pCallAverage[nIdx]); +    } + +    printf("\n\n"); + +    printf("group,average,max,total\n"); +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        const char* pGroupName = S.GroupInfo[j].pName; +        float fToMs =  S.GroupInfo[j].Type == MicroProfileTokenTypeGpu ? fToMsGPU : fToMsCPU; +        if(pGroupName[0] != '\0') +        { +            printf("\"%s\",%.3f,%.3f,%.3f\n", pGroupName, fToMs * S.AggregateGroup[j] / nAggregateFrames, fToMs * S.AggregateGroup[j] / nAggregateFrames, fToMs * S.AggregateGroup[j]); +        } +    } + +    printf("\n\n"); +    printf("group,thread,average,total\n"); +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        for(uint32_t i = 0; i < S.nNumLogs; ++i) +        { +            if(S.Pool[i]) +            { +                const char* pThreadName = &S.Pool[i]->ThreadName[0]; +                // MicroProfilePrintf(CB, Handle, "var ThreadGroupTime%d = [", i); +                float fToMs = S.Pool[i]->nGpu ? fToMsGPU : fToMsCPU; +                { +                    uint64_t nTicks = S.Pool[i]->nAggregateGroupTicks[j]; +                    float fTime = nTicks / nAggregateFrames * fToMs; +                    float fTimeTotal = nTicks * fToMs; +                    if(fTimeTotal > 0.01f) +                    { +                        const char* pGroupName = S.GroupInfo[j].pName; +                        printf("\"%s\",\"%s\",%.3f,%.3f\n", pGroupName, pThreadName, fTime, fTimeTotal); +                    } +                } +            } +        } +    } + +    printf("\n\n"); +    printf("frametimecpu\n"); + +    const uint32_t nCount = MICROPROFILE_MAX_FRAME_HISTORY - MICROPROFILE_GPU_FRAME_DELAY - 3; +    const uint32_t nStart = S.nFrameCurrent; +    for(uint32_t i = nCount; i > 0; i--) +    { +        uint32_t nFrame = (nStart + MICROPROFILE_MAX_FRAME_HISTORY - i) % MICROPROFILE_MAX_FRAME_HISTORY; +        uint32_t nFrameNext = (nStart + MICROPROFILE_MAX_FRAME_HISTORY - i + 1) % MICROPROFILE_MAX_FRAME_HISTORY; +        uint64_t nTicks = S.Frames[nFrameNext].nFrameStartCpu - S.Frames[nFrame].nFrameStartCpu; +        printf("%f,", nTicks * fToMsCPU); +    } +    printf("\n"); + +    printf("\n\n"); +    printf("frametimegpu\n"); + +    for(uint32_t i = nCount; i > 0; i--) +    { +        uint32_t nFrame = (nStart + MICROPROFILE_MAX_FRAME_HISTORY - i) % MICROPROFILE_MAX_FRAME_HISTORY; +        uint32_t nFrameNext = (nStart + MICROPROFILE_MAX_FRAME_HISTORY - i + 1) % MICROPROFILE_MAX_FRAME_HISTORY; +        uint64_t nTicks = S.Frames[nFrameNext].nFrameStartGpu - S.Frames[nFrame].nFrameStartGpu; +        printf("%f,", nTicks * fToMsGPU); +    } +    printf("\n\n"); +    printf("Meta\n");//only single frame snapshot +    printf("name,average,max,total\n"); +    for(int j = 0; j < MICROPROFILE_META_MAX; ++j) +    { +        if(S.MetaCounters[j].pName) +        { +            printf("\"%s\",%f,%lld,%lld\n",S.MetaCounters[j].pName, S.MetaCounters[j].nSumAggregate / (float)nAggregateFrames, S.MetaCounters[j].nSumAggregateMax,S.MetaCounters[j].nSumAggregate); +        } +    } +} +#undef printf + +void MicroProfileDumpHtml(MicroProfileWriteCallback CB, void* Handle, int nMaxFrames, const char* pHost) +{ +    uint32_t nRunning = S.nRunning; +    S.nRunning = 0; +    //stall pushing of timers +    uint64_t nActiveGroup = S.nActiveGroup; +    S.nActiveGroup = 0; +    S.nPauseTicks = MP_TICK(); + + +    for(size_t i = 0; i < g_MicroProfileHtml_begin_count; ++i) +    { +        CB(Handle, g_MicroProfileHtml_begin_sizes[i]-1, g_MicroProfileHtml_begin[i]); +    } +    //dump info +    uint64_t nTicks = MP_TICK(); + +    float fToMsCPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    float fToMsGPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondGpu()); +    float fAggregateMs = fToMsCPU * (nTicks - S.nAggregateFlipTick); +    MicroProfilePrintf(CB, Handle, "var DumpHost = '%s';\n", pHost ? pHost : ""); +    time_t CaptureTime; +    time(&CaptureTime); +    MicroProfilePrintf(CB, Handle, "var DumpUtcCaptureTime = %ld;\n", CaptureTime); +    MicroProfilePrintf(CB, Handle, "var AggregateInfo = {'Frames':%d, 'Time':%f};\n", S.nAggregateFrames, fAggregateMs); + +    //categories +    MicroProfilePrintf(CB, Handle, "var CategoryInfo = Array(%d);\n",S.nCategoryCount); +    for(uint32_t i = 0; i < S.nCategoryCount; ++i) +    { +        MicroProfilePrintf(CB, Handle, "CategoryInfo[%d] = \"%s\";\n", i, S.CategoryInfo[i].pName); +    } + +    //groups +    MicroProfilePrintf(CB, Handle, "var GroupInfo = Array(%d);\n\n",S.nGroupCount); +    uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +    float fRcpAggregateFrames = 1.f / nAggregateFrames; +    for(uint32_t i = 0; i < S.nGroupCount; ++i) +    { +        MP_ASSERT(i == S.GroupInfo[i].nGroupIndex); +        float fToMs = S.GroupInfo[i].Type == MicroProfileTokenTypeCpu ? fToMsCPU : fToMsGPU; +        MicroProfilePrintf(CB, Handle, "GroupInfo[%d] = MakeGroup(%d, \"%s\", %d, %d, %d, %f, %f, %f, '#%02x%02x%02x');\n", +            S.GroupInfo[i].nGroupIndex, +            S.GroupInfo[i].nGroupIndex, +            S.GroupInfo[i].pName, +            S.GroupInfo[i].nCategory, +            S.GroupInfo[i].nNumTimers, +            S.GroupInfo[i].Type == MicroProfileTokenTypeGpu?1:0, +            fToMs * S.AggregateGroup[i], +            fToMs * S.AggregateGroup[i] / nAggregateFrames, +            fToMs * S.AggregateGroupMax[i], +            MICROPROFILE_UNPACK_RED(S.GroupInfo[i].nColor) & 0xff, +            MICROPROFILE_UNPACK_GREEN(S.GroupInfo[i].nColor) & 0xff, +            MICROPROFILE_UNPACK_BLUE(S.GroupInfo[i].nColor) & 0xff); +    } +    //timers + +    uint32_t nNumTimers = S.nTotalTimers; +    uint32_t nBlockSize = 2 * nNumTimers; +    float* pTimers = (float*)alloca(nBlockSize * 8 * sizeof(float)); +    float* pAverage = pTimers + nBlockSize; +    float* pMax = pTimers + 2 * nBlockSize; +    float* pCallAverage = pTimers + 3 * nBlockSize; +    float* pTimersExclusive = pTimers + 4 * nBlockSize; +    float* pAverageExclusive = pTimers + 5 * nBlockSize; +    float* pMaxExclusive = pTimers + 6 * nBlockSize; +    float* pTotal = pTimers + 7 * nBlockSize; + +    MicroProfileCalcAllTimers(pTimers, pAverage, pMax, pCallAverage, pTimersExclusive, pAverageExclusive, pMaxExclusive, pTotal, nNumTimers); + +    MicroProfilePrintf(CB, Handle, "\nvar TimerInfo = Array(%d);\n\n", S.nTotalTimers); +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        uint32_t nIdx = i * 2; +        MP_ASSERT(i == S.TimerInfo[i].nTimerIndex); +        MicroProfilePrintf(CB, Handle, "var Meta%d = [", i); +        bool bOnce = true; +        for(int j = 0; j < MICROPROFILE_META_MAX; ++j) +        { +            if(S.MetaCounters[j].pName) +            { +                uint32_t lala = S.MetaCounters[j].nCounters[i]; +                MicroProfilePrintf(CB, Handle, bOnce ? "%d" : ",%d", lala); +                bOnce = false; +            } +        } +        MicroProfilePrintf(CB, Handle, "];\n"); +        MicroProfilePrintf(CB, Handle, "var MetaAvg%d = [", i); +        bOnce = true; +        for(int j = 0; j < MICROPROFILE_META_MAX; ++j) +        { +            if(S.MetaCounters[j].pName) +            { +                MicroProfilePrintf(CB, Handle, bOnce ? "%f" : ",%f", fRcpAggregateFrames * S.MetaCounters[j].nAggregate[i]); +                bOnce = false; +            } +        } +        MicroProfilePrintf(CB, Handle, "];\n"); +        MicroProfilePrintf(CB, Handle, "var MetaMax%d = [", i); +        bOnce = true; +        for(int j = 0; j < MICROPROFILE_META_MAX; ++j) +        { +            if(S.MetaCounters[j].pName) +            { +                MicroProfilePrintf(CB, Handle, bOnce ? "%d" : ",%d", S.MetaCounters[j].nAggregateMax[i]); +                bOnce = false; +            } +        } +        MicroProfilePrintf(CB, Handle, "];\n"); + + +        uint32_t nColor = S.TimerInfo[i].nColor; +        uint32_t nColorDark = (nColor >> 1) & ~0x80808080; +        MicroProfilePrintf(CB, Handle, "TimerInfo[%d] = MakeTimer(%d, \"%s\", %d, '#%02x%02x%02x','#%02x%02x%02x', %f, %f, %f, %f, %f, %d, %f, Meta%d, MetaAvg%d, MetaMax%d);\n", S.TimerInfo[i].nTimerIndex, S.TimerInfo[i].nTimerIndex, S.TimerInfo[i].pName, S.TimerInfo[i].nGroupIndex, +            MICROPROFILE_UNPACK_RED(nColor) & 0xff, +            MICROPROFILE_UNPACK_GREEN(nColor) & 0xff, +            MICROPROFILE_UNPACK_BLUE(nColor) & 0xff, +            MICROPROFILE_UNPACK_RED(nColorDark) & 0xff, +            MICROPROFILE_UNPACK_GREEN(nColorDark) & 0xff, +            MICROPROFILE_UNPACK_BLUE(nColorDark) & 0xff, +            pAverage[nIdx], +            pMax[nIdx], +            pAverageExclusive[nIdx], +            pMaxExclusive[nIdx], +            pCallAverage[nIdx], +            S.Aggregate[i].nCount, +            pTotal[nIdx], +            i,i,i); + +    } + +    MicroProfilePrintf(CB, Handle, "\nvar ThreadNames = ["); +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            MicroProfilePrintf(CB, Handle, "'%s',", S.Pool[i]->ThreadName); +        } +        else +        { +            MicroProfilePrintf(CB, Handle, "'Thread %d',", i); +        } +    } +    MicroProfilePrintf(CB, Handle, "];\n\n"); + + +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            MicroProfilePrintf(CB, Handle, "var ThreadGroupTime%d = [", i); +            float fToMs = S.Pool[i]->nGpu ? fToMsGPU : fToMsCPU; +            for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +            { +                MicroProfilePrintf(CB, Handle, "%f,", S.Pool[i]->nAggregateGroupTicks[j]/nAggregateFrames * fToMs); +            } +            MicroProfilePrintf(CB, Handle, "];\n"); +        } +    } +    MicroProfilePrintf(CB, Handle, "\nvar ThreadGroupTimeArray = ["); +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            MicroProfilePrintf(CB, Handle, "ThreadGroupTime%d,", i); +        } +    } +    MicroProfilePrintf(CB, Handle, "];\n"); + + +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            MicroProfilePrintf(CB, Handle, "var ThreadGroupTimeTotal%d = [", i); +            float fToMs = S.Pool[i]->nGpu ? fToMsGPU : fToMsCPU; +            for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +            { +                MicroProfilePrintf(CB, Handle, "%f,", S.Pool[i]->nAggregateGroupTicks[j] * fToMs); +            } +            MicroProfilePrintf(CB, Handle, "];\n"); +        } +    } +    MicroProfilePrintf(CB, Handle, "\nvar ThreadGroupTimeTotalArray = ["); +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            MicroProfilePrintf(CB, Handle, "ThreadGroupTimeTotal%d,", i); +        } +    } +    MicroProfilePrintf(CB, Handle, "];"); + + + + +    MicroProfilePrintf(CB, Handle, "\nvar ThreadIds = ["); +    for(uint32_t i = 0; i < S.nNumLogs; ++i) +    { +        if(S.Pool[i]) +        { +            ThreadIdType ThreadId = S.Pool[i]->nThreadId; +            if(!ThreadId) +            { +                ThreadId = (ThreadIdType)-1; +            } +            MicroProfilePrintf(CB, Handle, "%d,", ThreadId); +        } +        else +        { +            MicroProfilePrintf(CB, Handle, "-1,", i); +        } +    } +    MicroProfilePrintf(CB, Handle, "];\n\n"); + +    MicroProfilePrintf(CB, Handle, "\nvar MetaNames = ["); +    for(int i = 0; i < MICROPROFILE_META_MAX; ++i) +    { +        if(S.MetaCounters[i].pName) +        { +            MicroProfilePrintf(CB, Handle, "'%s',", S.MetaCounters[i].pName); +        } +    } + + +    MicroProfilePrintf(CB, Handle, "];\n\n"); + + + +    uint32_t nNumFrames = (MICROPROFILE_MAX_FRAME_HISTORY - MICROPROFILE_GPU_FRAME_DELAY - 3); //leave a few to not overwrite +    nNumFrames = MicroProfileMin(nNumFrames, (uint32_t)nMaxFrames); + + +    uint32_t nFirstFrame = (S.nFrameCurrent + MICROPROFILE_MAX_FRAME_HISTORY - nNumFrames) % MICROPROFILE_MAX_FRAME_HISTORY; +    uint32_t nLastFrame = (nFirstFrame + nNumFrames) % MICROPROFILE_MAX_FRAME_HISTORY; +    MP_ASSERT(nLastFrame == (S.nFrameCurrent % MICROPROFILE_MAX_FRAME_HISTORY)); +    MP_ASSERT(nFirstFrame < MICROPROFILE_MAX_FRAME_HISTORY); +    MP_ASSERT(nLastFrame  < MICROPROFILE_MAX_FRAME_HISTORY); +    const int64_t nTickStart = S.Frames[nFirstFrame].nFrameStartCpu; +    const int64_t nTickEnd = S.Frames[nLastFrame].nFrameStartCpu; +    int64_t nTickStartGpu = S.Frames[nFirstFrame].nFrameStartGpu; + +    int64_t nTickReferenceCpu, nTickReferenceGpu; +    int64_t nTicksPerSecondCpu = MicroProfileTicksPerSecondCpu(); +    int64_t nTicksPerSecondGpu = MicroProfileTicksPerSecondGpu(); +    int nTickReference = 0; +    if(MicroProfileGetGpuTickReference(&nTickReferenceCpu, &nTickReferenceGpu)) +    { +        nTickStartGpu = (nTickStart - nTickReferenceCpu) * nTicksPerSecondGpu / nTicksPerSecondCpu + nTickReferenceGpu; +        nTickReference = 1; +    } + + +#if MICROPROFILE_DEBUG +    printf("dumping %d frames\n", nNumFrames); +    printf("dumping frame %d to %d\n", nFirstFrame, nLastFrame); +#endif + + +    uint32_t* nTimerCounter = (uint32_t*)alloca(sizeof(uint32_t)* S.nTotalTimers); +    memset(nTimerCounter, 0, sizeof(uint32_t) * S.nTotalTimers); + +    MicroProfilePrintf(CB, Handle, "var Frames = Array(%d);\n", nNumFrames); +    for(uint32_t i = 0; i < nNumFrames; ++i) +    { +        uint32_t nFrameIndex = (nFirstFrame + i) % MICROPROFILE_MAX_FRAME_HISTORY; +        uint32_t nFrameIndexNext = (nFrameIndex + 1) % MICROPROFILE_MAX_FRAME_HISTORY; + +        for(uint32_t j = 0; j < S.nNumLogs; ++j) +        { +            MicroProfileThreadLog* pLog = S.Pool[j]; +            int64_t nStartTickBase = pLog->nGpu ? nTickStartGpu : nTickStart; +            uint32_t nLogStart = S.Frames[nFrameIndex].nLogStart[j]; +            uint32_t nLogEnd = S.Frames[nFrameIndexNext].nLogStart[j]; + +            float fToMsCpu = MicroProfileTickToMsMultiplier(nTicksPerSecondCpu); +            float fToMsBase = MicroProfileTickToMsMultiplier(pLog->nGpu ? nTicksPerSecondGpu : nTicksPerSecondCpu); +            MicroProfilePrintf(CB, Handle, "var ts_%d_%d = [", i, j); +            if(nLogStart != nLogEnd) +            { +                uint32_t k = nLogStart; +                uint32_t nLogType = MicroProfileLogType(pLog->Log[k]); +                float fToMs = nLogType == MP_LOG_GPU_EXTRA ? fToMsCpu : fToMsBase; +                int64_t nStartTick = nLogType == MP_LOG_GPU_EXTRA ? nTickStart : nStartTickBase; +                float fTime = nLogType == MP_LOG_META ? 0.f : MicroProfileLogTickDifference(nStartTick, pLog->Log[k]) * fToMs; +                MicroProfilePrintf(CB, Handle, "%f", fTime); +                for(k = (k+1) % MICROPROFILE_BUFFER_SIZE; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE) +                { +                    uint32_t nLogType = MicroProfileLogType(pLog->Log[k]); +                    float fToMs = nLogType == MP_LOG_GPU_EXTRA ? fToMsCpu : fToMsBase; +                    nStartTick = nLogType == MP_LOG_GPU_EXTRA ? nTickStart : nStartTickBase; +                    float fTime = nLogType == MP_LOG_META ? 0.f : MicroProfileLogTickDifference(nStartTick, pLog->Log[k]) * fToMs; +                    MicroProfilePrintf(CB, Handle, ",%f", fTime); +                } +            } +            MicroProfilePrintf(CB, Handle, "];\n"); +            MicroProfilePrintf(CB, Handle, "var tt_%d_%d = [", i, j); +            if(nLogStart != nLogEnd) +            { +                uint32_t k = nLogStart; +                MicroProfilePrintf(CB, Handle, "%d", MicroProfileLogType(pLog->Log[k])); +                for(k = (k+1) % MICROPROFILE_BUFFER_SIZE; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE) +                { +                    uint32_t nLogType = MicroProfileLogType(pLog->Log[k]); +                    if(nLogType == MP_LOG_META) +                    { +                        //for meta, store the count + 3, which is the tick part +                        nLogType = 3 + MicroProfileLogGetTick(pLog->Log[k]); +                    } +                    MicroProfilePrintf(CB, Handle, ",%d", nLogType); +                } +            } +            MicroProfilePrintf(CB, Handle, "];\n"); + +            MicroProfilePrintf(CB, Handle, "var ti_%d_%d = [", i, j); +            if(nLogStart != nLogEnd) +            { +                uint32_t k = nLogStart; +                MicroProfilePrintf(CB, Handle, "%d", (uint32_t)MicroProfileLogTimerIndex(pLog->Log[k])); +                for(k = (k+1) % MICROPROFILE_BUFFER_SIZE; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE) +                { +                    uint32_t nTimerIndex = (uint32_t)MicroProfileLogTimerIndex(pLog->Log[k]); +                    MicroProfilePrintf(CB, Handle, ",%d", nTimerIndex); +                    nTimerCounter[nTimerIndex]++; +                } +            } +            MicroProfilePrintf(CB, Handle, "];\n"); + +        } + +        MicroProfilePrintf(CB, Handle, "var ts%d = [", i); +        for(uint32_t j = 0; j < S.nNumLogs; ++j) +        { +            MicroProfilePrintf(CB, Handle, "ts_%d_%d,", i, j); +        } +        MicroProfilePrintf(CB, Handle, "];\n"); +        MicroProfilePrintf(CB, Handle, "var tt%d = [", i); +        for(uint32_t j = 0; j < S.nNumLogs; ++j) +        { +            MicroProfilePrintf(CB, Handle, "tt_%d_%d,", i, j); +        } +        MicroProfilePrintf(CB, Handle, "];\n"); + +        MicroProfilePrintf(CB, Handle, "var ti%d = [", i); +        for(uint32_t j = 0; j < S.nNumLogs; ++j) +        { +            MicroProfilePrintf(CB, Handle, "ti_%d_%d,", i, j); +        } +        MicroProfilePrintf(CB, Handle, "];\n"); + + +        int64_t nFrameStart = S.Frames[nFrameIndex].nFrameStartCpu; +        int64_t nFrameEnd = S.Frames[nFrameIndexNext].nFrameStartCpu; + +        float fToMs = MicroProfileTickToMsMultiplier(nTicksPerSecondCpu); +        float fFrameMs = MicroProfileLogTickDifference(nTickStart, nFrameStart) * fToMs; +        float fFrameEndMs = MicroProfileLogTickDifference(nTickStart, nFrameEnd) * fToMs; +        float fFrameGpuMs = 0; +        float fFrameGpuEndMs = 0; +        if(nTickReference) +        { +            fFrameGpuMs = MicroProfileLogTickDifference(nTickStartGpu, S.Frames[nFrameIndex].nFrameStartGpu) * fToMsGPU; +            fFrameGpuEndMs = MicroProfileLogTickDifference(nTickStartGpu, S.Frames[nFrameIndexNext].nFrameStartGpu) * fToMsGPU; +        } +        MicroProfilePrintf(CB, Handle, "Frames[%d] = MakeFrame(%d, %f, %f, %f, %f, ts%d, tt%d, ti%d);\n", i, 0, fFrameMs, fFrameEndMs, fFrameGpuMs, fFrameGpuEndMs, i, i, i); +    } + +    uint32_t nContextSwitchStart = 0; +    uint32_t nContextSwitchEnd = 0; +    MicroProfileContextSwitchSearch(&nContextSwitchStart, &nContextSwitchEnd, nTickStart, nTickEnd); + +    uint32_t nWrittenBefore = S.nWebServerDataSent; +    MicroProfilePrintf(CB, Handle, "var CSwitchThreadInOutCpu = ["); +    for(uint32_t j = nContextSwitchStart; j != nContextSwitchEnd; j = (j+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE) +    { +        MicroProfileContextSwitch CS = S.ContextSwitch[j]; +        int nCpu = CS.nCpu; +        MicroProfilePrintf(CB, Handle, "%d,%d,%d,", CS.nThreadIn, CS.nThreadOut, nCpu); +    } +    MicroProfilePrintf(CB, Handle, "];\n"); +    MicroProfilePrintf(CB, Handle, "var CSwitchTime = ["); +    float fToMsCpu = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    for(uint32_t j = nContextSwitchStart; j != nContextSwitchEnd; j = (j+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE) +    { +        MicroProfileContextSwitch CS = S.ContextSwitch[j]; +        float fTime = MicroProfileLogTickDifference(nTickStart, CS.nTicks) * fToMsCpu; +        MicroProfilePrintf(CB, Handle, "%f,", fTime); +    } +    MicroProfilePrintf(CB, Handle, "];\n"); +    uint32_t nWrittenAfter = S.nWebServerDataSent; +    MicroProfilePrintf(CB, Handle, "//CSwitch Size %d\n", nWrittenAfter - nWrittenBefore); + + +    for(size_t i = 0; i < g_MicroProfileHtml_end_count; ++i) +    { +        CB(Handle, g_MicroProfileHtml_end_sizes[i]-1, g_MicroProfileHtml_end[i]); +    } + +    uint32_t* nGroupCounter = (uint32_t*)alloca(sizeof(uint32_t)* S.nGroupCount); + +    memset(nGroupCounter, 0, sizeof(uint32_t) * S.nGroupCount); +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        uint32_t nGroupIndex = S.TimerInfo[i].nGroupIndex; +        nGroupCounter[nGroupIndex] += nTimerCounter[i]; +    } + +    uint32_t* nGroupCounterSort = (uint32_t*)alloca(sizeof(uint32_t)* S.nGroupCount); +    uint32_t* nTimerCounterSort = (uint32_t*)alloca(sizeof(uint32_t)* S.nTotalTimers); +    for(uint32_t i = 0; i < S.nGroupCount; ++i) +    { +        nGroupCounterSort[i] = i; +    } +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        nTimerCounterSort[i] = i; +    } +    std::sort(nGroupCounterSort, nGroupCounterSort + S.nGroupCount, +        [nGroupCounter](const uint32_t l, const uint32_t r) +        { +            return nGroupCounter[l] > nGroupCounter[r]; +        } +    ); + +    std::sort(nTimerCounterSort, nTimerCounterSort + S.nTotalTimers, +        [nTimerCounter](const uint32_t l, const uint32_t r) +        { +            return nTimerCounter[l] > nTimerCounter[r]; +        } +    ); + +    MicroProfilePrintf(CB, Handle, "\n<!--\nMarker Per Group\n"); +    for(uint32_t i = 0; i < S.nGroupCount; ++i) +    { +        uint32_t idx = nGroupCounterSort[i]; +        MicroProfilePrintf(CB, Handle, "%8d:%s\n", nGroupCounter[idx], S.GroupInfo[idx].pName); +    } +    MicroProfilePrintf(CB, Handle, "Marker Per Timer\n"); +    for(uint32_t i = 0; i < S.nTotalTimers; ++i) +    { +        uint32_t idx = nTimerCounterSort[i]; +        MicroProfilePrintf(CB, Handle, "%8d:%s(%s)\n", nTimerCounter[idx], S.TimerInfo[idx].pName, S.GroupInfo[S.TimerInfo[idx].nGroupIndex].pName); +    } +    MicroProfilePrintf(CB, Handle, "\n-->\n"); + +    S.nActiveGroup = nActiveGroup; +    S.nRunning = nRunning; + +#if MICROPROFILE_DEBUG +    int64_t nTicksEnd = MP_TICK(); +    float fMs = fToMsCpu * (nTicksEnd - S.nPauseTicks); +    printf("html dump took %6.2fms\n", fMs); +#endif + + +} + +void MicroProfileWriteFile(void* Handle, size_t nSize, const char* pData) +{ +    fwrite(pData, nSize, 1, (FILE*)Handle); +} + +void MicroProfileDumpToFile() +{ +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +    if(S.nDumpFileNextFrame&1) +    { +        FILE* F = fopen(S.HtmlDumpPath, "w"); +        if(F) +        { +            MicroProfileDumpHtml(MicroProfileWriteFile, F, MICROPROFILE_WEBSERVER_MAXFRAMES, S.HtmlDumpPath); +            fclose(F); +        } +    } +    if(S.nDumpFileNextFrame&2) +    { +        FILE* F = fopen(S.CsvDumpPath, "w"); +        if(F) +        { +            MicroProfileDumpCsv(MicroProfileWriteFile, F, MICROPROFILE_WEBSERVER_MAXFRAMES); +            fclose(F); +        } +    } +} + +void MicroProfileFlushSocket(MpSocket Socket) +{ +    send(Socket, &S.WebServerBuffer[0], S.WebServerPut, 0); +    S.WebServerPut = 0; + +} + +void MicroProfileWriteSocket(void* Handle, size_t nSize, const char* pData) +{ +    S.nWebServerDataSent += nSize; +    MpSocket Socket = *(MpSocket*)Handle; +    if(nSize > MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE / 2) +    { +        MicroProfileFlushSocket(Socket); +        send(Socket, pData, nSize, 0); + +    } +    else +    { +        memcpy(&S.WebServerBuffer[S.WebServerPut], pData, nSize); +        S.WebServerPut += nSize; +        if(S.WebServerPut > MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE/2) +        { +            MicroProfileFlushSocket(Socket); +        } +    } +} + +#if MICROPROFILE_MINIZ +#ifndef MICROPROFILE_COMPRESS_BUFFER_SIZE +#define MICROPROFILE_COMPRESS_BUFFER_SIZE (256<<10) +#endif + +#define MICROPROFILE_COMPRESS_CHUNK (MICROPROFILE_COMPRESS_BUFFER_SIZE/2) +struct MicroProfileCompressedSocketState +{ +    unsigned char DeflateOut[MICROPROFILE_COMPRESS_CHUNK]; +    unsigned char DeflateIn[MICROPROFILE_COMPRESS_CHUNK]; +    mz_stream Stream; +    MpSocket Socket; +    uint32_t nSize; +    uint32_t nCompressedSize; +    uint32_t nFlushes; +    uint32_t nMemmoveBytes; +}; + +void MicroProfileCompressedSocketFlush(MicroProfileCompressedSocketState* pState) +{ +    mz_stream& Stream = pState->Stream; +    unsigned char* pSendStart = &pState->DeflateOut[0]; +    unsigned char* pSendEnd = &pState->DeflateOut[MICROPROFILE_COMPRESS_CHUNK - Stream.avail_out]; +    if(pSendStart != pSendEnd) +    { +        send(pState->Socket, (const char*)pSendStart, pSendEnd - pSendStart, 0); +        pState->nCompressedSize += pSendEnd - pSendStart; +    } +    Stream.next_out = &pState->DeflateOut[0]; +    Stream.avail_out = MICROPROFILE_COMPRESS_CHUNK; + +} +void MicroProfileCompressedSocketStart(MicroProfileCompressedSocketState* pState, MpSocket Socket) +{ +    mz_stream& Stream = pState->Stream; +    memset(&Stream, 0, sizeof(Stream)); +    Stream.next_out = &pState->DeflateOut[0]; +    Stream.avail_out = MICROPROFILE_COMPRESS_CHUNK; +    Stream.next_in = &pState->DeflateIn[0]; +    Stream.avail_in = 0; +    mz_deflateInit(&Stream, Z_DEFAULT_COMPRESSION); +    pState->Socket = Socket; +    pState->nSize = 0; +    pState->nCompressedSize = 0; +    pState->nFlushes = 0; +    pState->nMemmoveBytes = 0; + +} +void MicroProfileCompressedSocketFinish(MicroProfileCompressedSocketState* pState) +{ +    mz_stream& Stream = pState->Stream; +    MicroProfileCompressedSocketFlush(pState); +    int r = mz_deflate(&Stream, MZ_FINISH); +    MP_ASSERT(r == MZ_STREAM_END); +    MicroProfileCompressedSocketFlush(pState); +    r = mz_deflateEnd(&Stream); +    MP_ASSERT(r == MZ_OK); +} + +void MicroProfileCompressedWriteSocket(void* Handle, size_t nSize, const char* pData) +{ +    MicroProfileCompressedSocketState* pState = (MicroProfileCompressedSocketState*)Handle; +    mz_stream& Stream = pState->Stream; +    const unsigned char* pDeflateInEnd = Stream.next_in + Stream.avail_in; +    const unsigned char* pDeflateInStart = &pState->DeflateIn[0]; +    const unsigned char* pDeflateInRealEnd = &pState->DeflateIn[MICROPROFILE_COMPRESS_CHUNK]; +    pState->nSize += nSize; +    if(nSize <= pDeflateInRealEnd - pDeflateInEnd) +    { +        memcpy((void*)pDeflateInEnd, pData, nSize); +        Stream.avail_in += nSize; +        MP_ASSERT(Stream.next_in + Stream.avail_in <= pDeflateInRealEnd); +        return; +    } +    int Flush = 0; +    while(nSize) +    { +        pDeflateInEnd = Stream.next_in + Stream.avail_in; +        if(Flush) +        { +            pState->nFlushes++; +            MicroProfileCompressedSocketFlush(pState); +            pDeflateInRealEnd = &pState->DeflateIn[MICROPROFILE_COMPRESS_CHUNK]; +            if(pDeflateInEnd == pDeflateInRealEnd) +            { +                if(Stream.avail_in) +                { +                    MP_ASSERT(pDeflateInStart != Stream.next_in); +                    memmove((void*)pDeflateInStart, Stream.next_in, Stream.avail_in); +                    pState->nMemmoveBytes += Stream.avail_in; +                } +                Stream.next_in = pDeflateInStart; +                pDeflateInEnd = Stream.next_in + Stream.avail_in; +            } +        } +        size_t nSpace = pDeflateInRealEnd - pDeflateInEnd; +        size_t nBytes = MicroProfileMin(nSpace, nSize); +        MP_ASSERT(nBytes + pDeflateInEnd <= pDeflateInRealEnd); +        memcpy((void*)pDeflateInEnd, pData, nBytes); +        Stream.avail_in += nBytes; +        nSize -= nBytes; +        pData += nBytes; +        int r = mz_deflate(&Stream, MZ_NO_FLUSH); +        Flush = r == MZ_BUF_ERROR || nBytes == 0 || Stream.avail_out == 0 ? 1 : 0; +        MP_ASSERT(r == MZ_BUF_ERROR || r == MZ_OK); +        if(r == MZ_BUF_ERROR) +        { +            r = mz_deflate(&Stream, MZ_SYNC_FLUSH); +        } +    } +} +#endif + + +#ifndef MicroProfileSetNonBlocking //fcntl doesnt work on a some unix like platforms.. +void MicroProfileSetNonBlocking(MpSocket Socket, int NonBlocking) +{ +#ifdef _WIN32 +    u_long nonBlocking = NonBlocking ? 1 : 0; +    ioctlsocket(Socket, FIONBIO, &nonBlocking); +#else +    int Options = fcntl(Socket, F_GETFL); +    if(NonBlocking) +    { +        fcntl(Socket, F_SETFL, Options|O_NONBLOCK); +    } +    else +    { +        fcntl(Socket, F_SETFL, Options&(~O_NONBLOCK)); +    } +#endif +} +#endif + +void MicroProfileWebServerStart() +{ +#ifdef _WIN32 +    WSADATA wsa; +    if(WSAStartup(MAKEWORD(2, 2), &wsa)) +    { +        S.ListenerSocket = -1; +        return; +    } +#endif + +    S.ListenerSocket = socket(PF_INET, SOCK_STREAM, 6); +    MP_ASSERT(!MP_INVALID_SOCKET(S.ListenerSocket)); +    MicroProfileSetNonBlocking(S.ListenerSocket, 1); + +    S.nWebServerPort = (uint32_t)-1; +    struct sockaddr_in Addr; +    Addr.sin_family = AF_INET; +    Addr.sin_addr.s_addr = INADDR_ANY; +    for(int i = 0; i < 20; ++i) +    { +        Addr.sin_port = htons(MICROPROFILE_WEBSERVER_PORT+i); +        if(0 == bind(S.ListenerSocket, (sockaddr*)&Addr, sizeof(Addr))) +        { +            S.nWebServerPort = MICROPROFILE_WEBSERVER_PORT+i; +            break; +        } +    } +    listen(S.ListenerSocket, 8); +} + +void MicroProfileWebServerStop() +{ +#ifdef _WIN32 +    closesocket(S.ListenerSocket); +    WSACleanup(); +#else +    close(S.ListenerSocket); +#endif +} + +int MicroProfileParseGet(const char* pGet) +{ +    const char* pStart = pGet; +    while(*pGet != '\0') +    { +        if(*pGet < '0' || *pGet > '9') +            return 0; +        pGet++; +    } +    int nFrames = atoi(pStart); +    if(nFrames) +    { +        return nFrames; +    } +    else +    { +        return MICROPROFILE_WEBSERVER_MAXFRAMES; +    } +} +bool MicroProfileWebServerUpdate() +{ +    MICROPROFILE_SCOPEI("MicroProfile", "Webserver-update", -1); +    MpSocket Connection = accept(S.ListenerSocket, 0, 0); +    bool bServed = false; +    if(!MP_INVALID_SOCKET(Connection)) +    { +        std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex()); +        char Req[8192]; +        MicroProfileSetNonBlocking(Connection, 0); +        int nReceived = recv(Connection, Req, sizeof(Req)-1, 0); +        if(nReceived > 0) +        { +            Req[nReceived] = '\0'; +#if MICROPROFILE_MINIZ +#define MICROPROFILE_HTML_HEADER "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Encoding: deflate\r\nExpires: Tue, 01 Jan 2199 16:00:00 GMT\r\n\r\n" +#else +#define MICROPROFILE_HTML_HEADER "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nExpires: Tue, 01 Jan 2199 16:00:00 GMT\r\n\r\n" +#endif +            char* pHttp = strstr(Req, "HTTP/"); +            char* pGet = strstr(Req, "GET /"); +            char* pHost = strstr(Req, "Host: "); +            auto Terminate = [](char* pString) +            { +                char* pEnd = pString; +                while(*pEnd != '\0') +                { +                    if(*pEnd == '\r' || *pEnd == '\n' || *pEnd == ' ') +                    { +                        *pEnd = '\0'; +                        return; +                    } +                    pEnd++; +                } +            }; +            if(pHost) +            { +                pHost += sizeof("Host: ")-1; +                Terminate(pHost); +            } + +            if(pHttp && pGet) +            { +                *pHttp = '\0'; +                pGet += sizeof("GET /")-1; +                Terminate(pGet); +                int nFrames = MicroProfileParseGet(pGet); +                if(nFrames) +                { +                    uint64_t nTickStart = MP_TICK(); +                    send(Connection, MICROPROFILE_HTML_HEADER, sizeof(MICROPROFILE_HTML_HEADER)-1, 0); +                    uint64_t nDataStart = S.nWebServerDataSent; +                    S.WebServerPut = 0; +    #if 0 == MICROPROFILE_MINIZ +                    MicroProfileDumpHtml(MicroProfileWriteSocket, &Connection, nFrames, pHost); +                    uint64_t nDataEnd = S.nWebServerDataSent; +                    uint64_t nTickEnd = MP_TICK(); +                    uint64_t nDiff = (nTickEnd - nTickStart); +                    float fMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()) * nDiff; +                    int nKb = ((nDataEnd-nDataStart)>>10) + 1; +                    int nCompressedKb = nKb; +                    MicroProfilePrintf(MicroProfileWriteSocket, &Connection, "\n<!-- Sent %dkb in %.2fms-->\n\n",nKb, fMs); +                    MicroProfileFlushSocket(Connection); +    #else +                    MicroProfileCompressedSocketState CompressState; +                    MicroProfileCompressedSocketStart(&CompressState, Connection); +                    MicroProfileDumpHtml(MicroProfileCompressedWriteSocket, &CompressState, nFrames, pHost); +                    S.nWebServerDataSent += CompressState.nSize; +                    uint64_t nDataEnd = S.nWebServerDataSent; +                    uint64_t nTickEnd = MP_TICK(); +                    uint64_t nDiff = (nTickEnd - nTickStart); +                    float fMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()) * nDiff; +                    int nKb = ((nDataEnd-nDataStart)>>10) + 1; +                    int nCompressedKb = ((CompressState.nCompressedSize)>>10) + 1; +                    MicroProfilePrintf(MicroProfileCompressedWriteSocket, &CompressState, "\n<!-- Sent %dkb(compressed %dkb) in %.2fms-->\n\n", nKb, nCompressedKb, fMs); +                    MicroProfileCompressedSocketFinish(&CompressState); +                    MicroProfileFlushSocket(Connection); +    #endif + +    #if MICROPROFILE_DEBUG +                    printf("\n<!-- Sent %dkb(compressed %dkb) in %.2fms-->\n\n", nKb, nCompressedKb, fMs); +    #endif +                } +            } +        } +#ifdef _WIN32 +        closesocket(Connection); +#else +        close(Connection); +#endif +    } +    return bServed; +} +#endif + + + + +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +//functions that need to be implemented per platform. +void* MicroProfileTraceThread(void* unused); +bool MicroProfileIsLocalThread(uint32_t nThreadId); + + +void MicroProfileStartContextSwitchTrace() +{ +    if(!S.bContextSwitchRunning) +    { +        S.bContextSwitchRunning    = true; +        S.bContextSwitchStop = false; +        MicroProfileThreadStart(&S.ContextSwitchThread, MicroProfileTraceThread); +    } +} + +void MicroProfileStopContextSwitchTrace() +{ +    if(S.bContextSwitchRunning) +    { +        S.bContextSwitchStop = true; +        MicroProfileThreadJoin(&S.ContextSwitchThread); +    } +} + + +#ifdef _WIN32 +#define INITGUID +#include <evntrace.h> +#include <evntcons.h> +#include <strsafe.h> + + +static GUID g_MicroProfileThreadClassGuid = { 0x3d6fa8d1, 0xfe05, 0x11d0, 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c }; + +struct MicroProfileSCSwitch +{ +    uint32_t NewThreadId; +    uint32_t OldThreadId; +    int8_t   NewThreadPriority; +    int8_t   OldThreadPriority; +    uint8_t  PreviousCState; +    int8_t   SpareByte; +    int8_t   OldThreadWaitReason; +    int8_t   OldThreadWaitMode; +    int8_t   OldThreadState; +    int8_t   OldThreadWaitIdealProcessor; +    uint32_t NewThreadWaitTime; +    uint32_t Reserved; +}; + + +VOID WINAPI MicroProfileContextSwitchCallback(PEVENT_TRACE pEvent) +{ +    if (pEvent->Header.Guid == g_MicroProfileThreadClassGuid) +    { +        if (pEvent->Header.Class.Type == 36) +        { +            MicroProfileSCSwitch* pCSwitch = (MicroProfileSCSwitch*) pEvent->MofData; +            if ((pCSwitch->NewThreadId != 0) || (pCSwitch->OldThreadId != 0)) +            { +                MicroProfileContextSwitch Switch; +                Switch.nThreadOut = pCSwitch->OldThreadId; +                Switch.nThreadIn = pCSwitch->NewThreadId; +                Switch.nCpu = pEvent->BufferContext.ProcessorNumber; +                Switch.nTicks = pEvent->Header.TimeStamp.QuadPart; +                MicroProfileContextSwitchPut(&Switch); +            } +        } +    } +} + +ULONG WINAPI MicroProfileBufferCallback(PEVENT_TRACE_LOGFILE Buffer) +{ +    return (S.bContextSwitchStop || !S.bContextSwitchRunning) ? FALSE : TRUE; +} + + +struct MicroProfileKernelTraceProperties : public EVENT_TRACE_PROPERTIES +{ +    char dummy[sizeof(KERNEL_LOGGER_NAME)]; +}; + +void MicroProfileContextSwitchShutdownTrace() +{ +    TRACEHANDLE SessionHandle = 0; +    MicroProfileKernelTraceProperties sessionProperties; + +    ZeroMemory(&sessionProperties, sizeof(sessionProperties)); +    sessionProperties.Wnode.BufferSize = sizeof(sessionProperties); +    sessionProperties.Wnode.Flags = WNODE_FLAG_TRACED_GUID; +    sessionProperties.Wnode.ClientContext = 1; //QPC clock resolution +    sessionProperties.Wnode.Guid = SystemTraceControlGuid; +    sessionProperties.BufferSize = 1; +    sessionProperties.NumberOfBuffers = 128; +    sessionProperties.EnableFlags = EVENT_TRACE_FLAG_CSWITCH; +    sessionProperties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; +    sessionProperties.MaximumFileSize = 0; +    sessionProperties.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); +    sessionProperties.LogFileNameOffset = 0; + +    EVENT_TRACE_LOGFILE log; +    ZeroMemory(&log, sizeof(log)); +    log.LoggerName = KERNEL_LOGGER_NAME; +    log.ProcessTraceMode = 0; +    TRACEHANDLE hLog = OpenTrace(&log); +    if (hLog) +    { +        ControlTrace(SessionHandle, KERNEL_LOGGER_NAME, &sessionProperties, EVENT_TRACE_CONTROL_STOP); +    } +    CloseTrace(hLog); + + +} + +void* MicroProfileTraceThread(void* unused) +{ + +    MicroProfileContextSwitchShutdownTrace(); +    ULONG status = ERROR_SUCCESS; +    TRACEHANDLE SessionHandle = 0; +    MicroProfileKernelTraceProperties sessionProperties; + +    ZeroMemory(&sessionProperties, sizeof(sessionProperties)); +    sessionProperties.Wnode.BufferSize = sizeof(sessionProperties); +    sessionProperties.Wnode.Flags = WNODE_FLAG_TRACED_GUID; +    sessionProperties.Wnode.ClientContext = 1; //QPC clock resolution +    sessionProperties.Wnode.Guid = SystemTraceControlGuid; +    sessionProperties.BufferSize = 1; +    sessionProperties.NumberOfBuffers = 128; +    sessionProperties.EnableFlags = EVENT_TRACE_FLAG_CSWITCH|EVENT_TRACE_FLAG_PROCESS; +    sessionProperties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; +    sessionProperties.MaximumFileSize = 0; +    sessionProperties.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); +    sessionProperties.LogFileNameOffset = 0; + + +    status = StartTrace((PTRACEHANDLE) &SessionHandle, KERNEL_LOGGER_NAME, &sessionProperties); + +    if (ERROR_SUCCESS != status) +    { +        S.bContextSwitchRunning = false; +        return 0; +    } + +    EVENT_TRACE_LOGFILE log; +    ZeroMemory(&log, sizeof(log)); + +    log.LoggerName = KERNEL_LOGGER_NAME; +    log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_RAW_TIMESTAMP; +    log.EventCallback = MicroProfileContextSwitchCallback; +    log.BufferCallback = MicroProfileBufferCallback; + +    TRACEHANDLE hLog = OpenTrace(&log); +    ProcessTrace(&hLog, 1, 0, 0); +    CloseTrace(hLog); +    MicroProfileContextSwitchShutdownTrace(); + +    S.bContextSwitchRunning = false; +    return 0; +} + +bool MicroProfileIsLocalThread(uint32_t nThreadId) +{ +    HANDLE h = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, nThreadId); +    if(h == NULL) +        return false; +    DWORD hProcess = GetProcessIdOfThread(h); +    CloseHandle(h); +    return GetCurrentProcessId() == hProcess; +} + +#elif defined(__APPLE__) +#include <sys/time.h> +void* MicroProfileTraceThread(void* unused) +{ +    FILE* pFile = fopen("mypipe", "r"); +    if(!pFile) +    { +        printf("CONTEXT SWITCH FAILED TO OPEN FILE: make sure to run dtrace script\n"); +        S.bContextSwitchRunning = false; +        return 0; +    } +    printf("STARTING TRACE THREAD\n"); +    char* pLine = 0; +    size_t cap = 0; +    size_t len = 0; +    struct timeval tv; + +    gettimeofday(&tv, NULL); + +    uint64_t nsSinceEpoch = ((uint64_t)(tv.tv_sec) * 1000000 + (uint64_t)(tv.tv_usec)) * 1000; +    uint64_t nTickEpoch = MP_TICK(); +    uint32_t nLastThread[MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS] = {0}; +    mach_timebase_info_data_t sTimebaseInfo; +    mach_timebase_info(&sTimebaseInfo); +    S.bContextSwitchRunning = true; + +    uint64_t nProcessed = 0; +    uint64_t nProcessedLast = 0; +    while((len = getline(&pLine, &cap, pFile))>0 && !S.bContextSwitchStop) +    { +        nProcessed += len; +        if(nProcessed - nProcessedLast > 10<<10) +        { +            nProcessedLast = nProcessed; +            printf("processed %llukb %llukb\n", (nProcessed-nProcessedLast)>>10,nProcessed >>10); +        } + +        char* pX = strchr(pLine, 'X'); +        if(pX) +        { +            int cpu = atoi(pX+1); +            char* pX2 = strchr(pX + 1, 'X'); +            char* pX3 = strchr(pX2 + 1, 'X'); +            int thread = atoi(pX2+1); +            char* lala; +            int64_t timestamp = strtoll(pX3 + 1, &lala, 10); +            MicroProfileContextSwitch Switch; + +            //convert to ticks. +            uint64_t nDeltaNsSinceEpoch = timestamp - nsSinceEpoch; +            uint64_t nDeltaTickSinceEpoch = sTimebaseInfo.numer * nDeltaNsSinceEpoch / sTimebaseInfo.denom; +            uint64_t nTicks = nDeltaTickSinceEpoch + nTickEpoch; +            if(cpu < MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS) +            { +                Switch.nThreadOut = nLastThread[cpu]; +                Switch.nThreadIn = thread; +                nLastThread[cpu] = thread; +                Switch.nCpu = cpu; +                Switch.nTicks = nTicks; +                MicroProfileContextSwitchPut(&Switch); +            } +        } +    } +    printf("EXITING TRACE THREAD\n"); +    S.bContextSwitchRunning = false; +    return 0; +} + +bool MicroProfileIsLocalThread(uint32_t nThreadId) +{ +    return false; +} + +#endif +#else + +bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;} +void MicroProfileStopContextSwitchTrace(){} +void MicroProfileStartContextSwitchTrace(){} + +#endif + + + + +#if MICROPROFILE_GPU_TIMERS_D3D11 +uint32_t MicroProfileGpuInsertTimeStamp() +{ +    MicroProfileD3D11Frame& Frame = S.GPU.m_QueryFrames[S.GPU.m_nQueryFrame]; +    if(Frame.m_nRateQueryStarted) +    { +        uint32_t nCurrent = (Frame.m_nQueryStart + Frame.m_nQueryCount) % MICROPROFILE_D3D_MAX_QUERIES; +        uint32_t nNext = (nCurrent + 1) % MICROPROFILE_D3D_MAX_QUERIES; +        if(nNext != S.GPU.m_nQueryGet) +        { +            Frame.m_nQueryCount++; +            ID3D11Query* pQuery = (ID3D11Query*)S.GPU.m_pQueries[nCurrent]; +            ID3D11DeviceContext* pContext = (ID3D11DeviceContext*)S.GPU.m_pDeviceContext; +            pContext->End(pQuery); +            S.GPU.m_nQueryPut = nNext; +            return nCurrent; +        } +    } +    return (uint32_t)-1; +} + +uint64_t MicroProfileGpuGetTimeStamp(uint32_t nIndex) +{ +    if(nIndex == (uint32_t)-1) +    { +        return (uint64_t)-1; +    } +    int64_t nResult = S.GPU.m_nQueryResults[nIndex]; +    MP_ASSERT(nResult != -1); +    return nResult; +} + +bool MicroProfileGpuGetData(void* pQuery, void* pData, uint32_t nDataSize) +{ +    HRESULT hr; +    do +    { +        hr = ((ID3D11DeviceContext*)S.GPU.m_pDeviceContext)->GetData((ID3D11Query*)pQuery, pData, nDataSize, 0); +    }while(hr == S_FALSE); +    switch(hr) +    { +        case DXGI_ERROR_DEVICE_REMOVED: +        case DXGI_ERROR_INVALID_CALL: +        case E_INVALIDARG: +            MP_BREAK(); +            return false; + +    } +    return true; +} + +uint64_t MicroProfileTicksPerSecondGpu() +{ +    return S.GPU.m_nQueryFrequency; +} + +void MicroProfileGpuFlip() +{ +    MicroProfileD3D11Frame& CurrentFrame = S.GPU.m_QueryFrames[S.GPU.m_nQueryFrame]; +    ID3D11DeviceContext* pContext = (ID3D11DeviceContext*)S.GPU.m_pDeviceContext; +    if(CurrentFrame.m_nRateQueryStarted) +    { +        pContext->End((ID3D11Query*)CurrentFrame.m_pRateQuery); +    } +    uint32_t nNextFrame = (S.GPU.m_nQueryFrame + 1) % MICROPROFILE_GPU_FRAME_DELAY; +    MicroProfileD3D11Frame& OldFrame = S.GPU.m_QueryFrames[nNextFrame]; +    if(OldFrame.m_nRateQueryStarted) +    { +        struct RateQueryResult +        { +            uint64_t nFrequency; +            BOOL bDisjoint; +        }; +        RateQueryResult Result; +        if(MicroProfileGpuGetData(OldFrame.m_pRateQuery, &Result, sizeof(Result))) +        { +            if(S.GPU.m_nQueryFrequency != (int64_t)Result.nFrequency) +            { +                if(S.GPU.m_nQueryFrequency) +                { +                    OutputDebugString("Query freq changing"); +                } +                S.GPU.m_nQueryFrequency = Result.nFrequency; +            } +            uint32_t nStart = OldFrame.m_nQueryStart; +            uint32_t nCount = OldFrame.m_nQueryCount; +            for(uint32_t i = 0; i < nCount; ++i) +            { +                uint32_t nIndex = (i + nStart) % MICROPROFILE_D3D_MAX_QUERIES; + + + +                if(!MicroProfileGpuGetData(S.GPU.m_pQueries[nIndex], &S.GPU.m_nQueryResults[nIndex], sizeof(uint64_t))) +                { +                    S.GPU.m_nQueryResults[nIndex] = -1; +                } +            } +        } +        else +        { +            uint32_t nStart = OldFrame.m_nQueryStart; +            uint32_t nCount = OldFrame.m_nQueryCount; +            for(uint32_t i = 0; i < nCount; ++i) +            { +                uint32_t nIndex = (i + nStart) % MICROPROFILE_D3D_MAX_QUERIES; +                S.GPU.m_nQueryResults[nIndex] = -1; +            } +        } +        S.GPU.m_nQueryGet = (OldFrame.m_nQueryStart + OldFrame.m_nQueryCount) % MICROPROFILE_D3D_MAX_QUERIES; +    } + +    S.GPU.m_nQueryFrame = nNextFrame; +    MicroProfileD3D11Frame& NextFrame = S.GPU.m_QueryFrames[nNextFrame]; +    pContext->Begin((ID3D11Query*)NextFrame.m_pRateQuery); +    NextFrame.m_nQueryStart = S.GPU.m_nQueryPut; +    NextFrame.m_nQueryCount = 0; + +    NextFrame.m_nRateQueryStarted = 1; +} + +void MicroProfileGpuInitD3D11(void* pDevice_, void* pDeviceContext_) +{ +    ID3D11Device* pDevice = (ID3D11Device*)pDevice_; +    ID3D11DeviceContext* pDeviceContext = (ID3D11DeviceContext*)pDeviceContext_; +    S.GPU.m_pDeviceContext = pDeviceContext_; + +    D3D11_QUERY_DESC Desc; +    Desc.MiscFlags = 0; +    Desc.Query = D3D11_QUERY_TIMESTAMP; +    for(uint32_t i = 0; i < MICROPROFILE_D3D_MAX_QUERIES; ++i) +    { +        HRESULT hr = pDevice->CreateQuery(&Desc, (ID3D11Query**)&S.GPU.m_pQueries[i]); +        MP_ASSERT(hr == S_OK); +        S.GPU.m_nQueryResults[i] = -1; +    } +    S.GPU.m_nQueryPut = 0; +    S.GPU.m_nQueryGet = 0; +    S.GPU.m_nQueryFrame = 0; +    S.GPU.m_nQueryFrequency = 0; +    Desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; +    for(uint32_t i = 0; i < MICROPROFILE_GPU_FRAME_DELAY; ++i) +    { +        S.GPU.m_QueryFrames[i].m_nQueryStart = 0; +        S.GPU.m_QueryFrames[i].m_nQueryCount = 0; +        S.GPU.m_QueryFrames[i].m_nRateQueryStarted = 0; +        HRESULT hr = pDevice->CreateQuery(&Desc, (ID3D11Query**)&S.GPU.m_QueryFrames[i].m_pRateQuery); +        MP_ASSERT(hr == S_OK); +    } +} + + +void MicroProfileGpuShutdown() +{ +    for(uint32_t i = 0; i < MICROPROFILE_D3D_MAX_QUERIES; ++i) +    { +        ((ID3D11Query*)&S.GPU.m_pQueries[i])->Release(); +        S.GPU.m_pQueries[i] = 0; +    } +    for(uint32_t i = 0; i < MICROPROFILE_GPU_FRAME_DELAY; ++i) +    { +        ((ID3D11Query*)S.GPU.m_QueryFrames[i].m_pRateQuery)->Release(); +        S.GPU.m_QueryFrames[i].m_pRateQuery = 0; +    } +} + +int MicroProfileGetGpuTickReference(int64_t* pOutCPU, int64_t* pOutGpu) +{ +    return 0; +} + + +#elif MICROPROFILE_GPU_TIMERS_GL +void MicroProfileGpuInitGL() +{ +    S.GPU.GLTimerPos = 0; +    glGenQueries(MICROPROFILE_GL_MAX_QUERIES, &S.GPU.GLTimers[0]); +} + +uint32_t MicroProfileGpuInsertTimeStamp() +{ +    uint32_t nIndex = (S.GPU.GLTimerPos+1)%MICROPROFILE_GL_MAX_QUERIES; +    glQueryCounter(S.GPU.GLTimers[nIndex], GL_TIMESTAMP); +    S.GPU.GLTimerPos = nIndex; +    return nIndex; +} +uint64_t MicroProfileGpuGetTimeStamp(uint32_t nKey) +{ +    uint64_t result; +    glGetQueryObjectui64v(S.GPU.GLTimers[nKey], GL_QUERY_RESULT, &result); +    return result; +} + +uint64_t MicroProfileTicksPerSecondGpu() +{ +    return 1000000000ll; +} + +int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu) +{ +    int64_t nGpuTimeStamp; +    glGetInteger64v(GL_TIMESTAMP, &nGpuTimeStamp); +    if(nGpuTimeStamp) +    { +        *pOutCpu = MP_TICK(); +        *pOutGpu = nGpuTimeStamp; +        #if 0 //debug test if timestamp diverges +        static int64_t nTicksPerSecondCpu = MicroProfileTicksPerSecondCpu(); +        static int64_t nTicksPerSecondGpu = MicroProfileTicksPerSecondGpu(); +        static int64_t nGpuStart = 0; +        static int64_t nCpuStart = 0; +        if(!nCpuStart) +        { +            nCpuStart = *pOutCpu; +            nGpuStart = *pOutGpu; +        } +        static int nCountDown = 100; +        if(0 == nCountDown--) +        { +            int64_t nCurCpu = *pOutCpu; +            int64_t nCurGpu = *pOutGpu; +            double fDistanceCpu = (nCurCpu - nCpuStart) / (double)nTicksPerSecondCpu; +            double fDistanceGpu = (nCurGpu - nGpuStart) / (double)nTicksPerSecondGpu; + +            char buf[254]; +            snprintf(buf, sizeof(buf)-1,"Distance %f %f diff %f\n", fDistanceCpu, fDistanceGpu, fDistanceCpu-fDistanceGpu); +            OutputDebugString(buf); +            nCountDown = 100; +        } +        #endif +        return 1; +    } +    return 0; +} + + +#endif + +#undef S + +#ifdef _WIN32 +#pragma warning(pop) +#endif + + + + + +#endif +#endif +#ifdef MICROPROFILE_EMBED_HTML +#include "microprofile_html.h" +#endif diff --git a/externals/microprofile/microprofile_html.h b/externals/microprofile/microprofile_html.h new file mode 100644 index 000000000..01b624b60 --- /dev/null +++ b/externals/microprofile/microprofile_html.h @@ -0,0 +1,3868 @@ +///start file generated from microprofile.html +#ifdef MICROPROFILE_EMBED_HTML +const char g_MicroProfileHtml_begin_0[] = +"<!DOCTYPE HTML>\n" +"<html>\n" +"<head>\n" +"<title>MicroProfile Capture</title>\n" +"<style>\n" +"/* about css: http://bit.ly/1eMQ42U */\n" +"body {margin: 0px;padding: 0px; font: 12px Courier New;background-color:#474747; color:white;overflow:hidden;}\n" +"ul {list-style-type: none;margin: 0;padding: 0;}\n" +"li{display: inline; float:left;border:5px; position:relative;text-align:center;}\n" +"a {\n" +"    float:left;\n" +"    text-decoration:none;\n" +"    display: inline;\n" +"    text-align: center;\n" +"   padding:5px;\n" +"   padding-bottom:0px;\n" +"   padding-top:0px;\n" +"    color: #FFFFFF;\n" +"    background-color: #474747;\n" +"}\n" +"a:hover, a:active{\n" +"   background-color: #000000;\n" +"}\n" +"\n" +"ul ul {\n" +"    position:absolute;\n" +"    left:0;\n" +"    top:100%;\n" +"    margin-left:-999em;\n" +"}\n" +"li:hover ul {\n" +"    margin-left:0;\n" +"    margin-right:0;\n" +"}\n" +"ul li ul{ display:block;float:none;width:100%;}\n" +"ul li ul li{ display:block;float:none;width:100%;}\n" +"li li a{ display:block;float:none;width:100%;text-align:left;}\n" +"#nav li:hover div {margin-left:0;}\n" +".help {position:absolute;z-index:5;text-align:left;padding:2px;margin-left:-999em;background-color: #313131;width:300px;}\n" +".helpstart {position:absolute;z-index:5;text-align:left;padding:2px;background-color: #313131;width:300px;display:none}\n" +".root {z-index:1;position:absolute;top:0px;left:0px;}\n" +"</style>\n" +"</head>\n" +"<body style=\"\">\n" +"<canvas id=\"History\" height=\"70\" style=\"background-color:#474747;margin:0px;padding:0px;\"></canvas><canvas id=\"DetailedView\" height=\"200\" style=\"background-color:#474747;margin:0px;padding:0px;\"></canvas>\n" +"<div id=\"root\" class=\"root\">\n" +"<ul id=\"nav\">\n" +"<li><a href=\"javascript:void(0)\" onclick=\"ToggleDebugMode();\">?</a>\n" +"<div class=\"helpstart\" id=\"helpwindow\" style=\"left:20px;top:20px\">\n" +"History View:<br>\n" +"Click + Drag: Pan View<br>\n" +"Right Click + Drag : Zoom on region<br>\n" +"Click Frame : Center on frame<br>\n" +"<hr>\n" +"Main View:<br>\n" +"Ctrl + Mouse up/down: Zoom<br>\n" +"Mousewheel : Zoom<br>\n" +"Right Click + Drag: Zoom to region<br>\n" +"Ctrl + Drag: Pan<br>\n" +"Click + Drag: Pan<br>\n" +"<hr>\n" +"<table style=\"width:100%\">\n" +"<tr>\n" +"<td width=\"50%\" align=\"left\"><a href=\'javascript:void(0)\' onclick=\"ShowHelp(0, 0);\">Close</a></td>\n" +"<td width=\"50%\" align=\"right\"><a href=\'javascript:void(0)\' onclick=\"ShowHelp(0, 1);\">Close, Never Show</a></td>\n" +"</tr>\n" +"</table>\n" +"</div>\n" +"<div class=\"help\" id=\"divFrameInfo\" style=\"left:20px;top:300px;width:auto;\">\n" +"</div>\n" +"</li>\n" +"<li><a id=\'ModeSubMenuText\'>Mode</a>\n" +"    <ul id=\'ModeSubMenu\'>\n" +"       <li><a href=\"javascript:void(0)\" onclick=\"SetMode(\'timers\', 0);\" id=\"buttonTimers\">Timers</a></li>\n" +"       <li><a href=\"javascript:void(0)\" onclick=\"SetMode(\'timers\', 1);\" id=\"buttonGroups\">Groups</a></li> \n" +"       <li><a href=\"javascript:void(0)\" onclick=\"SetMode(\'timers\', 2);\" id=\"buttonThreads\">Threads</a></li>\n" +"       <li><a href=\"javascript:void(0)\" onclick=\"SetMode(\'detailed\', 0);\" id=\"buttonDetailed\">Detailed</a></li>\n" +"   </ul>\n" +"</li>\n" +"<li><a>Reference</a>\n" +"    <ul id=\'ReferenceSubMenu\'>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'5ms\');\">5ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'10ms\');\">10ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'15ms\');\">15ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'20ms\');\">20ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'33ms\');\">33ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'50ms\');\">50ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'100ms\');\">100ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'250ms\');\">250ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'500ms\');\">500ms</a></li>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"SetReferenceTime(\'1000ms\');\">1000ms</a></li>\n" +"    </ul>\n" +"</li>\n" +"<li id=\"ilThreads\"><a>Threads</a>\n" +"    <ul id=\"ThreadSubMenu\">\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"ToggleThread();\">All</a></li>\n" +"        <li><a>---</a></li>\n" +"    </ul>\n" +"</li>\n" +"<li id=\"ilGroups\"><a>Groups</a>\n" +"    <ul id=\"GroupSubMenu\">\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"ToggleGroup();\">All</a></li>\n" +"        <li><a>---</a></li>\n" +"    </ul>\n" +"</li>\n" +"<li id=\"ilOptions\"><a>Options      </a>\n" +"    <ul id=\'OptionsMenu\'>\n" +"        <li><a href=\"javascript:void(0)\" onclick=\"ToggleContextSwitch();\">Context Switch</a></li>\n" +"       <li><a href=\"javascript:void(0)\" onclick=\"ToggleDisableMerge();\">MergeDisable</a></li>\n" +"       <li><a href=\"javascript:void(0)\" onclick=\"ToggleDisableLod();\">LodDisable</a></li>\n" +"       <li id=\'GroupColors\'><a href=\"javascript:void(0)\" onclick=\"ToggleGroupColors();\">Group Colors</a></li>\n" +"        <li id=\'TimersMeta\'><a href=\"javascript:void(0)\" onclick=\"ToggleTimersMeta();\">Meta</a></li>\n" +"        <li id=\'ShowHelp\'><a href=\"javascript:void(0)\" onclick=\"ShowHelp(1,1);\">Help</a></li>\n" +"<!--       <li><a href=\"javascript:void(0)\" onclick=\"ToggleDebug();\">DEBUG</a></li> -->\n" +"    </ul>\n" +"</li>\n" +"</ul>\n" +"</div>\n" +"<script>\n" +"function InvertColor(hexTripletColor) {\n" +"   var color = hexTripletColor;\n" +"   color = color.substring(1); // remove #\n" +"   color = parseInt(color, 16); // convert to integer\n" +"   var R = ((color >> 16) % 256)/255.0;\n" +"   var G = ((color >> 8) % 256)/255.0;\n" +"   var B = ((color >> 0) % 256)/255.0;\n" +"   var lum = (0.2126*R + 0.7152*G + 0.0722*B);\n" +"   if(lum < 0.7)\n" +"   {\n" +"       return \'#ffffff\';\n" +"   }\n" +"   else\n" +"   {\n" +"       return \'#333333\';\n" +"   }\n" +"}\n" +"function InvertColorIndex(hexTripletColor) {\n" +"   var color = hexTripletColor;\n" +"   color = color.substring(1); // remove #\n" +"   color = parseInt(color, 16); // convert to integer\n" +"   var R = ((color >> 16) % 256)/255.0;\n" +"   var G = ((color >> 8) % 256)/255.0;\n" +"   var B = ((color >> 0) % 256)/255.0;\n" +"   var lum = (0.2126*R + 0.7152*G + 0.0722*B);\n" +"   if(lum < 0.7)\n" +"   {\n" +"       return 0;\n" +"   }\n" +"   else\n" +"   {\n" +"       return 1;\n" +"   }\n" +"}\n" +"function MakeGroup(id, name, category, numtimers, isgpu, total, average, max, color)\n" +"{\n" +"   var group = {\"id\":id, \"name\":name, \"category\":category, \"numtimers\":numtimers, \"isgpu\":isgpu, \"total\": total, \"average\" : average, \"max\" : max, \"color\":color};\n" +"   return group;\n" +"}\n" +"\n" +"function MakeTimer(id, name, group, color, colordark, average, max, exclaverage, exclmax, callaverage, callcount, total, meta, metaavg, metamax)\n" +"{\n" +"   var timer = {\"id\":id, \"name\":name, \"len\":name.length, \"color\":color, \"colordark\":colordark,\"timercolor\":color, \"textcolor\":InvertColor(color), \"group\":group, \"average\":average, \"max\":max, \"exclaverage\":exclaverage, \"exclmax\":exclmax, \"callaverage\":callaverage, \"callcount\":callcount, \"total\":total, \"meta\":meta, \"textcolorindex\":InvertColorIndex(color), \"metaavg\":metaavg, \"metamax\":metamax};\n" +"   return timer;\n" +"}\n" +"function MakeFrame(id, framestart, frameend, framestartgpu, frameendgpu, ts, tt, ti)\n" +"{\n" +"   var frame = {\"id\":id, \"framestart\":framestart, \"frameend\":frameend, \"framestartgpu\":framestartgpu, \"frameendgpu\":frameendgpu, \"ts\":ts, \"tt\":tt, \"ti\":ti};\n" +"   return frame;\n" +"}\n" +"\n" +""; + +const size_t g_MicroProfileHtml_begin_0_size = sizeof(g_MicroProfileHtml_begin_0); +const char* g_MicroProfileHtml_begin[] = { +&g_MicroProfileHtml_begin_0[0], +}; +size_t g_MicroProfileHtml_begin_sizes[] = { +sizeof(g_MicroProfileHtml_begin_0), +}; +size_t g_MicroProfileHtml_begin_count = 1; +const char g_MicroProfileHtml_end_0[] = +"\n" +"\n" +"\n" +"var CanvasDetailedView = document.getElementById(\'DetailedView\');\n" +"var CanvasHistory = document.getElementById(\'History\');\n" +"var CanvasDetailedOffscreen = document.createElement(\'canvas\');\n" +"var g_Msg = \'0\';\n" +"\n" +"var Initialized = 0;\n" +"var fDetailedOffset = Frames[0].framestart;\n" +"var fDetailedRange = Frames[0].frameend - fDetailedOffset;\n" +"var nWidth = CanvasDetailedView.width;\n" +"var nHeight = CanvasDetailedView.height;\n" +"var ReferenceTime = 33;\n" +"var nHistoryHeight = 70;\n" +"var nOffsetY = 0;\n" +"var nOffsetBarsX = 0;\n" +"var nOffsetBarsY = 0;\n" +"var nBarsWidth = 80;\n" +"var NameWidth = 200;\n" +"var MouseButtonState = [0,0,0,0,0,0,0,0];\n" +"var KeyShiftDown = 0;\n" +"var MouseDragButton = 0;\n" +"var KeyCtrlDown = 0;\n" +"var FlipToolTip = 0;\n" +"var DetailedViewMouseX = 0;\n" +"var DetailedViewMouseY = 0;\n" +"var HistoryViewMouseX = -1;\n" +"var HistoryViewMouseY = -1;\n" +"var MouseHistory = 0;\n" +"var MouseDetailed = 0;\n" +"var FontHeight = 10;\n" +"var FontWidth = 1;\n" +"var FontAscent = 3; //Set manually\n" +"var Font = \'Bold \' + FontHeight + \'px Courier New\';\n" +"var FontFlash = \'Bold \' + 35 + \'px Courier New\';\n" +"var BoxHeight = FontHeight + 2;\n" +"var ThreadsActive = new Object();\n" +"var ThreadsAllActive = 1;\n" +"var GroupsActive = new Object();\n" +"var GroupsAllActive = 1;\n" +"var nMinWidth = 0.01;//subpixel width\n" +"var nMinWidthPan = 1.0;//subpixel width when panning\n" +"var nContextSwitchEnabled = 1;\n" +"var DisableLod = 0;\n" +"var DisableMerge = 0;\n" +"var GroupColors = 0;\n" +"var nModDown = 0;\n" +"var g_MSG = \'no\';\n" +"var nDrawCount = 0;\n" +"var nBackColors = [\'#474747\', \'#313131\' ];\n" +"var nBackColorOffset = \'#606060\';\n" +"var CSwitchColors =[\"#9DD8AF\",\"#D7B6DA\",\"#EAAC76\",\"#DBDA61\",\"#8AD5E1\",\"#8CE48B\",\"#C4D688\",\"#57E5C4\"];//generated by http://tools.medialab.sciences-po.fr/iwanthue/index.php\n" +"var CSwitchHeight = 5;\n" +"var FRAME_HISTORY_COLOR_CPU = \'#ff7f27\';\n" +"var FRAME_HISTORY_COLOR_GPU = \'#ffffff\';\n" +"var ZOOM_TIME = 0.5;\n" +"var AnimationActive = false;\n" +"var nHoverCSCpu = -1;\n" +"var nHoverCSCpuNext = -1;\n" +"var nHoverCSToolTip = null;\n" +"var nHoverToken = -1;\n" +"var nHoverFrame = -1;\n" +"var nHoverTokenIndex = -1;\n" +"var nHoverTokenLogIndex = -1;\n" +"var nHoverCounter = 0;\n" +"var nHoverCounterDelta = 8;\n" +"var nHoverTokenNext = -1;\n" +"var nHoverTokenLogIndexNext = -1;\n" +"var nHoverTokenIndexNext = -1;\n" +"var nHideHelp = 0;\n" +"\n" +"\n" +"var fFrameScale = 33.33;\n" +"var fRangeBegin = 0;\n" +"var fRangeEnd = -1;\n" +"var fRangeBeginNext = 0;\n" +"var fRangeEndNext = 0;\n" +"var fRangeBeginGpuNext = 0;\n" +"var fRangeEndGpuNext = 0;\n" +"var fRangeBeginHistory = -1;\n" +"var fRangeEndHistory = -1;\n" +"var fRangeBeginHistoryGpu = -1;\n" +"var fRangeEndHistoryGpu = -1;\n" +"var fRangeBeginSelect = 0;\n" +"var fRangeEndSelect = -1;\n" +"\n" +"var ModeDetailed = 0;\n" +"var ModeTimers = 1;\n" +"var Mode = ModeDetailed;\n" +"\n" +"var DebugDrawQuadCount = 0;\n" +"var DebugDrawTextCount = 0;\n" +"var ProfileMode = 0;\n" +"var ProfileFps = 0;\n" +"var ProfileFpsAggr = 0;\n" +"var ProfileFpsCount = 0;\n" +"var ProfileLastTimeStamp = new Date();\n" +"\n" +"var CSwitchCache = {};\n" +"var CSwitchOnlyThreads = [];\n" +"var ProfileData = {};\n" +"var ProfileStackTime = {};\n" +"var ProfileStackName = {};\n" +"var Debug = 1;\n" +"\n" +"var g_MaxStack = Array();\n" +"var g_TypeArray;\n" +"var g_TimeArray;\n" +"var g_IndexArray;\n" +"var LodData = new Array();\n" +"var NumLodSplits = 10;\n" +"var SplitMin = 100;\n" +"var SPLIT_LIMIT = 1e20;\n" +"var DPR = 1;\n" +"var DetailedRedrawState = {};\n" +"var OffscreenData;\n" +"var DetailedFrameCounter = 0;\n" +"var Invalidate = 0;\n" +"var GroupOrder = Array();\n" +"var ThreadOrder = Array();\n" +"var TimersGroups = 0;\n" +"var TimersMeta = 1;\n" +"var MetaLengths = Array();\n" +"var MetaLengthsAvg = Array();\n" +"var MetaLengthsMax = Array();\n" +"\n" +"\n" +"function ProfileModeClear()\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       ProfileData = new Object();\n" +"       ProfileStackTime = new Array();\n" +"       ProfileStackName = new Array();\n" +"   }\n" +"}\n" +"function ProfileEnter(Name)\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       ProfileStackTime.push(new Date());\n" +"       ProfileStackName.push(Name);\n" +"   }\n" +"}\n" +"function ProfileLeave()\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       var Time = new Date();\n" +"       var Delta = Time - ProfileStackTime.pop();\n" +"       var Name = ProfileStackName.pop();\n" +"       var Obj = ProfileData[Name];\n" +"       if(!Obj)\n" +"       {\n" +"           Obj = new Object();\n" +"           Obj.Count = 0;\n" +"           Obj.Name = Name;\n" +"           Obj.Time = 0;\n" +"           ProfileData[Name] = Obj;\n" +"       }\n" +"       Obj.Time += Delta;\n" +"       Obj.Count += 1;\n" +"   }\n" +"}\n" +"\n" +"function ProfilePlot(s)\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       var A = ProfileData.Plot;\n" +"       if(!A)\n" +"       {\n" +"           ProfileData.Plot = Array();\n" +"           A = ProfileData.Plot;\n" +"       }\n" +"       if(A.length<10)\n" +"       {\n" +"           A.push(s);\n" +"       }\n" +"   }\n" +"}\n" +"function ProfileModeDump()\n" +"{\n" +"   for(var idx in ProfileData)\n" +"   {\n" +"       var Timer = ProfileData[idx];\n" +"       console.log(Timer.Name + \" \" + Timer.Time + \"ms \" + Timer.Count);\n" +"   }\n" +"\n" +"}\n" +"function ProfileModeDraw(Canvas)\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       var StringArray = [];\n" +"       for(var idx in ProfileData)\n" +"       {\n" +"           if(idx == \"Plot\")\n" +"               continue;\n" +"           var Timer = ProfileData[idx];\n" +"           StringArray.push(Timer.Name);\n" +"           StringArray.push(Timer.Time + \"ms\");\n" +"           StringArray.push(\"#\");\n" +"           StringArray.push(\"\" + Timer.Count);\n" +"       }\n" +"       StringArray.push(\"debug\");\n" +"       StringArray.push(Debug);\n" +"       var Time = new Date();\n" +"       var Delta = Time - ProfileLastTimeStamp;\n" +"       ProfileLastTimeStamp = Time;\n" +"       StringArray.push(\"Frame Delta\");\n" +"       StringArray.push(Delta + \"ms\");\n" +"       if(ProfileMode == 2)\n" +"       {\n" +"           ProfileFpsAggr += Delta;\n" +"           ProfileFpsCount ++ ;\n" +"           var AggrFrames = 10;\n" +"           if(ProfileFpsCount == AggrFrames)\n" +"           {\n" +"               ProfileFps = 1000 / (ProfileFpsAggr / AggrFrames);\n" +"               ProfileFpsAggr = 0;\n" +"               ProfileFpsCount = 0;\n" +"           }\n" +"           StringArray.push(\"FPS\");\n" +"           StringArray.push(\"\" + ProfileFps.toFixed(2));\n" +"       }\n" +"\n" +"\n" +"       for(var i = 0; i < ProfileData.Plot; ++i)\n" +"       {\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(ProfileData.Plot[i]);\n" +"       }\n" +"       ProfileData.Plot = Array();\n" +"       DrawToolTip(StringArray, Canvas, 0, 200);\n" +"   }\n" +"}\n" +"\n" +"function ToggleDebugMode()\n" +"{\n" +"   ProfileMode = (ProfileMode+1)%4;\n" +"   console.log(\'Toggle Debug Mode \' + ProfileMode);\n" +"}\n" +"\n" +"function DetailedTotal()\n" +"{\n" +"   var Total = 0;\n" +"   for(var i = 0; i < Frames.length; i++)\n" +"   {\n" +"       var frfr = Frames[i];\n" +"       Total += frfr.frameend - frfr.framestart;\n" +"   }\n" +"   return Total;\n" +"}\n" +"\n" +"function InitFrameInfo()\n" +"{\n" +"\n" +"   var div = document.getElementById(\'divFrameInfo\');\n" +"   var txt = \'\';\n" +"   txt = txt + \'Timers View\' + \'<br>\';\n" +"   txt = txt + \'Frames:\' + AggregateInfo.Frames +\'<br>\';\n" +"   txt = txt + \'Time:\' + AggregateInfo.Time.toFixed(2) +\'ms<br>\';\n" +"   txt = txt + \'<hr>\';\n" +"   txt = txt + \'Detailed View\' + \'<br>\';\n" +"   txt = txt + \'Frames:\' + Frames.length +\'<br>\';\n" +"   txt = txt + \'Time:\' + DetailedTotal().toFixed(2) +\'ms<br>\';\n" +"   div.innerHTML = txt;\n" +"}\n" +"function InitGroups()\n" +"{\n" +"   for(groupid in GroupInfo)\n" +"   {\n" +"       var TimerArray = Array();\n" +"       for(timerid in TimerInfo)\n" +"       {\n" +"           if(TimerInfo[timerid].group == groupid)\n" +"           {\n" +"               TimerArray.push(timerid);\n" +"           }\n" +"       }\n" +"       GroupInfo[groupid].TimerArray = TimerArray;\n" +"   }\n" +"}\n" +"\n" +"function InitThreadMenu()\n" +"{\n" +"   var ulThreadMenu = document.getElementById(\'ThreadSubMenu\');\n" +"   var MaxLen = 7;\n" +"   ThreadOrder = CreateOrderArray(ThreadNames, function(a){return a;});\n" +"   for(var idx in ThreadOrder)\n" +"   {\n" +"       var name = ThreadNames[ThreadOrder[idx]];\n" +"       var li = document.createElement(\'li\');\n" +"       if(name.length > MaxLen)\n" +"       {\n" +"           MaxLen = name.length;\n" +"       }\n" +"       li.innerText = name;\n" +"       var asText = li.innerHTML;\n" +"       var html = \'<a href=\"javascript:void(0)\" onclick=\"ToggleThread(\\'\' + name + \'\\');\">\' + asText + \'</a>\';\n" +"       li.innerHTML = html;\n" +"       ulThreadMenu.appendChild(li);\n" +"   }\n" +"   var LenStr = (5+(1+MaxLen) * (1+FontWidth)) + \'px\';\n" +"   var Lis = ulThreadMenu.getElementsByTagName(\'li\');\n" +"   for(var i = 0; i < Lis.length; ++i)\n" +"   {\n" +"       Lis[i].style[\'width\'] = LenStr;\n" +"   }\n" +"}\n" +"\n" +"function UpdateThreadMenu()\n" +"{\n" +"   var ulThreadMenu = document.getElementById(\'ThreadSubMenu\');\n" +"   var as = ulThreadMenu.getElementsByTagName(\'a\');\n" +"   for(var i = 0; i < as.length; ++i)\n" +"   {\n" +"       var elem = as[i];\n" +"       var inner = elem.innerText;\n" +"       var bActive = false;\n" +"       if(i < 2)\n" +"       {\n" +"           if(inner == \'All\')\n" +"           {\n" +"               bActive = ThreadsAllActive;\n" +"           }\n" +"       }\n" +"       else\n" +"       {\n" +"           bActive = ThreadsActive[inner];\n" +"       }\n" +"       if(bActive)\n" +"       {\n" +"           elem.style[\'text-decoration\'] = \'underline\';\n" +"       }\n" +"       else\n" +"       {\n" +"           elem.style[\'text-decoration\'] = \'none\';\n" +"       }\n" +"   }\n" +"}\n" +"\n" +"function ToggleThread(ThreadName)\n" +"{\n" +"   if(ThreadName)\n" +"   {\n" +"       if(ThreadsActive[ThreadName])\n" +"       {\n" +"           ThreadsActive[ThreadName] = false;\n" +"       }\n" +"       else\n" +"       {\n" +"           ThreadsActive[ThreadName] = true;\n" +"       }\n" +"   }\n" +"   else\n" +"   {\n" +"       if(ThreadsAllActive)\n" +"       {\n" +"           ThreadsAllActive = 0;\n" +"       }\n" +"       else\n" +"       {\n" +"           ThreadsAllActive = 1;\n" +"       }\n" +"   }\n" +"   Invalidate = 0;\n" +"   UpdateThreadMenu();\n" +"   WriteCookie();\n" +"   Draw(1);\n" +"\n" +"}\n" +"\n" +"function CreateOrderArray(Source, NameFunc)\n" +"{\n" +"   var Temp = Array(Source.length);\n" +"   for(var i = 0; i < Source.length; ++i)\n" +"   {\n" +"       Temp[i] = {};\n" +"       Temp[i].index = i;\n" +"       Temp[i].namezz = NameFunc(Source[i]).toLowerCase();\n" +"   }\n" +"   Temp.sort(function(l, r)\n" +"   { \n" +"       if(r.namezz<l.namezz)\n" +"           {return 1;}\n" +"       if(l.namezz<r.namezz)\n" +"           {return -1;} \n" +"       return 0;\n" +"   } );\n" +"   var OrderArray = Array(Source.length);\n" +"   for(var i = 0; i < Source.length; ++i)\n" +"   {\n" +"       OrderArray[i] = Temp[i].index;\n" +"   }\n" +"   return OrderArray;\n" +"}\n" +"\n" +"\n" +"function InitGroupMenu()\n" +"{\n" +"   var ulGroupMenu = document.getElementById(\'GroupSubMenu\');\n" +"   var MaxLen = 7;\n" +"   var MenuArray = Array();\n" +"   for(var i = 0; i < GroupInfo.length; ++i)\n" +"   {\n" +"       var x = {};\n" +"       x.IsCategory = 0;\n" +"       x.category = GroupInfo[i].category;\n" +"       x.name = GroupInfo[i].name;\n" +"       x.index = i;\n" +"       MenuArray.push(x);\n" +"   }\n" +"   for(var i = 0; i < CategoryInfo.length; ++i)\n" +"   {\n" +"       var x = {};\n" +"       x.IsCategory = 1;\n" +"       x.category = i;\n" +"       x.name = CategoryInfo[i];\n" +"       x.index = i;\n" +"       MenuArray.push(x);\n" +"   }\n" +"   var OrderFunction = function(a){ return a.category + \"__\" + a.name; };\n" +"   var OrderFunctionMenu = function(a){ return a.IsCategory ? (a.category + \'\') : (a.category + \"__\" + a.name); };\n" +"   GroupOrder = CreateOrderArray(GroupInfo, OrderFunction);\n" +"   var MenuOrder = CreateOrderArray(MenuArray, OrderFunctionMenu);\n" +"\n" +"   for(var idx in MenuOrder)\n" +"   {\n" +"       var MenuItem = MenuArray[MenuOrder[idx]];\n" +"       var name = MenuItem.name;\n" +"       var li = document.createElement(\'li\');\n" +"       if(name.length > MaxLen)\n" +"       {\n" +"           MaxLen = name.length;\n" +"       }\n" +"       var jsfunc = \'\';\n" +"       if(MenuItem.IsCategory)\n" +"       {               \n" +"           li.innerText = \'[\' + name + \']\';\n" +"           jsfunc = \"ToggleCategory\";\n" +"       }\n" +"       else\n" +"       {\n" +"           li.innerText = name;\n" +"           jsfunc = \"ToggleGroup\";\n" +"       }\n" +"       var asText = li.innerHTML;\n" +"       var html = \'<a href=\"javascript:void(0)\" onclick=\"\' + jsfunc + \'(\\'\' + name + \'\\');\">\' + asText + \'</a>\';\n" +"       li.innerHTML = html;\n" +"       ulGroupMenu.appendChild(li);\n" +"   }\n" +"   var LenStr = (5+(1+MaxLen) * FontWidth) + \'px\';\n" +"   var Lis = ulGroupMenu.getElementsByTagName(\'li\');\n" +"   for(var i = 0; i < Lis.length; ++i)\n" +"   {\n" +"       Lis[i].style[\'width\'] = LenStr;\n" +"   }\n" +"   UpdateGroupMenu();\n" +"}\n" +"\n" +"function UpdateGroupMenu()\n" +"{\n" +"   var ulThreadMenu = document.getElementById(\'GroupSubMenu\');\n" +"   var as = ulThreadMenu.getElementsByTagName(\'a\');\n" +"   for(var i = 0; i < as.length; ++i)\n" +"   {\n" +"       var elem = as[i];\n" +"       var inner = elem.innerText;\n" +"       var bActive = false;\n" +"       if(i < 2)\n" +"       {\n" +"           if(inner == \'All\')\n" +"           {\n" +"               bActive = GroupsAllActive;\n" +"           }\n" +"       }\n" +"       else\n" +"       {\n" +"           var CategoryString = inner.length>2 ? inner.substring(1, inner.length-2) : \"\";\n" +"           var CategoryIdx = CategoryIndex(CategoryString);\n" +"           if(inner[0] == \'[\' && inner[inner.length-1] == \']\' && CategoryIdx >= 0)\n" +"           {\n" +"               bActive = IsCategoryActive(CategoryIdx);\n" +"           }\n" +"           else\n" +"           {\n" +"               bActive = GroupsActive[inner];\n" +"           }\n" +"       }\n" +"       if(bActive)\n" +"       {\n" +"           elem.style[\'text-decoration\'] = \'underline\';\n" +"       }\n" +"       else\n" +"       {\n" +"           elem.style[\'text-decoration\'] = \'none\';\n" +"       }\n" +"   }\n" +"}\n" +"function CategoryIndex(CategoryName)\n" +"{\n" +"   for(var i = 0; i < CategoryInfo.length; ++i)\n" +"   {\n" +"       if(CategoryInfo[i] == CategoryName)\n" +"       {\n" +"           return i;\n" +"       }\n" +"   }\n" +"   return -1;\n" +"}\n" +"function IsCategoryActive(CategoryIdx)\n" +"{\n" +"   for(var i = 0; i < GroupInfo.length; ++i)\n" +"   {\n" +"       if(GroupInfo[i].category == CategoryIdx)\n" +"       {\n" +"           var Name = GroupInfo[i].name;\n" +"           if(!GroupsActive[Name])\n" +"           {\n" +"               return false;\n" +"           }\n" +"       }\n" +"   }\n" +"   return true;\n" +"\n" +"}\n" +"function ToggleCategory(CategoryName)\n" +"{\n" +"   var CategoryIdx = CategoryIndex(CategoryName);\n" +"   if(CategoryIdx < 0)\n" +"       return;\n" +"   var CategoryActive = IsCategoryActive(CategoryIdx);\n" +"   for(var i = 0; i < GroupInfo.length; ++i)\n" +"   {\n" +"       if(GroupInfo[i].category == CategoryIdx)\n" +"       {\n" +"           var Name = GroupInfo[i].name;\n" +"           if(CategoryActive)\n" +"           {\n" +"               GroupsActive[Name] = false;\n" +"           }\n" +"           else\n" +"           {\n" +"               GroupsActive[Name] = true;\n" +"           }\n" +"       }\n" +"   }\n" +"   UpdateGroupMenu();\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"}\n" +"\n" +"function ToggleGroup(GroupName)\n" +"{\n" +"   if(GroupName)\n" +"   {\n" +"       if(GroupsActive[GroupName])\n" +"       {\n" +"           GroupsActive[GroupName] = false;\n" +"       }\n" +"       else\n" +"       {\n" +"           GroupsActive[GroupName] = true;\n" +"       }\n" +"   }\n" +"   else\n" +"   {\n" +"       if(GroupsAllActive)\n" +"       {\n" +"           GroupsAllActive = 0;\n" +"       }\n" +"       else\n" +"       {\n" +"           GroupsAllActive = 1;\n" +"       }\n" +"   }\n" +"   UpdateGroupMenu();\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"}\n" +"function UpdateGroupColors()\n" +"{\n" +"   for(var i = 0; i < TimerInfo.length; ++i)\n" +"   {\n" +"       if(GroupColors)\n" +"       {\n" +"           TimerInfo[i].color = GroupInfo[TimerInfo[i].group].color;\n" +"       }\n" +"       else\n" +"       {\n" +"           TimerInfo[i].color = TimerInfo[i].timercolor;\n" +"       }\n" +"       TimerInfo[i].textcolorindex = InvertColorIndex(TimerInfo[i].color);\n" +"   }\n" +"}\n" +"\n" +"function ToggleGroupColors()\n" +"{\n" +"   GroupColors = !GroupColors;\n" +"   UpdateGroupColors();\n" +"   UpdateOptionsMenu();\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"}\n" +"\n" +"function UpdateOptionsMenu()\n" +"{\n" +"   var ulTimersMeta = document.getElementById(\'TimersMeta\');\n" +"   ulTimersMeta.style[\'text-decoration\'] = TimersMeta ? \'underline\' : \'none\';\n" +"   var ulGroupColors = document.getElementById(\'GroupColors\');\n" +"   ulGroupColors.style[\'text-decoration\'] = GroupColors ? \'underline\' : \'none\';\n" +"}\n" +"\n" +"function ToggleTimersMeta()\n" +"{\n" +"   TimersMeta = TimersMeta ? 0 : 1;\n" +"   WriteCookie();\n" +"   UpdateOptionsMenu();\n" +"   RequestRedraw();\n" +"}\n" +"\n" +"function ShowHelp(Show, Forever)\n" +"{\n" +"   var HelpWindow = document.getElementById(\'helpwindow\');\n" +"   if(Show)\n" +"   {\n" +"       HelpWindow.style[\'display\'] = \'block\';\n" +"   }\n" +"   else\n" +"   {\n" +"       HelpWindow.style[\'display\'] = \'none\';\n" +"   }\n" +"   if(Forever)\n" +"   {\n" +"       nHideHelp = Show ? 0 : 1;\n" +"       WriteCookie();\n" +"   }\n" +"}\n" +"function SetMode(NewMode, Groups)\n" +"{\n" +"   var buttonTimers = document.getElementById(\'buttonTimers\');\n" +"   var buttonDetailed = document.getElementById(\'buttonDetailed\');\n" +"   var buttonGroups = document.getElementById(\'buttonGroups\');\n" +"   var buttonThreads = document.getElementById(\'buttonThreads\');\n" +"   var ilThreads = document.getElementById(\'ilThreads\');\n" +"   var ilGroups = document.getElementById(\'ilGroups\');\n" +"   var ModeElement = null;\n" +"   if(NewMode == \'timers\' || NewMode == ModeTimers)\n" +"   {\n" +"       TimersGroups = Groups;\n" +"       buttonTimers.style[\'text-decoration\'] = TimersGroups ? \'none\' : \'underline\';\n" +"       buttonGroups.style[\'text-decoration\'] = TimersGroups == 1 ? \'underline\' : \'none\';\n" +"       buttonThreads.style[\'text-decoration\'] = TimersGroups == 2 ? \'underline\' : \'none\';\n" +"       buttonDetailed.style[\'text-decoration\'] = \'none\';\n" +"       if(TimersGroups == 0)\n" +"       {\n" +"           ilThreads.style[\'display\'] = \'none\';\n" +"       }\n" +"       else\n" +"       {\n" +"           ilThreads.style[\'display\'] = \'block\';\n" +"       }\n" +"       ilGroups.style[\'display\'] = \'block\';\n" +"       Mode = ModeTimers;\n" +"       ModeElement = TimersGroups == 2 ? buttonThreads : TimersGroups == 1 ? buttonGroups : buttonTimers;\n" +"   }\n" +"   else if(NewMode == \'detailed\' || NewMode == ModeDetailed)\n" +"   {\n" +"       buttonTimers.style[\'text-decoration\'] = \'none\';\n" +"       buttonGroups.style[\'text-decoration\'] = \'none\';\n" +"       buttonThreads.style[\'text-decoration\'] = \'none\';\n" +"       buttonDetailed.style[\'text-decoration\'] = \'underline\';\n" +"       ilThreads.style[\'display\'] = \'block\';\n" +"       ilGroups.style[\'display\'] = \'none\';\n" +"       Mode = ModeDetailed;\n" +"       ModeElement = buttonDetailed;\n" +"   }\n" +"   var ModeSubMenuText = document.getElementById(\'ModeSubMenuText\');\n" +"   ModeSubMenuText.innerText = \'Mode[\' + ModeElement.innerText + \']\';\n" +"\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"\n" +"}\n" +"\n" +"function SetReferenceTime(TimeString)\n" +"{\n" +"   ReferenceTime = parseInt(TimeString);\n" +"   var ReferenceMenu = document.getElementById(\'ReferenceSubMenu\');\n" +"   var Links = ReferenceMenu.getElementsByTagName(\'a\');\n" +"   for(var i = 0; i < Links.length; ++i)\n" +"   {\n" +"       if(Links[i].innerHTML.match(\'^\' + TimeString))\n" +"       {\n" +"           Links[i].style[\'text-decoration\'] = \'underline\';\n" +"       }\n" +"       else\n" +"       {\n" +"           Links[i].style[\'text-decoration\'] = \'none\';\n" +"       }\n" +"   }\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"\n" +"}\n" +"\n" +"function ToggleContextSwitch()\n" +"{\n" +"   SetContextSwitch(nContextSwitchEnabled ? 0 : 1);\n" +"}\n" +"function SetContextSwitch(Enabled)\n" +"{\n" +"   nContextSwitchEnabled = Enabled ? 1 : 0;\n" +"   var ReferenceMenu = document.getElementById(\'OptionsMenu\');\n" +"   var Links = ReferenceMenu.getElementsByTagName(\'a\');\n" +"   Links[0].style[\'text-decoration\'] = nContextSwitchEnabled ? \'underline\' : \'none\';\n" +"   WriteCookie();\n" +"   RequestRedraw();\n" +"}\n" +"\n" +"function ToggleDebug()\n" +"{\n" +"   Debug = (Debug + 1) % 2;\n" +"}\n" +"\n" +"function ToggleDisableMerge()\n" +"{\n" +"   DisableMerge = DisableMerge ? 0 : 1;\n" +"   var ReferenceMenu = document.getElementById(\'OptionsMenu\');\n" +"   var Links = ReferenceMenu.getElementsByTagName(\'a\');\n" +"   if(DisableMerge)\n" +"   {\n" +"       Links[1].style[\'text-decoration\'] = \'underline\';\n" +"   }\n" +"   else\n" +"   {\n" +"       Links[1].style[\'text-decoration\'] = \'none\';\n" +"   }\n" +"\n" +"}\n" +"\n" +"function ToggleDisableLod()\n" +"{\n" +"   DisableLod = DisableLod ? 0 : 1;\n" +"   var ReferenceMenu = document.getElementById(\'OptionsMenu\');\n" +"   var Links = ReferenceMenu.getElementsByTagName(\'a\');\n" +"   if(DisableLod)\n" +"   {\n" +"       Links[2].style[\'text-decoration\'] = \'underline\';\n" +"   }\n" +"   else\n" +"   {\n" +"       Links[2].style[\'text-decoration\'] = \'none\';\n" +"   }\n" +"\n" +"}\n" +"\n" +"function GatherHoverMetaCounters(TimerIndex, StartIndex, nLog, nFrameLast)\n" +"{\n" +"   var HoverInfo = new Object();\n" +"   var StackPos = 1;\n" +"   //search backwards, count meta counters \n" +"   for(var i = nFrameLast; i >= 0; i--)\n" +"   {\n" +"       var fr = Frames[i];\n" +"       var ts = fr.ts[nLog];\n" +"       var ti = fr.ti[nLog];\n" +"       var tt = fr.tt[nLog];\n" +"       var start = i == nFrameLast ? StartIndex-1 : ts.length-1;\n" +"\n" +"       for(var j = start; j >= 0; j--)\n" +"       {\n" +"           var type = tt[j];\n" +"           var index = ti[j];\n" +"           var time = ts[j];\n" +"           if(type == 1)\n" +"           {\n" +"               StackPos--;\n" +"               if(StackPos == 0 && index == TimerIndex)\n" +"               {\n" +"                   return HoverInfo;\n" +"               }\n" +"           }\n" +"           else if(type == 0)\n" +"           {\n" +"               StackPos++;\n" +"           }\n" +"           else if(type > 3)\n" +"           {\n" +"               var nMetaCount = type - 3;\n" +"               var nMetaIndex = MetaNames[index];\n" +"               if(nMetaIndex in HoverInfo)\n" +"               {\n" +"                   HoverInfo[nMetaIndex] += nMetaCount;\n" +"               }\n" +"               else\n" +"               {\n" +"                   HoverInfo[nMetaIndex] = nMetaCount;\n" +"               }\n" +"           }\n" +"       }\n" +"   }\n" +"}\n" +"function CalculateAllTimers(fBegin, fEnd)\n" +"{\n" +"   var Sum = [];\n" +"   var Count = [];\n" +"   var Sorted = [];\n" +"   for(var i = 0; i < TimerInfo.length; ++i)\n" +"   {\n" +"       Sum.push(0.0);\n" +"       Count.push(0);\n" +"       Sorted.push(i);\n" +"   }\n" +"   var nFrameFirst = 0;\n" +"   var nFrameLast = Frames.length;\n" +"\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var StackPosArray = Array(nNumLogs);\n" +"   var StackArray = Array(nNumLogs);\n" +"   for(var i = 0; i < nNumLogs; ++i)\n" +"   {\n" +"       StackPosArray[i] = 0;\n" +"       StackArray[i] = Array(20);\n" +"   }\n" +"\n" +"   for(var i = nFrameFirst; i < nFrameLast; i++)\n" +"   {\n" +"       var fr = Frames[i];\n" +"       for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"       {\n" +"           var StackPos = StackPosArray[nLog];\n" +"           var Stack = StackArray[nLog];\n" +"           var ts = fr.ts[nLog];\n" +"           var ti = fr.ti[nLog];\n" +"           var tt = fr.tt[nLog];\n" +"           var count = ts.length;\n" +"           for(j = 0; j < count; j++)\n" +"           {\n" +"               var type = tt[j];\n" +"               var index = ti[j];\n" +"               var time = ts[j];\n" +"               if(type == 1 && time < fEnd) //enter\n" +"               {\n" +"                   Stack[StackPos] = time < fBegin ? fBegin : time;\n" +"                   if(StackArray[nLog][StackPos] != time)\n" +"                   {\n" +"                       console.log(\'fail fail fail\');\n" +"                   }\n" +"                   StackPos++;\n" +"               }\n" +"               else if(type == 0) // leave\n" +"               {\n" +"                   if(StackPos>0)\n" +"                   {\n" +"                       var timeend = time;\n" +"                       StackPos--;\n" +"                       timestart = Stack[StackPos];\n" +"                       var TimeDelta = timeend - timestart;\n" +"                       Sum[index] += TimeDelta;\n" +"                       Count[index]++;\n" +"                   }\n" +"               }\n" +"           }\n" +"           StackPosArray[nLog] = StackPos;\n" +"       }\n" +"   }\n" +"   Sorted.sort(function(a,b){ return Sum[b] - Sum[a]; } );\n" +"   var Result = {\"Sorted\" : Sorted, \"Sum\" : Sum, \"Count\" : Count};\n" +"   return Result;\n" +"}\n" +"function CalculateTimers(Result, TimerIndex, nFrameFirst, nFrameLast)\n" +"{\n" +"   if(!nFrameFirst || nFrameFirst < 0)\n" +"       nFrameFirst = 0;\n" +"   if(!nFrameLast || nFrameLast > Frames.length)\n" +"       nFrameLast = Frames.length;\n" +"   var FrameCount = nFrameLast - nFrameFirst;\n" +"   if(0 == FrameCount)\n" +"       return;\n" +"   var CallCount = 0;\n" +"   var Sum = 0;\n" +"   var Max = 0;\n" +"   var FrameMax = 0;\n" +"\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var StackPosArray = Array(nNumLogs);\n" +"   var StackArray = Array(nNumLogs);\n" +"   for(var i = 0; i < nNumLogs; ++i)\n" +"   {\n" +"       StackPosArray[i] = 0;\n" +"       StackArray[i] = Array(20);\n" +"   }\n" +"\n" +"   for(var i = nFrameFirst; i < nFrameLast; i++)\n" +"   {\n" +"       var FrameSum = 0;\n" +"       var fr = Frames[i];\n" +"       for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"       {\n" +"           var StackPos = StackPosArray[nLog];\n" +"           var Stack = StackArray[nLog];\n" +"           var ts = fr.ts[nLog];\n" +"           var ti = fr.ti[nLog];\n" +"           var tt = fr.tt[nLog];\n" +"           var count = ts.length;\n" +"           for(j = 0; j < count; j++)\n" +"           {\n" +"               var type = tt[j];\n" +"               var index = ti[j];\n" +"               var time = ts[j];\n" +"               if(type == 1) //enter\n" +"               {\n" +"                   //push\n" +"                   Stack[StackPos] = time;\n" +"                   if(StackArray[nLog][StackPos] != time)\n" +"                   {\n" +"                       console.log(\'fail fail fail\');\n" +"                   }\n" +"                   StackPos++;\n" +"               }\n" +"               else if(type == 0) // leave\n" +"               {\n" +"                   var timestart;\n" +"                   var timeend = time;\n" +"                   if(StackPos>0)\n" +"                   {\n" +"                       StackPos--;\n" +"                       timestart = Stack[StackPos];\n" +"                   }\n" +"                   else\n" +"                   {\n" +"                       timestart = Frames[nFrameFirst].framestart;\n" +"                   }\n" +"                   if(index == TimerIndex)\n" +"                   {\n" +"                       var TimeDelta = timeend - timestart;\n" +"                       CallCount++;\n" +"                       FrameSum += TimeDelta;\n" +"                       Sum += TimeDelta;\n" +"                       if(TimeDelta > Max)\n" +"                           Max = TimeDelta;\n" +"                   }\n" +"               }\n" +"               else\n" +"               {\n" +"                   //meta\n" +"               }\n" +"           }\n" +"           if(FrameSum > FrameMax)\n" +"           {\n" +"               FrameMax = FrameSum;\n" +"           }\n" +"           StackPosArray[nLog] = StackPos;\n" +"       }\n" +"   }\n" +"\n" +"   Result.CallCount = CallCount;\n" +"   Result.Sum = Sum.toFixed(3);\n" +"   Result.Max = Max.toFixed(3);\n" +"   Result.Average = (Sum / CallCount).toFixed(3);\n" +"   Result.FrameAverage = (Sum / FrameCount).toFixed(3);\n" +"   Result.FrameCallAverage = (CallCount / FrameCount).toFixed(3);\n" +"   Result.FrameMax = FrameMax.toFixed(3);\n" +"   return Result;\n" +"}\n" +"\n" +"function PreprocessCalculateAllTimers()\n" +"{\n" +"   ProfileEnter(\"CalculateAllTimers\");\n" +"   var nFrameFirst = 0;\n" +"   var nFrameLast = Frames.length;\n" +"   var FrameCount = nFrameLast - nFrameFirst;\n" +"   if(0 == FrameCount)\n" +"       return;\n" +"   for(var j = 0; j < TimerInfo.length; j++)\n" +"   {\n" +"       TimerInfo[j].CallCount = 0;\n" +"       TimerInfo[j].Sum = 0;\n" +"       TimerInfo[j].Max = 0;\n" +"       TimerInfo[j].FrameMax = 0;\n" +"   }\n" +"\n" +"\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var StackPosArray = Array(nNumLogs);\n" +"   var StackArray = Array(nNumLogs);\n" +"   for(var i = 0; i < nNumLogs; ++i)\n" +"   {\n" +"       StackPosArray[i] = 0;\n" +"       StackArray[i] = Array(20);\n" +"   }\n" +"\n" +"   for(var i = nFrameFirst; i < nFrameLast; i++)\n" +"   {\n" +"       for(var j = 0; j < TimerInfo.length; j++)\n" +"       {\n" +"           TimerInfo[j].FrameSum = 0;\n" +"       }\n" +"\n" +"       var fr = Frames[i];\n" +"       for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"       {\n" +"           var StackPos = StackPosArray[nLog];\n" +"           var Stack = StackArray[nLog];\n" +"           var ts = fr.ts[nLog];\n" +"           var ti = fr.ti[nLog];\n" +"           var tt = fr.tt[nLog];\n" +"           var count = ts.length;\n" +"           for(j = 0; j < count; j++)\n" +"           {\n" +"               var type = tt[j];\n" +"               var index = ti[j];\n" +"               var time = ts[j];\n" +"               if(type == 1) //enter\n" +"               {\n" +"                   //push\n" +"                   Stack[StackPos] = time;\n" +"                   if(StackArray[nLog][StackPos] != time)\n" +"                   {\n" +"                       console.log(\'fail fail fail\');\n" +"                   }\n" +"                   StackPos++;\n" +"               }\n" +"               else if(type == 0) // leave\n" +"               {\n" +"                   var timestart;\n" +"                   var timeend = time;\n" +"                   if(StackPos>0)\n" +"                   {\n" +"                       StackPos--;\n" +"                       timestart = Stack[StackPos];\n" +"                   }\n" +"                   else\n" +"                   {\n" +"                       timestart = Frames[nFrameFirst].framestart;\n" +"                   }\n" +"                   // if(index == TimerIndex)\n" +"                   {\n" +"                       var TimeDelta = timeend - timestart;\n" +"                       TimerInfo[index].CallCount++;\n" +"                       TimerInfo[index].FrameSum += TimeDelta;\n" +"                       TimerInfo[index].Sum += TimeDelta;\n" +"                       if(TimeDelta > TimerInfo[index].Max)\n" +"                           TimerInfo[index].Max = TimeDelta;\n" +"                   }\n" +"               }\n" +"               else\n" +"               {\n" +"                   //meta\n" +"               }\n" +"           }\n" +"           for(var j = 0; j < TimerInfo.length; j++)\n" +"           {\n" +"               if(TimerInfo[j].FrameSum > TimerInfo[j].FrameMax)\n" +"               {\n" +"                   TimerInfo[j].FrameMax = TimerInfo[j].FrameSum;\n" +"               }\n" +"           }\n" +"           StackPosArray[nLog] = StackPos;\n" +"       }\n" +"\n" +"\n" +"   }\n" +"\n" +"   for(var j = 0; j < TimerInfo.length; j++)\n" +"   {\n" +"       var CallCount = TimerInfo[j].CallCount;\n" +"       var Sum = TimerInfo[j].Sum.toFixed(3);\n" +"       var Max = TimerInfo[j].Max.toFixed(3);\n" +"       var Average = (TimerInfo[j].Sum / TimerInfo[j].CallCount).toFixed(3);\n" +"       var FrameAverage = (TimerInfo[j].Sum / FrameCount).toFixed(3);\n" +"       var FrameCallAverage = (TimerInfo[j].CallCount / FrameCount).toFixed(3);\n" +"       var FrameMax = TimerInfo[j].FrameMax.toFixed(3);\n" +"       TimerInfo[j].CallCount = CallCount;\n" +"       TimerInfo[j].Sum = Sum;\n" +"       TimerInfo[j].Max  = Max ;\n" +"       TimerInfo[j].Average = Average;\n" +"       TimerInfo[j].FrameAverage = FrameAverage;\n" +"       TimerInfo[j].FrameCallAverage = FrameCallAverage;\n" +"       TimerInfo[j].FrameMax = FrameMax;\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"var FlashFrames = 10;\n" +"var FlashFrameCounter = 0;\n" +"var FlashMessage = \'\';\n" +"function TimeString(Diff)\n" +"{\n" +"   var DiffString = \"0 sec\";\n" +"   var DiffTable = [1,60,60*60,60*60*24];\n" +"   var DiffNameTable = [\"sec\", \"min\", \"hr\", \"day\"];\n" +"   for(var i = 0; i < DiffTable.length; ++i)\n" +"   {\n" +"       if(Diff >= DiffTable[i])\n" +"       {\n" +"           DiffString = Math.floor(Diff / DiffTable[i]) + \" \" + DiffNameTable[i];\n" +"       }\n" +"   }\n" +"   return DiffString;\n" +"\n" +"}\n" +"function ShowFlashMessage(Message, FrameCount)\n" +"{\n" +"   FlashMessage = Message;\n" +"   FlashFrameCounter = FrameCount;\n" +"}\n" +"function OnPageReady()\n" +"{\n" +"   var DumpDate = DumpUtcCaptureTime;\n" +"   var CurrentDate = Date.now() / 1000;\n" +"   var Diff = CurrentDate - DumpDate;\n" +"   var Limit = 10*60;//flash old message when loading captures older than 10 minutes \n" +"   if(Diff > Limit)\n" +"   {\n" +"       ShowFlashMessage(\"Captured \" + TimeString(Diff) + \" ago\", 100);\n" +"   }\n" +"   if(!nHideHelp)\n" +"   {\n" +"       ShowHelp(1,0);\n" +"   }\n" +"}\n" +"\n" +"function DrawFlashMessage(context)\n" +"{\n" +"   if(FlashFrameCounter > 0)\n" +"   {\n" +"       if(FlashFrameCounter>1)\n" +"       {\n" +"           var FlashPrc = Math.sin(FlashFrameCounter / FlashFrames);\n" +"           context.font = FontFlash;\n" +"           context.globalAlpha = FlashPrc * 0.35 + 0.5;\n" +"           context.textAlign = \'center\';\n" +"           context.fillStyle = \'red\';\n" +"           context.fillText(FlashMessage, nWidth * 0.5, 50);\n" +"           context.globalAlpha = 1;\n" +"           context.textAlign = \'left\';\n" +"           context.font = Font;\n" +"       }\n" +"       FlashFrameCounter -= 1;\n" +"\n" +"   }\n" +"}\n" +"\n" +"function DrawCaptureInfo(context)\n" +"{\n" +"   context.fillStyle = \'white\';\n" +"   context.textAlign = \'right\';\n" +"   context.font = Font;\n" +"   var DumpDate = DumpUtcCaptureTime;\n" +"   var CurrentDate = Date.now() / 1000;\n" +"   var Diff = CurrentDate - DumpDate;\n" +"   var DiffString = TimeString(Diff) + \" ago\";\n" +"   context.fillText(new Date(DumpDate*1000).toLocaleString(), nWidth, FontHeight);\n" +"   if(Mode == ModeTimers)\n" +"   {\n" +"       context.fillText(\"Timer Frames: \" + AggregateInfo.Frames, nWidth, FontHeight*2);\n" +"   }\n" +"   else\n" +"   {\n" +"       context.fillText(\"Detailed Frames \"+ Frames.length, nWidth, FontHeight*2);\n" +"   }\n" +"   context.fillText(DumpHost, nWidth, FontHeight*3);\n" +"   context.fillText(DiffString, nWidth, FontHeight*4);\n" +"   context.textAlign = \'left\';\n" +"   DrawFlashMessage(context);\n" +"}\n" +"\n" +"function DrawDetailedFrameHistory()\n" +"{\n" +"   ProfileEnter(\"DrawDetailedFrameHistory\");\n" +"   var x = HistoryViewMouseX;\n" +"\n" +"   var context = CanvasHistory.getContext(\'2d\');\n" +"   context.clearRect(0, 0, CanvasHistory.width, CanvasHistory.height);\n" +"\n" +"   var fHeight = nHistoryHeight;\n" +"   var fWidth = nWidth / Frames.length;\n" +"   var fHeightScale = fHeight / ReferenceTime;\n" +"   var fX = 0;\n" +"   var FrameIndex = -1;\n" +"   var MouseDragging = MouseDragState != MouseDragOff;\n" +"   fRangeBeginHistory = fRangeEndHistory = -1;\n" +"   fRangeBeginHistoryGpu = fRangeEndHistoryGpu = -1;\n" +"\n" +"   var FrameFirst = -1;\n" +"   var FrameLast = nWidth;\n" +"   var fDetailedOffsetEnd = fDetailedOffset + fDetailedRange;\n" +"   for(i = 0; i < Frames.length; i++)\n" +"   {\n" +"       var fMs = Frames[i].frameend - Frames[i].framestart;\n" +"       if(fDetailedOffset <= Frames[i].frameend && fDetailedOffset >= Frames[i].framestart)\n" +"       {\n" +"           var lerp = (fDetailedOffset - Frames[i].framestart) / (Frames[i].frameend - Frames[i].framestart);\n" +"           FrameFirst = fX + fWidth * lerp;\n" +"       }\n" +"       if(fDetailedOffsetEnd <= Frames[i].frameend && fDetailedOffsetEnd >= Frames[i].framestart)\n" +"       {\n" +"           var lerp = (fDetailedOffsetEnd - Frames[i].framestart) / (Frames[i].frameend - Frames[i].framestart);\n" +"           FrameLast = fX + fWidth * lerp;\n" +"       }\n" +"       var fH = fHeightScale * fMs;\n" +"       var bMouse = x > fX && x < fX + fWidth;\n" +"       if(bMouse && !MouseDragging)\n" +"       {\n" +"           context.fillStyle = FRAME_HISTORY_COLOR_GPU;\n" +"           fRangeBeginHistory = Frames[i].framestart;\n" +"           fRangeEndHistory = Frames[i].frameend;\n" +"           if(Frames[i].framestartgpu)\n" +"           {\n" +"               fRangeBeginHistoryGpu = Frames[i].framestartgpu;\n" +"               fRangeEndHistoryGpu = Frames[i].frameendgpu;\n" +"           }\n" +"           FrameIndex = i;\n" +"       }\n" +"       else\n" +"       {\n" +"           context.fillStyle = FRAME_HISTORY_COLOR_CPU;\n" +"       }\n" +"       context.fillRect(fX, fHeight - fH, fWidth-1, fH);\n" +"       fX += fWidth;\n" +"   }\n" +"\n" +"   var fRangeHistoryBegin = FrameFirst;\n" +"   var fRangeHistoryEnd = FrameLast;\n" +"   var X = fRangeHistoryBegin;\n" +"   var Y = 0;\n" +"   var W = fRangeHistoryEnd - fRangeHistoryBegin;\n" +"   context.globalAlpha = 0.35;\n" +"   context.fillStyle = \'#009900\';\n" +"   context.fillRect(X, Y, W, fHeight);\n" +"   context.globalAlpha = 1;\n" +"   context.strokeStyle = \'#00ff00\';\n" +"   context.beginPath();\n" +"   context.moveTo(X, Y);\n" +"   context.lineTo(X, Y+fHeight);\n" +"   context.moveTo(X+W, Y);\n" +"   context.lineTo(X+W, Y+fHeight);\n" +"   context.stroke();\n" +"\n" +"\n" +"\n" +"\n" +"   DrawCaptureInfo(context);\n" +"\n" +"   if(FrameIndex>=0 && !MouseDragging)\n" +"   {\n" +"       var StringArray = [];\n" +"       StringArray.push(\"Frame\");\n" +"       StringArray.push(\"\" + FrameIndex);\n" +"       StringArray.push(\"Time\");\n" +"       StringArray.push(\"\" + (Frames[FrameIndex].frameend - Frames[FrameIndex].framestart).toFixed(3));\n" +"\n" +"       DrawToolTip(StringArray, CanvasHistory, HistoryViewMouseX, HistoryViewMouseY+20);\n" +"\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"function TimeToMsString(Time)\n" +"{\n" +"   return Time.toFixed(3) + \"ms\";\n" +"}\n" +"function TimeToString(Time)\n" +"{\n" +"   if(Time > 1000)\n" +"   {\n" +"       return (Time/1000.0).toFixed(0) +\"s\";\n" +"   }\n" +"   else if(Time > 0.9)\n" +"   {\n" +"       return Time.toFixed(0) + \"ms\";\n" +"   }\n" +"   else if(Time > 0.0009)\n" +"   {\n" +"       return (Time*1000).toFixed(0) + \"us\";\n" +"   }\n" +"   else\n" +"   {\n" +"       return (Time*1000000).toFixed(0) + \"ns\";\n" +"   }\n" +"}\n" +"\n" +"function DrawDetailedBackground(context)\n" +"{\n" +"   var fMs = fDetailedRange;\n" +"   var fMsEnd = fMs + fDetailedOffset;\n" +"   var fMsToScreen = nWidth / fMs;\n" +"   var fRate = Math.floor(2*((Math.log(fMs)/Math.log(10))-1))/2;\n" +"   var fStep = Math.pow(10, fRate);\n" +"   var fRcpStep = 1.0 / fStep;\n" +"   var nColorIndex = Math.floor(fDetailedOffset * fRcpStep) % 2;\n" +"   if(nColorIndex < 0)\n" +"       nColorIndex = -nColorIndex;\n" +"   var fStart = Math.floor(fDetailedOffset * fRcpStep) * fStep;\n" +"   var fHeight = CanvasDetailedView.height;\n" +"   var fScaleX = nWidth / fDetailedRange; \n" +"   var HeaderString = TimeToString(fStep);\n" +"   context.textAlign = \'center\';\n" +"   for(f = fStart; f < fMsEnd; )\n" +"   {\n" +"       var fNext = f + fStep;\n" +"       var X = (f - fDetailedOffset) * fScaleX;\n" +"       var W = (fNext-f)*fScaleX;\n" +"       context.fillStyle = nBackColors[nColorIndex];\n" +"       context.fillRect(X, 0, W+2, fHeight);\n" +"       nColorIndex = 1 - nColorIndex;\n" +"       context.fillStyle = \'#777777\'\n" +"       context.fillText(HeaderString, X + W * 0.5, 10);\n" +"       context.fillText(HeaderString, X + W * 0.5, nHeight - 10);\n" +"       f = fNext;\n" +"   }\n" +"   context.textAlign = \'left\';\n" +"   var fScaleX = nWidth / fDetailedRange; \n" +"   context.globalAlpha = 0.5;\n" +"   context.strokeStyle = \'#bbbbbb\';\n" +"   context.beginPath();\n" +"   for(var i = 0; i < Frames.length; i++)\n" +"   {\n" +"       var frfr = Frames[i];\n" +"       if(frfr.frameend < fDetailedOffset || frfr.framestart > fDetailedOffset + fDetailedRange)\n" +"       {\n" +"           continue;\n" +"       }\n" +"       var X = (frfr.framestart - fDetailedOffset) * fScaleX;\n" +"       if(X >= 0 && X < nWidth)\n" +"       {\n" +"           context.moveTo(X, 0);\n" +"           context.lineTo(X, nHeight);\n" +"       }\n" +"   }\n" +"   context.stroke();\n" +"   context.globalAlpha = 1;\n" +"\n" +"}\n" +"function DrawToolTip(StringArray, Canvas, x, y)\n" +"{\n" +"   var context = Canvas.getContext(\'2d\');\n" +"   context.font = Font;\n" +"   var WidthArray = Array(StringArray.length);\n" +"   var nMaxWidth = 0;\n" +"   var nHeight = 0;\n" +"   for(i = 0; i < StringArray.length; i += 2)\n" +"   {\n" +"       var nWidth0 = context.measureText(StringArray[i]).width;\n" +"       var nWidth1 = context.measureText(StringArray[i+1]).width;\n" +"       var nSum = nWidth0 + nWidth1;\n" +"       WidthArray[i] = nWidth0;\n" +"       WidthArray[i+1] = nWidth1;\n" +"       if(nSum > nMaxWidth)\n" +"       {\n" +"           nMaxWidth = nSum;\n" +"       }\n" +"       nHeight += BoxHeight;\n" +"   }\n" +"   nMaxWidth += 15;\n" +"   //bounds check.\n" +"   var CanvasRect = Canvas.getBoundingClientRect();\n" +"   if(y + nHeight > CanvasRect.height)\n" +"   {\n" +"       y = CanvasRect.height - nHeight;\n" +"       x += 20;\n" +"   }\n" +"   if(x + nMaxWidth > CanvasRect.width)\n" +"   {\n" +"       x = CanvasRect.width - nMaxWidth;\n" +"   }\n" +"\n" +"   context.fillStyle = \'black\';\n" +"   context.fillRect(x-1, y, nMaxWidth+2, nHeight);\n" +"   context.fillStyle = \'white\';\n" +"\n" +"   var XPos = x;\n" +"   var XPosRight = x + nMaxWidth;\n" +"   var YPos = y + BoxHeight-2;\n" +"   for(i = 0; i < StringArray.length; i += 2)\n" +"   {\n" +"       context.fillText(StringArray[i], XPos, YPos);\n" +"       context.fillText(StringArray[i+1], XPosRight - WidthArray[i+1], YPos);\n" +"       YPos += BoxHeight;\n" +"   }\n" +"}\n" +"function DrawHoverToolTip()\n" +"{\n" +"   ProfileEnter(\"DrawHoverToolTip\");\n" +"   if(nHoverToken != -1)\n" +"   {\n" +"       var StringArray = [];\n" +"       var groupid = TimerInfo[nHoverToken].group;\n" +"       StringArray.push(\"Timer\");\n" +"       StringArray.push(TimerInfo[nHoverToken].name);\n" +"       StringArray.push(\"Group\");\n" +"       StringArray.push(GroupInfo[groupid].name);\n" +"\n" +"       var bShowTimers = Mode == ModeTimers;\n" +"       if(FlipToolTip)\n" +"       {\n" +"           bShowTimers = !bShowTimers;\n" +"       }\n" +"       if(bShowTimers)\n" +"       {\n" +"\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"           var Timer = TimerInfo[nHoverToken];\n" +"           StringArray.push(\"Average\");\n" +"           StringArray.push(Timer.average);\n" +"           StringArray.push(\"Max\");\n" +"           StringArray.push(Timer.max);\n" +"           StringArray.push(\"Excl Max\");\n" +"           StringArray.push(Timer.exclmax);\n" +"           StringArray.push(\"Excl Average\");\n" +"           StringArray.push(Timer.exclaverage);\n" +"           StringArray.push(\"Call Average\");\n" +"           StringArray.push(Timer.callaverage);\n" +"           StringArray.push(\"Call Count\");\n" +"           StringArray.push(Timer.callcount);\n" +"\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"\n" +"\n" +"           StringArray.push(\"Group\");\n" +"           StringArray.push(GroupInfo[groupid].name);\n" +"           StringArray.push(\"Average\");\n" +"           StringArray.push(GroupInfo[groupid].average);\n" +"           StringArray.push(\"Max\");\n" +"           StringArray.push(GroupInfo[groupid].max);\n" +"\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"\n" +"           StringArray.push(\""; + +const size_t g_MicroProfileHtml_end_0_size = sizeof(g_MicroProfileHtml_end_0); +const char g_MicroProfileHtml_end_1[] = +"Timer Capture\");\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"Frames\");\n" +"           StringArray.push(AggregateInfo.Frames);\n" +"           StringArray.push(\"Time\");\n" +"           StringArray.push(AggregateInfo.Time.toFixed(2) + \"ms\");\n" +"\n" +"\n" +"\n" +"\n" +"       }\n" +"       else\n" +"       {\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"\n" +"\n" +"\n" +"           StringArray.push(\"Time\");\n" +"           StringArray.push((fRangeEnd-fRangeBegin).toFixed(3));\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"Total\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].Sum);\n" +"           StringArray.push(\"Max\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].Max);\n" +"           StringArray.push(\"Average\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].Average);\n" +"           StringArray.push(\"Count\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].CallCount);\n" +"\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"\n" +"           StringArray.push(\"Max/Frame\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].FrameMax);\n" +"\n" +"           StringArray.push(\"Average Time/Frame\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].FrameAverage);\n" +"\n" +"           StringArray.push(\"Average Count/Frame\");\n" +"           StringArray.push(\"\" + TimerInfo[nHoverToken].FrameCallAverage);\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"       \n" +"           if(nHoverFrame != -1)\n" +"           {\n" +"               StringArray.push(\"\");\n" +"               StringArray.push(\"\");\n" +"               StringArray.push(\"Frame \" + nHoverFrame);\n" +"               StringArray.push(\"\");\n" +"\n" +"               var FrameTime = new Object();\n" +"               CalculateTimers(FrameTime, nHoverToken, nHoverFrame, nHoverFrame+1);\n" +"               StringArray.push(\"Total\");\n" +"               StringArray.push(\"\" + FrameTime.Sum);\n" +"               StringArray.push(\"Count\");\n" +"               StringArray.push(\"\" + FrameTime.CallCount);\n" +"               StringArray.push(\"Average\");\n" +"               StringArray.push(\"\" + FrameTime.Average);\n" +"               StringArray.push(\"Max\");\n" +"               StringArray.push(\"\" + FrameTime.Max);\n" +"           }\n" +"\n" +"           var HoverInfo = GatherHoverMetaCounters(nHoverToken, nHoverTokenIndex, nHoverTokenLogIndex, nHoverFrame);\n" +"           var Header = 0;\n" +"           for(index in HoverInfo)\n" +"           {\n" +"               if(0 == Header)\n" +"               {\n" +"                   Header = 1;\n" +"                   StringArray.push(\"\");\n" +"                   StringArray.push(\"\");\n" +"                   StringArray.push(\"Meta\");\n" +"                   StringArray.push(\"\");\n" +"\n" +"               }\n" +"               StringArray.push(\"\"+index);\n" +"               StringArray.push(\"\"+HoverInfo[index]);\n" +"           }\n" +"\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"\");\n" +"\n" +"           StringArray.push(\"Detailed Capture\");\n" +"           StringArray.push(\"\");\n" +"           StringArray.push(\"Frames\");\n" +"           StringArray.push(Frames.length);\n" +"           StringArray.push(\"Time\");\n" +"           StringArray.push(DetailedTotal().toFixed(2) + \"ms\");\n" +"\n" +"\n" +"       }\n" +"       DrawToolTip(StringArray, CanvasDetailedView, DetailedViewMouseX, DetailedViewMouseY+20);\n" +"   }\n" +"   else if(nHoverCSCpu >= 0)\n" +"   {\n" +"       var StringArray = [];\n" +"       StringArray.push(\"Context Switch\");\n" +"       StringArray.push(\"\");\n" +"       StringArray.push(\"\");\n" +"       StringArray.push(\"\");\n" +"       StringArray.push(\"Cpu\");\n" +"       StringArray.push(\"\" + nHoverCSCpu);\n" +"       StringArray.push(\"Begin\");\n" +"       StringArray.push(\"\" + fRangeBegin);\n" +"       StringArray.push(\"End\");\n" +"       StringArray.push(\"\" + fRangeEnd);\n" +"       DrawToolTip(StringArray, CanvasDetailedView, DetailedViewMouseX, DetailedViewMouseY+20);\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"function FormatMeta(Value, Dec)\n" +"{\n" +"   if(!Value)\n" +"   {\n" +"       Value = \"0\";\n" +"   }\n" +"   else\n" +"   {\n" +"       Value = \'\' + Value.toFixed(Dec);\n" +"   }\n" +"   return Value;\n" +"}\n" +"\n" +"function DrawBarView()\n" +"{\n" +"   ProfileEnter(\"DrawBarView\");\n" +"   Invalidate++;\n" +"   nHoverToken = -1;\n" +"   nHoverFrame = -1;\n" +"   var context = CanvasDetailedView.getContext(\'2d\');\n" +"   context.clearRect(0, 0, nWidth, nHeight);\n" +"\n" +"   var Height = BoxHeight;\n" +"   var Width = nWidth;\n" +"\n" +"   //clamp offset to prevent scrolling into the void\n" +"   var nTotalRows = 0;\n" +"   for(var groupid in GroupInfo)\n" +"   {\n" +"       if(GroupsAllActive || GroupsActive[GroupInfo[groupid].name])\n" +"       {\n" +"           nTotalRows += GroupInfo[groupid].TimerArray.length + 1;\n" +"       }\n" +"   }\n" +"   var nTotalRowPixels = nTotalRows * Height;\n" +"   var nFrameRows = nHeight - BoxHeight;\n" +"   if(nOffsetBarsY + nFrameRows > nTotalRowPixels && nTotalRowPixels > nFrameRows)\n" +"   {\n" +"       nOffsetBarsY = nTotalRowPixels - nFrameRows;\n" +"   }\n" +"\n" +"\n" +"   var Y = -nOffsetBarsY + BoxHeight;\n" +"   if(TimersGroups)\n" +"   {\n" +"       nOffsetBarsX = 0;\n" +"   }\n" +"   var XBase = -nOffsetBarsX;\n" +"   var nColorIndex = 0;\n" +"\n" +"   context.fillStyle = \'white\';\n" +"   context.font = Font;\n" +"   var bMouseIn = 0;\n" +"   var RcpReferenceTime = 1.0 / ReferenceTime;\n" +"   var CountWidth = 8 * FontWidth;\n" +"   var nMetaLen = TimerInfo[0].meta.length;\n" +"   var nMetaCharacters = 10;\n" +"   for(var i = 0; i < nMetaLen; ++i)\n" +"   {\n" +"       if(nMetaCharacters < MetaNames[i].length)\n" +"           nMetaCharacters = MetaNames[i].length;\n" +"   }\n" +"   var nWidthMeta = nMetaCharacters * FontWidth + 6;\n" +"   function DrawHeaderSplit(Header)\n" +"   {\n" +"       context.fillStyle = \'white\';\n" +"       context.fillText(Header, X, Height-FontAscent);\n" +"       X += nWidthBars;\n" +"       context.fillStyle = nBackColorOffset;\n" +"       X += nWidthMs;\n" +"       if(X >= NameWidth)\n" +"       {\n" +"           context.fillRect(X-3, 0, 1, nHeight);\n" +"       }\n" +"   }\n" +"   function DrawHeaderSplitSingle(Header, Width)\n" +"   {\n" +"       context.fillStyle = \'white\';\n" +"       context.fillText(Header, X, Height-FontAscent);\n" +"       X += Width;\n" +"       context.fillStyle = nBackColorOffset;\n" +"       if(X >= NameWidth)\n" +"       {\n" +"           context.fillRect(X-3, 0, 1, nHeight);\n" +"       }\n" +"   }\n" +"   function DrawHeaderSplitLeftRight(HeaderLeft, HeaderRight, Width)\n" +"   {\n" +"       context.textAlign = \'left\';\n" +"       context.fillStyle = \'white\';\n" +"       context.fillText(HeaderLeft, X, Height-FontAscent);\n" +"       X += Width;\n" +"       context.textAlign = \'right\';\n" +"       context.fillText(HeaderRight, X-5, Height-FontAscent);\n" +"       context.textAlign = \'left\';\n" +"       context.fillStyle = nBackColorOffset;\n" +"       if(X >= NameWidth)\n" +"       {\n" +"           context.fillRect(X-3, 0, 1, nHeight);\n" +"       }\n" +"   }\n" +"   function DrawTimer(Value, Color)\n" +"   {\n" +"       var Prc = Value * RcpReferenceTime;\n" +"       var YText = Y+Height-FontAscent;\n" +"       if(Prc > 1)\n" +"       {\n" +"           Prc = 1;\n" +"       }\n" +"       context.fillStyle = Color;\n" +"       context.fillRect(X+1, Y+1, Prc * nBarsWidth, InnerBoxHeight);\n" +"       X += nWidthBars;\n" +"       context.fillStyle = \'white\';\n" +"       context.fillText((\"      \" + Value.toFixed(2)).slice(-TimerLen), X, YText);\n" +"       X += nWidthMs;\n" +"   }\n" +"   function DrawMeta(Value, Width, Dec)\n" +"   {\n" +"       Value = FormatMeta(Value, Dec);\n" +"       X += (FontWidth*Width);\n" +"       context.textAlign = \'right\';\n" +"       context.fillText(Value, X-FontWidth, YText);\n" +"       context.textAlign = \'left\';\n" +"   }\n" +"   var InnerBoxHeight = BoxHeight-2;\n" +"   var TimerLen = 6;\n" +"   var TimerWidth = TimerLen * FontWidth;\n" +"   var nWidthBars = nBarsWidth+2;\n" +"   var nWidthMs = TimerWidth+2+10;\n" +"\n" +"\n" +"   if(2 == TimersGroups)\n" +"   {\n" +"       for(var i = 0; i < ThreadNames.length; ++i)\n" +"       {\n" +"           if(ThreadsActive[ThreadNames[i]] || ThreadsAllActive)\n" +"           {\n" +"               var X = 0;\n" +"               var YText = Y+Height-FontAscent;\n" +"               bMouseIn = DetailedViewMouseY >= Y && DetailedViewMouseY < Y + BoxHeight;\n" +"               nColorIndex = 1-nColorIndex;\n" +"               context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"               context.fillRect(0, Y, Width, Height);\n" +"               var ThreadColor = CSwitchColors[i % CSwitchColors.length];\n" +"               context.fillStyle = ThreadColor;\n" +"               context.fillText(ThreadNames[i], 1, YText);\n" +"               context.textAlign = \'left\';\n" +"               Y += Height;\n" +"               for(var idx in GroupOrder)\n" +"               {\n" +"                   var groupid = GroupOrder[idx];\n" +"                   var Group = GroupInfo[groupid];\n" +"                   var PerThreadTimer = ThreadGroupTimeArray[i][groupid];\n" +"                   var PerThreadTimerTotal = ThreadGroupTimeTotalArray[i][groupid];\n" +"                   if((PerThreadTimer > 0.0001|| PerThreadTimerTotal>0.1) && (GroupsAllActive || GroupsActive[Group.name]))\n" +"                   {\n" +"                       var GColor = GroupColors ? GroupInfo[groupid].color : \'white\';\n" +"                       var X = 0;\n" +"                       nColorIndex = 1-nColorIndex;\n" +"                       bMouseIn = DetailedViewMouseY >= Y && DetailedViewMouseY < Y + BoxHeight;\n" +"                       context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"                       context.fillRect(0, Y, Width, nHeight);\n" +"                       context.fillStyle = GColor;\n" +"                       context.textAlign = \'right\';\n" +"                       context.fillText(Group.name, NameWidth - 5, Y+Height-FontAscent);\n" +"                       context.textAlign = \'left\';\n" +"                       X += NameWidth;\n" +"                       DrawTimer(PerThreadTimer, GColor);\n" +"                       X += nWidthBars + nWidthMs; \n" +"                       DrawTimer(PerThreadTimerTotal, GColor);\n" +"\n" +"                       Y += Height;\n" +"                   }\n" +"               }\n" +"           }\n" +"       }\n" +"   }\n" +"   else\n" +"   {\n" +"       for(var idx in GroupOrder)\n" +"       {\n" +"           var groupid = GroupOrder[idx];\n" +"           var Group = GroupInfo[groupid];\n" +"           var GColor = GroupColors ? GroupInfo[groupid].color : \'white\';\n" +"           if(GroupsAllActive || GroupsActive[Group.name])\n" +"           {\n" +"               var TimerArray = Group.TimerArray;\n" +"               var X = XBase;\n" +"               nColorIndex = 1-nColorIndex;\n" +"               bMouseIn = DetailedViewMouseY >= Y && DetailedViewMouseY < Y + BoxHeight;\n" +"               context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"               context.fillRect(0, Y, Width, nHeight);\n" +"               context.fillStyle = GColor;\n" +"               context.fillText(Group.name, 1, Y+Height-FontAscent);\n" +"               X += NameWidth;\n" +"               DrawTimer(Group.average, GColor);\n" +"               DrawTimer(Group.max, GColor);\n" +"               DrawTimer(Group.total, GColor);\n" +"\n" +"               context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"               context.fillRect(0, Y, NameWidth, nHeight);\n" +"               context.fillStyle = GColor;\n" +"               context.fillText(Group.name, 1, Y+Height-FontAscent);\n" +"\n" +"\n" +"\n" +"               Y += Height;\n" +"               if(TimersGroups)\n" +"               {\n" +"                   for(var i = 0; i < ThreadNames.length; ++i)\n" +"                   {\n" +"                       var PerThreadTimer = ThreadGroupTimeArray[i][groupid];\n" +"                       var PerThreadTimerTotal = ThreadGroupTimeTotalArray[i][groupid];\n" +"                       if((PerThreadTimer > 0.0001|| PerThreadTimerTotal>0.1) && (ThreadsActive[ThreadNames[i]] || ThreadsAllActive))\n" +"                       {\n" +"                           var YText = Y+Height-FontAscent;\n" +"                           bMouseIn = DetailedViewMouseY >= Y && DetailedViewMouseY < Y + BoxHeight;\n" +"                           nColorIndex = 1-nColorIndex;\n" +"                           context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"                           context.fillRect(0, Y, Width, Height);\n" +"                           var ThreadColor = CSwitchColors[i % CSwitchColors.length];\n" +"                           context.fillStyle = ThreadColor;\n" +"                           context.textAlign = \'right\';\n" +"                           context.fillText(ThreadNames[i], NameWidth - 5, YText);\n" +"                           context.textAlign = \'left\';\n" +"                           X = NameWidth;\n" +"                           DrawTimer(PerThreadTimer, ThreadColor);\n" +"                           X += nWidthBars + nWidthMs; \n" +"                           DrawTimer(PerThreadTimerTotal, ThreadColor);\n" +"                           Y += Height;\n" +"                       }\n" +"                   }\n" +"               }\n" +"               else\n" +"               {\n" +"                   for(var timerindex in TimerArray)\n" +"                   {\n" +"                       var timerid = TimerArray[timerindex];\n" +"                       var Timer = TimerInfo[timerid];\n" +"                       var Average = Timer.average;\n" +"                       var Max = Timer.max;\n" +"                       var ExclusiveMax = Timer.exclmax;\n" +"                       var ExclusiveAverage = Timer.exclaverage;\n" +"                       var CallAverage = Timer.callaverage;\n" +"                       var CallCount = Timer.callcount;\n" +"                       var YText = Y+Height-FontAscent;\n" +"                       X = NameWidth + XBase;\n" +"\n" +"                       nColorIndex = 1-nColorIndex;\n" +"                       bMouseIn = DetailedViewMouseY >= Y && DetailedViewMouseY < Y + BoxHeight;\n" +"                       if(bMouseIn)\n" +"                       {\n" +"                           nHoverToken = timerid;\n" +"                       }\n" +"                       context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"                       context.fillRect(0, Y, Width, Height);\n" +"\n" +"                       DrawTimer(Average, Timer.color);\n" +"                       DrawTimer(Max,Timer.color);\n" +"                       DrawTimer(Timer.total,Timer.color);\n" +"                       DrawTimer(CallAverage,Timer.color);\n" +"                       context.fillStyle = \'white\';\n" +"                       context.fillText(CallCount, X, YText);\n" +"                       X += CountWidth;\n" +"                       DrawTimer(ExclusiveAverage,Timer.color);\n" +"                       DrawTimer(ExclusiveMax,Timer.color);\n" +"\n" +"                       if(TimersMeta)\n" +"                       {\n" +"                           context.fillStyle = \'white\';\n" +"                           for(var j = 0; j < nMetaLen; ++j)\n" +"                           {\n" +"                               // var Len = MetaNames[j].length + 1;\n" +"                               DrawMeta(Timer.meta[j], MetaLengths[j], 0);\n" +"                               DrawMeta(Timer.metaavg[j], MetaLengthsAvg[j], 2);\n" +"                               DrawMeta(Timer.metamax[j], MetaLengthsMax[j], 0);\n" +"                           }\n" +"                       }\n" +"                       context.fillStyle = bMouseIn ? nBackColorOffset : nBackColors[nColorIndex];\n" +"                       context.fillRect(0, Y, NameWidth, Height);\n" +"                       context.textAlign = \'right\';\n" +"                       context.fillStyle = Timer.color;\n" +"                       context.fillText(Timer.name, NameWidth - 5, YText);\n" +"                       context.textAlign = \'left\';\n" +"\n" +"\n" +"                       Y += Height;\n" +"                   }           \n" +"               }\n" +"           }\n" +"       }\n" +"   }\n" +"   X = 0;\n" +"   context.fillStyle = nBackColorOffset;\n" +"   context.fillRect(0, 0, Width, Height);\n" +"   context.fillStyle = \'white\';\n" +"   if(TimersGroups)\n" +"   {\n" +"       if(2 == TimersGroups)\n" +"       {\n" +"           DrawHeaderSplitLeftRight(\'Thread\', \'Group\', NameWidth);\n" +"           DrawHeaderSplit(\'Average\');\n" +"       }\n" +"       else\n" +"       {\n" +"           DrawHeaderSplitLeftRight(\'Group\', \'Thread\', NameWidth);\n" +"           DrawHeaderSplit(\'Average\');\n" +"           DrawHeaderSplit(\'Max\');\n" +"           DrawHeaderSplit(\'Total\');\n" +"       }\n" +"   }\n" +"   else\n" +"   {\n" +"       X = NameWidth + XBase;\n" +"       DrawHeaderSplit(\'Average\');\n" +"       DrawHeaderSplit(\'Max\');\n" +"       DrawHeaderSplit(\'Total\');\n" +"       DrawHeaderSplit(\'Call Average\');\n" +"       DrawHeaderSplitSingle(\'Count\', CountWidth);\n" +"       DrawHeaderSplit(\'Excl Average\');\n" +"       DrawHeaderSplit(\'Excl Max\');\n" +"       if(TimersMeta)\n" +"       {\n" +"           for(var i = 0; i < nMetaLen; ++i)\n" +"           {\n" +"               DrawHeaderSplitSingle(MetaNames[i], MetaLengths[i] * FontWidth);\n" +"               DrawHeaderSplitSingle(MetaNames[i] + \" Avg\", MetaLengthsAvg[i] * FontWidth);\n" +"               DrawHeaderSplitSingle(MetaNames[i] + \" Max\", MetaLengthsMax[i] * FontWidth);\n" +"           }\n" +"       }\n" +"       X = 0;\n" +"       context.fillStyle = nBackColorOffset;\n" +"       context.fillRect(0, 0, NameWidth, Height);\n" +"       context.fillStyle = \'white\';\n" +"   \n" +"       DrawHeaderSplitLeftRight(\'Group\', \'Timer\', NameWidth);\n" +"   \n" +"   }\n" +"\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"\n" +"//preprocess context switch data to contain array per thread\n" +"function PreprocessContextSwitchCacheItem(ThreadId)\n" +"{\n" +"   console.log(\'context switch preparing \' + ThreadId);\n" +"   var CSObject = CSwitchCache[ThreadId];\n" +"   if(ThreadId > 0 && !CSObject)\n" +"   {\n" +"       CSArrayIn = new Array();\n" +"       CSArrayOut = new Array();\n" +"       CSArrayCpu = new Array();\n" +"       var nCount = CSwitchTime.length;\n" +"       var j = 0;\n" +"       var TimeIn = -1.0;\n" +"       for(var i = 0; i < nCount; ++i)\n" +"       {   \n" +"           var ThreadIn = CSwitchThreadInOutCpu[j];\n" +"           var ThreadOut = CSwitchThreadInOutCpu[j+1];\n" +"           var Cpu = CSwitchThreadInOutCpu[j+2];\n" +"           if(TimeIn < 0)\n" +"           {\n" +"               if(ThreadIn == ThreadId)\n" +"               {\n" +"                   TimeIn = CSwitchTime[i];\n" +"               }\n" +"           }\n" +"           else\n" +"           {\n" +"               if(ThreadOut == ThreadId)\n" +"               {\n" +"                   var TimeOut = CSwitchTime[i];\n" +"                   CSArrayIn.push(TimeIn);\n" +"                   CSArrayOut.push(TimeOut);\n" +"                   CSArrayCpu.push(Cpu);\n" +"                   TimeIn = -1;\n" +"               }\n" +"           }\n" +"           j += 3;\n" +"       }\n" +"       CSObject = new Object();\n" +"       CSObject.Size = CSArrayIn.length;\n" +"       CSObject.In = CSArrayIn;\n" +"       CSObject.Out = CSArrayOut;\n" +"       CSObject.Cpu = CSArrayCpu;\n" +"       CSwitchCache[ThreadId] = CSObject;\n" +"   }\n" +"\n" +"}\n" +"function PreprocessContextSwitchCache()\n" +"{\n" +"   ProfileEnter(\"PreprocessContextSwitchCache\");\n" +"   var AllThreads = {};\n" +"   var nCount = CSwitchTime.length;\n" +"   for(var i = 0; i < nCount; ++i)\n" +"   {   \n" +"       var nThreadIn = CSwitchThreadInOutCpu[i];\n" +"       if(!AllThreads[nThreadIn])\n" +"       {\n" +"           AllThreads[nThreadIn] = \'\' + nThreadIn;\n" +"           var FoundThread = false;\n" +"           for(var i = 0; i < ThreadIds.length; ++i)\n" +"           {\n" +"               if(ThreadIds[i] == nThreadIn)\n" +"               {\n" +"                   FoundThread = true;\n" +"               }\n" +"           }\n" +"           if(!FoundThread)\n" +"           {\n" +"               CSwitchOnlyThreads.push(nThreadIn);\n" +"           }\n" +"       }\n" +"   }\n" +"   for(var i = 0; i < CSwitchOnlyThreads.length; ++i)\n" +"   {\n" +"       PreprocessContextSwitchCacheItem(CSwitchOnlyThreads[i]);\n" +"   }\n" +"   for(var i = 0; i < ThreadIds.length; ++i)\n" +"   {\n" +"       PreprocessContextSwitchCacheItem(ThreadIds[i]); \n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"function DrawContextSwitchBars(context, ThreadId, fScaleX, fOffsetY, fDetailedOffset, nHoverColor, MinWidth, bDrawEnabled)\n" +"{\n" +"   ProfileEnter(\"DrawContextSwitchBars\");\n" +"   var CSObject = CSwitchCache[ThreadId];\n" +"   if(CSObject)\n" +"   {\n" +"       var Size = CSObject.Size;       \n" +"       var In = CSObject.In;\n" +"       var Out = CSObject.Out;\n" +"       var Cpu = CSObject.Cpu;\n" +"       var nNumColors = CSwitchColors.length;\n" +"       for(var i = 0; i < Size; ++i)\n" +"       {\n" +"           var TimeIn = In[i];\n" +"           var TimeOut = Out[i];\n" +"           var ActiveCpu = Cpu[i];\n" +"\n" +"           var X = (TimeIn - fDetailedOffset) * fScaleX;\n" +"           if(X > nWidth)\n" +"           {\n" +"               break;\n" +"           }\n" +"           var W = (TimeOut - TimeIn) * fScaleX;\n" +"           if(W > MinWidth && X+W > 0)\n" +"           {\n" +"               if(nHoverCSCpu == ActiveCpu || bDrawEnabled)\n" +"               {\n" +"                   if(nHoverCSCpu == ActiveCpu)\n" +"                   {\n" +"                       context.fillStyle = nHoverColor;\n" +"                   }\n" +"                   else\n" +"                   {\n" +"                       context.fillStyle = CSwitchColors[ActiveCpu % nNumColors];\n" +"                   }\n" +"                   context.fillRect(X, fOffsetY, W, CSwitchHeight);\n" +"               }\n" +"               if(DetailedViewMouseX >= X && DetailedViewMouseX <= X+W && DetailedViewMouseY < fOffsetY+CSwitchHeight && DetailedViewMouseY >= fOffsetY)\n" +"               {\n" +"                   nHoverCSCpuNext = ActiveCpu;\n" +"                   fRangeBeginNext = TimeIn;\n" +"                   fRangeEndNext = TimeOut;\n" +"                   fRangeBeginGpuNext = fRangeEndGpuNext = -1;\n" +"               }\n" +"           }\n" +"       }\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"function DrawDetailedView(context, MinWidth, bDrawEnabled)\n" +"{\n" +"   if(bDrawEnabled)\n" +"   {\n" +"       DrawDetailedBackground(context);\n" +"   }\n" +"\n" +"   var colors = [ \'#ff0000\', \'#ff00ff\', \'#ffff00\'];\n" +"\n" +"   var fScaleX = nWidth / fDetailedRange; \n" +"   var fOffsetY = -nOffsetY + BoxHeight;\n" +"   nHoverTokenNext = -1;\n" +"   nHoverTokenLogIndexNext = -1;\n" +"   nHoverTokenIndexNext = -1;\n" +"   nHoverCounter += nHoverCounterDelta;\n" +"   if(nHoverCounter >= 255) \n" +"   {\n" +"       nHoverCounter = 255;\n" +"       nHoverCounterDelta = -nHoverCounterDelta;\n" +"   }\n" +"   if(nHoverCounter < 128) \n" +"   {\n" +"       nHoverCounter = 128;\n" +"       nHoverCounterDelta = -nHoverCounterDelta;\n" +"   }\n" +"   var nHoverHigh = nHoverCounter.toString(16);\n" +"   var nHoverLow = (127+255-nHoverCounter).toString(16);\n" +"   var nHoverColor = \'#\' + nHoverHigh + nHoverHigh + nHoverHigh;\n" +"\n" +"   context.fillStyle = \'black\';\n" +"   context.font = Font;\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var fTimeEnd = fDetailedOffset + fDetailedRange;\n" +"\n" +"   var FirstFrame = 0;\n" +"   for(var i = 0; i < Frames.length ; i++)\n" +"   {\n" +"       if(Frames[i].frameend < fDetailedOffset)\n" +"       {\n" +"           FirstFrame = i;\n" +"       }\n" +"   }\n" +"   var nMinTimeMs = MinWidth / fScaleX;\n" +"   {\n" +"\n" +"       var Batches = new Array(TimerInfo.length);\n" +"       var BatchesTxt = Array();\n" +"       var BatchesTxtPos = Array();\n" +"       var BatchesTxtColor = [\'#ffffff\', \'#333333\'];\n" +"\n" +"       for(var i = 0; i < 2; ++i)\n" +"       {\n" +"           BatchesTxt[i] = Array();\n" +"           BatchesTxtPos[i] = Array();\n" +"       }\n" +"       for(var i = 0; i < Batches.length; ++i)\n" +"       {\n" +"           Batches[i] = Array();\n" +"       }\n" +"       for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"       {\n" +"           var ThreadName = ThreadNames[nLog];\n" +"           if(ThreadsAllActive || ThreadsActive[ThreadName])\n" +"           {\n" +"\n" +"               var LodIndex = 0;\n" +"               var MinDelta = 0;\n" +"               var NextLod = 1;\n" +"               while(NextLod < LodData.length && LodData[NextLod].MinDelta[nLog] < nMinTimeMs)\n" +"               {\n" +"                   LodIndex = NextLod;\n" +"                   NextLod = NextLod + 1;\n" +"                   MinDelta = LodData[LodIndex].MinDelta[nLog];\n" +"               }\n" +"               if(LodIndex == LodData.length)\n" +"               {\n" +"                   LodIndex = LodData.length-1;\n" +"               }\n" +"               if(DisableLod)\n" +"               {\n" +"                   LodIndex = 0;\n" +"               }\n" +"\n" +"               context.fillStyle = \'white\';\n" +"               fOffsetY += BoxHeight;\n" +"               context.fillText(ThreadName, 0, fOffsetY);\n" +"               if(nContextSwitchEnabled)\n" +"               {\n" +"                   DrawContextSwitchBars(context, ThreadIds[nLog], fScaleX, fOffsetY, fDetailedOffset, nHoverColor, MinWidth, bDrawEnabled);\n" +"                   fOffsetY += CSwitchHeight+1;\n" +"               }\n" +"               var MaxDepth = 1;\n" +"               var StackPos = 0;\n" +"               var Stack = Array(20);\n" +"               var Lod = LodData[LodIndex];\n" +"\n" +"               var TypeArray = Lod.TypeArray[nLog];\n" +"               var IndexArray = Lod.IndexArray[nLog];\n" +"               var TimeArray = Lod.TimeArray[nLog];\n" +"\n" +"               var LocalFirstFrame = Frames[FirstFrame].FirstFrameIndex[nLog];\n" +"               var IndexStart = Lod.LogStart[LocalFirstFrame][nLog];\n" +"               var IndexEnd = TimeArray.length;\n" +"               IndexEnd = TimeArray.length;\n" +"               var HasSetHover = 0;\n" +"\n" +"\n" +"               for(var j = IndexStart; j < IndexEnd; ++j)\n" +"               {\n" +"                   var type = TypeArray[j];\n" +"                   var index = IndexArray[j];\n" +"                   var time = TimeArray[j];\n" +"                   if(type == 1)\n" +"                   {\n" +"                       //push\n" +"                       Stack[StackPos] = j;\n" +"                       StackPos++;\n" +"                       if(StackPos > MaxDepth)\n" +"                       {\n" +"                           MaxDepth = StackPos;\n" +"                       }\n" +"                   }\n" +"                   else if(type == 0)\n" +"                   {\n" +"                       if(StackPos>0)\n" +"                       {\n" +"                           StackPos--;\n" +"\n" +"                           var StartIndex = Stack[StackPos];\n" +"                           var timestart = TimeArray[StartIndex];\n" +"                           var timeend = time;\n" +"                           var X = (timestart - fDetailedOffset) * fScaleX;\n" +"                           var Y = fOffsetY + StackPos * BoxHeight;\n" +"                           var W = (timeend-timestart)*fScaleX;\n" +"\n" +"                           if(W > MinWidth && X < nWidth && X+W > 0)\n" +"                           {\n" +"                               if(bDrawEnabled || index == nHoverToken)\n" +"                               {\n" +"                                   Batches[index].push(X);\n" +"                                   Batches[index].push(Y);\n" +"                                   Batches[index].push(W);\n" +"                                   DebugDrawQuadCount++;\n" +"\n" +"                                   var XText = X < 0 ? 0 : X;\n" +"                                   var WText = W - (XText-X);\n" +"                                   if(XText + WText > nWidth)\n" +"                                   {\n" +"                                       WText = nWidth - XText;\n" +"                                   }\n" +"                                   var Name = TimerInfo[index].name;\n" +"                                   var NameLen = TimerInfo[index].len;\n" +"                                   var BarTextLen = Math.floor((WText-2)/FontWidth);\n" +"                                   var TimeText = TimeToMsString(timeend-timestart);\n" +"                                   var TimeTextLen = TimeText.length;\n" +"\n" +"                                   if(BarTextLen >= 2)\n" +"                                   {\n" +"                                       if(BarTextLen < NameLen)\n" +"                                           Name = Name.substr(0, BarTextLen);\n" +"                                       var txtidx = TimerInfo[index].textcolorindex;\n" +"                                       var YPos = Y+BoxHeight-FontAscent;\n" +"                                       BatchesTxt[txtidx].push(Name);\n" +"                                       BatchesTxtPos[txtidx].push(XText+2);\n" +"\n" +"                                       BatchesTxtPos[txtidx].push(YPos);\n" +"                                       DebugDrawTextCount++;\n" +"                                       if(BarTextLen - NameLen > TimeTextLen)\n" +"                                       {\n" +"                                           BatchesTxt[txtidx].push(TimeText);\n" +"                                           BatchesTxtPos[txtidx].push(XText+WText-2 - TimeTextLen * FontWidth);\n" +"                                           BatchesTxtPos[txtidx].push(YPos);\n" +"                                           DebugDrawTextCount++;\n" +"                                       }\n" +"\n" +"                                   }\n" +"                               }\n" +"\n" +"                               if(DetailedViewMouseX >= X && DetailedViewMouseX <= X+W && DetailedViewMouseY < Y+BoxHeight && DetailedViewMouseY >= Y)\n" +"                               {\n" +"                                   fRangeBeginNext = timestart;\n" +"                                   fRangeEndNext = timeend;\n" +"                                   if(TypeArray[StartIndex+1] == 3 && TypeArray[j+1] == 3)\n" +"                                   {\n" +"                                       fRangeBeginGpuNext = fRangeBeginNext;\n" +"                                       fRangeEndGpuNext = fRangeEndNext;\n" +"                                       //cpu tick is stored following\n" +"                                       fRangeBeginNext = TimeArray[StartIndex+1];\n" +"                                       fRangeEndNext = TimeArray[j+1];\n" +"                                   }\n" +"                                   else\n" +"                                   {\n" +"                                       fRangeBeginGpuNext = -1;\n" +"                                       fRangeEndGpuNext = -1;\n" +"                                   }\n" +"\n" +"                                   nHoverTokenNext = index;\n" +"                                   nHoverTokenIndexNext = j;\n" +"                                   nHoverTokenLogIndexNext = nLog;\n" +"                                   bHasSetHover = 1;\n" +"                               }\n" +"                           }\n" +"                           if(StackPos == 0 && time > fTimeEnd)\n" +"                               break;                                          \n" +"                       }\n" +"                   }\n" +"               }\n" +"               fOffsetY += (1+g_MaxStack[nLog]) * BoxHeight;\n" +"\n" +"               if(HasSetHover)\n" +"               {\n" +"                   for(var i = 0; i < Frames.length-1; ++i)\n" +"                   {\n" +"                       var IndexStart = Lod.LogStart[i][nLog];\n" +"                       if(nHoverTokenNext >= IndexStart)\n" +"                       {\n" +"                           nHoverFrame = i;\n" +"                       }\n" +"                   }\n" +"               }\n" +"           }\n" +"       }\n" +"\n" +"       if(nContextSwitchEnabled) //non instrumented threads.\n" +"       {\n" +"           var ContextSwitchThreads = CSwitchOnlyThreads;\n" +"           for(var i = 0; i < ContextSwitchThreads.length; ++i)\n" +"           {\n" +"               var ThreadId = ContextSwitchThreads[i];\n" +"               var ThreadName = \'\' + ThreadId;\n" +"               DrawContextSwitchBars(context, ThreadId, fScaleX, fOffsetY, fDetailedOffset, nHoverColor, MinWidth, bDrawEnabled);\n" +"               context.fillStyle = \'white\';\n" +"               context.fillText(ThreadName, 0, fOffsetY+5);\n" +"               fOffsetY += BoxHeight + 1;\n" +"           }\n" +"       }\n" +"\n" +"\n" +"       {\n" +"           for(var i = 0; i < Batches.length; ++i)\n" +"           {\n" +"               var a = Batches[i];\n" +"               if(a.length)\n" +"               {\n" +"                   context.fillStyle = TimerInfo[i].colordark;\n" +"                   if(!DisableMerge)\n" +"                   {\n" +"                       for(var j = 0; j < a.length; j += 3)\n" +"                       {                       \n" +"                           var X = a[j];\n" +"                           var Y = a[j+1];\n" +"                           var BaseWidth = j + 2;\n" +"                           var W = a[BaseWidth];\n" +"                           while(j+1 < a.length && W < 1)\n" +"                           {\n" +"                               var jnext = j+3;\n" +"                               var XNext = a[jnext];\n" +"                               var YNext = a[jnext+1];\n" +"                               var WNext = a[jnext+2];\n" +"                               var Delta = XNext - (X+W);\n" +"                               var YDelta = Math.abs(Y - YNext);                           \n" +"                               if(Delta < 0.3 && YDelta < 0.5 && WNext < 1)\n" +"                               {\n" +"                                   W = (XNext+WNext) - X;\n" +"                                   a[BaseWidth] = W;\n" +"                                   a[jnext+2] = 0;\n" +"                                   j += 3;\n" +"                               }\n" +"                               else\n" +"                               {\n" +"                                   break;\n" +"                               }\n" +"\n" +"                           }\n" +"                       }\n" +"                   }\n" +"                   var off = 0.7;\n" +"                   var off2 = 2*off;\n" +"                   context.fillStyle = TimerInfo[i].colordark;\n" +"                   for(var j = 0; j < a.length; j += 3)\n" +"                   {                       \n" +"                       var X = a[j];\n" +"                       var Y = a[j+1];\n" +"                       var W = a[j+2];\n" +"                       if(W >= 1)\n" +"                       {\n" +"                           context.fillRect(X, Y, W, BoxHeight-1);\n" +"                       }\n" +"                   }\n" +"   \n" +"\n" +"                   if(i == nHoverToken)\n" +"                   {\n" +"                       context.fillStyle = nHoverColor;\n" +"                   }\n" +"                   else\n" +"                   {\n" +"                       context.fillStyle = TimerInfo[i].color;\n" +"                   }\n" +"                   for(var j = 0; j < a.length; j += 3)\n" +"                   {                       \n" +"                       var X = a[j];\n" +"                       var Y = a[j+1];\n" +"                       var W = a[j+2];\n" +"                       if(W > 0)\n" +"                       {\n" +"                           context.fillRect(X+off, Y+off, W-off2, BoxHeight-1-off2);\n" +"                       }\n" +"                   }\n" +"               }\n" +"           }   \n" +"       }\n" +"       for(var i = 0; i < BatchesTxt.length; ++i)\n" +"       {\n" +"           context.fillStyle = BatchesTxtColor[i];\n" +"           var TxtArray = BatchesTxt[i];\n" +"           var PosArray = BatchesTxtPos[i];\n" +"           for(var j = 0; j < TxtArray.length; ++j)\n" +"           {\n" +"               var k = j * 2;\n" +"               context.fillText(TxtArray[j], PosArray[k],PosArray[k+1]);\n" +"           }\n" +"       }\n" +"\n" +"   }\n" +"}\n" +"function DrawTextBox(context, text, x, y, align)\n" +"{\n" +"   var textsize = context.measureText(text).width;\n" +"   var offsetx = 0;\n" +"   var offsety = -FontHeight;\n" +"   if(align == \'center\')\n" +"   {\n" +"       offsetx = -textsize / 2.0;\n" +"   }\n" +"   else if(align == \'right\')\n" +"   {\n" +"       offsetx = -textsize;\n" +"   }\n" +"   context.fillStyle = nBackColors[0];\n" +"   context.fillRect(x + offsetx, y + offsety, textsize+2, FontHeight + 2);\n" +"   context.fillStyle = \'white\';\n" +"   context.fillText(text, x, y);\n" +"\n" +"}\n" +"function DrawRange(context, fBegin, fEnd, ColorBack, ColorFront, Offset, Name)\n" +"{\n" +"   if(fBegin < fEnd)\n" +"   {\n" +"       var fScaleX = nWidth / fDetailedRange; \n" +"       var X = (fBegin - fDetailedOffset) * fScaleX;\n" +"       var YSpace = (FontHeight+2);\n" +"       var Y = YSpace * (Offset);\n" +"       var W = (fEnd - fBegin) * fScaleX;\n" +"       context.globalAlpha = 0.1;\n" +"       context.fillStyle = ColorBack;\n" +"       context.fillRect(X, 0, W, nHeight);\n" +"       context.globalAlpha = 1;\n" +"       context.strokeStyle = ColorFront;\n" +"       context.beginPath();\n" +"       context.moveTo(X, 0);\n" +"       context.lineTo(X, nHeight);\n" +"       context.moveTo(X+W, 0);\n" +"       context.lineTo(X+W, nHeight);\n" +"       context.stroke();\n" +"       var Duration = (fEnd - fBegin).toFixed(2) + \"ms\";\n" +"       var Center = ((fBegin + fEnd) / 2.0) - fDetailedOffset;\n" +"       var DurationWidth = context.measureText(Duration+ \"   \").width;\n" +"\n" +"       context.fillStyle = \'white\';\n" +"       context.textAlign = \'right\';\n" +"       var TextPosY = Y + YSpace;\n" +"       DrawTextBox(context, \'\' + fBegin.toFixed(2), X-3, TextPosY, \'right\');\n" +"       if(DurationWidth < W + 10)\n" +"       {\n" +"           context.textAlign = \'center\';\n" +"           DrawTextBox(context,\'\' + Duration,Center * fScaleX, TextPosY, \'center\');\n" +"\n" +"           var W0 = W - DurationWidth + FontWidth*1.5;\n" +"           if(W0 > 6)\n" +"           {\n" +"               var Y0 = Y + FontHeight * 0.5;\n" +"               W0 = W0 / 2.0;\n" +"               var X0 = X + W0;\n" +"               var X1 = X + W - W0;\n" +"               context.strokeStyle = ColorFront;\n" +"               context.beginPath();\n" +"               context.moveTo(X, Y0);\n" +"               context.lineTo(X0, Y0);\n" +"               context.moveTo(X0, Y0-2);\n" +"               context.lineTo(X0, Y0+2);\n" +"               context.moveTo(X1, Y0-2);\n" +"               context.lineTo(X1, Y0+2);\n" +"               context.moveTo(X1, Y0);\n" +"               context.lineTo(X + W, Y0);\n" +"               context.stroke();\n" +"           }\n" +"       }\n" +"       context.textAlign = \'left\';\n" +"       DrawTextBox(context, \'\' + fEnd.toFixed(2), X + W + 2, TextPosY, \'left\');\n" +"       DrawTextBox(context, Name, X + W + 2, nHeight - FontHeight - YSpace*Offset, \'left\');\n" +"       Offset += 1;\n" +"   }\n" +"   return Offset;\n" +"}\n" +"\n" +"function DrawDetailed(Animation)\n" +"{\n" +"   if(AnimationActive != Animation || !Initialized)\n" +"   {\n" +"       return;\n" +"   }\n" +"   ProfileEnter(\"DrawDetailed\");\n" +"   DebugDrawQuadCount = 0;\n" +"   DebugDrawTextCount = 0;\n" +"   nHoverCSCpuNext = -1;\n" +"\n" +"   fRangeBeginNext = fRangeEndNext = -1;\n" +"   fRangeBeginGpuNext = fRangeEndGpuNext = -1;\n" +"   var fRangeBeginGpu = -1;\n" +"   var fRangeEndGpu = -1;\n" +"\n" +"   var start = new Date();\n" +"   nDrawCount++;\n" +"\n" +"   var context = CanvasDetailedView.getContext(\'2d\');\n" +"   var offscreen = CanvasDetailedOffscreen.getContext(\'2d\');\n" +"   var fScaleX = nWidth / fDetailedRange; \n" +"   var fOffsetY = -nOffsetY + BoxHeight;\n" +"\n" +"   if(DetailedRedrawState.fOffsetY == fOffsetY && DetailedRedrawState.fDetailedOffset == fDetailedOffset && DetailedRedrawState.fDetailedRange == fDetailedRange && !KeyCtrlDown && !KeyShiftDown && !MouseDragButton)\n" +"   {\n" +"       Invalidate++;\n" +"   }\n" +"   else\n" +"   {\n" +"       Invalidate = 0;\n" +"       DetailedRedrawState.fOffsetY = fOffsetY;\n" +"       DetailedRedrawState.fDetailedOffset = fDetailedOffset;\n" +"       DetailedRedrawState.fDetailedRange = fDetailedRange;\n" +"   }\n" +"   if(Invalidate == 0) //when panning, only draw bars that are a certain width to keep decent framerate\n" +"   {\n" +"       context.clearRect(0, 0, CanvasDetailedView.width, CanvasDetailedView.height);\n" +"       DrawDetailedView(context, nMinWidthPan, true);\n" +"   }\n" +"   else if(Invalidate == 1) //draw full and store\n" +"   {\n" +"       offscreen.clearRect(0, 0, CanvasDetailedView.width, CanvasDetailedView.height);\n" +"       DrawDetailedView(offscreen, nMinWidth, true);\n" +"       OffscreenData = offscreen.getImageData(0, 0, CanvasDetailedOffscreen.width, CanvasDetailedOffscreen.height);\n" +"   }\n" +"   else//reuse stored result untill next time viewport is changed.\n" +"   {\n" +"       context.clearRect(0, 0, CanvasDetailedView.width, CanvasDetailedView.height);\n" +"       context.putImageData(OffscreenData, 0, 0);\n" +"       DrawDetailedView(context, nMinWidth, false);\n" +"   }\n" +"\n" +"   if(KeyShiftDown || KeyCtrlDown || MouseDragButton || MouseDragSelectRange())\n" +"   {\n" +"       nHoverToken = -1;\n" +"       nHoverTokenIndex = -1;\n" +"       nHoverTokenLogIndex = -1;\n" +"       fRangeBegin = fRangeEnd = -1;\n" +"   }\n" +"   else\n" +"   {\n" +"       nHoverToken = nHoverTokenNext;\n" +"       nHoverTokenIndex = nHoverTokenIndexNext;\n" +"       nHoverTokenLogIndex = nHoverTokenLogIndexNext;\n" +"       if(fRangeBeginHistory < fRangeEndHistory)\n" +"       {\n" +"           fRangeBegin = fRangeBeginHistory;\n" +"           fRangeEnd = fRangeEndHistory;\n" +"           fRangeBeginGpu = fRangeBeginHistoryGpu;\n" +"           fRangeEndGpu = fRangeEndHistoryGpu;\n" +"       }\n" +"       else\n" +"       {\n" +"           fRangeBegin = fRangeBeginNext;\n" +"           fRangeEnd = fRangeEndNext;\n" +"           fRangeBeginGpu = fRangeBeginGpuNext;\n" +"           fRangeEndGpu = fRangeEndGpuNext;\n" +"       }\n" +"   }\n" +"\n" +"   DrawTextBox(context, TimeToMsString(fDetailedOffset), 0, FontHeight, \'left\');\n" +"   context.textAlign = \'right\';\n" +"   DrawTextBox(context, TimeToMsString(fDetailedOffset + fDetailedRange), nWidth, FontHeight, \'right\');\n" +"   context.textAlign = \'left\';\n" +"\n" +"   var Offset = 0;\n" +"   Offset = DrawRange(context, fRangeBeginSelect, fRangeEndSelect, \'#59d0ff\', \'#00ddff\', Offset, \"Selection\");\n" +"   Offset = DrawRange(context, fRangeBegin, fRangeEnd, \'#009900\', \'#00ff00\', Offset, \"Cpu\");\n" +"   Offset = DrawRange(context, fRangeBeginGpu, fRangeEndGpu, \'#996600\', \'#775500\', Offset, \"Gpu\");\n" +"\n" +"   nHoverCSCpu = nHoverCSCpuNext;\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"function ZoomTo(fZoomBegin, fZoomEnd)\n" +"{\n" +"   if(fZoomBegin < fZoomEnd)\n" +"   {\n" +"       AnimationActive = true;\n" +"       var fDetailedOffsetOriginal = fDetailedOffset;\n" +"       var fDetailedRangeOriginal = fDetailedRange;\n" +"       var fDetailedOffsetTarget = fZoomBegin;\n" +"       var fDetailedRangeTarget = fZoomEnd - fZoomBegin;\n" +"       var TimestampStart = new Date();\n" +"       var count = 0;\n" +"       function ZoomFunc(Timestamp)\n" +"       {\n" +"           var fPrc = (new Date() - TimestampStart) / (ZOOM_TIME * 1000.0);\n" +"           if(fPrc > 1.0)\n" +"           {\n" +"               fPrc = 1.0;\n" +"           }\n" +"           fPrc = Math.pow(fPrc, 0.3);\n" +"           fDetailedOffset = fDetailedOffsetOriginal + (fDetailedOffsetTarget - fDetailedOffsetOriginal) * fPrc;\n" +"           fDetailedRange = fDetailedRangeOriginal + (fDetailedRangeTarget - fDetailedRangeOriginal) * fPrc;\n" +"           DrawDetailed(true);\n" +"           if(fPrc >= 1.0)\n" +"           {\n" +"               AnimationActive = false;\n" +"               fDetailedOffset = fDetailedOffsetTarget;\n" +"               fDetailedRange = fDetailedRangeTarget;\n" +"           }\n" +"           else\n" +"           {\n" +"               requestAnimationFrame(ZoomFunc);\n" +"           }\n" +"       }\n" +"       requestAnimationFrame(ZoomFunc);\n" +"   }\n" +"}\n" +"function RequestRedraw()\n" +"{\n" +"   Invalidate = 0;\n" +"   Draw(1);\n" +"}\n" +"function Draw(RedrawMode)\n" +"{\n" +"   if(ProfileMode)\n" +"   {\n" +"       ProfileModeClear();\n" +"       ProfileEnter(\"Total\");\n" +"   }\n" +"   if(RedrawMode == 1)\n" +"   {\n" +"       if(Mode == ModeTimers)\n" +"       {\n" +"           DrawBarView();\n" +"           DrawHoverToolTip();\n" +"       }\n" +"       else if(Mode == ModeDetailed)\n" +"       {\n" +"           DrawDetailed(false);\n" +"           DrawHoverToolTip();\n" +"       }\n" +"   }\n" +"   DrawDetailedFrameHistory();\n" +"\n" +"   if(ProfileMode)\n" +"   {\n" +"       ProfileLeave();\n" +"       ProfileModeDraw(CanvasDetailedView);\n" +"   }\n" +"}\n" +"\n" +"function AutoRedraw(Timestamp)\n" +"{\n" +"   var RedrawMode = 0;\n" +"   if(Mode == ModeDetailed)\n" +"   {\n" +"       if(ProfileMode == 2 || ((nHoverCSCpu >= 0 || nHoverToken != -1) && !KeyCtrlDown && !KeyShiftDown && !MouseDragButton)||(Invalidate<2 && !KeyCtrlDown && !KeyShiftDown && !MouseDragButton))\n" +"       {\n" +"           RedrawMode = 1;\n" +"       }\n" +"   }\n" +"   else\n" +"   {\n" +"       if(Invalidate < 1)\n" +"       {\n" +"           RedrawMode = 1;\n" +"       }\n" +"   }\n" +"   if(RedrawMode)\n" +"   {\n" +"       Draw(RedrawMode);\n" +"   }\n" +"   else if(FlashFrameCounter>0)\n" +"   {\n" +"       Draw(0);\n" +"   }\n" +"   requestAnimationFrame(AutoRedraw);\n" +"}\n" +"\n" +"\n" +"function ZoomGraph(nZoom)\n" +"{\n" +"   var fOldRange = fDetailedRange;\n" +"   if(nZoom>0)\n" +"   {\n" +"       fDetailedRange *= Math.pow(nModDown ? 1.40 : 1.03, nZoom);\n" +"   }\n" +"   else\n" +"   {\n" +"       var fNewDetailedRange = fDetailedRange / Math.pow((nModDown ? 1.40 : 1.03), -nZoom);\n" +"       if(fNewDetailedRange < 0.0001) //100ns\n" +"           fNewDetailedRange = 0.0001;\n" +"       fDetailedRange = fNewDetailedRange;\n" +"   }\n" +"\n" +"   var fDiff = fOldRange - fDetailedRange;\n" +"   var fMousePrc = DetailedViewMouseX / nWidth;\n" +"   if(fMousePrc < 0)\n" +"   {\n" +"       fMousePrc = 0;\n" +"   }\n" +"   fDetailedOffset += fDiff * fMousePrc;\n" +"\n" +"}\n" +"\n" +"function MeasureFont()\n" +"{\n" +"   var context = CanvasDetailedView.getContext(\'2d\');\n" +"   context.font = Font;\n" +"   FontWidth = context.measureText(\'W\').width;\n" +"\n" +"}\n" +"function ResizeCanvas() \n" +"{\n" +"   nWidth = window.innerWidth;\n" +"   nHeight = window.innerHeight - CanvasHistory.height-2;\n" +"   DPR = window.devicePixelRatio;\n" +"\n" +"   if(DPR)\n" +"   {\n" +"       CanvasDetailedView.style.width = nWidth + \'px\'; \n" +"       CanvasDetailedView.style.height = nHeight + \'px\';\n" +"       CanvasDetailedView.width = nWidth * DPR;\n" +"       CanvasDetailedView.height = nHeight * DPR;\n" +"       CanvasHistory.style.width = window.innerWidth + \'px\';\n" +"       CanvasHistory.style.height = 70 + \'px\';\n" +"       CanvasHistory.width = window.innerWidth * DPR;\n" +"       CanvasHistory.height = 70 * DPR;\n" +"       CanvasHistory.getContext(\'2d\').scale(DPR,DPR);\n" +"       CanvasDetailedView.getContext(\'2d\').scale(DPR,DPR);\n" +"\n" +"       CanvasDetailedOffscreen.style.width = nWidth + \'px\';\n" +"       CanvasDetailedOffscreen.style.height = nHeight + \'px\';\n" +"       CanvasDetailedOffscreen.wid"; + +const size_t g_MicroProfileHtml_end_1_size = sizeof(g_MicroProfileHtml_end_1); +const char g_MicroProfileHtml_end_2[] = +"th = nWidth * DPR;\n" +"       CanvasDetailedOffscreen.height = nHeight * DPR;\n" +"       CanvasDetailedOffscreen.getContext(\'2d\').scale(DPR,DPR);\n" +"\n" +"   }\n" +"   else\n" +"   {\n" +"       DPR = 1;\n" +"       CanvasDetailedView.width = nWidth;\n" +"       CanvasDetailedView.height = nHeight;\n" +"       CanvasDetailedOffscreen.width = nWidth;\n" +"       CanvasDetailedOffscreen.height = nHeight;\n" +"       CanvasHistory.width = window.innerWidth;\n" +"   }\n" +"   RequestRedraw();\n" +"}\n" +"\n" +"var MouseDragOff = 0;\n" +"var MouseDragDown = 1;\n" +"var MouseDragUp = 2;\n" +"var MouseDragMove = 3;\n" +"var MouseDragState = MouseDragOff;\n" +"var MouseDragTarget = 0;\n" +"var MouseDragButton = 0;\n" +"var MouseDragKeyShift = 0;\n" +"var MouseDragKeyCtrl = 0;\n" +"var MouseDragX = 0;\n" +"var MouseDragY = 0;\n" +"var MouseDragXLast = 0;\n" +"var MouseDragYLast = 0;\n" +"var MouseDragXStart = 0;\n" +"var MouseDragYStart = 0;\n" +"\n" +"function clamp(number, min, max)\n" +"{\n" +"  return Math.max(min, Math.min(number, max));\n" +"}\n" +"\n" +"function MouseDragPan()\n" +"{\n" +"   return MouseDragButton == 1 || MouseDragKeyShift;\n" +"}\n" +"function MouseDragSelectRange()\n" +"{\n" +"   return MouseDragState == MouseDragMove && (MouseDragButton == 3 || (MouseDragKeyShift && MouseDragKeyCtrl));\n" +"}\n" +"function MouseHandleDrag()\n" +"{\n" +"   if(MouseDragTarget == CanvasDetailedView)\n" +"   {\n" +"       if(Mode == ModeDetailed)\n" +"       {\n" +"           if(MouseDragSelectRange())\n" +"           {\n" +"               var xStart = MouseDragXStart;\n" +"               var xEnd = MouseDragX;\n" +"               if(xStart > xEnd)\n" +"               {\n" +"                   var Temp = xStart;\n" +"                   xStart = xEnd;\n" +"                   xEnd = Temp;\n" +"               }\n" +"               if(xEnd - xStart > 1)\n" +"               {\n" +"                   fRangeBegin = fDetailedOffset + fDetailedRange * (xStart / nWidth);\n" +"                   fRangeEnd = fDetailedOffset + fDetailedRange * (xEnd / nWidth);\n" +"                   fRangeBeginSelect = fDetailedOffset + fDetailedRange * (xStart / nWidth);\n" +"                   fRangeEndSelect = fDetailedOffset + fDetailedRange * (xEnd / nWidth);\n" +"               }\n" +"           }\n" +"           else if(MouseDragPan())\n" +"           {\n" +"               var X = MouseDragX - MouseDragXLast;\n" +"               var Y = MouseDragY - MouseDragYLast;\n" +"               if(X)\n" +"               {\n" +"                   fDetailedOffset += -X * fDetailedRange / nWidth;\n" +"               }\n" +"               nOffsetY -= Y;\n" +"               if(nOffsetY < 0)\n" +"               {\n" +"                   nOffsetY = 0;\n" +"               }\n" +"           }\n" +"           else if(MouseDragKeyCtrl)\n" +"           {\n" +"               if(MouseDragY != MouseDragYLast)\n" +"               {\n" +"                   ZoomGraph(MouseDragY - MouseDragYLast);\n" +"               }\n" +"           }\n" +"       }\n" +"       else if(Mode == ModeTimers)\n" +"       {\n" +"           if(MouseDragKeyShift || MouseDragButton == 1)\n" +"           {\n" +"               var X = MouseDragX - MouseDragXLast;\n" +"               var Y = MouseDragY - MouseDragYLast;\n" +"               nOffsetBarsY -= Y;\n" +"               nOffsetBarsX -= X;\n" +"               if(nOffsetBarsY < 0)\n" +"               {\n" +"                   nOffsetBarsY = 0;\n" +"               }\n" +"               if(nOffsetBarsX < 0)\n" +"               {\n" +"                   nOffsetBarsX = 0;\n" +"               }\n" +"           }\n" +"\n" +"       }\n" +"\n" +"   }\n" +"   else if(MouseDragTarget == CanvasHistory)\n" +"   {\n" +"       function HistoryFrameTime(x)\n" +"       {\n" +"           var NumFrames = Frames.length;\n" +"           var fBarWidth = nWidth / NumFrames;\n" +"           var Index = clamp(Math.floor(NumFrames * x / nWidth), 0, NumFrames-1);\n" +"           var Lerp = clamp((x/fBarWidth - Index) , 0, 1);\n" +"           var time = Frames[Index].framestart + (Frames[Index].frameend - Frames[Index].framestart) * Lerp;\n" +"           return time;\n" +"       }\n" +"       if(MouseDragSelectRange())\n" +"       {\n" +"           fRangeBegin = fRangeEnd = -1;\n" +"\n" +"           var xStart = MouseDragXStart;\n" +"           var xEnd = MouseDragX;\n" +"           if(xStart > xEnd)\n" +"           {\n" +"               var Temp = xStart;\n" +"               xStart = xEnd;\n" +"               xEnd = Temp;\n" +"           }\n" +"           if(xEnd - xStart > 2)\n" +"           {\n" +"               var timestart = HistoryFrameTime(xStart);\n" +"               var timeend = HistoryFrameTime(xEnd);\n" +"               fDetailedOffset = timestart;\n" +"               fDetailedRange = timeend-timestart;\n" +"           }\n" +"       }\n" +"       else if(MouseDragPan())\n" +"       {\n" +"           var Time = HistoryFrameTime(MouseDragX);\n" +"           fDetailedOffset = Time - fDetailedRange / 2.0;\n" +"       }\n" +"   }\n" +"}\n" +"function MouseHandleDragEnd()\n" +"{\n" +"   if(MouseDragTarget == CanvasDetailedView)\n" +"   {\n" +"\n" +"   }\n" +"   else if(MouseDragTarget == CanvasHistory)\n" +"   {\n" +"       if(!MouseDragSelectRange() && !MouseDragPan())\n" +"       {\n" +"           ZoomTo(fRangeBegin, fRangeEnd);\n" +"           fRangeBegin = fRangeEnd = -1;\n" +"       }\n" +"\n" +"\n" +"   }\n" +"\n" +"}\n" +"\n" +"function MouseHandleDragClick()\n" +"{\n" +"   if(MouseDragTarget == CanvasDetailedView)\n" +"   {\n" +"       ZoomTo(fRangeBegin, fRangeEnd);\n" +"   }\n" +"   else if(MouseDragTarget == CanvasHistory)\n" +"   {\n" +"       if(Mode == ModeDetailed)\n" +"       {\n" +"           ZoomTo(fRangeBegin, fRangeEnd);\n" +"       }\n" +"   }\n" +"}\n" +"\n" +"function MapMouseButton(Event)\n" +"{\n" +"   if(event.button == 1 || event.which == 1)\n" +"   {\n" +"       return 1;\n" +"   }\n" +"   else if(event.button == 3 || event.which == 3)\n" +"   {\n" +"       return 3;\n" +"   }\n" +"   else\n" +"   {\n" +"       return 0;\n" +"   }\n" +"}\n" +"\n" +"function MouseDragReset()\n" +"{\n" +"   MouseDragState = MouseDragOff;\n" +"   MouseDragTarget = 0;\n" +"   MouseDragKeyShift = 0;\n" +"   MouseDragKeyCtrl = 0;\n" +"   MouseDragButton = 0;\n" +"}\n" +"function MouseDragKeyUp()\n" +"{\n" +"   if((MouseDragKeyShift && !KeyShiftDown) || (MouseDragKeyCtrl && !KeyCtrlDown))\n" +"   {\n" +"       MouseHandleDragEnd();\n" +"       MouseDragReset();\n" +"   }\n" +"}\n" +"function MouseDrag(Source, Event)\n" +"{\n" +"   if(Source == MouseDragOff || (MouseDragTarget && MouseDragTarget != Event.target))\n" +"   {\n" +"       MouseDragReset();\n" +"       return;\n" +"   }\n" +"\n" +"   var LocalRect = Event.target.getBoundingClientRect();\n" +"   MouseDragX = Event.clientX - LocalRect.left;\n" +"   MouseDragY = Event.clientY - LocalRect.top;\n" +"   // console.log(\'cur drag state \' + MouseDragState + \' Source \' + Source);\n" +"   if(MouseDragState == MouseDragMove)\n" +"   {\n" +"       var dx = Math.abs(MouseDragX - MouseDragXStart);\n" +"       var dy = Math.abs(MouseDragY - MouseDragYStart);\n" +"       if((Source == MouseDragUp && MapMouseButton(Event) == MouseDragButton) ||\n" +"           (MouseDragKeyCtrl && !KeyCtrlDown) ||\n" +"           (MouseDragKeyShift && !KeyShiftDown))\n" +"       {\n" +"           MouseHandleDragEnd();\n" +"           MouseDragReset();\n" +"           return;\n" +"       }\n" +"       else\n" +"       {\n" +"           MouseHandleDrag();\n" +"       }\n" +"   }\n" +"   else if(MouseDragState == MouseDragOff)\n" +"   {\n" +"       if(Source == MouseDragDown || KeyShiftDown || KeyCtrlDown)\n" +"       {\n" +"           MouseDragTarget = Event.target;\n" +"           MouseDragButton = MapMouseButton(Event);\n" +"           MouseDragState = MouseDragDown;\n" +"           MouseDragXStart = MouseDragX;\n" +"           MouseDragYStart = MouseDragY;\n" +"           MouseDragKeyCtrl = 0;\n" +"           MouseDragKeyShift = 0;\n" +"\n" +"           if(KeyShiftDown || KeyCtrlDown)\n" +"           {\n" +"               MouseDragKeyShift = KeyShiftDown;\n" +"               MouseDragKeyCtrl = KeyCtrlDown;\n" +"               MouseDragState = MouseDragMove;\n" +"           }\n" +"       }\n" +"   }\n" +"   else if(MouseDragState == MouseDragDown)\n" +"   {\n" +"       if(Source == MouseDragUp)\n" +"       {\n" +"           MouseHandleDragClick();\n" +"           MouseDragReset();\n" +"       }\n" +"       else if(Source == MouseDragMove)\n" +"       {\n" +"           var dx = Math.abs(MouseDragX - MouseDragXStart);\n" +"           var dy = Math.abs(MouseDragY - MouseDragYStart);\n" +"           if(dx+dy>1)\n" +"           {\n" +"               MouseDragState = MouseDragMove;\n" +"           }\n" +"       }\n" +"   }\n" +"   MouseDragXLast = MouseDragX;\n" +"   MouseDragYLast = MouseDragY;\n" +"}\n" +"\n" +"function MouseMove(evt)\n" +"{\n" +"    evt.preventDefault();\n" +"    MouseDrag(MouseDragMove, evt);\n" +"   MouseHistory = 0;\n" +"   MouseDetailed = 0;\n" +"   HistoryViewMouseX = HistoryViewMouseY = -1;\n" +"   var rect = evt.target.getBoundingClientRect();\n" +"   var x = evt.clientX - rect.left;\n" +"   var y = evt.clientY - rect.top;\n" +"   if(evt.target == CanvasDetailedView)\n" +"   {\n" +"       if(!MouseDragSelectRange())\n" +"       {\n" +"           fRangeBegin = fRangeEnd = -1;\n" +"       }\n" +"       DetailedViewMouseX = x;\n" +"       DetailedViewMouseY = y;\n" +"   }\n" +"   else if(evt.target = CanvasHistory)\n" +"   {\n" +"       var Rect = CanvasHistory.getBoundingClientRect();\n" +"       HistoryViewMouseX = x;\n" +"       HistoryViewMouseY = y;\n" +"   }\n" +"   Draw(1);\n" +"}\n" +"\n" +"function MouseButton(bPressed, evt)\n" +"{\n" +"    evt.preventDefault();\n" +"   MouseDrag(bPressed ? MouseDragDown : MouseDragUp, evt);\n" +"}\n" +"\n" +"function MouseOut(evt)\n" +"{\n" +"   MouseDrag(MouseDragOff, evt);\n" +"   KeyCtrlDown = 0;\n" +"   KeyShiftDown = 0;\n" +"   MouseDragButton = 0;\n" +"   nHoverToken = -1;\n" +"   fRangeBegin = fRangeEnd = -1;\n" +"}\n" +"\n" +"function MouseWheel(e)\n" +"{\n" +"    var e = window.event || e;\n" +"    var delta = (e.wheelDelta || e.detail * (-120));\n" +"    ZoomGraph((-4 * delta / 120.0) | 0);\n" +"    Draw(1);\n" +"}\n" +"\n" +"\n" +"function KeyUp(evt)\n" +"{\n" +"   if(evt.keyCode == 17)\n" +"   {\n" +"       KeyCtrlDown = 0;\n" +"       MouseDragKeyUp();\n" +"   }\n" +"   else if(evt.keyCode == 16)\n" +"   {\n" +"       KeyShiftDown = 0;\n" +"       MouseDragKeyUp();\n" +"   }\n" +"   if(evt.keyCode == 18)\n" +"   {\n" +"       FlipToolTip = 0;\n" +"   }\n" +"   if(evt.keyCode == 32)\n" +"   {\n" +"       if(fRangeBeginSelect < fRangeEndSelect)\n" +"       {\n" +"           ZoomTo(fRangeBeginSelect, fRangeEndSelect);\n" +"           fRangeBeginSelect = fRangeEndSelect = -1;\n" +"           MouseHandleDragEnd();\n" +"       }\n" +"   }\n" +"   if(evt.keyCode == 27)\n" +"   {\n" +"       fRangeBeginSelect = fRangeEndSelect = -1;       \n" +"   }\n" +"   Invalidate = 0;\n" +"}\n" +"\n" +"function KeyDown(evt)\n" +"{\n" +"   if(evt.keyCode == 18)\n" +"   {\n" +"       FlipToolTip = 1;\n" +"   }\n" +"   if(evt.keyCode == 17)\n" +"   {\n" +"       KeyCtrlDown = 1;\n" +"   }\n" +"   else if(evt.keyCode == 16)\n" +"   {\n" +"       KeyShiftDown = 1;\n" +"   }\n" +"   Invalidate = 0;\n" +"}\n" +"\n" +"function ReadCookie()\n" +"{\n" +"   var result = document.cookie.match(/fisk=([^;]+)/);\n" +"   var NewMode = ModeDetailed;\n" +"   var ReferenceTimeString = \'33ms\';\n" +"   if(result && result.length > 0)\n" +"   {\n" +"       var Obj = JSON.parse(result[1]);\n" +"       if(Obj.Mode)\n" +"       {\n" +"           NewMode = Obj.Mode;\n" +"       }\n" +"       if(Obj.ReferenceTime)\n" +"       {\n" +"           ReferenceTimeString = Obj.ReferenceTime;\n" +"       }\n" +"       if(Obj.ThreadsAllActive || Obj.ThreadsAllActive == 0 || Obj.ThreadsAllActive == false)\n" +"       {\n" +"           ThreadsAllActive = Obj.ThreadsAllActive;\n" +"       }\n" +"       else\n" +"       {\n" +"           ThreadsAllActive = 1;\n" +"       }\n" +"       if(Obj.ThreadsActive)\n" +"       {\n" +"           ThreadsActive = Obj.ThreadsActive;\n" +"       }\n" +"       if(Obj.GroupsAllActive || Obj.GroupsAllActive == 0 || Obj.GroupsAllActive)\n" +"       {\n" +"           GroupsAllActive = Obj.GroupsAllActive;\n" +"       }\n" +"       else\n" +"       {\n" +"           GroupsAllActive = 1;\n" +"       }\n" +"       if(Obj.GroupsActive)\n" +"       {\n" +"           GroupsActive = Obj.GroupsActive;\n" +"       }\n" +"       if(Obj.nContextSwitchEnabled)\n" +"       {\n" +"           nContextSwitchEnabled = Obj.nContextSwitchEnabled; \n" +"       }\n" +"       else\n" +"       {\n" +"           nContextSwitchEnabled = 1;\n" +"       }\n" +"       if(Obj.GroupColors)\n" +"       {\n" +"           GroupColors = Obj.GroupColors;\n" +"       }\n" +"       else\n" +"       {\n" +"           GroupColors = 0;\n" +"       }\n" +"       if(Obj.nHideHelp)\n" +"       {\n" +"           nHideHelp = 1;\n" +"       }\n" +"       TimersGroups = Obj.TimersGroups?Obj.TimersGroups:0;\n" +"       TimersMeta = Obj.TimersMeta?0:1;\n" +"   }\n" +"   SetContextSwitch(nContextSwitchEnabled);\n" +"   SetMode(NewMode, TimersGroups);\n" +"   SetReferenceTime(ReferenceTimeString);\n" +"   UpdateOptionsMenu();\n" +"   UpdateGroupColors();\n" +"}\n" +"function WriteCookie()\n" +"{\n" +"   var Obj = new Object();\n" +"   Obj.Mode = Mode;\n" +"   Obj.ReferenceTime = ReferenceTime + \'ms\';\n" +"   Obj.ThreadsActive = ThreadsActive;\n" +"   Obj.ThreadsAllActive = ThreadsAllActive;\n" +"   Obj.GroupsActive = GroupsActive;\n" +"   Obj.GroupsAllActive = GroupsAllActive;\n" +"   Obj.nContextSwitchEnabled = nContextSwitchEnabled;\n" +"   Obj.TimersGroups = TimersGroups?TimersGroups:0;\n" +"   Obj.TimersMeta = TimersMeta?0:1;\n" +"   Obj.GroupColors = GroupColors;\n" +"   if(nHideHelp)\n" +"   {\n" +"       Obj.nHideHelp = 1;\n" +"   }\n" +"   var date = new Date();\n" +"   date.setFullYear(2099);\n" +"   var cookie = \'fisk=\' + JSON.stringify(Obj) + \';expires=\' + date;\n" +"   document.cookie = cookie;\n" +"}\n" +"\n" +"var mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? \"DOMMouseScroll\" : \"mousewheel\" //FF doesn\'t recognize mousewheel as of FF3.x\n" +"\n" +"CanvasDetailedView.addEventListener(\'mousemove\', MouseMove, false);\n" +"CanvasDetailedView.addEventListener(\'mousedown\', function(evt) { MouseButton(true, evt); });\n" +"CanvasDetailedView.addEventListener(\'mouseup\', function(evt) { MouseButton(false, evt); } );\n" +"CanvasDetailedView.addEventListener(\'mouseout\', MouseOut);\n" +"CanvasDetailedView.addEventListener(\"contextmenu\", function (e) { e.preventDefault(); }, false);\n" +"CanvasDetailedView.addEventListener(mousewheelevt, MouseWheel, false);\n" +"CanvasHistory.addEventListener(\'mousemove\', MouseMove);\n" +"CanvasHistory.addEventListener(\'mousedown\', function(evt) { MouseButton(true, evt); });\n" +"CanvasHistory.addEventListener(\'mouseup\', function(evt) { MouseButton(false, evt); } );\n" +"CanvasHistory.addEventListener(\'mouseout\', MouseOut);\n" +"CanvasHistory.addEventListener(\"contextmenu\", function (e) { e.preventDefault(); }, false);\n" +"CanvasHistory.addEventListener(mousewheelevt, MouseWheel, false);\n" +"window.addEventListener(\'keydown\', KeyDown);\n" +"window.addEventListener(\'keyup\', KeyUp);\n" +"window.addEventListener(\'resize\', ResizeCanvas, false);\n" +"\n" +"function CalcAverage()\n" +"{\n" +"   var Sum = 0;\n" +"   var Count = 0;\n" +"   for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"   {\n" +"       StackPos = 0;\n" +"       for(var i = 0; i < Frames.length; i++)\n" +"       {\n" +"           var Frame_ = Frames[i];         \n" +"           var tt = Frame_.tt[nLog];\n" +"           var ts = Frame_.ts[nLog];\n" +"\n" +"           var count = tt.length;\n" +"           for(var j = 0; j < count; j++)\n" +"           {\n" +"               var type = tt[j];\n" +"               var time = ts[j];\n" +"               if(type == 1)\n" +"               {\n" +"                   Stack[StackPos] = time;//store the frame which it comes from\n" +"                   StackPos++;\n" +"               }\n" +"               else if(type == 0)\n" +"               {\n" +"                   if(StackPos>0)\n" +"                   {\n" +"\n" +"                       StackPos--;\n" +"                       var localtime = time - Stack[StackPos];\n" +"                       Count++;\n" +"                       Sum += localtime;\n" +"                   }\n" +"               }\n" +"           }\n" +"       }\n" +"   }\n" +"   return Sum / Count;\n" +"\n" +"}\n" +"\n" +"function MakeLod(index, MinDelta, TimeArray, TypeArray, IndexArray, LogStart)\n" +"{\n" +"   if(LodData[index])\n" +"   {\n" +"       console.log(\"error!!\");\n" +"   }\n" +"   // debugger;\n" +"   var o = new Object();\n" +"   o.MinDelta = MinDelta;\n" +"   o.TimeArray = TimeArray;\n" +"   o.TypeArray = TypeArray;\n" +"   o.IndexArray = IndexArray;\n" +"   o.LogStart = LogStart;\n" +"   LodData[index] = o;\n" +"}\n" +"\n" +"function PreprocessBuildSplitArray()\n" +"{\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"\n" +"   ProfileEnter(\"PreprocessBuildSplitArray\");\n" +"   var SplitArrays = new Array(nNumLogs);\n" +"\n" +"   for(nLog = 0; nLog < nNumLogs; ++nLog)\n" +"   {\n" +"       console.log(\"source log \" + nLog + \" size \" + LodData[0].TypeArray[nLog].length);\n" +"   }\n" +"\n" +"\n" +"   for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"   {\n" +"       var MaxDepth = 1;\n" +"       var StackPos = 0;\n" +"       var Stack = Array(20);\n" +"       var TypeArray = LodData[0].TypeArray[nLog];\n" +"       var TimeArray = LodData[0].TimeArray[nLog];\n" +"       var DeltaTimes = new Array(TypeArray.length);\n" +"\n" +"       for(var j = 0; j < TypeArray.length; ++j)\n" +"       {\n" +"           var type = TypeArray[j];\n" +"           var time = TimeArray[j];\n" +"           if(type == 1)\n" +"           {\n" +"               //push\n" +"               Stack[StackPos] = time;\n" +"               StackPos++;\n" +"           }\n" +"           else if(type == 0)\n" +"           {\n" +"               if(StackPos>0)\n" +"               {\n" +"                   StackPos--;\n" +"                   DeltaTimes[j] = time - Stack[StackPos];\n" +"               }\n" +"               else\n" +"               {\n" +"                   DeltaTimes[j] = 0;\n" +"               }\n" +"           }\n" +"       }\n" +"       DeltaTimes.sort(function(a,b){return b-a;});\n" +"       var SplitArray = Array(NumLodSplits);\n" +"       var SplitIndex = DeltaTimes.length;\n" +"\n" +"       var j = 0;\n" +"       for(j = 0; j < NumLodSplits; ++j)\n" +"       {\n" +"           SplitIndex = Math.floor(SplitIndex / 2);\n" +"           while(SplitIndex > 0 && !DeltaTimes[SplitIndex])\n" +"           {\n" +"               SplitIndex--;\n" +"           }\n" +"           if(SplitIndex < SplitMin)\n" +"           {\n" +"               break;\n" +"           }\n" +"           //search.. if 0\n" +"           var SplitTime = DeltaTimes[SplitIndex];\n" +"           if(SplitTime>=0)\n" +"           {\n" +"               SplitArray[j] = SplitTime;\n" +"           }\n" +"           else\n" +"           {\n" +"               SplitArray[j] = SPLIT_LIMIT;\n" +"           }\n" +"           if(j>0)\n" +"           {\n" +"               console.assert(SplitArray[j-1] <= SplitArray[j], \"must be less\");\n" +"           }\n" +"\n" +"       }\n" +"       for(; j < NumLodSplits; ++j)\n" +"       {\n" +"           SplitArray[j] = SPLIT_LIMIT;\n" +"           // console.log(\"split skipping \" + j + \" \" + SPLIT_LIMIT);\n" +"       }\n" +"\n" +"\n" +"       SplitArrays[nLog] = SplitArray;\n" +"   }\n" +"   ProfileLeave();\n" +"   return SplitArrays;\n" +"}\n" +"\n" +"function PreprocessBuildDurationArray()\n" +"{\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   ProfileEnter(\"PreprocessBuildDurationArray\");\n" +"   var DurationArrays = new Array(nNumLogs);\n" +"   for(nLog = 0; nLog < nNumLogs; ++nLog)\n" +"   {\n" +"       var MaxDepth = 1;\n" +"       var StackPos = 0;\n" +"       var Stack = Array(20);\n" +"       var StackIndex = Array(20);\n" +"       var TypeArray = LodData[0].TypeArray[nLog];\n" +"       var TimeArray = LodData[0].TimeArray[nLog];\n" +"       var DurationArray = Array(LodData[0].TypeArray[nLog].length);\n" +"       for(var j = 0; j < TypeArray.length; ++j)\n" +"       {\n" +"           var type = TypeArray[j];\n" +"           var time = TimeArray[j];\n" +"           if(type == 1)\n" +"           {\n" +"               //push\n" +"               Stack[StackPos] = time;\n" +"               StackIndex[StackPos] = j;\n" +"               StackPos++;\n" +"           }\n" +"           else if(type == 0)\n" +"           {\n" +"               if(StackPos>0)\n" +"               {\n" +"                   StackPos--;\n" +"                   var Duration = time - Stack[StackPos];\n" +"                   DurationArray[StackIndex[StackPos]] = Duration;\n" +"                   DurationArray[j] = Duration;\n" +"               }\n" +"               else\n" +"               {\n" +"                   DurationArray[j] = 0;\n" +"               }\n" +"           }\n" +"       }\n" +"       for(var j = 0; j < StackPos; ++j)\n" +"       {\n" +"           DurationArray[j] = 0;\n" +"       }\n" +"       DurationArrays[nLog] = DurationArray;\n" +"   }\n" +"   ProfileLeave();\n" +"   return DurationArrays;\n" +"\n" +"}\n" +"function PreprocessLods()\n" +"{\n" +"   ProfileEnter(\"PreprocessLods\");\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var SplitArrays = PreprocessBuildSplitArray();\n" +"   var DurationArrays = PreprocessBuildDurationArray();\n" +"   var Source = LodData[0];\n" +"   var SourceLogStart = Source.LogStart;\n" +"   var NumFrames = SourceLogStart.length;\n" +"\n" +"   for(var i = 0; i < NumLodSplits-1; ++i)\n" +"   {\n" +"       var DestLogStart = Array(SourceLogStart.length);\n" +"       for(var j = 0; j < DestLogStart.length; ++j)\n" +"       {\n" +"           DestLogStart[j] = Array(nNumLogs);\n" +"       }\n" +"       var MinDelta = Array(nNumLogs);\n" +"       var TimeArray = Array(nNumLogs);\n" +"       var IndexArray = Array(nNumLogs);\n" +"       var TypeArray = Array(nNumLogs);\n" +"\n" +"\n" +"\n" +"       for(nLog = 0; nLog < nNumLogs; ++nLog)\n" +"       {\n" +"           var SourceTypeArray = Source.TypeArray[nLog];\n" +"           var SourceTimeArray = Source.TimeArray[nLog];\n" +"           var SourceIndexArray = Source.IndexArray[nLog];\n" +"           var Duration = DurationArrays[nLog];\n" +"           console.assert(Duration.length == SourceTypeArray.length, \"must be equal!\");\n" +"           var SplitTime = SplitArrays[nLog][i];\n" +"\n" +"           MinDelta[nLog] = SplitTime;\n" +"           if(SplitTime < SPLIT_LIMIT)\n" +"           {\n" +"               var SourceCount = SourceTypeArray.length;\n" +"               var DestTypeArray = Array();\n" +"               var DestTimeArray = Array();\n" +"               var DestIndexArray = Array();\n" +"               var RemapArray = Array(SourceCount);\n" +"               var DiscardLast = 0;\n" +"\n" +"               for(var j = 0; j < SourceCount; ++j)\n" +"               {\n" +"                   RemapArray[j] = DestTypeArray.length;\n" +"                   if(Duration[j] >= SplitTime || (SourceTypeArray[j] == 3 && 0 == DiscardLast))\n" +"                   {\n" +"                       DiscardLast = 0;\n" +"                       DestTypeArray.push(SourceTypeArray[j]);\n" +"                       DestTimeArray.push(SourceTimeArray[j]);\n" +"                       DestIndexArray.push(SourceIndexArray[j]);\n" +"                   }\n" +"                   else\n" +"                   {\n" +"                       DiscardLast = 1;\n" +"                   }\n" +"               }\n" +"               TimeArray[nLog] = DestTimeArray;\n" +"               IndexArray[nLog] = DestIndexArray;\n" +"               TypeArray[nLog] = DestTypeArray;\n" +"               for(var j = 0; j < NumFrames; ++j)\n" +"               {\n" +"                   var OldStart = SourceLogStart[j][nLog];\n" +"                   var NewStart = RemapArray[OldStart];\n" +"                   var FrameArray = DestLogStart[j];\n" +"                   FrameArray[nLog] = NewStart;\n" +"               }\n" +"           }\n" +"           else\n" +"           {\n" +"\n" +"               for(var j = 0; j < NumFrames; ++j)\n" +"               {\n" +"                   var FrameArray = DestLogStart[j];\n" +"   \n" +"                   FrameArray[nLog] = 0;\n" +"               }\n" +"\n" +"           }\n" +"\n" +"       }\n" +"       MakeLod(i+1, MinDelta, TimeArray, TypeArray, IndexArray, DestLogStart);\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"function PreprocessGlobalArray()\n" +"{\n" +"   ProfileEnter(\"PreprocessGlobalArray\");\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   var CaptureStart = Frames[0].framestart;\n" +"   var CaptureEnd = Frames[Frames.length-1].frameend;\n" +"   g_TypeArray = new Array(nNumLogs);\n" +"   g_TimeArray = new Array(nNumLogs);\n" +"   g_IndexArray = new Array(nNumLogs);\n" +"   var StackPos = 0;\n" +"   var Stack = Array(20);\n" +"   var LogStartArray = new Array(Frames.length);\n" +"   for(var i = 0; i < Frames.length; i++)\n" +"   {\n" +"       Frames[i].LogStart = new Array(nNumLogs);   \n" +"       LogStartArray[i] = Frames[i].LogStart;\n" +"\n" +"       Frames[i].LogEnd = new Array(nNumLogs);\n" +"   }\n" +"   var MinDelta = Array(nNumLogs);\n" +"   for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"   {\n" +"       MinDelta[nLog] = 0;\n" +"       var Discard = 0;\n" +"       var TypeArray = new Array();\n" +"       var TimeArray = new Array();\n" +"       var IndexArray = new Array();\n" +"       for(var i = 0; i < Frames.length; i++)\n" +"       {\n" +"           var Frame_ = Frames[i]; \n" +"           Frame_.LogStart[nLog] = TimeArray.length;\n" +"           var FrameDiscard = Frame_.frameend + 33;//if timestamps are more than 33ms after current frame, we assume buffer has wrapped.\n" +"           var tt = Frame_.tt[nLog];\n" +"           var ts = Frame_.ts[nLog];\n" +"           var ti = Frame_.ti[nLog];\n" +"           var len = tt.length;\n" +"           var DiscardLast = 0;\n" +"           for(var xx = 0; xx < len; ++xx)\n" +"           {\n" +"               var Skip = (tt[i] == 3) ? DiscardLast : ts[xx] > FrameDiscard;\n" +"               if(Skip)\n" +"               {\n" +"                   Discard++;\n" +"                   DiscardLast = 1;\n" +"               }\n" +"               else\n" +"               {\n" +"                   DiscardLast = 0;\n" +"                   TypeArray.push(tt[xx]);\n" +"                   TimeArray.push(ts[xx]);\n" +"                   IndexArray.push(ti[xx]);\n" +"               }\n" +"           }\n" +"           Frame_.LogEnd[nLog] = TimeArray.length;\n" +"       }\n" +"       g_TypeArray[nLog] = TypeArray;\n" +"       g_TimeArray[nLog] = TimeArray;\n" +"       g_IndexArray[nLog] = IndexArray;\n" +"       if(Discard)\n" +"       {\n" +"           console.log(\'discarded \' + Discard + \' markers from \' + ThreadNames[nLog]);\n" +"       }\n" +"   }\n" +"   MakeLod(0, MinDelta, g_TimeArray, g_TypeArray, g_IndexArray, LogStartArray);\n" +"   ProfileLeave();\n" +"}\n" +"\n" +"function PreprocessFindFirstFrames()\n" +"{\n" +"   ProfileEnter(\"PreprocesFindFirstFrames\");\n" +"   //create arrays that show how far back we need to start search in order to get all markers.\n" +"   var nNumLogs = Frames[0].ts.length;\n" +"   for(var i = 0; i < Frames.length; i++)\n" +"   {\n" +"       Frames[i].FirstFrameIndex = new Array(nNumLogs);\n" +"   }\n" +"\n" +"   var StackPos = 0;\n" +"   var Stack = Array(20);\n" +"   g_MaxStack = Array(nNumLogs);\n" +"   \n" +"   for(nLog = 0; nLog < nNumLogs; nLog++)\n" +"   {\n" +"       var MaxStack = 0;\n" +"       StackPos = 0;\n" +"       for(var i = 0; i < Frames.length; i++)\n" +"       {\n" +"           var Frame_ = Frames[i];         \n" +"           var tt = Frame_.tt[nLog];\n" +"           var count = tt.length;\n" +"\n" +"           var FirstFrame = i;\n" +"           if(StackPos>0)\n" +"           {\n" +"               FirstFrame = Stack[0];\n" +"           }\n" +"           Frames[i].FirstFrameIndex[nLog] = FirstFrame;\n" +"\n" +"           for(var j = 0; j < count; j++)\n" +"           {\n" +"               var type = tt[j];\n" +"               if(type == 1)\n" +"               {\n" +"                   Stack[StackPos] = i;//store the frame which it comes from\n" +"                   StackPos++;\n" +"                   if(StackPos > MaxStack)\n" +"                   {\n" +"                       MaxStack = StackPos;\n" +"                   }\n" +"               }\n" +"               else if(type == 0)\n" +"               {\n" +"                   if(StackPos>0)\n" +"                   {\n" +"                       StackPos--;\n" +"                   }\n" +"               }\n" +"           }\n" +"       }\n" +"       g_MaxStack[nLog] = MaxStack;\n" +"   }\n" +"   ProfileLeave();\n" +"}\n" +"function PreprocessMeta()\n" +"{\n" +"   MetaLengths = Array(MetaNames.length);\n" +"   MetaLengthsAvg = Array(MetaNames.length);\n" +"   MetaLengthsMax = Array(MetaNames.length);\n" +"   for(var i = 0; i < MetaNames.length; ++i)\n" +"   {\n" +"       MetaLengths[i] = MetaNames[i].length+1;\n" +"       MetaLengthsAvg[i] = MetaNames[i].length+5;\n" +"       MetaLengthsMax[i] = MetaNames[i].length+5;\n" +"       if(MetaLengths[i]<12)\n" +"           MetaLengths[i] = 12;\n" +"       if(MetaLengthsAvg[i]<12)\n" +"           MetaLengthsAvg[i] = 12;\n" +"       if(MetaLengthsMax[i]<12)\n" +"           MetaLengthsMax[i] = 12;\n" +"   }\n" +"   for(var i = 0; i < TimerInfo.length; ++i)\n" +"   {\n" +"       var Timer = TimerInfo[i];\n" +"       for(var j = 0; j < MetaNames.length; ++j)\n" +"       {\n" +"           var Len = FormatMeta(Timer.meta[j],0).length + 2;\n" +"           var LenAvg = FormatMeta(Timer.meta[j],2).length + 2;\n" +"           var LenMax = FormatMeta(Timer.meta[j],0).length + 2;\n" +"           if(Len > MetaLengths[j])\n" +"           {\n" +"               MetaLengths[j] = Len;\n" +"           }\n" +"           if(LenAvg > MetaLengthsAvg[j])\n" +"           {\n" +"               MetaLengthsAvg[j] = LenAvg;\n" +"           }\n" +"           if(LenMax > MetaLengthsMax[j])\n" +"           {\n" +"               MetaLengthsMax[j] = LenMax;\n" +"           }\n" +"       }\n" +"   }\n" +"}\n" +"\n" +"function Preprocess()\n" +"{\n" +"   var ProfileModeOld = ProfileMode;\n" +"   ProfileMode = 1;\n" +"   ProfileModeClear();\n" +"   ProfileEnter(\"Preprocess\");\n" +"   PreprocessCalculateAllTimers();\n" +"   PreprocessFindFirstFrames();\n" +"   PreprocessGlobalArray();\n" +"   PreprocessLods();\n" +"   PreprocessMeta();\n" +"   PreprocessContextSwitchCache();\n" +"   ProfileLeave();\n" +"   ProfileModeDump();\n" +"   ProfileMode = ProfileModeOld;\n" +"   Initialized = 1;\n" +"}\n" +"\n" +"InitGroups();\n" +"ReadCookie();\n" +"MeasureFont()\n" +"InitThreadMenu();\n" +"InitGroupMenu();\n" +"InitFrameInfo();\n" +"UpdateThreadMenu();\n" +"ResizeCanvas();\n" +"Preprocess();\n" +"OnPageReady();\n" +"Draw(1);\n" +"AutoRedraw();\n" +"\n" +"</script>\n" +"</body>\n" +"</html>      "; + +const size_t g_MicroProfileHtml_end_2_size = sizeof(g_MicroProfileHtml_end_2); +const char* g_MicroProfileHtml_end[] = { +&g_MicroProfileHtml_end_0[0], +&g_MicroProfileHtml_end_1[0], +&g_MicroProfileHtml_end_2[0], +}; +size_t g_MicroProfileHtml_end_sizes[] = { +sizeof(g_MicroProfileHtml_end_0), +sizeof(g_MicroProfileHtml_end_1), +sizeof(g_MicroProfileHtml_end_2), +}; +size_t g_MicroProfileHtml_end_count = 3; +#endif //MICROPROFILE_EMBED_HTML + +///end file generated from  microprofile.html diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h new file mode 100644 index 000000000..eac1119a4 --- /dev/null +++ b/externals/microprofile/microprofileui.h @@ -0,0 +1,3348 @@ +#pragma once +// This is free and unencumbered software released into the public domain. +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// For more information, please refer to <http://unlicense.org/> +// +// *********************************************************************** +// +// +// + + +#ifndef MICROPROFILE_ENABLED +#error "microprofile.h must be included before including microprofileui.h" +#endif + +#ifndef MICROPROFILEUI_ENABLED +#define MICROPROFILEUI_ENABLED MICROPROFILE_ENABLED +#endif + +#ifndef MICROPROFILEUI_API +#define MICROPROFILEUI_API +#endif + + +#if 0 == MICROPROFILEUI_ENABLED +#define MicroProfileMouseButton(foo, bar) do{}while(0) +#define MicroProfileMousePosition(foo, bar, z) do{}while(0) +#define MicroProfileModKey(key) do{}while(0) +#define MicroProfileDraw(foo, bar) do{}while(0) +#define MicroProfileIsDrawing() 0 +#define MicroProfileToggleDisplayMode() do{}while(0) +#define MicroProfileSetDisplayMode(f) do{}while(0) +#else + +#ifndef MICROPROFILE_DRAWCURSOR +#define MICROPROFILE_DRAWCURSOR 0 +#endif + +#ifndef MICROPROFILE_DETAILED_BAR_NAMES +#define MICROPROFILE_DETAILED_BAR_NAMES 1 +#endif + +#ifndef MICROPROFILE_TEXT_WIDTH +#define MICROPROFILE_TEXT_WIDTH 5 +#endif + +#ifndef MICROPROFILE_TEXT_HEIGHT +#define MICROPROFILE_TEXT_HEIGHT 8 +#endif + +#ifndef MICROPROFILE_DETAILED_BAR_HEIGHT +#define MICROPROFILE_DETAILED_BAR_HEIGHT 12 +#endif + +#ifndef MICROPROFILE_DETAILED_CONTEXT_SWITCH_HEIGHT +#define MICROPROFILE_DETAILED_CONTEXT_SWITCH_HEIGHT 7 +#endif + +#ifndef MICROPROFILE_GRAPH_WIDTH +#define MICROPROFILE_GRAPH_WIDTH 256 +#endif + +#ifndef MICROPROFILE_GRAPH_HEIGHT +#define MICROPROFILE_GRAPH_HEIGHT 256 +#endif + +#ifndef MICROPROFILE_BORDER_SIZE +#define MICROPROFILE_BORDER_SIZE 1 +#endif + +#ifndef MICROPROFILE_HELP_LEFT +#define MICROPROFILE_HELP_LEFT "Left-Click" +#endif + +#ifndef MICROPROFILE_HELP_ALT +#define MICROPROFILE_HELP_ALT "Alt-Click" +#endif + +#ifndef MICROPROFILE_HELP_MOD +#define MICROPROFILE_HELP_MOD "Mod" +#endif + +#ifndef MICROPROFILE_BAR_WIDTH +#define MICROPROFILE_BAR_WIDTH 100 +#endif + +#ifndef MICROPROFILE_CUSTOM_MAX +#define MICROPROFILE_CUSTOM_MAX 8 +#endif + +#ifndef MICROPROFILE_CUSTOM_MAX_TIMERS +#define MICROPROFILE_CUSTOM_MAX_TIMERS 64 +#endif + +#ifndef MICROPROFILE_CUSTOM_PADDING +#define MICROPROFILE_CUSTOM_PADDING 12 +#endif + + +#define MICROPROFILE_FRAME_HISTORY_HEIGHT 50 +#define MICROPROFILE_FRAME_HISTORY_WIDTH 7 +#define MICROPROFILE_FRAME_HISTORY_COLOR_CPU 0xffff7f27 //255 127 39 +#define MICROPROFILE_FRAME_HISTORY_COLOR_GPU 0xff37a0ee //55 160 238 +#define MICROPROFILE_FRAME_HISTORY_COLOR_HIGHTLIGHT 0x7733bb44 +#define MICROPROFILE_FRAME_COLOR_HIGHTLIGHT 0x20009900 +#define MICROPROFILE_FRAME_COLOR_HIGHTLIGHT_GPU 0x20996600 +#define MICROPROFILE_NUM_FRAMES (MICROPROFILE_MAX_FRAME_HISTORY - (MICROPROFILE_GPU_FRAME_DELAY+1)) + +#define MICROPROFILE_TOOLTIP_MAX_STRINGS (32 + MICROPROFILE_MAX_GROUPS*2) +#define MICROPROFILE_TOOLTIP_STRING_BUFFER_SIZE (4*1024) +#define MICROPROFILE_TOOLTIP_MAX_LOCKED 3 + + +enum +{ +    MICROPROFILE_CUSTOM_BARS = 0x1, +    MICROPROFILE_CUSTOM_BAR_SOURCE_MAX = 0x2, +    MICROPROFILE_CUSTOM_BAR_SOURCE_AVG = 0, +    MICROPROFILE_CUSTOM_STACK = 0x4, +    MICROPROFILE_CUSTOM_STACK_SOURCE_MAX = 0x8, +    MICROPROFILE_CUSTOM_STACK_SOURCE_AVG = 0, +}; + + +MICROPROFILEUI_API void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight); //! call if drawing microprofilers +MICROPROFILEUI_API bool MicroProfileIsDrawing(); +MICROPROFILEUI_API void MicroProfileToggleGraph(MicroProfileToken nToken); +MICROPROFILEUI_API bool MicroProfileDrawGraph(uint32_t nScreenWidth, uint32_t nScreenHeight); +MICROPROFILEUI_API void MicroProfileToggleDisplayMode(); //switch between off, bars, detailed +MICROPROFILEUI_API void MicroProfileSetDisplayMode(int); //switch between off, bars, detailed +MICROPROFILEUI_API void MicroProfileClearGraph(); +MICROPROFILEUI_API void MicroProfileMousePosition(uint32_t nX, uint32_t nY, int nWheelDelta); +MICROPROFILEUI_API void MicroProfileModKey(uint32_t nKeyState); +MICROPROFILEUI_API void MicroProfileMouseButton(uint32_t nLeft, uint32_t nRight); +MICROPROFILEUI_API void MicroProfileDrawLineVertical(int nX, int nTop, int nBottom, uint32_t nColor); +MICROPROFILEUI_API void MicroProfileDrawLineHorizontal(int nLeft, int nRight, int nY, uint32_t nColor); +MICROPROFILEUI_API void MicroProfileLoadPreset(const char* pSuffix); +MICROPROFILEUI_API void MicroProfileSavePreset(const char* pSuffix); + +MICROPROFILEUI_API void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText, uint32_t nNumCharacters); +MICROPROFILEUI_API void MicroProfileDrawBox(int nX, int nY, int nX1, int nY1, uint32_t nColor, MicroProfileBoxType = MicroProfileBoxTypeFlat); +MICROPROFILEUI_API void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices, uint32_t nColor); +MICROPROFILEUI_API void MicroProfileDumpTimers(); + +MICROPROFILEUI_API void MicroProfileInitUI(); + +MICROPROFILEUI_API void MicroProfileCustomGroupToggle(const char* pCustomName); +MICROPROFILEUI_API void MicroProfileCustomGroupEnable(const char* pCustomName); +MICROPROFILEUI_API void MicroProfileCustomGroupEnable(uint32_t nIndex); +MICROPROFILEUI_API void MicroProfileCustomGroupDisable(); +MICROPROFILEUI_API void MicroProfileCustomGroup(const char* pCustomName, uint32_t nMaxTimers, uint32_t nAggregateFlip, float fReferenceTime, uint32_t nFlags); +MICROPROFILEUI_API void MicroProfileCustomGroupAddTimer(const char* pCustomName, const char* pGroup, const char* pTimer); + +#ifdef MICROPROFILEUI_IMPL +#ifdef _WIN32 +#define snprintf _snprintf +#endif +#include <stdlib.h> +#include <stdarg.h> +#include <math.h> +#include <algorithm> + +MICROPROFILE_DEFINE(g_MicroProfileDetailed, "MicroProfile", "Detailed View", 0x8888000); +MICROPROFILE_DEFINE(g_MicroProfileDrawGraph, "MicroProfile", "Draw Graph", 0xff44ee00); +MICROPROFILE_DEFINE(g_MicroProfileDrawBarView, "MicroProfile", "DrawBarView", 0x00dd77); +MICROPROFILE_DEFINE(g_MicroProfileDraw,"MicroProfile", "Draw", 0x737373); + + +struct MicroProfileStringArray +{ +    const char* ppStrings[MICROPROFILE_TOOLTIP_MAX_STRINGS]; +    char Buffer[MICROPROFILE_TOOLTIP_STRING_BUFFER_SIZE]; +    char* pBufferPos; +    uint32_t nNumStrings; +}; + +struct MicroProfileGroupMenuItem +{ +    uint32_t nIsCategory; +    uint32_t nCategoryIndex; +    uint32_t nIndex; +    const char* pName; +}; + +struct MicroProfileCustom +{ +    char pName[MICROPROFILE_NAME_MAX_LEN]; +    uint32_t nFlags; +    uint32_t nAggregateFlip; +    uint32_t nNumTimers; +    uint32_t nMaxTimers; +    uint64_t nGroupMask; +    float fReference; +    uint64_t* pTimers; +}; + +struct SOptionDesc +{ +    SOptionDesc(){} +    SOptionDesc(uint8_t nSubType, uint8_t nIndex, const char* fmt, ...):nSubType(nSubType), nIndex(nIndex) +    { +        va_list args; +        va_start (args, fmt); +        vsprintf(Text, fmt, args); +        va_end(args); +    } +    char Text[32]; +    uint8_t nSubType; +    uint8_t nIndex; +    bool bSelected; +}; +static uint32_t g_MicroProfileAggregatePresets[] = {0, 10, 20, 30, 60, 120}; +static float g_MicroProfileReferenceTimePresets[] = {5.f, 10.f, 15.f,20.f, 33.33f, 66.66f, 100.f, 250.f, 500.f, 1000.f}; +static uint32_t g_MicroProfileOpacityPresets[] = {0x40, 0x80, 0xc0, 0xff}; +static const char* g_MicroProfilePresetNames[] = +{ +    MICROPROFILE_DEFAULT_PRESET, +    "Render", +    "GPU", +    "Lighting", +    "AI", +    "Visibility", +    "Sound", +}; + +enum +{ +    MICROPROFILE_NUM_REFERENCE_PRESETS = sizeof(g_MicroProfileReferenceTimePresets)/sizeof(g_MicroProfileReferenceTimePresets[0]), +    MICROPROFILE_NUM_OPACITY_PRESETS = sizeof(g_MicroProfileOpacityPresets)/sizeof(g_MicroProfileOpacityPresets[0]), +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +    MICROPROFILE_OPTION_SIZE = MICROPROFILE_NUM_REFERENCE_PRESETS + MICROPROFILE_NUM_OPACITY_PRESETS * 2 + 2 + 7, +#else +    MICROPROFILE_OPTION_SIZE = MICROPROFILE_NUM_REFERENCE_PRESETS + MICROPROFILE_NUM_OPACITY_PRESETS * 2 + 2 + 3, +#endif +}; + +struct MicroProfileUI +{ +    //menu/mouse over stuff +    uint64_t nHoverToken; +    int64_t  nHoverTime; +    int      nHoverFrame; +#if MICROPROFILE_DEBUG +    uint64_t nHoverAddressEnter; +    uint64_t nHoverAddressLeave; +#endif + +    uint32_t nWidth; +    uint32_t nHeight; + + +    int nOffsetX; +    int nOffsetY; +    float fDetailedOffset; //display offset relative to start of latest displayable frame. +    float fDetailedRange; //no. of ms to display +    float fDetailedOffsetTarget; +    float fDetailedRangeTarget; +    uint32_t nOpacityBackground; +    uint32_t nOpacityForeground; +    bool bShowSpikes; + + + +    uint32_t                nMouseX; +    uint32_t                nMouseY; +    uint32_t                nMouseDownX; +    uint32_t                nMouseDownY; +    int                     nMouseWheelDelta; +    uint32_t                nMouseDownLeft; +    uint32_t                nMouseDownRight; +    uint32_t                nMouseLeft; +    uint32_t                nMouseRight; +    uint32_t                nMouseLeftMod; +    uint32_t                nMouseRightMod; +    uint32_t                nModDown; +    uint32_t                nActiveMenu; + +    MicroProfileLogEntry* pDisplayMouseOver; + +    int64_t                 nRangeBegin; +    int64_t                 nRangeEnd; +    int64_t                 nRangeBeginGpu; +    int64_t                 nRangeEndGpu; +    uint32_t                nRangeBeginIndex; +    uint32_t                nRangeEndIndex; +    MicroProfileThreadLog*  pRangeLog; +    uint32_t                nHoverColor; +    uint32_t                nHoverColorShared; + +    MicroProfileStringArray LockedToolTips[MICROPROFILE_TOOLTIP_MAX_LOCKED]; +    uint32_t                nLockedToolTipColor[MICROPROFILE_TOOLTIP_MAX_LOCKED]; +    int                     LockedToolTipFront; + +    MicroProfileGroupMenuItem   GroupMenu[MICROPROFILE_MAX_GROUPS + MICROPROFILE_MAX_CATEGORIES]; +    uint32_t                    GroupMenuCount; + + +    uint32_t                    nCustomActive; +    uint32_t                    nCustomTimerCount; +    uint32_t                    nCustomCount; +    MicroProfileCustom          Custom[MICROPROFILE_CUSTOM_MAX]; +    uint64_t                    CustomTimer[MICROPROFILE_CUSTOM_MAX_TIMERS]; + +    SOptionDesc Options[MICROPROFILE_OPTION_SIZE]; + + +}; + +MicroProfileUI g_MicroProfileUI; +#define UI g_MicroProfileUI +static uint32_t g_nMicroProfileBackColors[2] = {  0x474747, 0x313131 }; +#define MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS 16 +static uint32_t g_nMicroProfileContextSwitchThreadColors[MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS] = //palette generated by http://tools.medialab.sciences-po.fr/iwanthue/index.php +{ +    0x63607B, +    0x755E2B, +    0x326A55, +    0x523135, +    0x904F42, +    0x87536B, +    0x346875, +    0x5E6046, +    0x35404C, +    0x224038, +    0x413D1E, +    0x5E3A26, +    0x5D6161, +    0x4C6234, +    0x7D564F, +    0x5C4352, +}; + + +void MicroProfileInitUI() +{ +    static bool bInitialized = false; +    if(!bInitialized) +    { +        bInitialized = true; +        memset(&g_MicroProfileUI, 0, sizeof(g_MicroProfileUI)); +        UI.nActiveMenu = (uint32_t)-1; +        UI.fDetailedOffsetTarget = UI.fDetailedOffset = 0.f; +        UI.fDetailedRangeTarget = UI.fDetailedRange = 50.f; + +        UI.nOpacityBackground = 0xff<<24; +        UI.nOpacityForeground = 0xff<<24; + +        UI.bShowSpikes = false; + +        UI.nWidth = 100; +        UI.nHeight = 100; + +        UI.nCustomActive = (uint32_t)-1; +        UI.nCustomTimerCount = 0; +        UI.nCustomCount = 0; + +        int nIndex = 0; +        UI.Options[nIndex++] = SOptionDesc(0xff, 0, "%s", "Reference"); +        for(int i = 0; i < MICROPROFILE_NUM_REFERENCE_PRESETS; ++i) +        { +            UI.Options[nIndex++] = SOptionDesc(0, i, "  %6.2fms", g_MicroProfileReferenceTimePresets[i]); +        } +        UI.Options[nIndex++] = SOptionDesc(0xff, 0, "%s", "BG Opacity"); +        for(int i = 0; i < MICROPROFILE_NUM_OPACITY_PRESETS; ++i) +        { +            UI.Options[nIndex++] = SOptionDesc(1, i, "  %7d%%", (i+1)*25); +        } +        UI.Options[nIndex++] = SOptionDesc(0xff, 0, "%s", "FG Opacity"); +        for(int i = 0; i < MICROPROFILE_NUM_OPACITY_PRESETS; ++i) +        { +            UI.Options[nIndex++] = SOptionDesc(2, i, "  %7d%%", (i+1)*25); +        } +        UI.Options[nIndex++] = SOptionDesc(0xff, 0, "%s", "Spike Display"); +        UI.Options[nIndex++] = SOptionDesc(3, 0, "%s", "  Enable"); + +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +        UI.Options[nIndex++] = SOptionDesc(0xff, 0, "%s", "CSwitch Trace"); +        UI.Options[nIndex++] = SOptionDesc(4, 0, "%s", "  Enable"); +        UI.Options[nIndex++] = SOptionDesc(4, 1, "%s", "  All Threads"); +        UI.Options[nIndex++] = SOptionDesc(4, 2, "%s", "  No Bars"); +#endif +        MP_ASSERT(nIndex == MICROPROFILE_OPTION_SIZE); +    } +} + +void MicroProfileSetDisplayMode(int nValue) +{ +    MicroProfile& S = *MicroProfileGet(); +    nValue = nValue >= 0 && nValue < 4 ? nValue : S.nDisplay; +    S.nDisplay = nValue; +    UI.nOffsetY = 0; +} + +void MicroProfileToggleDisplayMode() +{ +    MicroProfile& S = *MicroProfileGet(); +    S.nDisplay = (S.nDisplay + 1) % 4; +    UI.nOffsetY = 0; +} + + +void MicroProfileStringArrayClear(MicroProfileStringArray* pArray) +{ +    pArray->nNumStrings = 0; +    pArray->pBufferPos = &pArray->Buffer[0]; +} + +void MicroProfileStringArrayAddLiteral(MicroProfileStringArray* pArray, const char* pLiteral) +{ +    MP_ASSERT(pArray->nNumStrings < MICROPROFILE_TOOLTIP_MAX_STRINGS); +    pArray->ppStrings[pArray->nNumStrings++] = pLiteral; +} + +void MicroProfileStringArrayFormat(MicroProfileStringArray* pArray, const char* fmt, ...) +{ +    MP_ASSERT(pArray->nNumStrings < MICROPROFILE_TOOLTIP_MAX_STRINGS); +    pArray->ppStrings[pArray->nNumStrings++] = pArray->pBufferPos; +    va_list args; +    va_start (args, fmt); +    pArray->pBufferPos += 1 + vsprintf(pArray->pBufferPos, fmt, args); +    va_end(args); +    MP_ASSERT(pArray->pBufferPos < pArray->Buffer + MICROPROFILE_TOOLTIP_STRING_BUFFER_SIZE); +} +void MicroProfileStringArrayCopy(MicroProfileStringArray* pDest, MicroProfileStringArray* pSrc) +{ +    memcpy(&pDest->ppStrings[0], &pSrc->ppStrings[0], sizeof(pDest->ppStrings)); +    memcpy(&pDest->Buffer[0], &pSrc->Buffer[0], sizeof(pDest->Buffer)); +    for(uint32_t i = 0; i < MICROPROFILE_TOOLTIP_MAX_STRINGS; ++i) +    { +        if(i < pSrc->nNumStrings) +        { +            if(pSrc->ppStrings[i] >= &pSrc->Buffer[0] && pSrc->ppStrings[i] < &pSrc->Buffer[0] + MICROPROFILE_TOOLTIP_STRING_BUFFER_SIZE) +            { +                pDest->ppStrings[i] += &pDest->Buffer[0] - &pSrc->Buffer[0]; +            } +        } +    } +    pDest->nNumStrings = pSrc->nNumStrings; +} + +void MicroProfileFloatWindowSize(const char** ppStrings, uint32_t nNumStrings, uint32_t* pColors, uint32_t& nWidth, uint32_t& nHeight, uint32_t* pStringLengths = 0) +{ +    uint32_t* nStringLengths = pStringLengths ? pStringLengths : (uint32_t*)alloca(nNumStrings * sizeof(uint32_t)); +    uint32_t nTextCount = nNumStrings/2; +    for(uint32_t i = 0; i < nTextCount; ++i) +    { +        uint32_t i0 = i * 2; +        uint32_t s0, s1; +        nStringLengths[i0] = s0 = (uint32_t)strlen(ppStrings[i0]); +        nStringLengths[i0+1] = s1 = (uint32_t)strlen(ppStrings[i0+1]); +        nWidth = MicroProfileMax(s0+s1, nWidth); +    } +    nWidth = (MICROPROFILE_TEXT_WIDTH+1) * (2+nWidth) + 2 * MICROPROFILE_BORDER_SIZE; +    if(pColors) +        nWidth += MICROPROFILE_TEXT_WIDTH + 1; +    nHeight = (MICROPROFILE_TEXT_HEIGHT+1) * nTextCount + 2 * MICROPROFILE_BORDER_SIZE; +} + +void MicroProfileDrawFloatWindow(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0) +{ +    uint32_t nWidth = 0, nHeight = 0; +    uint32_t* nStringLengths = (uint32_t*)alloca(nNumStrings * sizeof(uint32_t)); +    MicroProfileFloatWindowSize(ppStrings, nNumStrings, pColors, nWidth, nHeight, nStringLengths); +    uint32_t nTextCount = nNumStrings/2; +    if(nX + nWidth > UI.nWidth) +        nX = UI.nWidth - nWidth; +    if(nY + nHeight > UI.nHeight) +        nY = UI.nHeight - nHeight; +    MicroProfileDrawBox(nX-1, nY-1, nX + nWidth+1, nY + nHeight+1, 0xff000000|nColor); +    MicroProfileDrawBox(nX, nY, nX + nWidth, nY + nHeight, 0xff000000); +    if(pColors) +    { +        nX += MICROPROFILE_TEXT_WIDTH+1; +        nWidth -= MICROPROFILE_TEXT_WIDTH+1; +    } +    for(uint32_t i = 0; i < nTextCount; ++i) +    { +        int i0 = i * 2; +        if(pColors) +        { +            MicroProfileDrawBox(nX-MICROPROFILE_TEXT_WIDTH, nY, nX, nY + MICROPROFILE_TEXT_WIDTH, pColors[i]|0xff000000); +        } +        MicroProfileDrawText(nX + 1, nY + 1, (uint32_t)-1, ppStrings[i0], (uint32_t)strlen(ppStrings[i0])); +        MicroProfileDrawText(nX + nWidth - nStringLengths[i0+1] * (MICROPROFILE_TEXT_WIDTH+1), nY + 1, (uint32_t)-1, ppStrings[i0+1], (uint32_t)strlen(ppStrings[i0+1])); +        nY += (MICROPROFILE_TEXT_HEIGHT+1); +    } +} +void MicroProfileDrawTextBox(uint32_t nX, uint32_t nY, const char** ppStrings, uint32_t nNumStrings, uint32_t nColor, uint32_t* pColors = 0) +{ +    uint32_t nWidth = 0, nHeight = 0; +    uint32_t* nStringLengths = (uint32_t*)alloca(nNumStrings * sizeof(uint32_t)); +    for(uint32_t i = 0; i < nNumStrings; ++i) +    { +        nStringLengths[i] = (uint32_t)strlen(ppStrings[i]); +        nWidth = MicroProfileMax(nWidth, nStringLengths[i]); +        nHeight++; +    } +    nWidth = (MICROPROFILE_TEXT_WIDTH+1) * (2+nWidth) + 2 * MICROPROFILE_BORDER_SIZE; +    nHeight = (MICROPROFILE_TEXT_HEIGHT+1) * nHeight + 2 * MICROPROFILE_BORDER_SIZE; +    if(nX + nWidth > UI.nWidth) +        nX = UI.nWidth - nWidth; +    if(nY + nHeight > UI.nHeight) +        nY = UI.nHeight - nHeight; +    MicroProfileDrawBox(nX, nY, nX + nWidth, nY + nHeight, 0xff000000); +    for(uint32_t i = 0; i < nNumStrings; ++i) +    { +        MicroProfileDrawText(nX + 1, nY + 1, (uint32_t)-1, ppStrings[i], (uint32_t)strlen(ppStrings[i])); +        nY += (MICROPROFILE_TEXT_HEIGHT+1); +    } +} + + + +void MicroProfileToolTipMeta(MicroProfileStringArray* pToolTip) +{ +    MicroProfile& S = *MicroProfileGet(); +    if(UI.nRangeBeginIndex != UI.nRangeEndIndex && UI.pRangeLog) +    { +        uint64_t nMetaSum[MICROPROFILE_META_MAX] = {0}; +        uint64_t nMetaSumInclusive[MICROPROFILE_META_MAX] = {0}; +        int nStackDepth = 0; +        uint32_t nRange[2][2]; +        MicroProfileThreadLog* pLog = UI.pRangeLog; + + +        MicroProfileGetRange(UI.nRangeEndIndex, UI.nRangeBeginIndex, nRange); +        for(uint32_t i = 0; i < 2; ++i) +        { +            uint32_t nStart = nRange[i][0]; +            uint32_t nEnd = nRange[i][1]; +            for(uint32_t j = nStart; j < nEnd; ++j) +            { +                MicroProfileLogEntry LE = pLog->Log[j]; +                int nType = MicroProfileLogType(LE); +                switch(nType) +                { +                case MP_LOG_META: +                    { +                        int64_t nMetaIndex = MicroProfileLogTimerIndex(LE); +                        int64_t nMetaCount = MicroProfileLogGetTick(LE); +                        MP_ASSERT(nMetaIndex < MICROPROFILE_META_MAX); +                        if(nStackDepth>1) +                        { +                            nMetaSumInclusive[nMetaIndex] += nMetaCount; +                        } +                        else +                        { +                            nMetaSum[nMetaIndex] += nMetaCount; +                        } +                    } +                    break; +                case MP_LOG_LEAVE: +                    if(nStackDepth) +                    { +                        nStackDepth--; +                    } +                    else +                    { +                        for(int i = 0; i < MICROPROFILE_META_MAX; ++i) +                        { +                            nMetaSumInclusive[i] += nMetaSum[i]; +                            nMetaSum[i] = 0; +                        } +                    } +                    break; +                case MP_LOG_ENTER: +                    nStackDepth++; +                    break; +                } + +            } +        } +        bool bSpaced = false; +        for(int i = 0; i < MICROPROFILE_META_MAX; ++i) +        { +            if(S.MetaCounters[i].pName && (nMetaSum[i]||nMetaSumInclusive[i])) +            { +                if(!bSpaced) +                { +                    bSpaced = true; +                    MicroProfileStringArrayAddLiteral(pToolTip, ""); +                    MicroProfileStringArrayAddLiteral(pToolTip, ""); +                } +                MicroProfileStringArrayFormat(pToolTip, "%s excl", S.MetaCounters[i].pName); +                MicroProfileStringArrayFormat(pToolTip, "%5d", nMetaSum[i]); +                MicroProfileStringArrayFormat(pToolTip, "%s incl", S.MetaCounters[i].pName); +                MicroProfileStringArrayFormat(pToolTip, "%5d", nMetaSum[i] + nMetaSumInclusive[i]); +            } +        } +    } +} + +void MicroProfileDrawFloatTooltip(uint32_t nX, uint32_t nY, uint32_t nToken, uint64_t nTime) +{ +    MicroProfile& S = *MicroProfileGet(); + +    uint32_t nIndex = MicroProfileGetTimerIndex(nToken); +    uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +    uint32_t nAggregateCount = S.Aggregate[nIndex].nCount ? S.Aggregate[nIndex].nCount : 1; + +    uint32_t nGroupId = MicroProfileGetGroupIndex(nToken); +    uint32_t nTimerId = MicroProfileGetTimerIndex(nToken); +    bool bGpu = S.GroupInfo[nGroupId].Type == MicroProfileTokenTypeGpu; + +    float fToMs = MicroProfileTickToMsMultiplier(bGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); + +    float fMs = fToMs * (nTime); +    float fFrameMs = fToMs * (S.Frame[nIndex].nTicks); +    float fAverage = fToMs * (S.Aggregate[nIndex].nTicks/nAggregateFrames); +    float fCallAverage = fToMs * (S.Aggregate[nIndex].nTicks / nAggregateCount); +    float fMax = fToMs * (S.AggregateMax[nIndex]); + +    float fFrameMsExclusive = fToMs * (S.FrameExclusive[nIndex]); +    float fAverageExclusive = fToMs * (S.AggregateExclusive[nIndex]/nAggregateFrames); +    float fMaxExclusive = fToMs * (S.AggregateMaxExclusive[nIndex]); + +    float fGroupAverage = fToMs * (S.AggregateGroup[nGroupId] / nAggregateFrames); +    float fGroupMax = fToMs * (S.AggregateGroupMax[nGroupId]); +    float fGroup = fToMs * (S.FrameGroup[nGroupId]); + + +    MicroProfileStringArray ToolTip; +    MicroProfileStringArrayClear(&ToolTip); +    const char* pGroupName = S.GroupInfo[nGroupId].pName; +    const char* pTimerName = S.TimerInfo[nTimerId].pName; +    MicroProfileStringArrayAddLiteral(&ToolTip, "Timer:"); +    MicroProfileStringArrayFormat(&ToolTip, "%s", pTimerName); + +#if MICROPROFILE_DEBUG +    MicroProfileStringArrayFormat(&ToolTip,"0x%p", UI.nHoverAddressEnter); +    MicroProfileStringArrayFormat(&ToolTip,"0x%p", UI.nHoverAddressLeave); +#endif + +    if(nTime != (uint64_t)0) +    { +        MicroProfileStringArrayAddLiteral(&ToolTip, "Time:"); +        MicroProfileStringArrayFormat(&ToolTip,"%6.3fms",  fMs); +        MicroProfileStringArrayAddLiteral(&ToolTip, ""); +        MicroProfileStringArrayAddLiteral(&ToolTip, ""); +    } + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Time:"); +    MicroProfileStringArrayFormat(&ToolTip,"%6.3fms",  fFrameMs); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Average:"); +    MicroProfileStringArrayFormat(&ToolTip,"%6.3fms",  fAverage); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Max:"); +    MicroProfileStringArrayFormat(&ToolTip,"%6.3fms",  fMax); + +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Call Average:"); +    MicroProfileStringArrayFormat(&ToolTip,"%6.3fms",  fCallAverage); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Call Count:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6d",  nAggregateCount / nAggregateFrames); + +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Exclusive Frame Time:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3fms",  fFrameMsExclusive); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Exclusive Average:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3fms",  fAverageExclusive); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Exclusive Max:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3fms",  fMaxExclusive); + +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); +    MicroProfileStringArrayAddLiteral(&ToolTip, ""); + +    MicroProfileStringArrayAddLiteral(&ToolTip, "Group:"); +    MicroProfileStringArrayFormat(&ToolTip, "%s", pGroupName); +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Time:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3f", fGroup); +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Average:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3f", fGroupAverage); +    MicroProfileStringArrayAddLiteral(&ToolTip, "Frame Max:"); +    MicroProfileStringArrayFormat(&ToolTip, "%6.3f", fGroupMax); + + + + +    MicroProfileToolTipMeta(&ToolTip); + + +    MicroProfileDrawFloatWindow(nX, nY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, S.TimerInfo[nTimerId].nColor); + +    if(UI.nMouseLeftMod) +    { +        int nIndex = (g_MicroProfileUI.LockedToolTipFront + MICROPROFILE_TOOLTIP_MAX_LOCKED - 1) % MICROPROFILE_TOOLTIP_MAX_LOCKED; +        g_MicroProfileUI.nLockedToolTipColor[nIndex] = S.TimerInfo[nTimerId].nColor; +        MicroProfileStringArrayCopy(&g_MicroProfileUI.LockedToolTips[nIndex], &ToolTip); +        g_MicroProfileUI.LockedToolTipFront = nIndex; + +    } +} + + +void MicroProfileZoomTo(int64_t nTickStart, int64_t nTickEnd) +{ +    MicroProfile& S = *MicroProfileGet(); + +    int64_t nStart = S.Frames[S.nFrameCurrent].nFrameStartCpu; +    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    UI.fDetailedOffsetTarget = MicroProfileLogTickDifference(nStart, nTickStart) * fToMs; +    UI.fDetailedRangeTarget = MicroProfileLogTickDifference(nTickStart, nTickEnd) * fToMs; +} + +void MicroProfileCenter(int64_t nTickCenter) +{ +    MicroProfile& S = *MicroProfileGet(); +    int64_t nStart = S.Frames[S.nFrameCurrent].nFrameStartCpu; +    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    float fCenter = MicroProfileLogTickDifference(nStart, nTickCenter) * fToMs; +    UI.fDetailedOffsetTarget = UI.fDetailedOffset = fCenter - 0.5f * UI.fDetailedRange; +} +#ifdef MICROPROFILE_DEBUG +uint64_t* g_pMicroProfileDumpStart = 0; +uint64_t* g_pMicroProfileDumpEnd = 0; +void MicroProfileDebugDumpRange() +{ +    MicroProfile& S = *MicroProfileGet(); +    if(g_pMicroProfileDumpStart != g_pMicroProfileDumpEnd) +    { +        uint64_t* pStart = g_pMicroProfileDumpStart; +        uint64_t* pEnd = g_pMicroProfileDumpEnd; +        while(pStart != pEnd) +        { +            uint64_t nTick = MicroProfileLogGetTick(*pStart); +            uint64_t nToken = MicroProfileLogTimerIndex(*pStart); +            uint32_t nTimerId = MicroProfileGetTimerIndex(nToken); + +            const char* pTimerName = S.TimerInfo[nTimerId].pName; +            char buffer[256]; +            int type = MicroProfileLogType(*pStart); + +            const char* pBegin = type == MP_LOG_LEAVE ? "END" : +                (type == MP_LOG_ENTER ? "BEGIN" : "META"); +            snprintf(buffer, 255, "DUMP 0x%p: %s :: %llx: %s\n", pStart, pBegin,  nTick, pTimerName); +#ifdef _WIN32 +            OutputDebugString(buffer); +#else +            printf("%s", buffer); +#endif +            pStart++; +        } + +        g_pMicroProfileDumpStart = g_pMicroProfileDumpEnd; +    } +} +#define MP_DEBUG_DUMP_RANGE() MicroProfileDebugDumpRange(); +#else +#define MP_DEBUG_DUMP_RANGE() do{} while(0) +#endif + +#define MICROPROFILE_HOVER_DIST 0.5f + +void MicroProfileDrawDetailedContextSwitchBars(uint32_t nY, uint32_t nThreadId, uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, int64_t nBaseTicks, uint32_t nBaseY) +{ +    MicroProfile& S = *MicroProfileGet(); +    int64_t nTickIn = -1; +    uint32_t nThreadBefore = -1; +    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +    float fMsToScreen = UI.nWidth / UI.fDetailedRange; +    float fMouseX = (float)UI.nMouseX; +    float fMouseY = (float)UI.nMouseY; + + +    for(uint32_t j = nContextSwitchStart; j != nContextSwitchEnd; j = (j+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE) +    { +        MP_ASSERT(j < MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE); +        MicroProfileContextSwitch CS = S.ContextSwitch[j]; + +        if(nTickIn == -1) +        { +            if(CS.nThreadIn == nThreadId) +            { +                nTickIn = CS.nTicks; +                nThreadBefore = CS.nThreadOut; +            } +        } +        else +        { +            if(CS.nThreadOut == nThreadId) +            { +                int64_t nTickOut = CS.nTicks; +                float fMsStart = fToMs * MicroProfileLogTickDifference(nBaseTicks, nTickIn); +                float fMsEnd = fToMs * MicroProfileLogTickDifference(nBaseTicks, nTickOut); +                if(fMsStart <= fMsEnd) +                { +                    float fXStart = fMsStart * fMsToScreen; +                    float fXEnd = fMsEnd * fMsToScreen; +                    float fYStart = (float)nY; +                    float fYEnd = fYStart + (MICROPROFILE_DETAILED_CONTEXT_SWITCH_HEIGHT); +                    uint32_t nColor = g_nMicroProfileContextSwitchThreadColors[CS.nCpu%MICROPROFILE_NUM_CONTEXT_SWITCH_COLORS]; +                    float fXDist = MicroProfileMax(fXStart - fMouseX, fMouseX - fXEnd); +                    bool bHover = fXDist < MICROPROFILE_HOVER_DIST && fYStart <= fMouseY && fMouseY <= fYEnd && nBaseY < fMouseY; +                    if(bHover) +                    { +                        UI.nRangeBegin = nTickIn; +                        UI.nRangeEnd = nTickOut; +                        S.nContextSwitchHoverTickIn = nTickIn; +                        S.nContextSwitchHoverTickOut = nTickOut; +                        S.nContextSwitchHoverThread = CS.nThreadOut; +                        S.nContextSwitchHoverThreadBefore = nThreadBefore; +                        S.nContextSwitchHoverThreadAfter = CS.nThreadIn; +                        S.nContextSwitchHoverCpuNext = CS.nCpu; +                        nColor = UI.nHoverColor; +                    } +                    if(CS.nCpu == S.nContextSwitchHoverCpu) +                    { +                        nColor = UI.nHoverColorShared; +                    } +                    MicroProfileDrawBox(fXStart, fYStart, fXEnd, fYEnd, nColor|UI.nOpacityForeground, MicroProfileBoxTypeFlat); +                } +                nTickIn = -1; +            } +        } +    } +} + +void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY, int nSelectedFrame) +{ +    MicroProfile& S = *MicroProfileGet(); +    MP_DEBUG_DUMP_RANGE(); +    int nY = nBaseY - UI.nOffsetY; +    int64_t nNumBoxes = 0; +    int64_t nNumLines = 0; + +    uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY; +    MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent]; +    MicroProfileFrameState* pFrameNext = &S.Frames[nFrameNext]; + +    UI.nRangeBegin = 0; +    UI.nRangeEnd = 0; +    UI.nRangeBeginGpu = 0; +    UI.nRangeEndGpu = 0; +    UI.nRangeBeginIndex = UI.nRangeEndIndex = 0; +    UI.pRangeLog = 0; +    int64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu; +    int64_t nFrameStartGpu = pFrameCurrent->nFrameStartGpu; +    int64_t nTicksPerSecondCpu = MicroProfileTicksPerSecondCpu(); +    int64_t nTicksPerSecondGpu = MicroProfileTicksPerSecondGpu(); +    float fToMsCpu = MicroProfileTickToMsMultiplier(nTicksPerSecondCpu); +    float fToMsGpu = MicroProfileTickToMsMultiplier(nTicksPerSecondGpu); + +    float fDetailedOffset = UI.fDetailedOffset; +    float fDetailedRange = UI.fDetailedRange; + + +    int64_t nDetailedOffsetTicksCpu = MicroProfileMsToTick(fDetailedOffset, MicroProfileTicksPerSecondCpu()); +    int64_t nDetailedOffsetTicksGpu = MicroProfileMsToTick(fDetailedOffset, MicroProfileTicksPerSecondGpu()); +    int64_t nBaseTicksCpu = nDetailedOffsetTicksCpu + nFrameStartCpu; +    int64_t nBaseTicksGpu = nDetailedOffsetTicksGpu + nFrameStartGpu; +    int64_t nBaseTicksEndCpu = nBaseTicksCpu + MicroProfileMsToTick(fDetailedRange, MicroProfileTicksPerSecondCpu()); + +    int64_t nTickReferenceCpu = 0, nTickReferenceGpu = 0; +    static int64_t nRefCpu = 0, nRefGpu = 0; +    if(MicroProfileGetGpuTickReference(&nTickReferenceCpu, &nTickReferenceGpu)) +    { +        if(0 == nRefCpu || abs(nRefCpu-nBaseTicksCpu) > abs(nTickReferenceCpu-nBaseTicksCpu)) +        { +            nRefCpu = nTickReferenceCpu; +            nRefGpu = nTickReferenceGpu; +        } +        else +        { +            nTickReferenceCpu = nRefCpu; +            nTickReferenceGpu = nRefGpu; +        } +        nBaseTicksGpu = (nBaseTicksCpu - nTickReferenceCpu) * nTicksPerSecondGpu / nTicksPerSecondCpu + nTickReferenceGpu; +    } +    int64_t nBaseTicksEndGpu = nBaseTicksCpu + MicroProfileMsToTick(fDetailedRange, MicroProfileTicksPerSecondCpu()); + +    MicroProfileFrameState* pFrameFirst = pFrameCurrent; +    int64_t nGapTime = MicroProfileTicksPerSecondCpu() * MICROPROFILE_GAP_TIME / 1000; +    for(uint32_t i = 0; i < MICROPROFILE_MAX_FRAME_HISTORY - MICROPROFILE_GPU_FRAME_DELAY; ++i) +    { +        uint32_t nNextIndex = (S.nFrameCurrent + MICROPROFILE_MAX_FRAME_HISTORY - i) % MICROPROFILE_MAX_FRAME_HISTORY; +        pFrameFirst = &S.Frames[nNextIndex]; +        if(pFrameFirst->nFrameStartCpu <= nBaseTicksCpu-nGapTime) +            break; +    } + +    float fMsBase = fToMsCpu * nDetailedOffsetTicksCpu; +    float fMs = fDetailedRange; +    float fMsEnd = fMs + fMsBase; +    float fWidth = (float)nWidth; +    float fMsToScreen = fWidth / fMs; + +    { +        float fRate = floor(2*(log10(fMs)-1))/2; +        float fStep = powf(10.f, fRate); +        float fRcpStep = 1.f / fStep; +        int nColorIndex = (int)(floor(fMsBase*fRcpStep)); +        float fStart = floor(fMsBase*fRcpStep) * fStep; +        for(float f = fStart; f < fMsEnd; ) +        { +            float fStart = f; +            float fNext = f + fStep; +            MicroProfileDrawBox(((fStart-fMsBase) * fMsToScreen), nBaseY, (fNext-fMsBase) * fMsToScreen+1, nBaseY + nHeight, UI.nOpacityBackground | g_nMicroProfileBackColors[nColorIndex++ & 1]); +            f = fNext; +        } +    } + +    nY += MICROPROFILE_TEXT_HEIGHT+1; +    MicroProfileLogEntry* pMouseOver = UI.pDisplayMouseOver; +    MicroProfileLogEntry* pMouseOverNext = 0; +    uint64_t nMouseOverToken = pMouseOver ? MicroProfileLogTimerIndex(*pMouseOver) : MICROPROFILE_INVALID_TOKEN; +    float fMouseX = (float)UI.nMouseX; +    float fMouseY = (float)UI.nMouseY; +    uint64_t nHoverToken = MICROPROFILE_INVALID_TOKEN; +    int64_t nHoverTime = 0; + +    static int nHoverCounter = 155; +    static int nHoverCounterDelta = 10; +    nHoverCounter += nHoverCounterDelta; +    if(nHoverCounter >= 245) +        nHoverCounterDelta = -10; +    else if(nHoverCounter < 100) +        nHoverCounterDelta = 10; +    UI.nHoverColor = (nHoverCounter<<24)|(nHoverCounter<<16)|(nHoverCounter<<8)|nHoverCounter; +    uint32_t nHoverCounterShared = nHoverCounter>>2; +    UI.nHoverColorShared = (nHoverCounterShared<<24)|(nHoverCounterShared<<16)|(nHoverCounterShared<<8)|nHoverCounterShared; + +    uint32_t nLinesDrawn[MICROPROFILE_STACK_MAX]={0}; + +    uint32_t nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadAfter; +    uint32_t nContextSwitchHoverThreadBefore = S.nContextSwitchHoverThreadBefore; +    S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = -1; + +    uint32_t nContextSwitchStart = -1; +    uint32_t nContextSwitchEnd = -1; +    S.nContextSwitchHoverCpuNext = 0xff; +    S.nContextSwitchHoverTickIn = -1; +    S.nContextSwitchHoverTickOut = -1; +    if(S.bContextSwitchRunning) +    { +        MicroProfileContextSwitchSearch(&nContextSwitchStart, &nContextSwitchEnd, nBaseTicksCpu, nBaseTicksEndCpu); +    } + +    bool bSkipBarView = S.bContextSwitchRunning && S.bContextSwitchNoBars; + +    if(!bSkipBarView) +    { +        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +        { +            MicroProfileThreadLog* pLog = S.Pool[i]; +            if(!pLog) +                continue; + +            uint32_t nPut = pFrameNext->nLogStart[i]; +            ///note: this may display new samples as old data, but this will only happen when +            //       unpaused, where the detailed view is hardly perceptible +            uint32_t nFront = S.Pool[i]->nPut.load(std::memory_order_relaxed); +            MicroProfileFrameState* pFrameLogFirst = pFrameCurrent; +            MicroProfileFrameState* pFrameLogLast = pFrameNext; +            uint32_t nGet = pFrameLogFirst->nLogStart[i]; +            do +            { +                MP_ASSERT(pFrameLogFirst >= &S.Frames[0] && pFrameLogFirst < &S.Frames[MICROPROFILE_MAX_FRAME_HISTORY]); +                uint32_t nNewGet = pFrameLogFirst->nLogStart[i]; +                bool bIsValid = false; +                if(nPut < nFront) +                { +                    bIsValid = nNewGet <= nPut || nNewGet >= nFront; +                } +                else +                { +                    bIsValid = nNewGet <= nPut && nNewGet >= nFront; +                } +                if(bIsValid) +                { +                    nGet = nNewGet; +                    pFrameLogFirst--; +                    if(pFrameLogFirst < &S.Frames[0]) +                        pFrameLogFirst = &S.Frames[MICROPROFILE_MAX_FRAME_HISTORY-1]; +                } +                else +                { +                    break; +                } +            }while(pFrameLogFirst != pFrameFirst); + + +            if(nGet == (uint32_t)-1) +                continue; +            MP_ASSERT(nGet != (uint32_t)-1); + +            nPut = pFrameLogLast->nLogStart[i]; + +            uint32_t nRange[2][2] = { {0, 0}, {0, 0}, }; + +            MicroProfileGetRange(nPut, nGet, nRange); +            if(nPut == nGet) +                continue; +            uint32_t nMaxStackDepth = 0; + +            bool bGpu = pLog->nGpu != 0; +            float fToMs = bGpu ? fToMsGpu : fToMsCpu; +            int64_t nBaseTicks = bGpu ? nBaseTicksGpu : nBaseTicksCpu; +            char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16]; +            uint64_t nThreadId = pLog->nThreadId; +            snprintf(ThreadName, sizeof(ThreadName)-1, "%04llx: %s", nThreadId, &pLog->ThreadName[0] ); +            nY += 3; +            uint32_t nThreadColor = -1; +            if(pLog->nThreadId == nContextSwitchHoverThreadAfter || pLog->nThreadId == nContextSwitchHoverThreadBefore) +                nThreadColor = UI.nHoverColorShared|0x906060; +            MicroProfileDrawText(0, nY, nThreadColor, &ThreadName[0], (uint32_t)strlen(&ThreadName[0])); +            nY += 3; +            nY += MICROPROFILE_TEXT_HEIGHT + 1; + +            if(S.bContextSwitchRunning) +            { +                MicroProfileDrawDetailedContextSwitchBars(nY, pLog->nThreadId, nContextSwitchStart, nContextSwitchEnd, nBaseTicks, nBaseY); +                nY -= MICROPROFILE_DETAILED_BAR_HEIGHT; +                nY += MICROPROFILE_DETAILED_CONTEXT_SWITCH_HEIGHT+1; +            } + +            uint32_t nYDelta = MICROPROFILE_DETAILED_BAR_HEIGHT; +            uint32_t nStack[MICROPROFILE_STACK_MAX]; +            uint32_t nStackPos = 0; +            for(uint32_t j = 0; j < 2; ++j) +            { +                uint32_t nStart = nRange[j][0]; +                uint32_t nEnd = nRange[j][1]; +                for(uint32_t k = nStart; k < nEnd; ++k) +                { +                    MicroProfileLogEntry* pEntry = pLog->Log + k; +                    int nType = MicroProfileLogType(*pEntry); +                    if(MP_LOG_ENTER == nType) +                    { +                        MP_ASSERT(nStackPos < MICROPROFILE_STACK_MAX); +                        nStack[nStackPos++] = k; +                    } +                    else if(MP_LOG_META == nType) +                    { + +                    } +                    else if(MP_LOG_LEAVE == nType) +                    { +                        if(0 == nStackPos) +                        { +                            continue; +                        } + +                        MicroProfileLogEntry* pEntryEnter = pLog->Log + nStack[nStackPos-1]; +                        if(MicroProfileLogTimerIndex(*pEntryEnter) != MicroProfileLogTimerIndex(*pEntry)) +                        { +                            //uprintf("mismatch %llx %llx\n", pEntryEnter->nToken, pEntry->nToken); +                            continue; +                        } +                        int64_t nTickStart = MicroProfileLogGetTick(*pEntryEnter); +                        int64_t nTickEnd = MicroProfileLogGetTick(*pEntry); +                        uint64_t nTimerIndex = MicroProfileLogTimerIndex(*pEntry); +                        uint32_t nColor = S.TimerInfo[nTimerIndex].nColor; +                        if(nMouseOverToken == nTimerIndex) +                        { +                            if(pEntry == pMouseOver) +                            { +                                nColor = UI.nHoverColor; +                                if(bGpu) +                                { +                                    UI.nRangeBeginGpu = *pEntryEnter; +                                    UI.nRangeEndGpu = *pEntry; +                                    uint32_t nCpuBegin = (nStack[nStackPos-1] + 1) % MICROPROFILE_BUFFER_SIZE; +                                    uint32_t nCpuEnd = (k + 1) % MICROPROFILE_BUFFER_SIZE; +                                    MicroProfileLogEntry LogCpuBegin = pLog->Log[nCpuBegin]; +                                    MicroProfileLogEntry LogCpuEnd = pLog->Log[nCpuEnd]; +                                    if(MicroProfileLogType(LogCpuBegin)==3 && MicroProfileLogType(LogCpuEnd) == 3) +                                    { +                                        UI.nRangeBegin = LogCpuBegin; +                                        UI.nRangeEnd = LogCpuEnd; +                                    } +                                    UI.nRangeBeginIndex = nStack[nStackPos-1]; +                                    UI.nRangeEndIndex = k; +                                    UI.pRangeLog = pLog; +                                } +                                else +                                { +                                    UI.nRangeBegin = *pEntryEnter; +                                    UI.nRangeEnd = *pEntry; +                                    UI.nRangeBeginIndex = nStack[nStackPos-1]; +                                    UI.nRangeEndIndex = k; +                                    UI.pRangeLog = pLog; + +                                } +                            } +                            else +                            { +                                nColor = UI.nHoverColorShared; +                            } +                        } + +                        nMaxStackDepth = MicroProfileMax(nMaxStackDepth, nStackPos); +                        float fMsStart = fToMs * MicroProfileLogTickDifference(nBaseTicks, nTickStart); +                        float fMsEnd = fToMs * MicroProfileLogTickDifference(nBaseTicks, nTickEnd); +                        float fXStart = fMsStart * fMsToScreen; +                        float fXEnd = fMsEnd * fMsToScreen; +                        float fYStart = (float)(nY + nStackPos * nYDelta); +                        float fYEnd = fYStart + (MICROPROFILE_DETAILED_BAR_HEIGHT); +                        float fXDist = MicroProfileMax(fXStart - fMouseX, fMouseX - fXEnd); +                        bool bHover = fXDist < MICROPROFILE_HOVER_DIST && fYStart <= fMouseY && fMouseY <= fYEnd && nBaseY < fMouseY; +                        uint32_t nIntegerWidth = (uint32_t)(fXEnd - fXStart); +                        if(nIntegerWidth) +                        { +                            if(bHover && UI.nActiveMenu == -1) +                            { +                                nHoverToken = MicroProfileLogTimerIndex(*pEntry); +    #if MICROPROFILE_DEBUG +                                UI.nHoverAddressEnter = (uint64_t)pEntryEnter; +                                UI.nHoverAddressLeave = (uint64_t)pEntry; +    #endif +                                nHoverTime = MicroProfileLogTickDifference(nTickStart, nTickEnd); +                                pMouseOverNext = pEntry; +                            } + +                            MicroProfileDrawBox(fXStart, fYStart, fXEnd, fYEnd, nColor|UI.nOpacityForeground, MicroProfileBoxTypeBar); +#if MICROPROFILE_DETAILED_BAR_NAMES +                            if(nIntegerWidth>3*MICROPROFILE_TEXT_WIDTH) +                            { +                                float fXStartText = MicroProfileMax(fXStart, 0.f); +                                int nTextWidth = (int)(fXEnd - fXStartText); +                                int nCharacters = (nTextWidth - 2*MICROPROFILE_TEXT_WIDTH) / MICROPROFILE_TEXT_WIDTH; +                                if(nCharacters>0) +                                { +                                    MicroProfileDrawText(fXStartText+1, fYStart+1, -1, S.TimerInfo[nTimerIndex].pName, MicroProfileMin<uint32_t>(S.TimerInfo[nTimerIndex].nNameLen, nCharacters)); +                                } +                            } +#endif +                            ++nNumBoxes; +                        } +                        else +                        { +                            float fXAvg = 0.5f * (fXStart + fXEnd); +                            int nLineX = (int)floor(fXAvg+0.5f); +                            if(nLineX != (int)nLinesDrawn[nStackPos]) +                            { +                                if(bHover && UI.nActiveMenu == -1) +                                { +                                    nHoverToken = (uint32_t)MicroProfileLogTimerIndex(*pEntry); +                                    nHoverTime = MicroProfileLogTickDifference(nTickStart, nTickEnd); +                                    pMouseOverNext = pEntry; +                                } +                                nLinesDrawn[nStackPos] = nLineX; +                                MicroProfileDrawLineVertical(nLineX, fYStart + 0.5f, fYEnd + 0.5f, nColor|UI.nOpacityForeground); +                                ++nNumLines; +                            } +                        } +                        nStackPos--; +                        if(0 == nStackPos) +                        { +                            if(bGpu ? (nTickStart > nBaseTicksEndGpu) : (nTickStart > nBaseTicksEndCpu)) +                            { +                                break; +                            } +                        } +                    } +                } +            } +            nY += nMaxStackDepth * nYDelta + MICROPROFILE_DETAILED_BAR_HEIGHT+1; +        } +    } +    if(S.bContextSwitchRunning && (S.bContextSwitchAllThreads||S.bContextSwitchNoBars)) +    { +        uint32_t nNumThreads = 0; +        uint32_t nThreads[MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS]; +        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS && S.Pool[i]; ++i) +            nThreads[nNumThreads++] = S.Pool[i]->nThreadId; +        uint32_t nNumThreadsBase = nNumThreads; +        if(S.bContextSwitchAllThreads) +        { +            for(uint32_t i = nContextSwitchStart; i != nContextSwitchEnd; i = (i+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE) +            { +                MicroProfileContextSwitch CS = S.ContextSwitch[i]; +                ThreadIdType nThreadId = CS.nThreadIn; +                if(nThreadId) +                { +                    bool bSeen = false; +                    for(uint32_t j = 0; j < nNumThreads; ++j) +                    { +                        if(nThreads[j] == nThreadId) +                        { +                            bSeen = true; +                            break; +                        } +                    } +                    if(!bSeen) +                    { +                        nThreads[nNumThreads++] = nThreadId; +                    } +                } +                if(nNumThreads == MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS) +                { +                    S.nOverflow = 10; +                    break; +                } +            } +            std::sort(&nThreads[nNumThreadsBase], &nThreads[nNumThreads]); +        } +        uint32_t nStart = nNumThreadsBase; +        if(S.bContextSwitchNoBars) +            nStart = 0; +        for(uint32_t i = nStart; i < nNumThreads; ++i) +        { +            ThreadIdType nThreadId = nThreads[i]; +            if(nThreadId) +            { +                char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16]; +                const char* cLocal = MicroProfileIsLocalThread(nThreadId) ? "*": " "; + +                int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04x: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) ); +                uint32_t nThreadColor = -1; +                if(nThreadId == nContextSwitchHoverThreadAfter || nThreadId == nContextSwitchHoverThreadBefore) +                    nThreadColor = UI.nHoverColorShared|0x906060; +                MicroProfileDrawDetailedContextSwitchBars(nY+2, nThreadId, nContextSwitchStart, nContextSwitchEnd, nBaseTicksCpu, nBaseY); +                MicroProfileDrawText(0, nY, nThreadColor, &ThreadName[0], nStrLen); +                nY += MICROPROFILE_TEXT_HEIGHT+1; +            } +        } +    } + +    S.nContextSwitchHoverCpu = S.nContextSwitchHoverCpuNext; + + + + +    UI.pDisplayMouseOver = pMouseOverNext; + +    if(!S.nRunning) +    { +        if(nHoverToken != MICROPROFILE_INVALID_TOKEN && nHoverTime) +        { +            UI.nHoverToken = nHoverToken; +            UI.nHoverTime = nHoverTime; +        } + +        if(nSelectedFrame != -1) +        { +            UI.nRangeBegin = S.Frames[nSelectedFrame].nFrameStartCpu; +            UI.nRangeEnd = S.Frames[(nSelectedFrame+1)%MICROPROFILE_MAX_FRAME_HISTORY].nFrameStartCpu; +            UI.nRangeBeginGpu = S.Frames[nSelectedFrame].nFrameStartGpu; +            UI.nRangeEndGpu = S.Frames[(nSelectedFrame+1)%MICROPROFILE_MAX_FRAME_HISTORY].nFrameStartGpu; +        } +        if(UI.nRangeBegin != UI.nRangeEnd) +        { +            float fMsStart = fToMsCpu * MicroProfileLogTickDifference(nBaseTicksCpu, UI.nRangeBegin); +            float fMsEnd = fToMsCpu * MicroProfileLogTickDifference(nBaseTicksCpu, UI.nRangeEnd); +            float fXStart = fMsStart * fMsToScreen; +            float fXEnd = fMsEnd * fMsToScreen; +            MicroProfileDrawBox(fXStart, nBaseY, fXEnd, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT, MicroProfileBoxTypeFlat); +            MicroProfileDrawLineVertical(fXStart, nBaseY, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT | 0x44000000); +            MicroProfileDrawLineVertical(fXEnd, nBaseY, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT | 0x44000000); + +            fMsStart += fDetailedOffset; +            fMsEnd += fDetailedOffset; +            char sBuffer[32]; +            uint32_t nLenStart = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsStart); +            float fStartTextWidth = (float)((1+MICROPROFILE_TEXT_WIDTH) * nLenStart); +            float fStartTextX = fXStart - fStartTextWidth - 2; +            MicroProfileDrawBox(fStartTextX, nBaseY, fStartTextX + fStartTextWidth + 2, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat); +            MicroProfileDrawText(fStartTextX+1, nBaseY, (uint32_t)-1, sBuffer, nLenStart); +            uint32_t nLenEnd = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsEnd); +            MicroProfileDrawBox(fXEnd+1, nBaseY, fXEnd+1+(1+MICROPROFILE_TEXT_WIDTH) * nLenEnd + 3, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat); +            MicroProfileDrawText(fXEnd+2, nBaseY+1, (uint32_t)-1, sBuffer, nLenEnd); + +            if(UI.nMouseRight) +            { +                MicroProfileZoomTo(UI.nRangeBegin, UI.nRangeEnd); +            } +        } + +        if(UI.nRangeBeginGpu != UI.nRangeEndGpu) +        { +            float fMsStart = fToMsGpu * MicroProfileLogTickDifference(nBaseTicksGpu, UI.nRangeBeginGpu); +            float fMsEnd = fToMsGpu * MicroProfileLogTickDifference(nBaseTicksGpu, UI.nRangeEndGpu); +            float fXStart = fMsStart * fMsToScreen; +            float fXEnd = fMsEnd * fMsToScreen; +            MicroProfileDrawBox(fXStart, nBaseY, fXEnd, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT_GPU, MicroProfileBoxTypeFlat); +            MicroProfileDrawLineVertical(fXStart, nBaseY, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT_GPU | 0x44000000); +            MicroProfileDrawLineVertical(fXEnd, nBaseY, nHeight, MICROPROFILE_FRAME_COLOR_HIGHTLIGHT_GPU | 0x44000000); + +            nBaseY += MICROPROFILE_TEXT_HEIGHT+1; + +            fMsStart += fDetailedOffset; +            fMsEnd += fDetailedOffset; +            char sBuffer[32]; +            uint32_t nLenStart = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsStart); +            float fStartTextWidth = (float)((1+MICROPROFILE_TEXT_WIDTH) * nLenStart); +            float fStartTextX = fXStart - fStartTextWidth - 2; +            MicroProfileDrawBox(fStartTextX, nBaseY, fStartTextX + fStartTextWidth + 2, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat); +            MicroProfileDrawText(fStartTextX+1, nBaseY, (uint32_t)-1, sBuffer, nLenStart); +            uint32_t nLenEnd = snprintf(sBuffer, sizeof(sBuffer)-1, "%.2fms", fMsEnd); +            MicroProfileDrawBox(fXEnd+1, nBaseY, fXEnd+1+(1+MICROPROFILE_TEXT_WIDTH) * nLenEnd + 3, MICROPROFILE_TEXT_HEIGHT + 2 + nBaseY, 0x33000000, MicroProfileBoxTypeFlat); +            MicroProfileDrawText(fXEnd+2, nBaseY+1, (uint32_t)-1, sBuffer, nLenEnd); +        } +    } +} + + +void MicroProfileDrawDetailedFrameHistory(uint32_t nWidth, uint32_t nHeight, uint32_t nBaseY, uint32_t nSelectedFrame) +{ +    MicroProfile& S = *MicroProfileGet(); + +    const uint32_t nBarHeight = MICROPROFILE_FRAME_HISTORY_HEIGHT; +    float fBaseX = (float)nWidth; +    float fDx = fBaseX / MICROPROFILE_NUM_FRAMES; + +    uint32_t nLastIndex =  (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY; +    MicroProfileDrawBox(0, nBaseY, nWidth, nBaseY+MICROPROFILE_FRAME_HISTORY_HEIGHT, 0xff000000 | g_nMicroProfileBackColors[0], MicroProfileBoxTypeFlat); +    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()) * S.fRcpReferenceTime; +    float fToMsGpu = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondGpu()) * S.fRcpReferenceTime; + + +    MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent]; +    uint64_t nFrameStartCpu = pFrameCurrent->nFrameStartCpu; +    int64_t nDetailedOffsetTicksCpu = MicroProfileMsToTick(UI.fDetailedOffset, MicroProfileTicksPerSecondCpu()); +    int64_t nCpuStart = nDetailedOffsetTicksCpu + nFrameStartCpu; +    int64_t nCpuEnd = nCpuStart + MicroProfileMsToTick(UI.fDetailedRange, MicroProfileTicksPerSecondCpu());; + + +    float fSelectionStart = (float)nWidth; +    float fSelectionEnd = 0.f; +    for(uint32_t i = 0; i < MICROPROFILE_NUM_FRAMES; ++i) +    { +        uint32_t nIndex = (S.nFrameCurrent + MICROPROFILE_MAX_FRAME_HISTORY - i) % MICROPROFILE_MAX_FRAME_HISTORY; +        MicroProfileFrameState* pCurrent = &S.Frames[nIndex]; +        MicroProfileFrameState* pNext = &S.Frames[nLastIndex]; + +        int64_t nTicks = pNext->nFrameStartCpu - pCurrent->nFrameStartCpu; +        int64_t nTicksGpu = pNext->nFrameStartGpu - pCurrent->nFrameStartGpu; +        float fScale = fToMs * nTicks; +        float fScaleGpu = fToMsGpu * nTicksGpu; +        fScale = fScale > 1.f ? 0.f : 1.f - fScale; +        fScaleGpu = fScaleGpu > 1.f ? 0.f : 1.f - fScaleGpu; +        float fXEnd = fBaseX; +        float fXStart = fBaseX - fDx; +        fBaseX = fXStart; +        uint32_t nColor = MICROPROFILE_FRAME_HISTORY_COLOR_CPU; +        if(nIndex == nSelectedFrame) +            nColor = (uint32_t)-1; +        MicroProfileDrawBox(fXStart, nBaseY + fScale * nBarHeight, fXEnd, nBaseY+MICROPROFILE_FRAME_HISTORY_HEIGHT, nColor, MicroProfileBoxTypeBar); +        if(pNext->nFrameStartCpu > nCpuStart) +        { +            fSelectionStart = fXStart; +        } +        if(pCurrent->nFrameStartCpu < nCpuEnd && fSelectionEnd == 0.f) +        { +            fSelectionEnd = fXEnd; +        } +        nLastIndex = nIndex; +    } +    MicroProfileDrawBox(fSelectionStart, nBaseY, fSelectionEnd, nBaseY+MICROPROFILE_FRAME_HISTORY_HEIGHT, MICROPROFILE_FRAME_HISTORY_COLOR_HIGHTLIGHT, MicroProfileBoxTypeFlat); +} +void MicroProfileDrawDetailedView(uint32_t nWidth, uint32_t nHeight) +{ +    MicroProfile& S = *MicroProfileGet(); + +    MICROPROFILE_SCOPE(g_MicroProfileDetailed); +    uint32_t nBaseY = MICROPROFILE_TEXT_HEIGHT + 1; + +    int nSelectedFrame = -1; +    if(UI.nMouseY > nBaseY && UI.nMouseY <= nBaseY + MICROPROFILE_FRAME_HISTORY_HEIGHT && UI.nActiveMenu == -1) +    { + +        nSelectedFrame = ((MICROPROFILE_NUM_FRAMES) * (UI.nWidth-UI.nMouseX) / UI.nWidth); +        nSelectedFrame = (S.nFrameCurrent + MICROPROFILE_MAX_FRAME_HISTORY - nSelectedFrame) % MICROPROFILE_MAX_FRAME_HISTORY; +        UI.nHoverFrame = nSelectedFrame; +        if(UI.nMouseRight) +        { +            int64_t nRangeBegin = S.Frames[nSelectedFrame].nFrameStartCpu; +            int64_t nRangeEnd = S.Frames[(nSelectedFrame+1)%MICROPROFILE_MAX_FRAME_HISTORY].nFrameStartCpu; +            MicroProfileZoomTo(nRangeBegin, nRangeEnd); +        } +        if(UI.nMouseDownLeft) +        { +            uint64_t nFrac = (1024 * (MICROPROFILE_NUM_FRAMES) * (UI.nMouseX) / UI.nWidth) % 1024; +            int64_t nRangeBegin = S.Frames[nSelectedFrame].nFrameStartCpu; +            int64_t nRangeEnd = S.Frames[(nSelectedFrame+1)%MICROPROFILE_MAX_FRAME_HISTORY].nFrameStartCpu; +            MicroProfileCenter(nRangeBegin + (nRangeEnd-nRangeBegin) * nFrac / 1024); +        } +    } +    else +    { +        UI.nHoverFrame = -1; +    } + +    MicroProfileDrawDetailedBars(nWidth, nHeight, nBaseY + MICROPROFILE_FRAME_HISTORY_HEIGHT, nSelectedFrame); +    MicroProfileDrawDetailedFrameHistory(nWidth, nHeight, nBaseY, nSelectedFrame); +} + +void MicroProfileDrawTextRight(uint32_t nX, uint32_t nY, uint32_t nColor, const char* pStr, uint32_t nStrLen) +{ +    MicroProfileDrawText(nX - nStrLen * (MICROPROFILE_TEXT_WIDTH+1), nY, nColor, pStr, nStrLen); +} +void MicroProfileDrawHeader(int32_t nX, uint32_t nWidth, const char* pName) +{ +    if(pName) +    { +        MicroProfileDrawBox(nX-8, MICROPROFILE_TEXT_HEIGHT + 2, nX + nWidth+5, MICROPROFILE_TEXT_HEIGHT + 2 + (MICROPROFILE_TEXT_HEIGHT+1), 0xff000000|g_nMicroProfileBackColors[1]); +        MicroProfileDrawText(nX, MICROPROFILE_TEXT_HEIGHT + 2, (uint32_t)-1, pName, (uint32_t)strlen(pName)); +    } +} + + +typedef void (*MicroProfileLoopGroupCallback)(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pData); + +void MicroProfileLoopActiveGroupsDraw(int32_t nX, int32_t nY, const char* pName, MicroProfileLoopGroupCallback CB, void* pData) +{ +    MicroProfile& S = *MicroProfileGet(); +    nY += MICROPROFILE_TEXT_HEIGHT + 2; +    uint64_t nGroup = S.nAllGroupsWanted ? S.nGroupMask : S.nActiveGroupWanted; +    uint32_t nCount = 0; +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        uint64_t nMask = 1ll << j; +        if(nMask & nGroup) +        { +            nY += MICROPROFILE_TEXT_HEIGHT + 1; +            for(uint32_t i = 0; i < S.nTotalTimers;++i) +            { +                uint64_t nTokenMask = MicroProfileGetGroupMask(S.TimerInfo[i].nToken); +                if(nTokenMask & nMask) +                { +                    if(nY >= 0) +                        CB(i, nCount, nMask, nX, nY, pData); + +                    nCount += 2; +                    nY += MICROPROFILE_TEXT_HEIGHT + 1; + +                    if(nY > (int)UI.nHeight) +                        return; +                } +            } + +        } +    } +} + + +void MicroProfileCalcTimers(float* pTimers, float* pAverage, float* pMax, float* pCallAverage, float* pExclusive, float* pAverageExclusive, float* pMaxExclusive, uint64_t nGroup, uint32_t nSize) +{ +    MicroProfile& S = *MicroProfileGet(); + +    uint32_t nCount = 0; +    uint64_t nMask = 1; + +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        if(nMask & nGroup) +        { +            const float fToMs = MicroProfileTickToMsMultiplier(S.GroupInfo[j].Type == MicroProfileTokenTypeGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +            for(uint32_t i = 0; i < S.nTotalTimers;++i) +            { +                uint64_t nTokenMask = MicroProfileGetGroupMask(S.TimerInfo[i].nToken); +                if(nTokenMask & nMask) +                { +                    { +                        uint32_t nTimer = i; +                        uint32_t nIdx = nCount; +                        uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +                        uint32_t nAggregateCount = S.Aggregate[nTimer].nCount ? S.Aggregate[nTimer].nCount : 1; +                        float fToPrc = S.fRcpReferenceTime; +                        float fMs = fToMs * (S.Frame[nTimer].nTicks); +                        float fPrc = MicroProfileMin(fMs * fToPrc, 1.f); +                        float fAverageMs = fToMs * (S.Aggregate[nTimer].nTicks / nAggregateFrames); +                        float fAveragePrc = MicroProfileMin(fAverageMs * fToPrc, 1.f); +                        float fMaxMs = fToMs * (S.AggregateMax[nTimer]); +                        float fMaxPrc = MicroProfileMin(fMaxMs * fToPrc, 1.f); +                        float fCallAverageMs = fToMs * (S.Aggregate[nTimer].nTicks / nAggregateCount); +                        float fCallAveragePrc = MicroProfileMin(fCallAverageMs * fToPrc, 1.f); +                        float fMsExclusive = fToMs * (S.FrameExclusive[nTimer]); +                        float fPrcExclusive = MicroProfileMin(fMsExclusive * fToPrc, 1.f); +                        float fAverageMsExclusive = fToMs * (S.AggregateExclusive[nTimer] / nAggregateFrames); +                        float fAveragePrcExclusive = MicroProfileMin(fAverageMsExclusive * fToPrc, 1.f); +                        float fMaxMsExclusive = fToMs * (S.AggregateMaxExclusive[nTimer]); +                        float fMaxPrcExclusive = MicroProfileMin(fMaxMsExclusive * fToPrc, 1.f); +                        pTimers[nIdx] = fMs; +                        pTimers[nIdx+1] = fPrc; +                        pAverage[nIdx] = fAverageMs; +                        pAverage[nIdx+1] = fAveragePrc; +                        pMax[nIdx] = fMaxMs; +                        pMax[nIdx+1] = fMaxPrc; +                        pCallAverage[nIdx] = fCallAverageMs; +                        pCallAverage[nIdx+1] = fCallAveragePrc; +                        pExclusive[nIdx] = fMsExclusive; +                        pExclusive[nIdx+1] = fPrcExclusive; +                        pAverageExclusive[nIdx] = fAverageMsExclusive; +                        pAverageExclusive[nIdx+1] = fAveragePrcExclusive; +                        pMaxExclusive[nIdx] = fMaxMsExclusive; +                        pMaxExclusive[nIdx+1] = fMaxPrcExclusive; +                    } +                    nCount += 2; +                } +            } +        } +        nMask <<= 1ll; +    } +} + +#define SBUF_MAX 32 + +void MicroProfileDrawBarArrayCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra) +{ +    const uint32_t nHeight = MICROPROFILE_TEXT_HEIGHT; +    const uint32_t nTextWidth = 6 * (1+MICROPROFILE_TEXT_WIDTH); +    const float fWidth = (float)MICROPROFILE_BAR_WIDTH; + +    float* pTimers = ((float**)pExtra)[0]; +    float* pTimers2 = ((float**)pExtra)[1]; +    MicroProfile& S = *MicroProfileGet(); +    char sBuffer[SBUF_MAX]; +    if (pTimers2 && pTimers2[nIdx] > 0.1f) +        snprintf(sBuffer, SBUF_MAX-1, "%5.2f %3.1fx", pTimers[nIdx], pTimers[nIdx] / pTimers2[nIdx]); +    else +        snprintf(sBuffer, SBUF_MAX-1, "%5.2f", pTimers[nIdx]); +    if (!pTimers2) +        MicroProfileDrawBox(nX + nTextWidth, nY, nX + nTextWidth + fWidth * pTimers[nIdx+1], nY + nHeight, UI.nOpacityForeground|S.TimerInfo[nTimer].nColor, MicroProfileBoxTypeBar); +    MicroProfileDrawText(nX, nY, (uint32_t)-1, sBuffer, (uint32_t)strlen(sBuffer)); +} + + +uint32_t MicroProfileDrawBarArray(int32_t nX, int32_t nY, float* pTimers, const char* pName, uint32_t nTotalHeight, float* pTimers2 = NULL) +{ +    const uint32_t nTextWidth = 6 * (1+MICROPROFILE_TEXT_WIDTH); +    const uint32_t nWidth = MICROPROFILE_BAR_WIDTH; + +    MicroProfileDrawLineVertical(nX-5, 0, nTotalHeight+nY, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +    float* pTimersArray[2] = {pTimers, pTimers2}; +    MicroProfileLoopActiveGroupsDraw(nX, nY, pName, MicroProfileDrawBarArrayCallback, pTimersArray); +    MicroProfileDrawHeader(nX, nTextWidth + nWidth, pName); +    return nWidth + 5 + nTextWidth; + +} +void MicroProfileDrawBarCallCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra) +{ +    MicroProfile& S = *MicroProfileGet(); +    char sBuffer[SBUF_MAX]; +    int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5d", S.Frame[nTimer].nCount);//fix +    MicroProfileDrawText(nX, nY, (uint32_t)-1, sBuffer, nLen); +} + +uint32_t MicroProfileDrawBarCallCount(int32_t nX, int32_t nY, const char* pName) +{ +    MicroProfileLoopActiveGroupsDraw(nX, nY, pName, MicroProfileDrawBarCallCountCallback, 0); +    const uint32_t nTextWidth = 6 * MICROPROFILE_TEXT_WIDTH; +    MicroProfileDrawHeader(nX, 5 + nTextWidth, pName); +    return 5 + nTextWidth; +} + +struct MicroProfileMetaAverageArgs +{ +    uint64_t* pCounters; +    float fRcpFrames; +}; + +void MicroProfileDrawBarMetaAverageCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra) +{ +    MicroProfileMetaAverageArgs* pArgs = (MicroProfileMetaAverageArgs*)pExtra; +    uint64_t* pCounters = pArgs->pCounters; +    float fRcpFrames = pArgs->fRcpFrames; +    char sBuffer[SBUF_MAX]; +    int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5.2f", pCounters[nTimer] * fRcpFrames); +    MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, (uint32_t)-1, sBuffer, nLen); +} + +uint32_t MicroProfileDrawBarMetaAverage(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight) +{ +    if(!pName) +        return 0; +    MicroProfileDrawLineVertical(nX-5, 0, nTotalHeight+nY, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +    uint32_t nTextWidth = (1+MICROPROFILE_TEXT_WIDTH) * MicroProfileMax<uint32_t>(6, (uint32_t)strlen(pName)); +    float fRcpFrames = 1.f / (MicroProfileGet()->nAggregateFrames ? MicroProfileGet()->nAggregateFrames : 1); +    MicroProfileMetaAverageArgs Args = {pCounters, fRcpFrames}; +    MicroProfileLoopActiveGroupsDraw(nX + nTextWidth, nY, pName, MicroProfileDrawBarMetaAverageCallback, &Args); +    MicroProfileDrawHeader(nX, 5 + nTextWidth, pName); +    return 5 + nTextWidth; +} + + +void MicroProfileDrawBarMetaCountCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra) +{ +    uint64_t* pCounters = (uint64_t*)pExtra; +    char sBuffer[SBUF_MAX]; +    int nLen = snprintf(sBuffer, SBUF_MAX-1, "%5llu", pCounters[nTimer]); +    MicroProfileDrawText(nX - nLen * (MICROPROFILE_TEXT_WIDTH+1), nY, (uint32_t)-1, sBuffer, nLen); +} + +uint32_t MicroProfileDrawBarMetaCount(int32_t nX, int32_t nY, uint64_t* pCounters, const char* pName, uint32_t nTotalHeight) +{ +    if(!pName) +        return 0; + +    MicroProfileDrawLineVertical(nX-5, 0, nTotalHeight+nY, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +    uint32_t nTextWidth = (1+MICROPROFILE_TEXT_WIDTH) * MicroProfileMax<uint32_t>(6, (uint32_t)strlen(pName)); +    MicroProfileLoopActiveGroupsDraw(nX + nTextWidth, nY, pName, MicroProfileDrawBarMetaCountCallback, pCounters); +    MicroProfileDrawHeader(nX, 5 + nTextWidth, pName); +    return 5 + nTextWidth; +} + +void MicroProfileDrawBarLegendCallback(uint32_t nTimer, uint32_t nIdx, uint64_t nGroupMask, uint32_t nX, uint32_t nY, void* pExtra) +{ +    MicroProfile& S = *MicroProfileGet(); +    if (S.TimerInfo[nTimer].bGraph) +    { +        MicroProfileDrawText(nX, nY, S.TimerInfo[nTimer].nColor, ">", 1); +    } +    MicroProfileDrawTextRight(nX, nY, S.TimerInfo[nTimer].nColor, S.TimerInfo[nTimer].pName, (uint32_t)strlen(S.TimerInfo[nTimer].pName)); +    if(UI.nMouseY >= nY && UI.nMouseY < nY + MICROPROFILE_TEXT_HEIGHT+1) +    { +        UI.nHoverToken = nTimer; +        UI.nHoverTime = 0; +    } +} + +uint32_t MicroProfileDrawBarLegend(int32_t nX, int32_t nY, uint32_t nTotalHeight, uint32_t nMaxWidth) +{ +    MicroProfileDrawLineVertical(nX-5, nY, nTotalHeight, UI.nOpacityBackground | g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +    MicroProfileLoopActiveGroupsDraw(nMaxWidth, nY, 0, MicroProfileDrawBarLegendCallback, 0); +    return nX; +} + +bool MicroProfileDrawGraph(uint32_t nScreenWidth, uint32_t nScreenHeight) +{ +    MicroProfile& S = *MicroProfileGet(); + +    MICROPROFILE_SCOPE(g_MicroProfileDrawGraph); +    bool bEnabled = false; +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +        if(S.Graph[i].nToken != MICROPROFILE_INVALID_TOKEN) +            bEnabled = true; +    if(!bEnabled) +        return false; + +    uint32_t nX = nScreenWidth - MICROPROFILE_GRAPH_WIDTH; +    uint32_t nY = nScreenHeight - MICROPROFILE_GRAPH_HEIGHT; +    MicroProfileDrawBox(nX, nY, nX + MICROPROFILE_GRAPH_WIDTH, nY + MICROPROFILE_GRAPH_HEIGHT, 0x88000000 | g_nMicroProfileBackColors[0]); +    bool bMouseOver = UI.nMouseX >= nX && UI.nMouseY >= nY; +    float fMouseXPrc =(float(UI.nMouseX - nX)) / MICROPROFILE_GRAPH_WIDTH; +    if(bMouseOver) +    { +        float fXAvg = fMouseXPrc * MICROPROFILE_GRAPH_WIDTH + nX; +        MicroProfileDrawLineVertical(fXAvg, nY, nY + MICROPROFILE_GRAPH_HEIGHT, (uint32_t)-1); +    } + + +    float fY = (float)nScreenHeight; +    float fDX = MICROPROFILE_GRAPH_WIDTH * 1.f / MICROPROFILE_GRAPH_HISTORY; +    float fDY = MICROPROFILE_GRAPH_HEIGHT; +    uint32_t nPut = S.nGraphPut; +    float* pGraphData = (float*)alloca(sizeof(float)* MICROPROFILE_GRAPH_HISTORY*2); +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +    { +        if(S.Graph[i].nToken != MICROPROFILE_INVALID_TOKEN) +        { +            uint32_t nGroupId = MicroProfileGetGroupIndex(S.Graph[i].nToken); +            bool bGpu = S.GroupInfo[nGroupId].Type == MicroProfileTokenTypeGpu; +            float fToMs = MicroProfileTickToMsMultiplier(bGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +            float fToPrc = fToMs * S.fRcpReferenceTime * 3 / 4; + +            float fX = (float)nX; +            for(uint32_t j = 0; j < MICROPROFILE_GRAPH_HISTORY; ++j) +            { +                float fWeigth = MicroProfileMin(fToPrc * (S.Graph[i].nHistory[(j+nPut)%MICROPROFILE_GRAPH_HISTORY]), 1.f); +                pGraphData[(j*2)] = fX; +                pGraphData[(j*2)+1] = fY - fDY * fWeigth; +                fX += fDX; +            } +            MicroProfileDrawLine2D(MICROPROFILE_GRAPH_HISTORY, pGraphData, S.TimerInfo[MicroProfileGetTimerIndex(S.Graph[i].nToken)].nColor); +        } +    } +    { +        float fY1 = 0.25f * MICROPROFILE_GRAPH_HEIGHT + nY; +        float fY2 = 0.50f * MICROPROFILE_GRAPH_HEIGHT + nY; +        float fY3 = 0.75f * MICROPROFILE_GRAPH_HEIGHT + nY; +        MicroProfileDrawLineHorizontal(nX, nX + MICROPROFILE_GRAPH_WIDTH, fY1, 0xffdd4444); +        MicroProfileDrawLineHorizontal(nX, nX + MICROPROFILE_GRAPH_WIDTH, fY2, 0xff000000| g_nMicroProfileBackColors[0]); +        MicroProfileDrawLineHorizontal(nX, nX + MICROPROFILE_GRAPH_WIDTH, fY3, 0xff000000|g_nMicroProfileBackColors[0]); + +        char buf[32]; +        int nLen = snprintf(buf, sizeof(buf)-1, "%5.2fms", S.fReferenceTime); +        MicroProfileDrawText(nX+1, fY1 - (2+MICROPROFILE_TEXT_HEIGHT), (uint32_t)-1, buf, nLen); +    } + + + +    if(bMouseOver) +    { +        uint32_t pColors[MICROPROFILE_MAX_GRAPHS]; +        MicroProfileStringArray Strings; +        MicroProfileStringArrayClear(&Strings); +        uint32_t nTextCount = 0; +        uint32_t nGraphIndex = (S.nGraphPut + MICROPROFILE_GRAPH_HISTORY - int(MICROPROFILE_GRAPH_HISTORY*(1.f - fMouseXPrc))) % MICROPROFILE_GRAPH_HISTORY; + +        uint32_t nX = UI.nMouseX; +        uint32_t nY = UI.nMouseY + 20; + +        for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +        { +            if(S.Graph[i].nToken != MICROPROFILE_INVALID_TOKEN) +            { +                uint32_t nGroupId = MicroProfileGetGroupIndex(S.Graph[i].nToken); +                bool bGpu = S.GroupInfo[nGroupId].Type == MicroProfileTokenTypeGpu; +                float fToMs = MicroProfileTickToMsMultiplier(bGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +                uint32_t nIndex = MicroProfileGetTimerIndex(S.Graph[i].nToken); +                uint32_t nColor = S.TimerInfo[nIndex].nColor; +                const char* pName = S.TimerInfo[nIndex].pName; +                pColors[nTextCount++] = nColor; +                MicroProfileStringArrayAddLiteral(&Strings, pName); +                MicroProfileStringArrayFormat(&Strings, "%5.2fms", fToMs * (S.Graph[i].nHistory[nGraphIndex])); +            } +        } +        if(nTextCount) +        { +            MicroProfileDrawFloatWindow(nX, nY, Strings.ppStrings, Strings.nNumStrings, 0, pColors); +        } + +        if(UI.nMouseRight) +        { +            for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +            { +                S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +            } +        } +    } + +    return bMouseOver; +} + +void MicroProfileDumpTimers() +{ +    MicroProfile& S = *MicroProfileGet(); + +    uint64_t nActiveGroup = S.nGroupMask; + +    uint32_t nNumTimers = S.nTotalTimers; +    uint32_t nBlockSize = 2 * nNumTimers; +    float* pTimers = (float*)alloca(nBlockSize * 7 * sizeof(float)); +    float* pAverage = pTimers + nBlockSize; +    float* pMax = pTimers + 2 * nBlockSize; +    float* pCallAverage = pTimers + 3 * nBlockSize; +    float* pTimersExclusive = pTimers + 4 * nBlockSize; +    float* pAverageExclusive = pTimers + 5 * nBlockSize; +    float* pMaxExclusive = pTimers + 6 * nBlockSize; +    MicroProfileCalcTimers(pTimers, pAverage, pMax, pCallAverage, pTimersExclusive, pAverageExclusive, pMaxExclusive, nActiveGroup, nNumTimers); + +    MICROPROFILE_PRINTF("%11s, ", "Time"); +    MICROPROFILE_PRINTF("%11s, ", "Average"); +    MICROPROFILE_PRINTF("%11s, ", "Max"); +    MICROPROFILE_PRINTF("%11s, ", "Call Avg"); +    MICROPROFILE_PRINTF("%9s, ", "Count"); +    MICROPROFILE_PRINTF("%11s, ", "Excl"); +    MICROPROFILE_PRINTF("%11s, ", "Avg Excl"); +    MICROPROFILE_PRINTF("%11s, \n", "Max Excl"); + +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        uint64_t nMask = 1ll << j; +        if(nMask & nActiveGroup) +        { +            MICROPROFILE_PRINTF("%s\n", S.GroupInfo[j].pName); +            for(uint32_t i = 0; i < S.nTotalTimers;++i) +            { +                uint64_t nTokenMask = MicroProfileGetGroupMask(S.TimerInfo[i].nToken); +                if(nTokenMask & nMask) +                { +                    uint32_t nIdx = i * 2; +                    MICROPROFILE_PRINTF("%9.2fms, ", pTimers[nIdx]); +                    MICROPROFILE_PRINTF("%9.2fms, ", pAverage[nIdx]); +                    MICROPROFILE_PRINTF("%9.2fms, ", pMax[nIdx]); +                    MICROPROFILE_PRINTF("%9.2fms, ", pCallAverage[nIdx]); +                    MICROPROFILE_PRINTF("%9d, ", S.Frame[i].nCount); +                    MICROPROFILE_PRINTF("%9.2fms, ", pTimersExclusive[nIdx]); +                    MICROPROFILE_PRINTF("%9.2fms, ", pAverageExclusive[nIdx]); +                    MICROPROFILE_PRINTF("%9.2fms, ", pMaxExclusive[nIdx]); +                    MICROPROFILE_PRINTF("%s\n", S.TimerInfo[i].pName); +                } +            } +        } +    } +} + +void MicroProfileDrawBarView(uint32_t nScreenWidth, uint32_t nScreenHeight) +{ +    MicroProfile& S = *MicroProfileGet(); + +    uint64_t nActiveGroup = S.nAllGroupsWanted ? S.nGroupMask : S.nActiveGroupWanted; +    if(!nActiveGroup) +        return; +    MICROPROFILE_SCOPE(g_MicroProfileDrawBarView); + +    const uint32_t nHeight = MICROPROFILE_TEXT_HEIGHT; +    int nColorIndex = 0; +    uint32_t nMaxTimerNameLen = 1; +    uint32_t nNumTimers = 0; +    uint32_t nNumGroups = 0; +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        if(nActiveGroup & (1ll << j)) +        { +            nNumTimers += S.GroupInfo[j].nNumTimers; +            nNumGroups += 1; +            nMaxTimerNameLen = MicroProfileMax(nMaxTimerNameLen, S.GroupInfo[j].nMaxTimerNameLen); +        } +    } +    uint32_t nTimerWidth = 2+(4+nMaxTimerNameLen) * (MICROPROFILE_TEXT_WIDTH+1); +    uint32_t nX = nTimerWidth + UI.nOffsetX; +    uint32_t nY = nHeight + 3 - UI.nOffsetY; +    uint32_t nBlockSize = 2 * nNumTimers; +    float* pTimers = (float*)alloca(nBlockSize * 7 * sizeof(float)); +    float* pAverage = pTimers + nBlockSize; +    float* pMax = pTimers + 2 * nBlockSize; +    float* pCallAverage = pTimers + 3 * nBlockSize; +    float* pTimersExclusive = pTimers + 4 * nBlockSize; +    float* pAverageExclusive = pTimers + 5 * nBlockSize; +    float* pMaxExclusive = pTimers + 6 * nBlockSize; +    MicroProfileCalcTimers(pTimers, pAverage, pMax, pCallAverage, pTimersExclusive, pAverageExclusive, pMaxExclusive, nActiveGroup, nNumTimers); +    uint32_t nWidth = 0; +    { +        uint32_t nMetaIndex = 0; +        for(uint32_t i = 1; i ; i <<= 1) +        { +            if(S.nBars & i) +            { +                if(i >= MP_DRAW_META_FIRST) +                { +                    if(nMetaIndex < MICROPROFILE_META_MAX && S.MetaCounters[nMetaIndex].pName) +                    { +                        uint32_t nStrWidth = strlen(S.MetaCounters[nMetaIndex].pName); +                        if(S.nBars & MP_DRAW_TIMERS) +                            nWidth += 6 + (1+MICROPROFILE_TEXT_WIDTH) * (nStrWidth); +                        if(S.nBars & MP_DRAW_AVERAGE) +                            nWidth += 6 + (1+MICROPROFILE_TEXT_WIDTH) * (nStrWidth + 4); +                        if(S.nBars & MP_DRAW_MAX) +                            nWidth += 6 + (1+MICROPROFILE_TEXT_WIDTH) * (nStrWidth + 4); +                    } +                } +                else +                { +                    nWidth += MICROPROFILE_BAR_WIDTH + 6 + 6 * (1+MICROPROFILE_TEXT_WIDTH); +                    if(i & MP_DRAW_CALL_COUNT) +                        nWidth += 6 + 6 * MICROPROFILE_TEXT_WIDTH; +                } +            } +            if(i >= MP_DRAW_META_FIRST) +            { +                ++nMetaIndex; +            } +        } +        nWidth += (1+nMaxTimerNameLen) * (MICROPROFILE_TEXT_WIDTH+1); +        for(uint32_t i = 0; i < nNumTimers+nNumGroups+1; ++i) +        { +            uint32_t nY0 = nY + i * (nHeight + 1); +            bool bInside = (UI.nActiveMenu == -1) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1))); +            MicroProfileDrawBox(nX, nY0, nWidth+nX, nY0 + (nHeight+1)+1, UI.nOpacityBackground | (g_nMicroProfileBackColors[nColorIndex++ & 1] + ((bInside) ? 0x002c2c2c : 0))); +        } +        nX += 10; +    } +    int nTotalHeight = (nNumTimers+nNumGroups+1) * (nHeight+1); +    uint32_t nLegendOffset = 1; +    if(S.nBars & MP_DRAW_TIMERS) +        nX += MicroProfileDrawBarArray(nX, nY, pTimers, "Time", nTotalHeight) + 1; +    if(S.nBars & MP_DRAW_AVERAGE) +        nX += MicroProfileDrawBarArray(nX, nY, pAverage, "Average", nTotalHeight) + 1; +    if(S.nBars & MP_DRAW_MAX) +        nX += MicroProfileDrawBarArray(nX, nY, pMax, (!UI.bShowSpikes) ? "Max Time" : "Max Time, Spike", nTotalHeight, UI.bShowSpikes ? pAverage : NULL) + 1; +    if(S.nBars & MP_DRAW_CALL_COUNT) +    { +        nX += MicroProfileDrawBarArray(nX, nY, pCallAverage, "Call Average", nTotalHeight) + 1; +        nX += MicroProfileDrawBarCallCount(nX, nY, "Count") + 1; +    } +    if(S.nBars & MP_DRAW_TIMERS_EXCLUSIVE) +        nX += MicroProfileDrawBarArray(nX, nY, pTimersExclusive, "Exclusive Time", nTotalHeight) + 1; +    if(S.nBars & MP_DRAW_AVERAGE_EXCLUSIVE) +        nX += MicroProfileDrawBarArray(nX, nY, pAverageExclusive, "Exclusive Average", nTotalHeight) + 1; +    if(S.nBars & MP_DRAW_MAX_EXCLUSIVE) +        nX += MicroProfileDrawBarArray(nX, nY, pMaxExclusive, (!UI.bShowSpikes) ? "Exclusive Max Time" :"Excl Max Time, Spike", nTotalHeight, UI.bShowSpikes ? pAverageExclusive : NULL) + 1; + +    for(int i = 0; i < MICROPROFILE_META_MAX; ++i) +    { +        if(0 != (S.nBars & (MP_DRAW_META_FIRST<<i)) && S.MetaCounters[i].pName) +        { +            uint32_t nBufferSize = strlen(S.MetaCounters[i].pName) + 32; +            char* buffer = (char*)alloca(nBufferSize); +            if(S.nBars & MP_DRAW_TIMERS) +                nX += MicroProfileDrawBarMetaCount(nX, nY, &S.MetaCounters[i].nCounters[0], S.MetaCounters[i].pName, nTotalHeight) + 1; +            if(S.nBars & MP_DRAW_AVERAGE) +            { +                snprintf(buffer, nBufferSize-1, "%s Avg", S.MetaCounters[i].pName); +                nX += MicroProfileDrawBarMetaAverage(nX, nY, &S.MetaCounters[i].nAggregate[0], buffer, nTotalHeight) + 1; +            } +            if(S.nBars & MP_DRAW_MAX) +            { +                snprintf(buffer, nBufferSize-1, "%s Max", S.MetaCounters[i].pName); +                nX += MicroProfileDrawBarMetaCount(nX, nY, &S.MetaCounters[i].nAggregateMax[0], buffer, nTotalHeight) + 1; +            } +        } +    } +    nX = 0; +    nY = nHeight + 3 - UI.nOffsetY; +    for(uint32_t i = 0; i < nNumTimers+nNumGroups+1; ++i) +    { +        uint32_t nY0 = nY + i * (nHeight + 1); +        bool bInside = (UI.nActiveMenu == -1) && ((UI.nMouseY >= nY0) && (UI.nMouseY < (nY0 + nHeight + 1))); +        MicroProfileDrawBox(nX, nY0, nTimerWidth, nY0 + (nHeight+1)+1, 0xff0000000 | (g_nMicroProfileBackColors[nColorIndex++ & 1] + ((bInside) ? 0x002c2c2c : 0))); +    } +    nX += MicroProfileDrawBarLegend(nX, nY, nTotalHeight, nTimerWidth-5) + 1; + +    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +    { +        if(nActiveGroup & (1ll << j)) +        { +            MicroProfileDrawText(nX, nY + (1+nHeight) * nLegendOffset, (uint32_t)-1, S.GroupInfo[j].pName, S.GroupInfo[j].nNameLen); +            nLegendOffset += S.GroupInfo[j].nNumTimers+1; +        } +    } +    MicroProfileDrawHeader(nX, nTimerWidth-5, "Group"); +    MicroProfileDrawTextRight(nTimerWidth-3, MICROPROFILE_TEXT_HEIGHT + 2, (uint32_t)-1, "Timer", 5); +    MicroProfileDrawLineVertical(nTimerWidth, 0, nTotalHeight+nY, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +    MicroProfileDrawLineHorizontal(0, nWidth, 2*MICROPROFILE_TEXT_HEIGHT + 3, UI.nOpacityBackground|g_nMicroProfileBackColors[0]|g_nMicroProfileBackColors[1]); +} + +typedef const char* (*MicroProfileSubmenuCallback)(int, bool* bSelected); +typedef void (*MicroProfileClickCallback)(int); + + +const char* MicroProfileUIMenuMode(int nIndex, bool* bSelected) +{ +    MicroProfile& S = *MicroProfileGet(); +    switch(nIndex) +    { +        case 0: +            *bSelected = S.nDisplay == MP_DRAW_DETAILED; +            return "Detailed"; +        case 1: +            *bSelected = S.nDisplay == MP_DRAW_BARS; +            return "Timers"; +        case 2: +            *bSelected = S.nDisplay == MP_DRAW_HIDDEN; +            return "Hidden"; +        case 3: +            *bSelected = true; +            return "Off"; +        case 4: +            *bSelected = true; +            return "------"; +        case 5: +            *bSelected = S.nForceEnable != 0; +            return "Force Enable"; + +        default: return 0; +    } +} + +const char* MicroProfileUIMenuGroups(int nIndex, bool* bSelected) +{ +    MicroProfile& S = *MicroProfileGet(); +    *bSelected = false; +    if(nIndex == 0) +    { +        *bSelected = S.nAllGroupsWanted != 0; +        return "[ALL]"; +    } +    else +    { +        nIndex = nIndex-1; +        if(nIndex < UI.GroupMenuCount) +        { +            MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex]; +            static char buffer[MICROPROFILE_NAME_MAX_LEN+32]; +            if(Item.nIsCategory) +            { +                uint64_t nGroupMask = S.CategoryInfo[Item.nIndex].nGroupMask; +                *bSelected = nGroupMask == (nGroupMask & S.nActiveGroupWanted); +                snprintf(buffer, sizeof(buffer)-1, "[%s]", Item.pName); +            } +            else +            { +                *bSelected = 0 != (S.nActiveGroupWanted & (1ll << Item.nIndex)); +                snprintf(buffer, sizeof(buffer)-1, "   %s", Item.pName); +            } +            return buffer; +        } +        return 0; +    } +} + +const char* MicroProfileUIMenuAggregate(int nIndex, bool* bSelected) +{ +    MicroProfile& S = *MicroProfileGet(); +    if(nIndex < sizeof(g_MicroProfileAggregatePresets)/sizeof(g_MicroProfileAggregatePresets[0])) +    { +        int val = g_MicroProfileAggregatePresets[nIndex]; +        *bSelected = (int)S.nAggregateFlip == val; +        if(0 == val) +            return "Infinite"; +        else +        { +            static char buf[128]; +            snprintf(buf, sizeof(buf)-1, "%7d", val); +            return buf; +        } +    } +    return 0; + +} + +const char* MicroProfileUIMenuTimers(int nIndex, bool* bSelected) +{ +    MicroProfile& S = *MicroProfileGet(); +    *bSelected = 0 != (S.nBars & (1 << nIndex)); +    switch(nIndex) +    { +        case 0: return "Time"; +        case 1: return "Average"; +        case 2: return "Max"; +        case 3: return "Call Count"; +        case 4: return "Exclusive Timers"; +        case 5: return "Exclusive Average"; +        case 6: return "Exclusive Max"; +    } +    int nMetaIndex = nIndex - 7; +    if(nMetaIndex < MICROPROFILE_META_MAX) +    { +        return S.MetaCounters[nMetaIndex].pName; +    } +    return 0; +} + +const char* MicroProfileUIMenuOptions(int nIndex, bool* bSelected) +{ +    MicroProfile& S = *MicroProfileGet(); +    if(nIndex >= MICROPROFILE_OPTION_SIZE) return 0; +    switch(UI.Options[nIndex].nSubType) +    { +    case 0: +        *bSelected = S.fReferenceTime == g_MicroProfileReferenceTimePresets[UI.Options[nIndex].nIndex]; +        break; +    case 1: +        *bSelected = UI.nOpacityBackground>>24 == g_MicroProfileOpacityPresets[UI.Options[nIndex].nIndex]; +        break; +    case 2: +        *bSelected = UI.nOpacityForeground>>24 == g_MicroProfileOpacityPresets[UI.Options[nIndex].nIndex]; +        break; +    case 3: +        *bSelected = UI.bShowSpikes; +        break; +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +    case 4: +        { +            switch(UI.Options[nIndex].nIndex) +            { +            case 0: +                *bSelected = S.bContextSwitchRunning; +                break; +            case 1: +                *bSelected = S.bContextSwitchAllThreads; +                break; +            case 2: +                *bSelected = S.bContextSwitchNoBars; +                break; +            } +        } +        break; +#endif +    } +    return UI.Options[nIndex].Text; +} + +const char* MicroProfileUIMenuPreset(int nIndex, bool* bSelected) +{ +    static char buf[128]; +    *bSelected = false; +    int nNumPresets = sizeof(g_MicroProfilePresetNames) / sizeof(g_MicroProfilePresetNames[0]); +    int nIndexSave = nIndex - nNumPresets - 1; +    if(nIndex == nNumPresets) +        return "--"; +    else if(nIndexSave >=0 && nIndexSave <nNumPresets) +    { +        snprintf(buf, sizeof(buf)-1, "Save '%s'", g_MicroProfilePresetNames[nIndexSave]); +        return buf; +    } +    else if(nIndex < nNumPresets) +    { +        snprintf(buf, sizeof(buf)-1, "Load '%s'", g_MicroProfilePresetNames[nIndex]); +        return buf; +    } +    else +    { +        return 0; +    } +} + +const char* MicroProfileUIMenuCustom(int nIndex, bool* bSelected) +{ +    if((uint32_t)-1 == UI.nCustomActive) +    { +        *bSelected = nIndex == 0; +    } +    else +    { +        *bSelected = nIndex-2 == UI.nCustomActive; +    } +    switch(nIndex) +    { +    case 0: return "Disable"; +    case 1: return "--"; +    default: +        nIndex -= 2; +        if(nIndex < UI.nCustomCount) +        { +            return UI.Custom[nIndex].pName; +        } +        else +        { +            return 0; +        } +    } +} + +const char* MicroProfileUIMenuEmpty(int nIndex, bool* bSelected) +{ +    return 0; +} + + +void MicroProfileUIClickMode(int nIndex) +{ +    MicroProfile& S = *MicroProfileGet(); +    switch(nIndex) +    { +        case 0: +            S.nDisplay = MP_DRAW_DETAILED; +            break; +        case 1: +            S.nDisplay = MP_DRAW_BARS; +            break; +        case 2: +            S.nDisplay = MP_DRAW_HIDDEN; +            break; +        case 3: +            S.nDisplay = 0; +            break; +        case 4: +            break; +        case 5: +            S.nForceEnable = !S.nForceEnable; +            break; +    } +} + +void MicroProfileUIClickGroups(int nIndex) +{ +    MicroProfile& S = *MicroProfileGet(); +    if(nIndex == 0) +        S.nAllGroupsWanted = 1-S.nAllGroupsWanted; +    else +    { +        nIndex -= 1; +        if(nIndex < UI.GroupMenuCount) +        { +            MicroProfileGroupMenuItem& Item = UI.GroupMenu[nIndex]; +            if(Item.nIsCategory) +            { +                uint64_t nGroupMask = S.CategoryInfo[Item.nIndex].nGroupMask; +                if(nGroupMask != (nGroupMask & S.nActiveGroupWanted)) +                { +                    S.nActiveGroupWanted |= nGroupMask; +                } +                else +                { +                    S.nActiveGroupWanted &= ~nGroupMask; +                } +            } +            else +            { +                MP_ASSERT(Item.nIndex < S.nGroupCount); +                S.nActiveGroupWanted ^= (1ll << Item.nIndex); +            } +        } +    } +} + +void MicroProfileUIClickAggregate(int nIndex) +{ +    MicroProfile& S = *MicroProfileGet(); +    S.nAggregateFlip = g_MicroProfileAggregatePresets[nIndex]; +    if(0 == S.nAggregateFlip) +    { +        S.nAggregateClear = 1; +    } +} + +void MicroProfileUIClickTimers(int nIndex) +{ +    MicroProfile& S = *MicroProfileGet(); +    S.nBars ^= (1 << nIndex); +} + +void MicroProfileUIClickOptions(int nIndex) +{ +    MicroProfile& S = *MicroProfileGet(); +    switch(UI.Options[nIndex].nSubType) +    { +    case 0: +        S.fReferenceTime = g_MicroProfileReferenceTimePresets[UI.Options[nIndex].nIndex]; +        S.fRcpReferenceTime = 1.f / S.fReferenceTime; +        break; +    case 1: +        UI.nOpacityBackground = g_MicroProfileOpacityPresets[UI.Options[nIndex].nIndex]<<24; +        break; +    case 2: +        UI.nOpacityForeground = g_MicroProfileOpacityPresets[UI.Options[nIndex].nIndex]<<24; +        break; +    case 3: +        UI.bShowSpikes = !UI.bShowSpikes; +        break; +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +    case 4: +        { +            switch(UI.Options[nIndex].nIndex) +            { +            case 0: +                if(S.bContextSwitchRunning) +                { +                    MicroProfileStopContextSwitchTrace(); +                } +                else +                { +                    MicroProfileStartContextSwitchTrace(); +                } +                break; +            case 1: +                S.bContextSwitchAllThreads = !S.bContextSwitchAllThreads; +                break; +            case 2: +                S.bContextSwitchNoBars= !S.bContextSwitchNoBars; +                break; + +            } +        } +        break; +#endif +    } +} + +void MicroProfileUIClickPreset(int nIndex) +{ +    int nNumPresets = sizeof(g_MicroProfilePresetNames) / sizeof(g_MicroProfilePresetNames[0]); +    int nIndexSave = nIndex - nNumPresets - 1; +    if(nIndexSave >= 0 && nIndexSave < nNumPresets) +    { +        MicroProfileSavePreset(g_MicroProfilePresetNames[nIndexSave]); +    } +    else if(nIndex >= 0 && nIndex < nNumPresets) +    { +        MicroProfileLoadPreset(g_MicroProfilePresetNames[nIndex]); +    } +} + +void MicroProfileUIClickCustom(int nIndex) +{ +    if(nIndex == 0) +    { +        MicroProfileCustomGroupDisable(); +    } +    else +    { +        MicroProfileCustomGroupEnable(nIndex-2); +    } + +} + +void MicroProfileUIClickEmpty(int nIndex) +{ + +} + + +void MicroProfileDrawMenu(uint32_t nWidth, uint32_t nHeight) +{ +    MicroProfile& S = *MicroProfileGet(); + +    uint32_t nX = 0; +    uint32_t nY = 0; +    bool bMouseOver = UI.nMouseY < MICROPROFILE_TEXT_HEIGHT + 1; +#define SBUF_SIZE 256 +    char buffer[256]; +    MicroProfileDrawBox(nX, nY, nX + nWidth, nY + (MICROPROFILE_TEXT_HEIGHT+1)+1, 0xff000000|g_nMicroProfileBackColors[1]); + +#define MICROPROFILE_MENU_MAX 16 +    const char* pMenuText[MICROPROFILE_MENU_MAX] = {0}; +    uint32_t    nMenuX[MICROPROFILE_MENU_MAX] = {0}; +    uint32_t nNumMenuItems = 0; + +    int nLen = snprintf(buffer, 127, "MicroProfile"); +    MicroProfileDrawText(nX, nY, (uint32_t)-1, buffer, nLen); +    nX += (sizeof("MicroProfile")+2) * (MICROPROFILE_TEXT_WIDTH+1); +    pMenuText[nNumMenuItems++] = "Mode"; +    pMenuText[nNumMenuItems++] = "Groups"; +    char AggregateText[64]; +    snprintf(AggregateText, sizeof(AggregateText)-1, "Aggregate[%d]", S.nAggregateFlip ? S.nAggregateFlip : S.nAggregateFlipCount); +    pMenuText[nNumMenuItems++] = &AggregateText[0]; +    pMenuText[nNumMenuItems++] = "Timers"; +    pMenuText[nNumMenuItems++] = "Options"; +    pMenuText[nNumMenuItems++] = "Preset"; +    pMenuText[nNumMenuItems++] = "Custom"; +    const int nPauseIndex = nNumMenuItems; +    pMenuText[nNumMenuItems++] = S.nRunning ? "Pause" : "Unpause"; +    pMenuText[nNumMenuItems++] = "Help"; + +    if(S.nOverflow) +    { +        pMenuText[nNumMenuItems++] = "!BUFFERSFULL!"; +    } + + +    if(UI.GroupMenuCount != S.nGroupCount + S.nCategoryCount) +    { +        UI.GroupMenuCount = S.nGroupCount + S.nCategoryCount; +        for(uint32_t i = 0; i < S.nCategoryCount; ++i) +        { +            UI.GroupMenu[i].nIsCategory = 1; +            UI.GroupMenu[i].nCategoryIndex = i; +            UI.GroupMenu[i].nIndex = i; +            UI.GroupMenu[i].pName = S.CategoryInfo[i].pName; +        } +        for(uint32_t i = 0; i < S.nGroupCount; ++i) +        { +            uint32_t idx = i + S.nCategoryCount; +            UI.GroupMenu[idx].nIsCategory = 0; +            UI.GroupMenu[idx].nCategoryIndex = S.GroupInfo[i].nCategory; +            UI.GroupMenu[idx].nIndex = i; +            UI.GroupMenu[idx].pName = S.GroupInfo[i].pName; +        } +        std::sort(&UI.GroupMenu[0], &UI.GroupMenu[UI.GroupMenuCount], +            [] (const MicroProfileGroupMenuItem& l, const MicroProfileGroupMenuItem& r) -> bool +            { +                if(l.nCategoryIndex < r.nCategoryIndex) +                { +                    return true; +                } +                else if(r.nCategoryIndex < l.nCategoryIndex) +                { +                    return false; +                } +                if(r.nIsCategory || l.nIsCategory) +                { +                    return l.nIsCategory > r.nIsCategory; +                } +                return MP_STRCASECMP(l.pName, r.pName)<0; +            } +        ); +    } + +    MicroProfileSubmenuCallback GroupCallback[MICROPROFILE_MENU_MAX] = +    { +        MicroProfileUIMenuMode, +        MicroProfileUIMenuGroups, +        MicroProfileUIMenuAggregate, +        MicroProfileUIMenuTimers, +        MicroProfileUIMenuOptions, +        MicroProfileUIMenuPreset, +        MicroProfileUIMenuCustom, +        MicroProfileUIMenuEmpty, +        MicroProfileUIMenuEmpty, +        MicroProfileUIMenuEmpty, +    }; + +    MicroProfileClickCallback CBClick[MICROPROFILE_MENU_MAX] = +    { +        MicroProfileUIClickMode, +        MicroProfileUIClickGroups, +        MicroProfileUIClickAggregate, +        MicroProfileUIClickTimers, +        MicroProfileUIClickOptions, +        MicroProfileUIClickPreset, +        MicroProfileUIClickCustom, +        MicroProfileUIClickEmpty, +        MicroProfileUIClickEmpty, +        MicroProfileUIClickEmpty, +    }; + + +    uint32_t nSelectMenu = (uint32_t)-1; +    for(uint32_t i = 0; i < nNumMenuItems; ++i) +    { +        nMenuX[i] = nX; +        uint32_t nLen = (uint32_t)strlen(pMenuText[i]); +        uint32_t nEnd = nX + nLen * (MICROPROFILE_TEXT_WIDTH+1); +        if(UI.nMouseY <= MICROPROFILE_TEXT_HEIGHT && UI.nMouseX <= nEnd && UI.nMouseX >= nX) +        { +            MicroProfileDrawBox(nX-1, nY, nX + nLen * (MICROPROFILE_TEXT_WIDTH+1), nY +(MICROPROFILE_TEXT_HEIGHT+1)+1, 0xff888888); +            nSelectMenu = i; +            if((UI.nMouseLeft || UI.nMouseRight) && i == (int)nPauseIndex) +            { +                S.nToggleRunning = 1; +            } +        } +        MicroProfileDrawText(nX, nY, (uint32_t)-1, pMenuText[i], (uint32_t)strlen(pMenuText[i])); +        nX += (nLen+1) * (MICROPROFILE_TEXT_WIDTH+1); +    } +    uint32_t nMenu = nSelectMenu != (uint32_t)-1 ? nSelectMenu : UI.nActiveMenu; +    UI.nActiveMenu = nMenu; +    if((uint32_t)-1 != nMenu) +    { +        nX = nMenuX[nMenu]; +        nY += MICROPROFILE_TEXT_HEIGHT+1; +        MicroProfileSubmenuCallback CB = GroupCallback[nMenu]; +        int nNumLines = 0; +        bool bSelected = false; +        const char* pString = CB(nNumLines, &bSelected); +        uint32_t nWidth = 0, nHeight = 0; +        while(pString) +        { +            nWidth = MicroProfileMax<int>(nWidth, (int)strlen(pString)); +            nNumLines++; +            pString = CB(nNumLines, &bSelected); +        } +        nWidth = (2+nWidth) * (MICROPROFILE_TEXT_WIDTH+1); +        nHeight = nNumLines * (MICROPROFILE_TEXT_HEIGHT+1); +        if(UI.nMouseY <= nY + nHeight+0 && UI.nMouseY >= nY-0 && UI.nMouseX <= nX + nWidth + 0 && UI.nMouseX >= nX - 0) +        { +            UI.nActiveMenu = nMenu; +        } +        else if(nSelectMenu == (uint32_t)-1) +        { +            UI.nActiveMenu = (uint32_t)-1; +        } +        MicroProfileDrawBox(nX, nY, nX + nWidth, nY + nHeight, 0xff000000|g_nMicroProfileBackColors[1]); +        for(int i = 0; i < nNumLines; ++i) +        { +            bool bSelected = false; +            const char* pString = CB(i, &bSelected); +            if(UI.nMouseY >= nY && UI.nMouseY < nY + MICROPROFILE_TEXT_HEIGHT + 1) +            { +                bMouseOver = true; +                if(UI.nMouseLeft || UI.nMouseRight) +                { +                    CBClick[nMenu](i); +                } +                MicroProfileDrawBox(nX, nY, nX + nWidth, nY + MICROPROFILE_TEXT_HEIGHT + 1, 0xff888888); +            } +            int nLen = snprintf(buffer, SBUF_SIZE-1, "%c %s", bSelected ? '*' : ' ' ,pString); +            MicroProfileDrawText(nX, nY, (uint32_t)-1, buffer, nLen); +            nY += MICROPROFILE_TEXT_HEIGHT+1; +        } +    } + + +    { +        static char FrameTimeMessage[64]; +        float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +        uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +        float fMs = fToMs * (S.nFlipTicks); +        float fAverageMs = fToMs * (S.nFlipAggregateDisplay / nAggregateFrames); +        float fMaxMs = fToMs * S.nFlipMaxDisplay; +        int nLen = snprintf(FrameTimeMessage, sizeof(FrameTimeMessage)-1, "Time[%6.2f] Avg[%6.2f] Max[%6.2f]", fMs, fAverageMs, fMaxMs); +        pMenuText[nNumMenuItems++] = &FrameTimeMessage[0]; +        MicroProfileDrawText(nWidth - nLen * (MICROPROFILE_TEXT_WIDTH+1), 0, -1, FrameTimeMessage, nLen); +    } +} + + +void MicroProfileMoveGraph() +{ + +    int nZoom = UI.nMouseWheelDelta; +    int nPanX = 0; +    int nPanY = 0; +    static int X = 0, Y = 0; +    if(UI.nMouseDownLeft && !UI.nModDown) +    { +        nPanX = UI.nMouseX - X; +        nPanY = UI.nMouseY - Y; +    } +    X = UI.nMouseX; +    Y = UI.nMouseY; + +    if(nZoom) +    { +        float fOldRange = UI.fDetailedRange; +        if(nZoom>0) +        { +            UI.fDetailedRangeTarget = UI.fDetailedRange *= UI.nModDown ? 1.40f : 1.05f; +        } +        else +        { +            float fNewDetailedRange = UI.fDetailedRange / (UI.nModDown ? 1.40f : 1.05f); +            if(fNewDetailedRange < 1e-4f) //100ns +                fNewDetailedRange = 1e-4f; +            UI.fDetailedRangeTarget = UI.fDetailedRange = fNewDetailedRange; +        } + +        float fDiff = fOldRange - UI.fDetailedRange; +        float fMousePrc = MicroProfileMax((float)UI.nMouseX / UI.nWidth ,0.f); +        UI.fDetailedOffsetTarget = UI.fDetailedOffset += fDiff * fMousePrc; + +    } +    if(nPanX) +    { +        UI.fDetailedOffsetTarget = UI.fDetailedOffset += -nPanX * UI.fDetailedRange / UI.nWidth; +    } +    UI.nOffsetY -= nPanY; +    UI.nOffsetX += nPanX; +    if(UI.nOffsetX > 0) +        UI.nOffsetX = 0; +    if(UI.nOffsetY<0) +        UI.nOffsetY = 0; +} + +void MicroProfileDrawCustom(uint32_t nWidth, uint32_t nHeight) +{ +    if((uint32_t)-1 != UI.nCustomActive) +    { +        MicroProfile& S = *MicroProfileGet(); +        MP_ASSERT(UI.nCustomActive < MICROPROFILE_CUSTOM_MAX); +        MicroProfileCustom* pCustom = &UI.Custom[UI.nCustomActive]; +        uint32_t nCount = pCustom->nNumTimers; +        uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1; +        uint32_t nExtraOffset = 1 + ((pCustom->nFlags & MICROPROFILE_CUSTOM_STACK) != 0 ? 3 : 0); +        uint32_t nOffsetYBase = nHeight - (nExtraOffset+nCount)* (1+MICROPROFILE_TEXT_HEIGHT) - MICROPROFILE_CUSTOM_PADDING; +        uint32_t nOffsetY = nOffsetYBase; +        float fReference = pCustom->fReference; +        float fRcpReference = 1.f / fReference; +        uint32_t nReducedWidth = UI.nWidth - 2*MICROPROFILE_CUSTOM_PADDING - MICROPROFILE_GRAPH_WIDTH; + +        char Buffer[MICROPROFILE_NAME_MAX_LEN*2+1]; +        float* pTime = (float*)alloca(sizeof(float)*nCount); +        float* pTimeAvg = (float*)alloca(sizeof(float)*nCount); +        float* pTimeMax = (float*)alloca(sizeof(float)*nCount); +        uint32_t* pColors = (uint32_t*)alloca(sizeof(uint32_t)*nCount); +        uint32_t nMaxOffsetX = 0; +        MicroProfileDrawBox(MICROPROFILE_CUSTOM_PADDING-1, nOffsetY-1, MICROPROFILE_CUSTOM_PADDING+nReducedWidth+1, UI.nHeight - MICROPROFILE_CUSTOM_PADDING+1, 0x88000000|g_nMicroProfileBackColors[0]); + +        for(uint32_t i = 0; i < nCount; ++i) +        { +            uint16_t nTimerIndex = MicroProfileGetTimerIndex(pCustom->pTimers[i]); +            uint16_t nGroupIndex = MicroProfileGetGroupIndex(pCustom->pTimers[i]); +            float fToMs = MicroProfileTickToMsMultiplier(S.GroupInfo[nGroupIndex].Type == MicroProfileTokenTypeGpu ? MicroProfileTicksPerSecondGpu() : MicroProfileTicksPerSecondCpu()); +            pTime[i] = S.Frame[nTimerIndex].nTicks * fToMs; +            pTimeAvg[i] = fToMs * (S.Aggregate[nTimerIndex].nTicks / nAggregateFrames); +            pTimeMax[i] = fToMs * (S.AggregateMax[nTimerIndex]); +            pColors[i] = S.TimerInfo[nTimerIndex].nColor; +        } + +        MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 3*MICROPROFILE_TEXT_WIDTH, nOffsetY, (uint32_t)-1, "Avg", sizeof("Avg")-1); +        MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING + 13*MICROPROFILE_TEXT_WIDTH, nOffsetY, (uint32_t)-1, "Max", sizeof("Max")-1); +        for(uint32_t i = 0; i < nCount; ++i) +        { +            nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT); +            uint16_t nTimerIndex = MicroProfileGetTimerIndex(pCustom->pTimers[i]); +            uint16_t nGroupIndex = MicroProfileGetGroupIndex(pCustom->pTimers[i]); +            MicroProfileTimerInfo* pTimerInfo = &S.TimerInfo[nTimerIndex]; +            int nSize; +            uint32_t nOffsetX = MICROPROFILE_CUSTOM_PADDING; +            nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2f", pTimeAvg[i]); +            MicroProfileDrawText(nOffsetX, nOffsetY, (uint32_t)-1, Buffer, nSize); +            nOffsetX += (nSize+2) * (MICROPROFILE_TEXT_WIDTH+1); +            nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2f", pTimeMax[i]); +            MicroProfileDrawText(nOffsetX, nOffsetY, (uint32_t)-1, Buffer, nSize); +            nOffsetX += (nSize+2) * (MICROPROFILE_TEXT_WIDTH+1); +            nSize = snprintf(Buffer, sizeof(Buffer)-1, "%s:%s", S.GroupInfo[nGroupIndex].pName, pTimerInfo->pName); +            MicroProfileDrawText(nOffsetX, nOffsetY, pTimerInfo->nColor, Buffer, nSize); +            nOffsetX += (nSize+2) * (MICROPROFILE_TEXT_WIDTH+1); +            nMaxOffsetX = MicroProfileMax(nMaxOffsetX, nOffsetX); +        } +        uint32_t nMaxWidth = nReducedWidth- nMaxOffsetX; + +        if(pCustom->nFlags & MICROPROFILE_CUSTOM_BARS) +        { +            nOffsetY = nOffsetYBase; +            float* pMs = pCustom->nFlags & MICROPROFILE_CUSTOM_BAR_SOURCE_MAX ? pTimeMax : pTimeAvg; +            const char* pString = pCustom->nFlags & MICROPROFILE_CUSTOM_BAR_SOURCE_MAX ? "Max" : "Avg"; +            MicroProfileDrawText(nMaxOffsetX, nOffsetY, (uint32_t)-1, pString, strlen(pString)); +            int nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2fms", fReference); +            MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, (uint32_t)-1, Buffer, nSize); +            for(uint32_t i = 0; i < nCount; ++i) +            { +                nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT); +                uint32_t nWidth = MicroProfileMin(nMaxWidth, (uint32_t)(nMaxWidth * pMs[i] * fRcpReference)); +                MicroProfileDrawBox(nMaxOffsetX, nOffsetY, nMaxOffsetX+nWidth, nOffsetY+MICROPROFILE_TEXT_HEIGHT, pColors[i]|0xff000000); +            } +        } +        if(pCustom->nFlags & MICROPROFILE_CUSTOM_STACK) +        { +            nOffsetY += 2*(1+MICROPROFILE_TEXT_HEIGHT); +            const char* pString = pCustom->nFlags & MICROPROFILE_CUSTOM_STACK_SOURCE_MAX ? "Max" : "Avg"; +            MicroProfileDrawText(MICROPROFILE_CUSTOM_PADDING, nOffsetY, (uint32_t)-1, pString, strlen(pString)); +            int nSize = snprintf(Buffer, sizeof(Buffer)-1, "%6.2fms", fReference); +            MicroProfileDrawText(nReducedWidth - (1+nSize) * (MICROPROFILE_TEXT_WIDTH+1), nOffsetY, (uint32_t)-1, Buffer, nSize); +            nOffsetY += (1+MICROPROFILE_TEXT_HEIGHT); +            float fPosX = MICROPROFILE_CUSTOM_PADDING; +            float* pMs = pCustom->nFlags & MICROPROFILE_CUSTOM_STACK_SOURCE_MAX ? pTimeMax : pTimeAvg; +            for(uint32_t i = 0; i < nCount; ++i) +            { +                float fWidth = pMs[i] * fRcpReference * nReducedWidth; +                uint32_t nX = fPosX; +                fPosX += fWidth; +                uint32_t nXEnd = fPosX; +                if(nX < nXEnd) +                { +                    MicroProfileDrawBox(nX, nOffsetY, nXEnd, nOffsetY+MICROPROFILE_TEXT_HEIGHT, pColors[i]|0xff000000); +                } +            } +        } +    } +} +void MicroProfileDraw(uint32_t nWidth, uint32_t nHeight) +{ +    MICROPROFILE_SCOPE(g_MicroProfileDraw); +    MicroProfile& S = *MicroProfileGet(); + +    { +        static int once = 0; +        if(0 == once) +        { +            std::recursive_mutex& m = MicroProfileGetMutex(); +            m.lock(); +            MicroProfileInitUI(); + + + +            uint32_t nDisplay = S.nDisplay; +            MicroProfileLoadPreset(MICROPROFILE_DEFAULT_PRESET); +            once++; +            S.nDisplay = nDisplay;// dont load display, just state +            m.unlock(); + +        } +    } + + +    if(S.nDisplay) +    { +        std::recursive_mutex& m = MicroProfileGetMutex(); +        m.lock(); +        UI.nWidth = nWidth; +        UI.nHeight = nHeight; +        UI.nHoverToken = MICROPROFILE_INVALID_TOKEN; +        UI.nHoverTime = 0; +        UI.nHoverFrame = -1; +        if(S.nDisplay != MP_DRAW_DETAILED) +            S.nContextSwitchHoverThread = S.nContextSwitchHoverThreadAfter = S.nContextSwitchHoverThreadBefore = -1; +        MicroProfileMoveGraph(); + + +        if(S.nDisplay == MP_DRAW_DETAILED) +        { +            MicroProfileDrawDetailedView(nWidth, nHeight); +        } +        else if(S.nDisplay == MP_DRAW_BARS && S.nBars) +        { +            MicroProfileDrawBarView(nWidth, nHeight); +        } + +        MicroProfileDrawMenu(nWidth, nHeight); +        bool bMouseOverGraph = MicroProfileDrawGraph(nWidth, nHeight); +        MicroProfileDrawCustom(nWidth, nHeight); +        bool bHidden = S.nDisplay == MP_DRAW_HIDDEN; +        if(!bHidden) +        { +            uint32_t nLockedToolTipX = 3; +            bool bDeleted = false; +            for(int i = 0; i < MICROPROFILE_TOOLTIP_MAX_LOCKED; ++i) +            { +                int nIndex = (g_MicroProfileUI.LockedToolTipFront + i) % MICROPROFILE_TOOLTIP_MAX_LOCKED; +                if(g_MicroProfileUI.LockedToolTips[nIndex].ppStrings[0]) +                { +                    uint32_t nToolTipWidth = 0, nToolTipHeight = 0; +                    MicroProfileFloatWindowSize(g_MicroProfileUI.LockedToolTips[nIndex].ppStrings, g_MicroProfileUI.LockedToolTips[nIndex].nNumStrings, 0, nToolTipWidth, nToolTipHeight, 0); +                    uint32_t nStartY = nHeight - nToolTipHeight - 2; +                    if(!bDeleted && UI.nMouseY > nStartY && UI.nMouseX > nLockedToolTipX && UI.nMouseX <= nLockedToolTipX + nToolTipWidth && (UI.nMouseLeft || UI.nMouseRight) ) +                    { +                        bDeleted = true; +                        int j = i; +                        for(; j < MICROPROFILE_TOOLTIP_MAX_LOCKED-1; ++j) +                        { +                            int nIndex0 = (g_MicroProfileUI.LockedToolTipFront + j) % MICROPROFILE_TOOLTIP_MAX_LOCKED; +                            int nIndex1 = (g_MicroProfileUI.LockedToolTipFront + j+1) % MICROPROFILE_TOOLTIP_MAX_LOCKED; +                            MicroProfileStringArrayCopy(&g_MicroProfileUI.LockedToolTips[nIndex0], &g_MicroProfileUI.LockedToolTips[nIndex1]); +                        } +                        MicroProfileStringArrayClear(&g_MicroProfileUI.LockedToolTips[(g_MicroProfileUI.LockedToolTipFront + j) % MICROPROFILE_TOOLTIP_MAX_LOCKED]); +                    } +                    else +                    { +                        MicroProfileDrawFloatWindow(nLockedToolTipX, nHeight-nToolTipHeight-2, &g_MicroProfileUI.LockedToolTips[nIndex].ppStrings[0], g_MicroProfileUI.LockedToolTips[nIndex].nNumStrings, g_MicroProfileUI.nLockedToolTipColor[nIndex]); +                        nLockedToolTipX += nToolTipWidth + 4; +                    } +                } +            } + +            if(UI.nActiveMenu == 8) +            { +                if(S.nDisplay & MP_DRAW_DETAILED) +                { +                    MicroProfileStringArray DetailedHelp; +                    MicroProfileStringArrayClear(&DetailedHelp); +                    MicroProfileStringArrayFormat(&DetailedHelp, "%s", MICROPROFILE_HELP_LEFT); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Toggle Graph"); +                    MicroProfileStringArrayFormat(&DetailedHelp, "%s", MICROPROFILE_HELP_ALT); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Zoom"); +                    MicroProfileStringArrayFormat(&DetailedHelp, "%s + %s", MICROPROFILE_HELP_MOD, MICROPROFILE_HELP_LEFT); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Lock Tooltip"); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Drag"); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Pan View"); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Mouse Wheel"); +                    MicroProfileStringArrayAddLiteral(&DetailedHelp, "Zoom"); +                    MicroProfileDrawFloatWindow(nWidth, MICROPROFILE_FRAME_HISTORY_HEIGHT+20, DetailedHelp.ppStrings, DetailedHelp.nNumStrings, 0xff777777); + +                    MicroProfileStringArray DetailedHistoryHelp; +                    MicroProfileStringArrayClear(&DetailedHistoryHelp); +                    MicroProfileStringArrayFormat(&DetailedHistoryHelp, "%s", MICROPROFILE_HELP_LEFT); +                    MicroProfileStringArrayAddLiteral(&DetailedHistoryHelp, "Center View"); +                    MicroProfileStringArrayFormat(&DetailedHistoryHelp, "%s", MICROPROFILE_HELP_ALT); +                    MicroProfileStringArrayAddLiteral(&DetailedHistoryHelp, "Zoom to frame"); +                    MicroProfileDrawFloatWindow(nWidth, 20, DetailedHistoryHelp.ppStrings, DetailedHistoryHelp.nNumStrings, 0xff777777); + + + +                } +                else if(0 != (S.nDisplay & MP_DRAW_BARS) && S.nBars) +                { +                    MicroProfileStringArray BarHelp; +                    MicroProfileStringArrayClear(&BarHelp); +                    MicroProfileStringArrayFormat(&BarHelp, "%s", MICROPROFILE_HELP_LEFT); +                    MicroProfileStringArrayAddLiteral(&BarHelp, "Toggle Graph"); +                    MicroProfileStringArrayFormat(&BarHelp, "%s + %s", MICROPROFILE_HELP_MOD, MICROPROFILE_HELP_LEFT); +                    MicroProfileStringArrayAddLiteral(&BarHelp, "Lock Tooltip"); +                    MicroProfileStringArrayAddLiteral(&BarHelp, "Drag"); +                    MicroProfileStringArrayAddLiteral(&BarHelp, "Pan View"); +                    MicroProfileDrawFloatWindow(nWidth, MICROPROFILE_FRAME_HISTORY_HEIGHT+20, BarHelp.ppStrings, BarHelp.nNumStrings, 0xff777777); + +                } +                MicroProfileStringArray Debug; +                MicroProfileStringArrayClear(&Debug); +                MicroProfileStringArrayAddLiteral(&Debug, "Memory Usage"); +                MicroProfileStringArrayFormat(&Debug, "%4.2fmb", S.nMemUsage / (1024.f * 1024.f)); +                MicroProfileStringArrayAddLiteral(&Debug, "Web Server Port"); +                MicroProfileStringArrayFormat(&Debug, "%d", MicroProfileWebServerPort()); +                uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY; +                MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent]; +                MicroProfileFrameState* pFrameNext = &S.Frames[nFrameNext]; + + +                MicroProfileStringArrayAddLiteral(&Debug, ""); +                MicroProfileStringArrayAddLiteral(&Debug, ""); +                MicroProfileStringArrayAddLiteral(&Debug, "Usage"); +                MicroProfileStringArrayAddLiteral(&Debug, "markers [frames] "); + +#if MICROPROFILE_CONTEXT_SWITCH_TRACE +                MicroProfileStringArrayAddLiteral(&Debug, "Context Switch"); +                MicroProfileStringArrayFormat(&Debug, "%9d [%7d]", S.nContextSwitchUsage, MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE / S.nContextSwitchUsage ); +#endif + +                for(int i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +                { +                    if(pFrameCurrent->nLogStart[i] && S.Pool[i]) +                    { +                        uint32_t nEnd = pFrameNext->nLogStart[i]; +                        uint32_t nStart = pFrameCurrent->nLogStart[i]; +                        uint32_t nUsage = nStart < nEnd ? (nEnd - nStart) : (nEnd + MICROPROFILE_BUFFER_SIZE - nStart); +                        uint32_t nFrameSupport = MICROPROFILE_BUFFER_SIZE / nUsage; +                        MicroProfileStringArrayFormat(&Debug, "%s", &S.Pool[i]->ThreadName[0]); +                        MicroProfileStringArrayFormat(&Debug, "%9d [%7d]", nUsage, nFrameSupport); +                    } +                } + +                MicroProfileDrawFloatWindow(0, nHeight-10, Debug.ppStrings, Debug.nNumStrings, 0xff777777); +            } + + + +            if(UI.nActiveMenu == -1 && !bMouseOverGraph) +            { +                if(UI.nHoverToken != MICROPROFILE_INVALID_TOKEN) +                { +                    MicroProfileDrawFloatTooltip(UI.nMouseX, UI.nMouseY, UI.nHoverToken, UI.nHoverTime); +                } +                else if(S.nContextSwitchHoverThreadAfter != -1 && S.nContextSwitchHoverThreadBefore != -1) +                { +                    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +                    MicroProfileStringArray ToolTip; +                    MicroProfileStringArrayClear(&ToolTip); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "Context Switch"); +                    MicroProfileStringArrayFormat(&ToolTip, "%04x", S.nContextSwitchHoverThread); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "Before"); +                    MicroProfileStringArrayFormat(&ToolTip, "%04x", S.nContextSwitchHoverThreadBefore); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "After"); +                    MicroProfileStringArrayFormat(&ToolTip, "%04x", S.nContextSwitchHoverThreadAfter); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "Duration"); +                    int64_t nDifference = MicroProfileLogTickDifference(S.nContextSwitchHoverTickIn, S.nContextSwitchHoverTickOut); +                    MicroProfileStringArrayFormat(&ToolTip, "%6.2fms", fToMs * nDifference ); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "CPU"); +                    MicroProfileStringArrayFormat(&ToolTip, "%d", S.nContextSwitchHoverCpu); +                    MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, -1); + + +                } +                else if(UI.nHoverFrame != -1) +                { +                    uint32_t nNextFrame = (UI.nHoverFrame+1)%MICROPROFILE_MAX_FRAME_HISTORY; +                    int64_t nTick = S.Frames[UI.nHoverFrame].nFrameStartCpu; +                    int64_t nTickNext = S.Frames[nNextFrame].nFrameStartCpu; +                    int64_t nTickGpu = S.Frames[UI.nHoverFrame].nFrameStartGpu; +                    int64_t nTickNextGpu = S.Frames[nNextFrame].nFrameStartGpu; + +                    float fToMs = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu()); +                    float fToMsGpu = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondGpu()); +                    float fMs = fToMs * (nTickNext - nTick); +                    float fMsGpu = fToMsGpu * (nTickNextGpu - nTickGpu); +                    MicroProfileStringArray ToolTip; +                    MicroProfileStringArrayClear(&ToolTip); +                    MicroProfileStringArrayFormat(&ToolTip, "Frame %d", UI.nHoverFrame); +    #if MICROPROFILE_DEBUG +                    MicroProfileStringArrayFormat(&ToolTip, "%p", &S.Frames[UI.nHoverFrame]); +    #else +                    MicroProfileStringArrayAddLiteral(&ToolTip, ""); +    #endif +                    MicroProfileStringArrayAddLiteral(&ToolTip, "CPU Time"); +                    MicroProfileStringArrayFormat(&ToolTip, "%6.2fms", fMs); +                    MicroProfileStringArrayAddLiteral(&ToolTip, "GPU Time"); +                    MicroProfileStringArrayFormat(&ToolTip, "%6.2fms", fMsGpu); +                    #if MICROPROFILE_DEBUG +                    for(int i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +                    { +                        if(S.Frames[UI.nHoverFrame].nLogStart[i]) +                        { +                            MicroProfileStringArrayFormat(&ToolTip, "%d", i); +                            MicroProfileStringArrayFormat(&ToolTip, "%d", S.Frames[UI.nHoverFrame].nLogStart[i]); +                        } +                    } +                    #endif +                    MicroProfileDrawFloatWindow(UI.nMouseX, UI.nMouseY+20, &ToolTip.ppStrings[0], ToolTip.nNumStrings, -1); +                } +                if(UI.nMouseLeft) +                { +                    if(UI.nHoverToken != MICROPROFILE_INVALID_TOKEN) +                        MicroProfileToggleGraph(UI.nHoverToken); +                } +            } +        } + +#if MICROPROFILE_DRAWCURSOR +        { +            float fCursor[8] = +            { +                MicroProfileMax(0, (int)UI.nMouseX-3), UI.nMouseY, +                MicroProfileMin(nWidth, UI.nMouseX+3), UI.nMouseY, +                UI.nMouseX, MicroProfileMax((int)UI.nMouseY-3, 0), +                UI.nMouseX, MicroProfileMin(nHeight, UI.nMouseY+3), +            }; +            MicroProfileDrawLine2D(2, &fCursor[0], 0xff00ff00); +            MicroProfileDrawLine2D(2, &fCursor[4], 0xff00ff00); +        } +#endif +        m.unlock(); +    } +    else if(UI.nCustomActive != (uint32_t)-1) +    { +        std::recursive_mutex& m = MicroProfileGetMutex(); +        m.lock(); +        MicroProfileDrawGraph(nWidth, nHeight); +        MicroProfileDrawCustom(nWidth, nHeight); +        m.unlock(); + +    } +    UI.nMouseLeft = UI.nMouseRight = 0; +    UI.nMouseLeftMod = UI.nMouseRightMod = 0; +    UI.nMouseWheelDelta = 0; +    if(S.nOverflow) +        S.nOverflow--; + +    UI.fDetailedOffset = UI.fDetailedOffset + (UI.fDetailedOffsetTarget - UI.fDetailedOffset) * MICROPROFILE_ANIM_DELAY_PRC; +    UI.fDetailedRange = UI.fDetailedRange + (UI.fDetailedRangeTarget - UI.fDetailedRange) * MICROPROFILE_ANIM_DELAY_PRC; + + +} + +bool MicroProfileIsDrawing() +{ +    MicroProfile& S = *MicroProfileGet(); +    return S.nDisplay != 0; +} + +void MicroProfileToggleGraph(MicroProfileToken nToken) +{ +    MicroProfile& S = *MicroProfileGet(); +    uint32_t nTimerId = MicroProfileGetTimerIndex(nToken); +    nToken &= 0xffff; +    int32_t nMinSort = 0x7fffffff; +    int32_t nFreeIndex = -1; +    int32_t nMinIndex = 0; +    int32_t nMaxSort = 0x80000000; +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +    { +        if(S.Graph[i].nToken == MICROPROFILE_INVALID_TOKEN) +            nFreeIndex = i; +        if(S.Graph[i].nToken == nToken) +        { +            S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +            S.TimerInfo[nTimerId].bGraph = false; +            return; +        } +        if(S.Graph[i].nKey < nMinSort) +        { +            nMinSort = S.Graph[i].nKey; +            nMinIndex = i; +        } +        if(S.Graph[i].nKey > nMaxSort) +        { +            nMaxSort = S.Graph[i].nKey; +        } +    } +    int nIndex = nFreeIndex > -1 ? nFreeIndex : nMinIndex; +    if (nFreeIndex == -1) +    { +        uint32_t idx = MicroProfileGetTimerIndex(S.Graph[nIndex].nToken); +        S.TimerInfo[idx].bGraph = false; +    } +    S.Graph[nIndex].nToken = nToken; +    S.Graph[nIndex].nKey = nMaxSort+1; +    memset(&S.Graph[nIndex].nHistory[0], 0, sizeof(S.Graph[nIndex].nHistory)); +    S.TimerInfo[nTimerId].bGraph = true; +} + + +void MicroProfileMousePosition(uint32_t nX, uint32_t nY, int nWheelDelta) +{ +    UI.nMouseX = nX; +    UI.nMouseY = nY; +    UI.nMouseWheelDelta = nWheelDelta; +} + +void MicroProfileModKey(uint32_t nKeyState) +{ +    UI.nModDown = nKeyState ? 1 : 0; +} + +void MicroProfileClearGraph() +{ +    MicroProfile& S = *MicroProfileGet(); +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +    { +        if(S.Graph[i].nToken != 0) +        { +            S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +        } +    } +} + +void MicroProfileMouseButton(uint32_t nLeft, uint32_t nRight) +{ +    bool bCanRelease = abs((int)(UI.nMouseDownX - UI.nMouseX)) + abs((int)(UI.nMouseDownY - UI.nMouseY)) < 3; + +    if(0 == nLeft && UI.nMouseDownLeft && bCanRelease) +    { +        if(UI.nModDown) +            UI.nMouseLeftMod = 1; +        else +            UI.nMouseLeft = 1; +    } + +    if(0 == nRight && UI.nMouseDownRight && bCanRelease) +    { +        if(UI.nModDown) +            UI.nMouseRightMod = 1; +        else +            UI.nMouseRight = 1; +    } +    if((nLeft || nRight) && !(UI.nMouseDownLeft || UI.nMouseDownRight)) +    { +        UI.nMouseDownX = UI.nMouseX; +        UI.nMouseDownY = UI.nMouseY; +    } + +    UI.nMouseDownLeft = nLeft; +    UI.nMouseDownRight = nRight; + +} + +void MicroProfileDrawLineVertical(int nX, int nTop, int nBottom, uint32_t nColor) +{ +    MicroProfileDrawBox(nX, nTop, nX + 1, nBottom, nColor); +} + +void MicroProfileDrawLineHorizontal(int nLeft, int nRight, int nY, uint32_t nColor) +{ +    MicroProfileDrawBox(nLeft, nY, nRight, nY + 1, nColor); +} + + + +#include <stdio.h> + +#define MICROPROFILE_PRESET_HEADER_MAGIC 0x28586813 +#define MICROPROFILE_PRESET_HEADER_VERSION 0x00000102 +struct MicroProfilePresetHeader +{ +    uint32_t nMagic; +    uint32_t nVersion; +    //groups, threads, aggregate, reference frame, graphs timers +    uint32_t nGroups[MICROPROFILE_MAX_GROUPS]; +    uint32_t nThreads[MICROPROFILE_MAX_THREADS]; +    uint32_t nGraphName[MICROPROFILE_MAX_GRAPHS]; +    uint32_t nGraphGroupName[MICROPROFILE_MAX_GRAPHS]; +    uint32_t nAllGroupsWanted; +    uint32_t nAllThreadsWanted; +    uint32_t nAggregateFlip; +    float fReferenceTime; +    uint32_t nBars; +    uint32_t nDisplay; +    uint32_t nOpacityBackground; +    uint32_t nOpacityForeground; +    uint32_t nShowSpikes; +}; + +#ifndef MICROPROFILE_PRESET_FILENAME_FUNC +#define MICROPROFILE_PRESET_FILENAME_FUNC MicroProfilePresetFilename +static const char* MicroProfilePresetFilename(const char* pSuffix) +{ +    static char filename[512]; +    snprintf(filename, sizeof(filename)-1, ".microprofilepreset.%s", pSuffix); +    return filename; +} +#endif + +void MicroProfileSavePreset(const char* pPresetName) +{ +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileGetMutex()); +    FILE* F = fopen(MICROPROFILE_PRESET_FILENAME_FUNC(pPresetName), "wb"); +    if(!F) return; + +    MicroProfile& S = *MicroProfileGet(); + +    MicroProfilePresetHeader Header; +    memset(&Header, 0, sizeof(Header)); +    Header.nAggregateFlip = S.nAggregateFlip; +    Header.nBars = S.nBars; +    Header.fReferenceTime = S.fReferenceTime; +    Header.nAllGroupsWanted = S.nAllGroupsWanted; +    Header.nAllThreadsWanted = S.nAllThreadsWanted; +    Header.nMagic = MICROPROFILE_PRESET_HEADER_MAGIC; +    Header.nVersion = MICROPROFILE_PRESET_HEADER_VERSION; +    Header.nDisplay = S.nDisplay; +    Header.nOpacityBackground = UI.nOpacityBackground; +    Header.nOpacityForeground = UI.nOpacityForeground; +    Header.nShowSpikes = UI.bShowSpikes ? 1 : 0; +    fwrite(&Header, sizeof(Header), 1, F); +    uint64_t nMask = 1; +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +    { +        if(S.nActiveGroupWanted & nMask) +        { +            uint32_t offset = ftell(F); +            const char* pName = S.GroupInfo[i].pName; +            int nLen = (int)strlen(pName)+1; +            fwrite(pName, nLen, 1, F); +            Header.nGroups[i] = offset; +        } +        nMask <<= 1; +    } +    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +    { +        MicroProfileThreadLog* pLog = S.Pool[i]; +        if(pLog && S.nThreadActive[i]) +        { +            uint32_t nOffset = ftell(F); +            const char* pName = &pLog->ThreadName[0]; +            int nLen = (int)strlen(pName)+1; +            fwrite(pName, nLen, 1, F); +            Header.nThreads[i] = nOffset; +        } +    } +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +    { +        MicroProfileToken nToken = S.Graph[i].nToken; +        if(nToken != MICROPROFILE_INVALID_TOKEN) +        { +            uint32_t nGroupIndex = MicroProfileGetGroupIndex(nToken); +            uint32_t nTimerIndex = MicroProfileGetTimerIndex(nToken); +            const char* pGroupName = S.GroupInfo[nGroupIndex].pName; +            const char* pTimerName = S.TimerInfo[nTimerIndex].pName; +            MP_ASSERT(pGroupName); +            MP_ASSERT(pTimerName); +            int nGroupLen = (int)strlen(pGroupName)+1; +            int nTimerLen = (int)strlen(pTimerName)+1; + +            uint32_t nOffsetGroup = ftell(F); +            fwrite(pGroupName, nGroupLen, 1, F); +            uint32_t nOffsetTimer = ftell(F); +            fwrite(pTimerName, nTimerLen, 1, F); +            Header.nGraphName[i] = nOffsetTimer; +            Header.nGraphGroupName[i] = nOffsetGroup; +        } +    } +    fseek(F, 0, SEEK_SET); +    fwrite(&Header, sizeof(Header), 1, F); + +    fclose(F); + +} + + + +void MicroProfileLoadPreset(const char* pSuffix) +{ +    std::lock_guard<std::recursive_mutex> Lock(MicroProfileGetMutex()); +    FILE* F = fopen(MICROPROFILE_PRESET_FILENAME_FUNC(pSuffix), "rb"); +    if(!F) +    { +        return; +    } +    fseek(F, 0, SEEK_END); +    int nSize = ftell(F); +    char* const pBuffer = (char*)alloca(nSize); +    fseek(F, 0, SEEK_SET); +    int nRead = (int)fread(pBuffer, nSize, 1, F); +    fclose(F); +    if(1 != nRead) +        return; + +    MicroProfile& S = *MicroProfileGet(); + +    MicroProfilePresetHeader& Header = *(MicroProfilePresetHeader*)pBuffer; + +    if(Header.nMagic != MICROPROFILE_PRESET_HEADER_MAGIC || Header.nVersion != MICROPROFILE_PRESET_HEADER_VERSION) +    { +        return; +    } + +    S.nAggregateFlip = Header.nAggregateFlip; +    S.nBars = Header.nBars; +    S.fReferenceTime = Header.fReferenceTime; +    S.fRcpReferenceTime = 1.f / Header.fReferenceTime; +    S.nAllGroupsWanted = Header.nAllGroupsWanted; +    S.nAllThreadsWanted = Header.nAllThreadsWanted; +    S.nDisplay = Header.nDisplay; +    S.nActiveGroupWanted = 0; +    UI.nOpacityBackground = Header.nOpacityBackground; +    UI.nOpacityForeground = Header.nOpacityForeground; +    UI.bShowSpikes = Header.nShowSpikes == 1; + +    memset(&S.nThreadActive[0], 0, sizeof(S.nThreadActive)); + +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) +    { +        if(Header.nGroups[i]) +        { +            const char* pGroupName = pBuffer + Header.nGroups[i]; +            for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) +            { +                if(0 == MP_STRCASECMP(pGroupName, S.GroupInfo[j].pName)) +                { +                    S.nActiveGroupWanted |= (1ll << j); +                } +            } +        } +    } +    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i) +    { +        if(Header.nThreads[i]) +        { +            const char* pThreadName = pBuffer + Header.nThreads[i]; +            for(uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j) +            { +                MicroProfileThreadLog* pLog = S.Pool[j]; +                if(pLog && 0 == MP_STRCASECMP(pThreadName, &pLog->ThreadName[0])) +                { +                    S.nThreadActive[j] = 1; +                } +            } +        } +    } +    for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +    { +        MicroProfileToken nPrevToken = S.Graph[i].nToken; +        S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +        if(Header.nGraphName[i] && Header.nGraphGroupName[i]) +        { +            const char* pGraphName = pBuffer + Header.nGraphName[i]; +            const char* pGraphGroupName = pBuffer + Header.nGraphGroupName[i]; +            for(uint32_t j = 0; j < S.nTotalTimers; ++j) +            { +                uint64_t nGroupIndex = S.TimerInfo[j].nGroupIndex; +                if(0 == MP_STRCASECMP(pGraphName, S.TimerInfo[j].pName) && 0 == MP_STRCASECMP(pGraphGroupName, S.GroupInfo[nGroupIndex].pName)) +                { +                    MicroProfileToken nToken = MicroProfileMakeToken(1ll << nGroupIndex, (uint16_t)j); +                    S.Graph[i].nToken = nToken;         // note: group index is stored here but is checked without in MicroProfileToggleGraph()! +                    S.TimerInfo[j].bGraph = true; +                    if(nToken != nPrevToken) +                    { +                        memset(&S.Graph[i].nHistory, 0, sizeof(S.Graph[i].nHistory)); +                    } +                    break; +                } +            } +        } +    } +} + +uint32_t MicroProfileCustomGroupFind(const char* pCustomName) +{ +    for(uint32_t i = 0; i < UI.nCustomCount; ++i) +    { +        if(!MP_STRCASECMP(pCustomName, UI.Custom[i].pName)) +        { +            return i; +        } +    } +    return (uint32_t)-1; +} + +uint32_t MicroProfileCustomGroup(const char* pCustomName) +{ +    for(uint32_t i = 0; i < UI.nCustomCount; ++i) +    { +        if(!MP_STRCASECMP(pCustomName, UI.Custom[i].pName)) +        { +            return i; +        } +    } +    MP_ASSERT(UI.nCustomCount < MICROPROFILE_CUSTOM_MAX); +    uint32_t nIndex = UI.nCustomCount; +    UI.nCustomCount++; +    memset(&UI.Custom[nIndex], 0, sizeof(UI.Custom[nIndex])); +    uint32_t nLen = (uint32_t)strlen(pCustomName); +    if(nLen > MICROPROFILE_NAME_MAX_LEN-1) +        nLen = MICROPROFILE_NAME_MAX_LEN-1; +    memcpy(&UI.Custom[nIndex].pName[0], pCustomName, nLen); +    UI.Custom[nIndex].pName[nLen] = '\0'; +    return nIndex; +} +void MicroProfileCustomGroup(const char* pCustomName, uint32_t nMaxTimers, uint32_t nAggregateFlip, float fReferenceTime, uint32_t nFlags) +{ +    uint32_t nIndex = MicroProfileCustomGroup(pCustomName); +    MP_ASSERT(UI.Custom[nIndex].pTimers == 0);//only call once! +    UI.Custom[nIndex].pTimers = &UI.CustomTimer[UI.nCustomTimerCount]; +    UI.Custom[nIndex].nMaxTimers = nMaxTimers; +    UI.Custom[nIndex].fReference = fReferenceTime; +    UI.nCustomTimerCount += nMaxTimers; +    MP_ASSERT(UI.nCustomTimerCount <= MICROPROFILE_CUSTOM_MAX_TIMERS); //bump MICROPROFILE_CUSTOM_MAX_TIMERS +    UI.Custom[nIndex].nFlags = nFlags; +    UI.Custom[nIndex].nAggregateFlip = nAggregateFlip; +} + +void MicroProfileCustomGroupEnable(uint32_t nIndex) +{ +    if(nIndex < UI.nCustomCount) +    { +        MicroProfile& S = *MicroProfileGet(); +        S.nForceGroupUI = UI.Custom[nIndex].nGroupMask; +        MicroProfileSetAggregateFrames(UI.Custom[nIndex].nAggregateFlip); +        S.fReferenceTime = UI.Custom[nIndex].fReference; +        S.fRcpReferenceTime = 1.f / UI.Custom[nIndex].fReference; +        UI.nCustomActive = nIndex; + +        for(uint32_t i = 0; i < MICROPROFILE_MAX_GRAPHS; ++i) +        { +            if(S.Graph[i].nToken != MICROPROFILE_INVALID_TOKEN) +            { +                uint32_t nTimerId = MicroProfileGetTimerIndex(S.Graph[i].nToken); +                S.TimerInfo[nTimerId].bGraph = false; +                S.Graph[i].nToken = MICROPROFILE_INVALID_TOKEN; +            } +        } + +        for(uint32_t i = 0; i < UI.Custom[nIndex].nNumTimers; ++i) +        { +            if(i == MICROPROFILE_MAX_GRAPHS) +            { +                break; +            } +            S.Graph[i].nToken = UI.Custom[nIndex].pTimers[i]; +            S.Graph[i].nKey = i; +            uint32_t nTimerId = MicroProfileGetTimerIndex(S.Graph[i].nToken); +            S.TimerInfo[nTimerId].bGraph = true; +        } +    } +} + +void MicroProfileCustomGroupToggle(const char* pCustomName) +{ +    uint32_t nIndex = MicroProfileCustomGroupFind(pCustomName); +    if(nIndex == (uint32_t)-1 || nIndex == UI.nCustomActive) +    { +        MicroProfileCustomGroupDisable(); +    } +    else +    { +        MicroProfileCustomGroupEnable(nIndex); +    } +} + +void MicroProfileCustomGroupEnable(const char* pCustomName) +{ +    uint32_t nIndex = MicroProfileCustomGroupFind(pCustomName); +    MicroProfileCustomGroupEnable(nIndex); +} +void MicroProfileCustomGroupDisable() +{ +    MicroProfile& S = *MicroProfileGet(); +    S.nForceGroupUI = 0; +    UI.nCustomActive = (uint32_t)-1; +} + +void MicroProfileCustomGroupAddTimer(const char* pCustomName, const char* pGroup, const char* pTimer) +{ +    uint32_t nIndex = MicroProfileCustomGroupFind(pCustomName); +    if((uint32_t)-1 == nIndex) +    { +        return; +    } +    uint32_t nTimerIndex = UI.Custom[nIndex].nNumTimers; +    MP_ASSERT(nTimerIndex < UI.Custom[nIndex].nMaxTimers); +    uint64_t nToken = MicroProfileFindToken(pGroup, pTimer); +    MP_ASSERT(nToken != MICROPROFILE_INVALID_TOKEN); //Timer must be registered first. +    UI.Custom[nIndex].pTimers[nTimerIndex] = nToken; +    uint16_t nGroup = MicroProfileGetGroupIndex(nToken); +    UI.Custom[nIndex].nGroupMask |= (1ll << nGroup); +    UI.Custom[nIndex].nNumTimers++; +} + +#undef UI + +#endif +#endif diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index d6fcb66a5..46f4a07c9 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -6,6 +6,9 @@  #include <thread>  #include <iostream> +// This needs to be included before getopt.h because the latter #defines symbols used by it +#include "common/microprofile.h" +  #ifdef _MSC_VER  #include <getopt.h>  #else @@ -59,6 +62,8 @@ int main(int argc, char **argv) {      Log::Filter log_filter(Log::Level::Debug);      Log::SetFilter(&log_filter); +    MicroProfileOnThreadCreate("EmuThread"); +      if (boot_filename.empty()) {          LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");          return -1; @@ -89,5 +94,7 @@ int main(int argc, char **argv) {      delete emu_window; +    MicroProfileShutdown(); +      return 0;  } diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index a96fbea5f..f8aacb527 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -14,6 +14,7 @@  #include "common/string_util.h"  #include "common/scm_rev.h"  #include "common/key_map.h" +#include "common/microprofile.h"  #include "core/core.h"  #include "core/settings.h" @@ -37,6 +38,8 @@ EmuThread::EmuThread(GRenderWindow* render_window) :  void EmuThread::run() {      render_window->MakeCurrent(); +    MicroProfileOnThreadCreate("EmuThread"); +      stop_run = false;      // holds whether the cpu was running during the last iteration, @@ -69,6 +72,8 @@ void EmuThread::run() {          }      } +    MicroProfileOnThreadExit(); +      render_window->moveContext();  } diff --git a/src/citra_qt/debugger/profiler.cpp b/src/citra_qt/debugger/profiler.cpp index 89b28c2f4..5261d4836 100644 --- a/src/citra_qt/debugger/profiler.cpp +++ b/src/citra_qt/debugger/profiler.cpp @@ -2,9 +2,21 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <QMouseEvent> +#include <QPainter> +#include <QString> +  #include "profiler.h" +#include "citra_qt/util/util.h" +  #include "common/profiler_reporting.h" +#include "common/microprofile.h" + +// Include the implementation of the UI in this file. This isn't in microprofile.cpp because the +// non-Qt frontends don't need it (and don't implement the UI drawing hooks either). +#define MICROPROFILEUI_IMPL 1 +#include "common/microprofileui.h"  using namespace Common::Profiling; @@ -136,3 +148,193 @@ void ProfilerWidget::setProfilingInfoUpdateEnabled(bool enable)          update_timer.stop();      }  } + +class MicroProfileWidget : public QWidget { +public: +    MicroProfileWidget(QWidget* parent = 0); + +protected: +    void paintEvent(QPaintEvent* ev) override; +    void showEvent(QShowEvent* ev) override; +    void hideEvent(QHideEvent* ev) override; + +    void mouseMoveEvent(QMouseEvent* ev) override; +    void mousePressEvent(QMouseEvent* ev) override; +    void mouseReleaseEvent(QMouseEvent* ev) override; +    void wheelEvent(QWheelEvent* ev) override; + +    void keyPressEvent(QKeyEvent* ev) override; +    void keyReleaseEvent(QKeyEvent* ev) override; + +private: +    /// This timer is used to redraw the widget's contents continuously. To save resources, it only +    /// runs while the widget is visible. +    QTimer update_timer; +}; + +MicroProfileDialog::MicroProfileDialog(QWidget* parent) +    : QWidget(parent, Qt::Dialog) +{ +    setObjectName("MicroProfile"); +    setWindowTitle(tr("MicroProfile")); +    resize(1000, 600); +    // Remove the "?" button from the titlebar and enable the maximize button +    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint | Qt::WindowMaximizeButtonHint); + +    MicroProfileWidget* widget = new MicroProfileWidget(this); + +    QLayout* layout = new QVBoxLayout(this); +    layout->setContentsMargins(0, 0, 0, 0); +    layout->addWidget(widget); +    setLayout(layout); + +    // Configure focus so that widget is focusable and the dialog automatically forwards focus to it. +    setFocusProxy(widget); +    widget->setFocusPolicy(Qt::StrongFocus); +    widget->setFocus(); +} + +QAction* MicroProfileDialog::toggleViewAction() { +    if (toggle_view_action == nullptr) { +        toggle_view_action = new QAction(windowTitle(), this); +        toggle_view_action->setCheckable(true); +        toggle_view_action->setChecked(isVisible()); +        connect(toggle_view_action, SIGNAL(toggled(bool)), SLOT(setVisible(bool))); +    } + +    return toggle_view_action; +} + +void MicroProfileDialog::showEvent(QShowEvent* ev) { +    if (toggle_view_action) { +        toggle_view_action->setChecked(isVisible()); +    } +    QWidget::showEvent(ev); +} + +void MicroProfileDialog::hideEvent(QHideEvent* ev) { +    if (toggle_view_action) { +        toggle_view_action->setChecked(isVisible()); +    } +    QWidget::hideEvent(ev); +} + +/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the +/// QPainter available inside the drawing callbacks. +static QPainter* mp_painter = nullptr; + +MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { +    // Send mouse motion events even when not dragging. +    setMouseTracking(true); + +    MicroProfileSetDisplayMode(1); // Timers screen +    MicroProfileInitUI(); + +    connect(&update_timer, SIGNAL(timeout()), SLOT(update())); +} + +void MicroProfileWidget::paintEvent(QPaintEvent* ev) { +    QPainter painter(this); + +    painter.setBackground(Qt::black); +    painter.eraseRect(rect()); + +    QFont font = GetMonospaceFont(); +    font.setPixelSize(MICROPROFILE_TEXT_HEIGHT); +    painter.setFont(font); + +    mp_painter = &painter; +    MicroProfileDraw(rect().width(), rect().height()); +    mp_painter = nullptr; +} + +void MicroProfileWidget::showEvent(QShowEvent* ev) { +    update_timer.start(15); // ~60 Hz +    QWidget::showEvent(ev); +} + +void MicroProfileWidget::hideEvent(QHideEvent* ev) { +    update_timer.stop(); +    QWidget::hideEvent(ev); +} + +void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { +    MicroProfileMousePosition(ev->x(), ev->y(), 0); +    ev->accept(); +} + +void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { +    MicroProfileMousePosition(ev->x(), ev->y(), 0); +    MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); +    ev->accept(); +} + +void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { +    MicroProfileMousePosition(ev->x(), ev->y(), 0); +    MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); +    ev->accept(); +} + +void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { +    MicroProfileMousePosition(ev->x(), ev->y(), ev->delta() / 120); +    ev->accept(); +} + +void MicroProfileWidget::keyPressEvent(QKeyEvent* ev) { +    if (ev->key() == Qt::Key_Control) { +        // Inform MicroProfile that the user is holding Ctrl. +        MicroProfileModKey(1); +    } +    QWidget::keyPressEvent(ev); +} + +void MicroProfileWidget::keyReleaseEvent(QKeyEvent* ev) { +    if (ev->key() == Qt::Key_Control) { +        MicroProfileModKey(0); +    } +    QWidget::keyReleaseEvent(ev); +} + +// These functions are called by MicroProfileDraw to draw the interface elements on the screen. + +void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 text_length) { +    // hex_color does not include an alpha, so it must be assumed to be 255 +    mp_painter->setPen(QColor::fromRgb(hex_color)); + +    // It's impossible to draw a string using a monospaced font with a fixed width per cell in a +    // way that's reliable across different platforms and fonts as far as I (yuriks) can tell, so +    // draw each character individually in order to precisely control the text advance. +    for (u32 i = 0; i < text_length; ++i) { +        // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice +        // vertical alignment of text for a wide range of tested fonts. +        mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QChar(text[i])); +        x += MICROPROFILE_TEXT_WIDTH + 1; +    } +} + +void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color, MicroProfileBoxType type) { +    QColor color = QColor::fromRgba(hex_color); +    QBrush brush = color; +    if (type == MicroProfileBoxTypeBar) { +        QLinearGradient gradient(left, top, left, bottom); +        gradient.setColorAt(0.f, color.lighter(125)); +        gradient.setColorAt(1.f, color.darker(125)); +        brush = gradient; +    } +    mp_painter->fillRect(left, top, right - left, bottom - top, brush); +} + +void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) { +    // Temporary vector used to convert between the float array and QPointF. Marked static to reuse +    // the allocation across calls. +    static std::vector<QPointF> point_buf; + +    for (u32 i = 0; i < vertices_length; ++i) { +        point_buf.emplace_back(vertices[i*2 + 0], vertices[i*2 + 1]); +    } + +    // hex_color does not include an alpha, so it must be assumed to be 255 +    mp_painter->setPen(QColor::fromRgb(hex_color)); +    mp_painter->drawPolyline(point_buf.data(), vertices_length); +    point_buf.clear(); +} diff --git a/src/citra_qt/debugger/profiler.h b/src/citra_qt/debugger/profiler.h index fabf279b8..2199eaef1 100644 --- a/src/citra_qt/debugger/profiler.h +++ b/src/citra_qt/debugger/profiler.h @@ -48,3 +48,20 @@ private:      QTimer update_timer;  }; + +class MicroProfileDialog : public QWidget { +    Q_OBJECT + +public: +    MicroProfileDialog(QWidget* parent = 0); + +    /// Returns a QAction that can be used to toggle visibility of this dialog. +    QAction* toggleViewAction(); + +protected: +    void showEvent(QShowEvent* ev) override; +    void hideEvent(QHideEvent* ev) override; + +private: +    QAction* toggle_view_action = nullptr; +}; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 8bf2a3e13..7fb1b0dcb 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -17,6 +17,7 @@  #include "common/logging/backend.h"  #include "common/logging/filter.h"  #include "common/make_unique.h" +#include "common/microprofile.h"  #include "common/platform.h"  #include "common/scm_rev.h"  #include "common/scope_exit.h" @@ -64,6 +65,9 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)      addDockWidget(Qt::BottomDockWidgetArea, profilerWidget);      profilerWidget->hide(); +    microProfileDialog = new MicroProfileDialog(this); +    microProfileDialog->hide(); +      disasmWidget = new DisassemblerWidget(this, emu_thread.get());      addDockWidget(Qt::BottomDockWidgetArea, disasmWidget);      disasmWidget->hide(); @@ -102,6 +106,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)      QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));      debug_menu->addAction(profilerWidget->toggleViewAction()); +    debug_menu->addAction(microProfileDialog->toggleViewAction());      debug_menu->addAction(disasmWidget->toggleViewAction());      debug_menu->addAction(registersWidget->toggleViewAction());      debug_menu->addAction(callstackWidget->toggleViewAction()); @@ -128,6 +133,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)      restoreGeometry(settings.value("geometry").toByteArray());      restoreState(settings.value("state").toByteArray());      render_window->restoreGeometry(settings.value("geometryRenderWindow").toByteArray()); +    microProfileDialog->restoreGeometry(settings.value("microProfileDialogGeometry").toByteArray()); +    microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool());      ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer);      SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked()); @@ -434,6 +441,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) {      settings.setValue("geometry", saveGeometry());      settings.setValue("state", saveState());      settings.setValue("geometryRenderWindow", render_window->saveGeometry()); +    settings.setValue("microProfileDialogGeometry", microProfileDialog->saveGeometry()); +    settings.setValue("microProfileDialogVisible", microProfileDialog->isVisible());      settings.setValue("singleWindowMode", ui.action_Single_Window_Mode->isChecked());      settings.setValue("displayTitleBars", ui.actionDisplay_widget_title_bars->isChecked());      settings.setValue("firstStart", false); @@ -456,6 +465,11 @@ int main(int argc, char* argv[]) {      Log::Filter log_filter(Log::Level::Info);      Log::SetFilter(&log_filter); +    MicroProfileOnThreadCreate("Frontend"); +    SCOPE_EXIT({ +        MicroProfileShutdown(); +    }); +      // Init settings params      QSettings::setDefaultFormat(QSettings::IniFormat);      QCoreApplication::setOrganizationName("Citra team"); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 6f1292295..32523fded 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -14,6 +14,7 @@ class GImageInfo;  class GRenderWindow;  class EmuThread;  class ProfilerWidget; +class MicroProfileDialog;  class DisassemblerWidget;  class RegistersWidget;  class CallstackWidget; @@ -104,6 +105,7 @@ private:      std::unique_ptr<EmuThread> emu_thread;      ProfilerWidget* profilerWidget; +    MicroProfileDialog* microProfileDialog;      DisassemblerWidget* disasmWidget;      RegistersWidget* registersWidget;      CallstackWidget* callstackWidget; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e743a026d..7f3712efa 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS              logging/text_formatter.cpp              logging/backend.cpp              memory_util.cpp +            microprofile.cpp              misc.cpp              profiler.cpp              scm_rev.cpp @@ -43,6 +44,8 @@ set(HEADERS              make_unique.h              math_util.h              memory_util.h +            microprofile.h +            microprofileui.h              platform.h              profiler.h              profiler_reporting.h diff --git a/src/common/microprofile.cpp b/src/common/microprofile.cpp new file mode 100644 index 000000000..ee25dd37f --- /dev/null +++ b/src/common/microprofile.cpp @@ -0,0 +1,7 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Includes the MicroProfile implementation in this file for compilation +#define MICROPROFILE_IMPL 1 +#include "common/microprofile.h" diff --git a/src/common/microprofile.h b/src/common/microprofile.h new file mode 100644 index 000000000..9eb6016a8 --- /dev/null +++ b/src/common/microprofile.h @@ -0,0 +1,25 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +// Customized Citra settings. +// This file wraps the MicroProfile header so that these are consistent everywhere. +#define MICROPROFILE_WEBSERVER 0 +#define MICROPROFILE_GPU_TIMERS 0 // TODO: Implement timer queries when we upgrade to OpenGL 3.3 +#define MICROPROFILE_CONTEXT_SWITCH_TRACE 0 +#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (2048<<12) // 8 MB + +#include <microprofile.h> + +#define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0) + +// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with +// identifiers we use. +#ifdef PAGE_SIZE +#undef PAGE_SIZE +#endif +#ifdef PAGE_MASK +#undef PAGE_MASK +#endif diff --git a/src/common/microprofileui.h b/src/common/microprofileui.h new file mode 100644 index 000000000..97c369bd9 --- /dev/null +++ b/src/common/microprofileui.h @@ -0,0 +1,16 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/microprofile.h" + +// Customized Citra settings. +// This file wraps the MicroProfile header so that these are consistent everywhere. +#define MICROPROFILE_TEXT_WIDTH 6 +#define MICROPROFILE_TEXT_HEIGHT 12 +#define MICROPROFILE_HELP_ALT "Right-Click" +#define MICROPROFILE_HELP_MOD "Ctrl" + +#include <microprofileui.h> diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 422e80b50..01c712f24 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -9,6 +9,7 @@  #include "common/common_types.h"  #include "common/logging/log.h" +#include "common/microprofile.h"  #include "common/profiler.h"  #include "core/memory.h" @@ -3522,8 +3523,11 @@ enum {      FETCH_EXCEPTION  }; +MICROPROFILE_DEFINE(DynCom_Decode, "DynCom", "Decode", MP_RGB(255, 64, 64)); +  static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) {      Common::Profiling::ScopeTimer timer_decode(profile_decode); +    MICROPROFILE_SCOPE(DynCom_Decode);      // Decode instruction, get index      // Allocate memory and init InsCream @@ -3588,8 +3592,11 @@ static int clz(unsigned int x) {      return n;  } +MICROPROFILE_DEFINE(DynCom_Execute, "DynCom", "Execute", MP_RGB(255, 0, 0)); +  unsigned InterpreterMainLoop(ARMul_State* cpu) {      Common::Profiling::ScopeTimer timer_execute(profile_execute); +    MICROPROFILE_SCOPE(DynCom_Execute);      #undef RM      #undef RS diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index fde508a13..c3d0d28a5 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include "common/bit_field.h" +#include "common/microprofile.h"  #include "core/memory.h"  #include "core/hle/kernel/event.h" @@ -229,6 +230,10 @@ void SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) {      if (Pica::g_debug_context)          Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); + +    if (screen_id == 0) { +        MicroProfileFlip(); +    }  }  /** diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 89ac45a6f..19f750d72 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -5,6 +5,7 @@  #include <map>  #include "common/logging/log.h" +#include "common/microprofile.h"  #include "common/profiler.h"  #include "common/string_util.h"  #include "common/symbols.h" @@ -969,8 +970,11 @@ static const FunctionDef* GetSVCInfo(u32 func_num) {      return &SVC_Table[func_num];  } +MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70)); +  void CallSVC(u32 immediate) {      Common::Profiling::ScopeTimer timer_svc(profiler_svc); +    MICROPROFILE_SCOPE(Kernel_SVC);      const FunctionDef* info = GetSVCInfo(immediate);      if (info) { diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 68ae38289..bc7bde903 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -9,6 +9,7 @@  #include "common/color.h"  #include "common/common_types.h"  #include "common/logging/log.h" +#include "common/microprofile.h"  #include "common/vector_math.h"  #include "core/settings.h" @@ -85,6 +86,9 @@ static Math::Vec4<u8> DecodePixel(Regs::PixelFormat input_format, const u8* src_      }  } +MICROPROFILE_DEFINE(GPU_DisplayTransfer, "GPU", "DisplayTransfer", MP_RGB(100, 100, 255)); +MICROPROFILE_DEFINE(GPU_CmdlistProcessing, "GPU", "Cmdlist Processing", MP_RGB(100, 255, 100)); +  template <typename T>  inline void Write(u32 addr, const T data) {      addr -= HW::VADDR_GPU; @@ -150,6 +154,8 @@ inline void Write(u32 addr, const T data) {      case GPU_REG_INDEX(display_transfer_config.trigger):      { +        MICROPROFILE_SCOPE(GPU_DisplayTransfer); +          const auto& config = g_regs.display_transfer_config;          if (config.trigger & 1) { @@ -344,6 +350,8 @@ inline void Write(u32 addr, const T data) {          const auto& config = g_regs.command_processor_config;          if (config.trigger & 1)          { +            MICROPROFILE_SCOPE(GPU_CmdlistProcessing); +              u32* buffer = (u32*)Memory::GetPhysicalPointer(config.GetPhysicalAddress());              if (Pica::g_debug_context && Pica::g_debug_context->recorder) { diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index d82e20f86..a78985510 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -4,6 +4,7 @@  #include <boost/range/algorithm/fill.hpp> +#include "common/microprofile.h"  #include "common/profiler.h"  #include "core/hle/service/gsp_gpu.h" @@ -43,6 +44,8 @@ static const u32 expand_bits_to_bytes[] = {      0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff  }; +MICROPROFILE_DEFINE(GPU_Drawing, "GPU", "Drawing", MP_RGB(50, 50, 240)); +  static void WritePicaReg(u32 id, u32 value, u32 mask) {      auto& regs = g_state.regs; @@ -126,6 +129,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {          case PICA_REG_INDEX(trigger_draw_indexed):          {              Common::Profiling::ScopeTimer scope_timer(category_drawing); +            MICROPROFILE_SCOPE(GPU_Drawing);  #if PICA_LOG_TEV              DebugUtils::DumpTevStageConfig(regs.GetTevStages()); diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index b83798b0f..4a159da8e 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -7,6 +7,7 @@  #include "common/color.h"  #include "common/common_types.h"  #include "common/math_util.h" +#include "common/microprofile.h"  #include "common/profiler.h"  #include "core/hw/gpu.h" @@ -267,6 +268,7 @@ static int SignedArea (const Math::Vec2<Fix12P4>& vtx1,  };  static Common::Profiling::TimingCategory rasterization_category("Rasterization"); +MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240));  /**   * Helper function for ProcessTriangle with the "reversed" flag to allow for implementing @@ -279,6 +281,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0,  {      const auto& regs = g_state.regs;      Common::Profiling::ScopeTimer timer(rasterization_category); +    MICROPROFILE_SCOPE(GPU_Rasterization);      // vertex positions in rasterizer coordinates      static auto FloatToFix = [](float24 flt) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 9f1552adf..deb9971bb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -7,6 +7,7 @@  #include "common/color.h"  #include "common/math_util.h" +#include "common/microprofile.h"  #include "common/profiler.h"  #include "core/hw/gpu.h" @@ -777,12 +778,16 @@ void RasterizerOpenGL::SyncDrawState() {      state.Apply();  } +MICROPROFILE_DEFINE(OpenGL_FramebufferReload, "OpenGL", "FB Reload", MP_RGB(70, 70, 200)); +  void RasterizerOpenGL::ReloadColorBuffer() {      u8* color_buffer = Memory::GetPhysicalPointer(Pica::g_state.regs.framebuffer.GetColorBufferPhysicalAddress());      if (color_buffer == nullptr)          return; +    MICROPROFILE_SCOPE(OpenGL_FramebufferReload); +      u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format);      std::unique_ptr<u8[]> temp_fb_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); @@ -822,6 +827,8 @@ void RasterizerOpenGL::ReloadDepthBuffer() {      if (depth_buffer == nullptr)          return; +    MICROPROFILE_SCOPE(OpenGL_FramebufferReload); +      u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format);      // OpenGL needs 4 bpp alignment for D24 @@ -868,6 +875,7 @@ void RasterizerOpenGL::ReloadDepthBuffer() {  }  Common::Profiling::TimingCategory buffer_commit_category("Framebuffer Commit"); +MICROPROFILE_DEFINE(OpenGL_FramebufferCommit, "OpenGL", "FB Commit", MP_RGB(70, 70, 200));  void RasterizerOpenGL::CommitColorBuffer() {      if (last_fb_color_addr != 0) { @@ -875,6 +883,7 @@ void RasterizerOpenGL::CommitColorBuffer() {          if (color_buffer != nullptr) {              Common::Profiling::ScopeTimer timer(buffer_commit_category); +            MICROPROFILE_SCOPE(OpenGL_FramebufferCommit);              u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); @@ -911,6 +920,7 @@ void RasterizerOpenGL::CommitDepthBuffer() {          if (depth_buffer != nullptr) {              Common::Profiling::ScopeTimer timer(buffer_commit_category); +            MICROPROFILE_SCOPE(OpenGL_FramebufferCommit);              u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 70f0ba5f1..e4247051c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -4,6 +4,7 @@  #include "common/make_unique.h"  #include "common/math_util.h" +#include "common/microprofile.h"  #include "common/vector_math.h"  #include "core/memory.h" @@ -16,6 +17,8 @@ RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {      FullFlush();  } +MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); +  void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) {      PAddr texture_addr = config.config.GetPhysicalAddress(); @@ -25,6 +28,8 @@ void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned text          state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle;          state.Apply();      } else { +        MICROPROFILE_SCOPE(OpenGL_TextureUpload); +          std::unique_ptr<CachedTexture> new_texture = Common::make_unique<CachedTexture>();          new_texture->texture.Create(); diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 4e9836c80..be5588c00 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -9,6 +9,7 @@  #include "common/hash.h"  #include "common/make_unique.h" +#include "common/microprofile.h"  #include "common/profiler.h"  #include "video_core/debug_utils/debug_utils.h" @@ -55,11 +56,13 @@ void Shutdown() {  }  static Common::Profiling::TimingCategory shader_category("Vertex Shader"); +MICROPROFILE_DEFINE(GPU_VertexShader, "GPU", "Vertex Shader", MP_RGB(50, 50, 240));  OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {      auto& config = g_state.regs.vs;      Common::Profiling::ScopeTimer timer(shader_category); +    MICROPROFILE_SCOPE(GPU_VertexShader);      state.program_counter = config.main_offset;      state.debug.max_offset = 0;  | 
