#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
//  MICROPROFILE_LABEL
//  MICROPROFILE_LABELF
//
//	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 MicroProfileGpuInsertTimer(void* pContext);
// 		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 1
// 			call MicroProfileGpuInitGL() on startup
//		D3D11:
// 			in .c file where MICROPROFILE_IMPL is defined:
//   		#define MICROPROFILE_GPU_TIMERS_D3D11 1
// 			call MicroProfileGpuInitD3D11(Device) on startup
//		D3D12:
// 			in .c file where MICROPROFILE_IMPL is defined:
//   		#define MICROPROFILE_GPU_TIMERS_D3D12 1
// 			call MicroProfileGpuInitD3D12(Device, CommandQueue) on startup
//			single-threaded: call MicroProfileGpuSetContext(CommandList) every frame before issuing GPU markers
//			multi-threaded:
//				#define MICROPROFILE_GPU_TIMERS_MULTITHREADED 1
//				on recording thread before using command list, call MicroProfileGpuBegin(CommandList)
//				on recording thread after you're done with command list, call work = MicroProfileGpuEnd()
//				when replaying, call MicroProfileGpuSubmit(work) in the same order as ExecuteCommandLists
//		Vulkan:
// 			in .c file where MICROPROFILE_IMPL is defined:
//   		#define MICROPROFILE_GPU_TIMERS_VK 1
// 			call MicroProfileGpuInitVK(Device, PhysicalDevice, Queue) on startup
// 			the rest is the same as for D3D12
//
// Limitations:
//  GPU timestamps can only be inserted from one thread.


// Windows on ARM64 does not support OpenGL > 1.1, so disable profiler to prevent crash
#ifdef _M_ARM64
#undef MICROPROFILE_ENABLED
#endif

#ifndef MICROPROFILE_ENABLED
#define MICROPROFILE_ENABLED 0
#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_DECLARE_GPU(var)
#define MICROPROFILE_DEFINE_GPU(var, name, color)
#define MICROPROFILE_SCOPE(var) do{}while(0)
#define MICROPROFILE_SCOPE_TOKEN(token) 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_LABEL(group, name) do{}while(0)
#define MICROPROFILE_LABELF(group, name, ...) do{}while(0)
#define MICROPROFILE_COUNTER_ADD(name, count) do{} while(0)
#define MICROPROFILE_COUNTER_SUB(name, count) do{} while(0)
#define MICROPROFILE_COUNTER_SET(name, count) do{} while(0)
#define MICROPROFILE_COUNTER_SET_LIMIT(name, count) do{} while(0)
#define MICROPROFILE_COUNTER_CONFIG(name, type, limit, flags) do{} while(0)

#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 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 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 MicroProfileContextSwitchTraceStart() do{} while(0)
#define MicroProfileContextSwitchTraceStop() do{} while(0)
#define MicroProfileDumpFile(path,type,frames) do{} while(0)
#define MicroProfileDumpHtml(cb,handle,frames,host) do{} while(0)
#define MicroProfileWebServerStart() do{} while(0)
#define MicroProfileWebServerStop() do{} while(0)
#define MicroProfileWebServerPort() 0

#define MicroProfileGpuSetContext(c) do{} while(0)
#define MicroProfileGpuBegin(c) do{} while(0)
#define MicroProfileGpuEnd() 0
#define MicroProfileGpuSubmit(w) do{} while(0)

#define MicroProfileCounterAdd(name, count) do{} while(0)

#else

#include "compat.h"
#include "miniz.h"
#include "glad/glad.h"

#define MICROPROFILE_GPU_TIMERS_GL 1
#define MICROPROFILE_GPU_TIMERS_MULTITHREADED 1
#define MICROPROFILE_MINIZ 1

#include <stdint.h>
#include <string.h>

#ifndef MICROPROFILE_NOCXX11
#include <thread>
#include <mutex>
#include <atomic>
#endif

#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>

#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(nullptr, &tid);
    return tid;
}

#define MP_BREAK() __builtin_trap()
#if __has_feature(tls)
#define MP_THREAD_LOCAL __thread
#endif
#define MP_STRCASECMP strcasecmp
#define MP_GETCURRENTTHREADID() MicroProfileGetCurrentThreadId()
typedef uint64_t MicroProfileThreadIdType;
#define MP_GETCURRENTPROCESSID() getpid()
typedef uint32_t MicroProfileProcessIdType;
#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 MicroProfileThreadIdType;
#define MP_GETCURRENTPROCESSID() GetCurrentProcessId()
typedef uint32_t MicroProfileProcessIdType;

#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()
#ifndef __ANDROID__ // __thread is incompatible with ffunction-sections/fdata-sections
#define MP_THREAD_LOCAL __thread
#endif
#define MP_STRCASECMP strcasecmp
#define MP_GETCURRENTTHREADID() (uint64_t)pthread_self()
typedef uint64_t MicroProfileThreadIdType;
#define MP_GETCURRENTPROCESSID() getpid()
typedef uint32_t MicroProfileProcessIdType;
#endif


#ifndef MP_GETCURRENTTHREADID
#define MP_GETCURRENTTHREADID() 0
typedef uint32_t MicroProfileThreadIdType;
#endif

#ifndef MP_GETCURRENTPROCESSID
#define MP_GETCURRENTPROCESSID() 0
typedef uint32_t MicroProfileProcessIdType;
#endif

#ifndef MP_ASSERT
#define MP_ASSERT(a) do{if(!(a)){MP_BREAK();} }while(0)
#endif

#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_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) MicroProfileScopeHandler 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); MicroProfileScopeHandler MICROPROFILE_TOKEN_PASTE(foo,__LINE__)( MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__))
#define MICROPROFILE_META_CPU(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__) = MicroProfileGetMetaToken(name); MicroProfileMetaUpdate(MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__), count, MicroProfileTokenTypeCpu); } while(0)
#define MICROPROFILE_META_GPU(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__) = MicroProfileGetMetaToken(name); MicroProfileMetaUpdate(MICROPROFILE_TOKEN_PASTE(g_mp_meta,__LINE__), count, MicroProfileTokenTypeGpu); } while(0)
#define MICROPROFILE_LABEL(group, name) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__) = MicroProfileGetLabelToken(group); MicroProfileLabel(MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__), name); } while(0)
#define MICROPROFILE_LABELF(group, name, ...) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__) = MicroProfileGetLabelToken(group); MicroProfileLabelFormat(MICROPROFILE_TOKEN_PASTE(g_mp,__LINE__), name, ## __VA_ARGS__); } while(0)
#define MICROPROFILE_COUNTER_ADD(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__) = MicroProfileGetCounterToken(name); MicroProfileCounterAdd(MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__), count); } while(0)
#define MICROPROFILE_COUNTER_SUB(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__) = MicroProfileGetCounterToken(name); MicroProfileCounterAdd(MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__), -(int64_t)count); } while(0)
#define MICROPROFILE_COUNTER_SET(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__) = MicroProfileGetCounterToken(name); MicroProfileCounterSet(MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__), count); } while(0)
#define MICROPROFILE_COUNTER_SET_LIMIT(name, count) do { static MicroProfileToken MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__) = MicroProfileGetCounterToken(name); MicroProfileCounterSetLimit(MICROPROFILE_TOKEN_PASTE(g_mp_counter,__LINE__), count); } while(0)
#define MICROPROFILE_COUNTER_CONFIG(name, type, limit, flags) MicroProfileCounterConfig(name, type, limit, flags)

#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_PER_THREAD_GPU_BUFFER_SIZE
#define MICROPROFILE_PER_THREAD_GPU_BUFFER_SIZE (1024<<10)
#endif

#ifndef MICROPROFILE_MAX_FRAME_HISTORY
#define MICROPROFILE_MAX_FRAME_HISTORY 512
#endif

#ifndef MICROPROFILE_PRINTF
#define MICROPROFILE_PRINTF initprintf
#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_FRAMES
#define MICROPROFILE_WEBSERVER_FRAMES 30
#endif

#ifndef MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE
#define MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE (16<<10)
#endif

#ifndef MICROPROFILE_LABEL_BUFFER_SIZE
#define MICROPROFILE_LABEL_BUFFER_SIZE (1024<<10)
#endif

#ifndef MICROPROFILE_GPU_MAX_QUERIES
#define MICROPROFILE_GPU_MAX_QUERIES (8<<10)
#endif

#ifndef MICROPROFILE_GPU_FRAME_DELAY
#define MICROPROFILE_GPU_FRAME_DELAY 3
#endif

#ifndef MICROPROFILE_NAME_MAX_LEN
#define MICROPROFILE_NAME_MAX_LEN 64
#endif

#ifndef MICROPROFILE_LABEL_MAX_LEN
#define MICROPROFILE_LABEL_MAX_LEN 256
#endif

#ifndef MICROPROFILE_EMBED_HTML
#define MICROPROFILE_EMBED_HTML 1
#endif

#ifndef MICROPROFILE_GPU_TIMERS_MULTITHREADED
#define MICROPROFILE_GPU_TIMERS_MULTITHREADED 0
#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)0

enum MicroProfileTokenType
{
    MicroProfileTokenTypeCpu,
    MicroProfileTokenTypeGpu,
};

enum MicroProfileBoxType
{
    MicroProfileBoxTypeBar,
    MicroProfileBoxTypeFlat,
};

enum MicroProfileDumpType
{
    MicroProfileDumpTypeHtml,
    MicroProfileDumpTypeCsv
};

#ifdef __GNUC__
#define MICROPROFILE_FORMAT(a, b) __attribute__((format(printf, a, b)))
#else
#define MICROPROFILE_FORMAT(a, b)
#endif

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 MicroProfileGetLabelToken(const char* sGroup, MicroProfileTokenType Token = MicroProfileTokenTypeCpu);
MICROPROFILE_API MicroProfileToken MicroProfileGetMetaToken(const char* pName);
MICROPROFILE_API MicroProfileToken MicroProfileGetCounterToken(const char* pName);
MICROPROFILE_API void MicroProfileMetaUpdate(MicroProfileToken, int nCount, MicroProfileTokenType eTokenType);
MICROPROFILE_API void MicroProfileCounterAdd(MicroProfileToken nToken, int64_t nCount);
MICROPROFILE_API void MicroProfileCounterSet(MicroProfileToken nToken, int64_t nCount);
MICROPROFILE_API void MicroProfileCounterSetLimit(MicroProfileToken nToken, int64_t nCount);
MICROPROFILE_API void MicroProfileCounterConfig(const char* pCounterName, uint32_t nFormat, int64_t nLimit, uint32_t nFlags);
MICROPROFILE_API uint64_t MicroProfileEnter(MicroProfileToken nToken);
MICROPROFILE_API void MicroProfileLeave(MicroProfileToken nToken, uint64_t nTick);
MICROPROFILE_API void MicroProfileLabel(MicroProfileToken nToken, const char* pName);
MICROPROFILE_FORMAT(2, 3) MICROPROFILE_API void MicroProfileLabelFormat(MicroProfileToken nToken, const char* pName, ...);
MICROPROFILE_API void MicroProfileLabelFormatV(MicroProfileToken nToken, const char* pName, va_list args);
MICROPROFILE_API void MicroProfileLabelLiteral(MicroProfileToken nToken, const char* pName);
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 MicroProfileOnThreadCreate(const char* pThreadName); //should be called from newly created threads
MICROPROFILE_API void MicroProfileOnThreadExit(); //call on exit to reuse log
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 MicroProfileContextSwitchTraceStart();
MICROPROFILE_API void MicroProfileContextSwitchTraceStop();

struct MicroProfileThreadInfo
{
    MicroProfileProcessIdType nProcessId;
    MicroProfileThreadIdType nThreadId;
};

MICROPROFILE_API void MicroProfileContextSwitchSearch(uint32_t* pContextSwitchStart, uint32_t* pContextSwitchEnd, uint64_t nBaseTicksCpu, uint64_t nBaseTicksEndCpu);
MICROPROFILE_API uint32_t MicroProfileContextSwitchGatherThreads(uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, MicroProfileThreadInfo* Threads, uint32_t* nNumThreadsBase);

MICROPROFILE_API const char* MicroProfileGetProcessName(MicroProfileProcessIdType nId, char* Buffer, uint32_t nSize);

MICROPROFILE_API void MicroProfileDumpFile(const char* pPath, MicroProfileDumpType eType, uint32_t nFrames);

typedef void MicroProfileWriteCallback(void* Handle, size_t size, const char* pData);
MICROPROFILE_API void MicroProfileDumpHtml(MicroProfileWriteCallback CB, void* Handle, int nMaxFrames, const char* pHost);

MICROPROFILE_API int MicroProfileFormatCounter(int eFormat, int64_t nCounter, char* pOut, uint32_t nBufferSize);

MICROPROFILE_API void MicroProfileWebServerStart();
MICROPROFILE_API void MicroProfileWebServerStop();
MICROPROFILE_API uint32_t MicroProfileWebServerPort();

MICROPROFILE_API void MicroProfileGpuInitGL();
MICROPROFILE_API void MicroProfileGpuInitD3D11(struct ID3D11Device* pDevice);
MICROPROFILE_API void MicroProfileGpuInitD3D12(struct ID3D12Device* pDevice, struct ID3D12CommandQueue* pCommandQueue);
MICROPROFILE_API void MicroProfileGpuInitVK(struct VkDevice_T* pDevice, struct VkPhysicalDevice_T* pPhysicalDevice, struct VkQueue_T* pQueue);
MICROPROFILE_API void MicroProfileGpuShutdown();

MICROPROFILE_API void MicroProfileGpuSetContext(void* pContext);
MICROPROFILE_API void MicroProfileGpuBegin(void* pContext);
MICROPROFILE_API uint64_t MicroProfileGpuEnd();
MICROPROFILE_API void MicroProfileGpuSubmit(uint64_t nWork);

MICROPROFILE_API uint32_t MicroProfileGpuInsertTimer(void* pContext);
MICROPROFILE_API uint64_t MicroProfileGpuGetTimeStamp(uint32_t nKey);
MICROPROFILE_API uint64_t MicroProfileTicksPerSecondGpu();
MICROPROFILE_API uint32_t MicroProfileGpuFlip();
MICROPROFILE_API bool MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu);


#if MICROPROFILE_USE_THREAD_NAME_CALLBACK
MICROPROFILE_API const char* MicroProfileGetThreadName();
#else
#define MicroProfileGetThreadName() "<Unknown>"
#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);
    }
};

#define MICROPROFILE_MAX_COUNTERS 256
#define MICROPROFILE_MAX_COUNTER_NAME_CHARS (MICROPROFILE_MAX_COUNTERS*16)

#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_GPU_BUFFER_SIZE ((MICROPROFILE_PER_THREAD_GPU_BUFFER_SIZE)/sizeof(MicroProfileLogEntry))
#define MICROPROFILE_GPU_FRAMES ((MICROPROFILE_GPU_FRAME_DELAY)+1)
#define MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS 256
#define MICROPROFILE_STACK_MAX 128
//#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_TIMERS
#define MICROPROFILE_MAX_TIMERS 2048
#endif

#ifndef MICROPROFILE_MAX_THREADS
#define MICROPROFILE_MAX_THREADS 32
#endif

#define MP_AUTO              0xffffffff
//from http://fugal.net/vim/rgbtxt.html
#define MP_SNOW              0xfffafa
#define MP_GHOSTWHITE        0xf8f8ff
#define MP_WHITESMOKE        0xf5f5f5
#define MP_GAINSBORO         0xdcdcdc
#define MP_FLORALWHITE       0xfffaf0
#define MP_OLDLACE           0xfdf5e6
#define MP_LINEN             0xfaf0e6
#define MP_ANTIQUEWHITE      0xfaebd7
#define MP_PAPAYAWHIP        0xffefd5
#define MP_BLANCHEDALMOND    0xffebcd
#define MP_BISQUE            0xffe4c4
#define MP_PEACHPUFF         0xffdab9
#define MP_NAVAJOWHITE       0xffdead
#define MP_MOCCASIN          0xffe4b5
#define MP_CORNSILK          0xfff8dc
#define MP_IVORY             0xfffff0
#define MP_LEMONCHIFFON      0xfffacd
#define MP_SEASHELL          0xfff5ee
#define MP_HONEYDEW          0xf0fff0
#define MP_MINTCREAM         0xf5fffa
#define MP_AZURE             0xf0ffff
#define MP_ALICEBLUE         0xf0f8ff
#define MP_LAVENDER          0xe6e6fa
#define MP_LAVENDERBLUSH     0xfff0f5
#define MP_MISTYROSE         0xffe4e1
#define MP_WHITE             0xffffff
#define MP_BLACK             0x010101
#define MP_DARKSLATEGRAY     0x2f4f4f
#define MP_DARKSLATEGREY     0x2f4f4f
#define MP_DIMGRAY           0x696969
#define MP_DIMGREY           0x696969
#define MP_SLATEGRAY         0x708090
#define MP_SLATEGREY         0x708090
#define MP_LIGHTSLATEGRAY    0x778899
#define MP_LIGHTSLATEGREY    0x778899
#define MP_GRAY              0xbebebe
#define MP_GREY              0xbebebe
#define MP_LIGHTGREY         0xd3d3d3
#define MP_LIGHTGRAY         0xd3d3d3
#define MP_MIDNIGHTBLUE      0x191970
#define MP_NAVY              0x000080
#define MP_NAVYBLUE          0x000080
#define MP_CORNFLOWERBLUE    0x6495ed
#define MP_DARKSLATEBLUE     0x483d8b
#define MP_SLATEBLUE         0x6a5acd
#define MP_MEDIUMSLATEBLUE   0x7b68ee
#define MP_LIGHTSLATEBLUE    0x8470ff
#define MP_MEDIUMBLUE        0x0000cd
#define MP_ROYALBLUE         0x4169e1
#define MP_BLUE              0x0000ff
#define MP_DODGERBLUE        0x1e90ff
#define MP_DEEPSKYBLUE       0x00bfff
#define MP_SKYBLUE           0x87ceeb
#define MP_LIGHTSKYBLUE      0x87cefa
#define MP_STEELBLUE         0x4682b4
#define MP_LIGHTSTEELBLUE    0xb0c4de
#define MP_LIGHTBLUE         0xadd8e6
#define MP_POWDERBLUE        0xb0e0e6
#define MP_PALETURQUOISE     0xafeeee
#define MP_DARKTURQUOISE     0x00ced1
#define MP_MEDIUMTURQUOISE   0x48d1cc
#define MP_TURQUOISE         0x40e0d0
#define MP_CYAN              0x00ffff
#define MP_LIGHTCYAN         0xe0ffff
#define MP_CADETBLUE         0x5f9ea0
#define MP_MEDIUMAQUAMARINE  0x66cdaa
#define MP_AQUAMARINE        0x7fffd4
#define MP_DARKGREEN         0x006400
#define MP_DARKOLIVEGREEN    0x556b2f
#define MP_DARKSEAGREEN      0x8fbc8f
#define MP_SEAGREEN          0x2e8b57
#define MP_MEDIUMSEAGREEN    0x3cb371
#define MP_LIGHTSEAGREEN     0x20b2aa
#define MP_PALEGREEN         0x98fb98
#define MP_SPRINGGREEN       0x00ff7f
#define MP_LAWNGREEN         0x7cfc00
#define MP_GREEN             0x00ff00
#define MP_CHARTREUSE        0x7fff00
#define MP_MEDIUMSPRINGGREEN 0x00fa9a
#define MP_GREENYELLOW       0xadff2f
#define MP_LIMEGREEN         0x32cd32
#define MP_YELLOWGREEN       0x9acd32
#define MP_FORESTGREEN       0x228b22
#define MP_OLIVEDRAB         0x6b8e23
#define MP_DARKKHAKI         0xbdb76b
#define MP_KHAKI             0xf0e68c
#define MP_PALEGOLDENROD     0xeee8aa
#define MP_GOLDENRODYELLOW   0xfafad2
#define MP_LIGHTYELLOW       0xffffe0
#define MP_YELLOW            0xffff00
#define MP_GOLD              0xffd700
#define MP_LIGHTGOLDENROD    0xeedd82
#define MP_GOLDENROD         0xdaa520
#define MP_DARKGOLDENROD     0xb8860b
#define MP_ROSYBROWN         0xbc8f8f
#define MP_INDIANRED         0xcd5c5c
#define MP_SADDLEBROWN       0x8b4513
#define MP_SIENNA            0xa0522d
#define MP_PERU              0xcd853f
#define MP_BURLYWOOD         0xdeb887
#define MP_BEIGE             0xf5f5dc
#define MP_WHEAT             0xf5deb3
#define MP_SANDYBROWN        0xf4a460
#define MP_TAN               0xd2b48c
#define MP_CHOCOLATE         0xd2691e
#define MP_FIREBRICK         0xb22222
#define MP_BROWN             0xa52a2a
#define MP_DARKSALMON        0xe9967a
#define MP_SALMON            0xfa8072
#define MP_LIGHTSALMON       0xffa07a
#define MP_ORANGE            0xffa500
#define MP_DARKORANGE        0xff8c00
#define MP_CORAL             0xff7f50
#define MP_LIGHTCORAL        0xf08080
#define MP_TOMATO            0xff6347
#define MP_ORANGERED         0xff4500
#define MP_RED               0xff0000
#define MP_HOTPINK           0xff69b4
#define MP_DEEPPINK          0xff1493
#define MP_PINK              0xffc0cb
#define MP_LIGHTPINK         0xffb6c1
#define MP_PALEVIOLETRED     0xdb7093
#define MP_MAROON            0xb03060
#define MP_MEDIUMVIOLETRED   0xc71585
#define MP_VIOLETRED         0xd02090
#define MP_MAGENTA           0xff00ff
#define MP_VIOLET            0xee82ee
#define MP_PLUM              0xdda0dd
#define MP_ORCHID            0xda70d6
#define MP_MEDIUMORCHID      0xba55d3
#define MP_DARKORCHID        0x9932cc
#define MP_DARKVIOLET        0x9400d3
#define MP_BLUEVIOLET        0x8a2be2
#define MP_PURPLE            0xa020f0
#define MP_MEDIUMPURPLE      0x9370db
#define MP_THISTLE           0xd8bfd8
#define MP_SNOW1             0xfffafa
#define MP_SNOW2             0xeee9e9
#define MP_SNOW3             0xcdc9c9
#define MP_SNOW4             0x8b8989
#define MP_SEASHELL1         0xfff5ee
#define MP_SEASHELL2         0xeee5de
#define MP_SEASHELL3         0xcdc5bf
#define MP_SEASHELL4         0x8b8682
#define MP_ANTIQUEWHITE1     0xffefdb
#define MP_ANTIQUEWHITE2     0xeedfcc
#define MP_ANTIQUEWHITE3     0xcdc0b0
#define MP_ANTIQUEWHITE4     0x8b8378
#define MP_BISQUE1           0xffe4c4
#define MP_BISQUE2           0xeed5b7
#define MP_BISQUE3           0xcdb79e
#define MP_BISQUE4           0x8b7d6b
#define MP_PEACHPUFF1        0xffdab9
#define MP_PEACHPUFF2        0xeecbad
#define MP_PEACHPUFF3        0xcdaf95
#define MP_PEACHPUFF4        0x8b7765
#define MP_NAVAJOWHITE1      0xffdead
#define MP_NAVAJOWHITE2      0xeecfa1
#define MP_NAVAJOWHITE3      0xcdb38b
#define MP_NAVAJOWHITE4      0x8b795e
#define MP_LEMONCHIFFON1     0xfffacd
#define MP_LEMONCHIFFON2     0xeee9bf
#define MP_LEMONCHIFFON3     0xcdc9a5
#define MP_LEMONCHIFFON4     0x8b8970
#define MP_CORNSILK1         0xfff8dc
#define MP_CORNSILK2         0xeee8cd
#define MP_CORNSILK3         0xcdc8b1
#define MP_CORNSILK4         0x8b8878
#define MP_IVORY1            0xfffff0
#define MP_IVORY2            0xeeeee0
#define MP_IVORY3            0xcdcdc1
#define MP_IVORY4            0x8b8b83
#define MP_HONEYDEW1         0xf0fff0
#define MP_HONEYDEW2         0xe0eee0
#define MP_HONEYDEW3         0xc1cdc1
#define MP_HONEYDEW4         0x838b83
#define MP_LAVENDERBLUSH1    0xfff0f5
#define MP_LAVENDERBLUSH2    0xeee0e5
#define MP_LAVENDERBLUSH3    0xcdc1c5
#define MP_LAVENDERBLUSH4    0x8b8386
#define MP_MISTYROSE1        0xffe4e1
#define MP_MISTYROSE2        0xeed5d2
#define MP_MISTYROSE3        0xcdb7b5
#define MP_MISTYROSE4        0x8b7d7b
#define MP_AZURE1            0xf0ffff
#define MP_AZURE2            0xe0eeee
#define MP_AZURE3            0xc1cdcd
#define MP_AZURE4            0x838b8b
#define MP_SLATEBLUE1        0x836fff
#define MP_SLATEBLUE2        0x7a67ee
#define MP_SLATEBLUE3        0x6959cd
#define MP_SLATEBLUE4        0x473c8b
#define MP_ROYALBLUE1        0x4876ff
#define MP_ROYALBLUE2        0x436eee
#define MP_ROYALBLUE3        0x3a5fcd
#define MP_ROYALBLUE4        0x27408b
#define MP_BLUE1             0x0000ff
#define MP_BLUE2             0x0000ee
#define MP_BLUE3             0x0000cd
#define MP_BLUE4             0x00008b
#define MP_DODGERBLUE1       0x1e90ff
#define MP_DODGERBLUE2       0x1c86ee
#define MP_DODGERBLUE3       0x1874cd
#define MP_DODGERBLUE4       0x104e8b
#define MP_STEELBLUE1        0x63b8ff
#define MP_STEELBLUE2        0x5cacee
#define MP_STEELBLUE3        0x4f94cd
#define MP_STEELBLUE4        0x36648b
#define MP_DEEPSKYBLUE1      0x00bfff
#define MP_DEEPSKYBLUE2      0x00b2ee
#define MP_DEEPSKYBLUE3      0x009acd
#define MP_DEEPSKYBLUE4      0x00688b
#define MP_SKYBLUE1          0x87ceff
#define MP_SKYBLUE2          0x7ec0ee
#define MP_SKYBLUE3          0x6ca6cd
#define MP_SKYBLUE4          0x4a708b
#define MP_LIGHTSKYBLUE1     0xb0e2ff
#define MP_LIGHTSKYBLUE2     0xa4d3ee
#define MP_LIGHTSKYBLUE3     0x8db6cd
#define MP_LIGHTSKYBLUE4     0x607b8b
#define MP_SLATEGRAY1        0xc6e2ff
#define MP_SLATEGRAY2        0xb9d3ee
#define MP_SLATEGRAY3        0x9fb6cd
#define MP_SLATEGRAY4        0x6c7b8b
#define MP_LIGHTSTEELBLUE1   0xcae1ff
#define MP_LIGHTSTEELBLUE2   0xbcd2ee
#define MP_LIGHTSTEELBLUE3   0xa2b5cd
#define MP_LIGHTSTEELBLUE4   0x6e7b8b
#define MP_LIGHTBLUE1        0xbfefff
#define MP_LIGHTBLUE2        0xb2dfee
#define MP_LIGHTBLUE3        0x9ac0cd
#define MP_LIGHTBLUE4        0x68838b
#define MP_LIGHTCYAN1        0xe0ffff
#define MP_LIGHTCYAN2        0xd1eeee
#define MP_LIGHTCYAN3        0xb4cdcd
#define MP_LIGHTCYAN4        0x7a8b8b
#define MP_PALETURQUOISE1    0xbbffff
#define MP_PALETURQUOISE2    0xaeeeee
#define MP_PALETURQUOISE3    0x96cdcd
#define MP_PALETURQUOISE4    0x668b8b
#define MP_CADETBLUE1        0x98f5ff
#define MP_CADETBLUE2        0x8ee5ee
#define MP_CADETBLUE3        0x7ac5cd
#define MP_CADETBLUE4        0x53868b
#define MP_TURQUOISE1        0x00f5ff
#define MP_TURQUOISE2        0x00e5ee
#define MP_TURQUOISE3        0x00c5cd
#define MP_TURQUOISE4        0x00868b
#define MP_CYAN1             0x00ffff
#define MP_CYAN2             0x00eeee
#define MP_CYAN3             0x00cdcd
#define MP_CYAN4             0x008b8b
#define MP_DARKSLATEGRAY1    0x97ffff
#define MP_DARKSLATEGRAY2    0x8deeee
#define MP_DARKSLATEGRAY3    0x79cdcd
#define MP_DARKSLATEGRAY4    0x528b8b
#define MP_AQUAMARINE1       0x7fffd4
#define MP_AQUAMARINE2       0x76eec6
#define MP_AQUAMARINE3       0x66cdaa
#define MP_AQUAMARINE4       0x458b74
#define MP_DARKSEAGREEN1     0xc1ffc1
#define MP_DARKSEAGREEN2     0xb4eeb4
#define MP_DARKSEAGREEN3     0x9bcd9b
#define MP_DARKSEAGREEN4     0x698b69
#define MP_SEAGREEN1         0x54ff9f
#define MP_SEAGREEN2         0x4eee94
#define MP_SEAGREEN3         0x43cd80
#define MP_SEAGREEN4         0x2e8b57
#define MP_PALEGREEN1        0x9aff9a
#define MP_PALEGREEN2        0x90ee90
#define MP_PALEGREEN3        0x7ccd7c
#define MP_PALEGREEN4        0x548b54
#define MP_SPRINGGREEN1      0x00ff7f
#define MP_SPRINGGREEN2      0x00ee76
#define MP_SPRINGGREEN3      0x00cd66
#define MP_SPRINGGREEN4      0x008b45
#define MP_GREEN1            0x00ff00
#define MP_GREEN2            0x00ee00
#define MP_GREEN3            0x00cd00
#define MP_GREEN4            0x008b00
#define MP_CHARTREUSE1       0x7fff00
#define MP_CHARTREUSE2       0x76ee00
#define MP_CHARTREUSE3       0x66cd00
#define MP_CHARTREUSE4       0x458b00
#define MP_OLIVEDRAB1        0xc0ff3e
#define MP_OLIVEDRAB2        0xb3ee3a
#define MP_OLIVEDRAB3        0x9acd32
#define MP_OLIVEDRAB4        0x698b22
#define MP_DARKOLIVEGREEN1   0xcaff70
#define MP_DARKOLIVEGREEN2   0xbcee68
#define MP_DARKOLIVEGREEN3   0xa2cd5a
#define MP_DARKOLIVEGREEN4   0x6e8b3d
#define MP_KHAKI1            0xfff68f
#define MP_KHAKI2            0xeee685
#define MP_KHAKI3            0xcdc673
#define MP_KHAKI4            0x8b864e
#define MP_LIGHTGOLDENROD1   0xffec8b
#define MP_LIGHTGOLDENROD2   0xeedc82
#define MP_LIGHTGOLDENROD3   0xcdbe70
#define MP_LIGHTGOLDENROD4   0x8b814c
#define MP_LIGHTYELLOW1      0xffffe0
#define MP_LIGHTYELLOW2      0xeeeed1
#define MP_LIGHTYELLOW3      0xcdcdb4
#define MP_LIGHTYELLOW4      0x8b8b7a
#define MP_YELLOW1           0xffff00
#define MP_YELLOW2           0xeeee00
#define MP_YELLOW3           0xcdcd00
#define MP_YELLOW4           0x8b8b00
#define MP_GOLD1             0xffd700
#define MP_GOLD2             0xeec900
#define MP_GOLD3             0xcdad00
#define MP_GOLD4             0x8b7500
#define MP_GOLDENROD1        0xffc125
#define MP_GOLDENROD2        0xeeb422
#define MP_GOLDENROD3        0xcd9b1d
#define MP_GOLDENROD4        0x8b6914
#define MP_DARKGOLDENROD1    0xffb90f
#define MP_DARKGOLDENROD2    0xeead0e
#define MP_DARKGOLDENROD3    0xcd950c
#define MP_DARKGOLDENROD4    0x8b6508
#define MP_ROSYBROWN1        0xffc1c1
#define MP_ROSYBROWN2        0xeeb4b4
#define MP_ROSYBROWN3        0xcd9b9b
#define MP_ROSYBROWN4        0x8b6969
#define MP_INDIANRED1        0xff6a6a
#define MP_INDIANRED2        0xee6363
#define MP_INDIANRED3        0xcd5555
#define MP_INDIANRED4        0x8b3a3a
#define MP_SIENNA1           0xff8247
#define MP_SIENNA2           0xee7942
#define MP_SIENNA3           0xcd6839
#define MP_SIENNA4           0x8b4726
#define MP_BURLYWOOD1        0xffd39b
#define MP_BURLYWOOD2        0xeec591
#define MP_BURLYWOOD3        0xcdaa7d
#define MP_BURLYWOOD4        0x8b7355
#define MP_WHEAT1            0xffe7ba
#define MP_WHEAT2            0xeed8ae
#define MP_WHEAT3            0xcdba96
#define MP_WHEAT4            0x8b7e66
#define MP_TAN1              0xffa54f
#define MP_TAN2              0xee9a49
#define MP_TAN3              0xcd853f
#define MP_TAN4              0x8b5a2b
#define MP_CHOCOLATE1        0xff7f24
#define MP_CHOCOLATE2        0xee7621
#define MP_CHOCOLATE3        0xcd661d
#define MP_CHOCOLATE4        0x8b4513
#define MP_FIREBRICK1        0xff3030
#define MP_FIREBRICK2        0xee2c2c
#define MP_FIREBRICK3        0xcd2626
#define MP_FIREBRICK4        0x8b1a1a
#define MP_BROWN1            0xff4040
#define MP_BROWN2            0xee3b3b
#define MP_BROWN3            0xcd3333
#define MP_BROWN4            0x8b2323
#define MP_SALMON1           0xff8c69
#define MP_SALMON2           0xee8262
#define MP_SALMON3           0xcd7054
#define MP_SALMON4           0x8b4c39
#define MP_LIGHTSALMON1      0xffa07a
#define MP_LIGHTSALMON2      0xee9572
#define MP_LIGHTSALMON3      0xcd8162
#define MP_LIGHTSALMON4      0x8b5742
#define MP_ORANGE1           0xffa500
#define MP_ORANGE2           0xee9a00
#define MP_ORANGE3           0xcd8500
#define MP_ORANGE4           0x8b5a00
#define MP_DARKORANGE1       0xff7f00
#define MP_DARKORANGE2       0xee7600
#define MP_DARKORANGE3       0xcd6600
#define MP_DARKORANGE4       0x8b4500
#define MP_CORAL1            0xff7256
#define MP_CORAL2            0xee6a50
#define MP_CORAL3            0xcd5b45
#define MP_CORAL4            0x8b3e2f
#define MP_TOMATO1           0xff6347
#define MP_TOMATO2           0xee5c42
#define MP_TOMATO3           0xcd4f39
#define MP_TOMATO4           0x8b3626
#define MP_ORANGERED1        0xff4500
#define MP_ORANGERED2        0xee4000
#define MP_ORANGERED3        0xcd3700
#define MP_ORANGERED4        0x8b2500
#define MP_RED1              0xff0000
#define MP_RED2              0xee0000
#define MP_RED3              0xcd0000
#define MP_RED4              0x8b0000
#define MP_DEEPPINK1         0xff1493
#define MP_DEEPPINK2         0xee1289
#define MP_DEEPPINK3         0xcd1076
#define MP_DEEPPINK4         0x8b0a50
#define MP_HOTPINK1          0xff6eb4
#define MP_HOTPINK2          0xee6aa7
#define MP_HOTPINK3          0xcd6090
#define MP_HOTPINK4          0x8b3a62
#define MP_PINK1             0xffb5c5
#define MP_PINK2             0xeea9b8
#define MP_PINK3             0xcd919e
#define MP_PINK4             0x8b636c
#define MP_LIGHTPINK1        0xffaeb9
#define MP_LIGHTPINK2        0xeea2ad
#define MP_LIGHTPINK3        0xcd8c95
#define MP_LIGHTPINK4        0x8b5f65
#define MP_PALEVIOLETRED1    0xff82ab
#define MP_PALEVIOLETRED2    0xee799f
#define MP_PALEVIOLETRED3    0xcd6889
#define MP_PALEVIOLETRED4    0x8b475d
#define MP_MAROON1           0xff34b3
#define MP_MAROON2           0xee30a7
#define MP_MAROON3           0xcd2990
#define MP_MAROON4           0x8b1c62
#define MP_VIOLETRED1        0xff3e96
#define MP_VIOLETRED2        0xee3a8c
#define MP_VIOLETRED3        0xcd3278
#define MP_VIOLETRED4        0x8b2252
#define MP_MAGENTA1          0xff00ff
#define MP_MAGENTA2          0xee00ee
#define MP_MAGENTA3          0xcd00cd
#define MP_MAGENTA4          0x8b008b
#define MP_ORCHID1           0xff83fa
#define MP_ORCHID2           0xee7ae9
#define MP_ORCHID3           0xcd69c9
#define MP_ORCHID4           0x8b4789
#define MP_PLUM1             0xffbbff
#define MP_PLUM2             0xeeaeee
#define MP_PLUM3             0xcd96cd
#define MP_PLUM4             0x8b668b
#define MP_MEDIUMORCHID1     0xe066ff
#define MP_MEDIUMORCHID2     0xd15fee
#define MP_MEDIUMORCHID3     0xb452cd
#define MP_MEDIUMORCHID4     0x7a378b
#define MP_DARKORCHID1       0xbf3eff
#define MP_DARKORCHID2       0xb23aee
#define MP_DARKORCHID3       0x9a32cd
#define MP_DARKORCHID4       0x68228b
#define MP_PURPLE1           0x9b30ff
#define MP_PURPLE2           0x912cee
#define MP_PURPLE3           0x7d26cd
#define MP_PURPLE4           0x551a8b
#define MP_MEDIUMPURPLE1     0xab82ff
#define MP_MEDIUMPURPLE2     0x9f79ee
#define MP_MEDIUMPURPLE3     0x8968cd
#define MP_MEDIUMPURPLE4     0x5d478b
#define MP_THISTLE1          0xffe1ff
#define MP_THISTLE2          0xeed2ee
#define MP_THISTLE3          0xcdb5cd
#define MP_THISTLE4          0x8b7b8b
#define MP_GRAY0             0x000000
#define MP_GREY0             0x000000
#define MP_GRAY1             0x030303
#define MP_GREY1             0x030303
#define MP_GRAY2             0x050505
#define MP_GREY2             0x050505
#define MP_GRAY3             0x080808
#define MP_GREY3             0x080808
#define MP_GRAY4             0x0a0a0a
#define MP_GREY4             0x0a0a0a
#define MP_GRAY5             0x0d0d0d
#define MP_GREY5             0x0d0d0d
#define MP_GRAY6             0x0f0f0f
#define MP_GREY6             0x0f0f0f
#define MP_GRAY7             0x121212
#define MP_GREY7             0x121212
#define MP_GRAY8             0x141414
#define MP_GREY8             0x141414
#define MP_GRAY9             0x171717
#define MP_GREY9             0x171717
#define MP_GRAY10            0x1a1a1a
#define MP_GREY10            0x1a1a1a
#define MP_GRAY11            0x1c1c1c
#define MP_GREY11            0x1c1c1c
#define MP_GRAY12            0x1f1f1f
#define MP_GREY12            0x1f1f1f
#define MP_GRAY13            0x212121
#define MP_GREY13            0x212121
#define MP_GRAY14            0x242424
#define MP_GREY14            0x242424
#define MP_GRAY15            0x262626
#define MP_GREY15            0x262626
#define MP_GRAY16            0x292929
#define MP_GREY16            0x292929
#define MP_GRAY17            0x2b2b2b
#define MP_GREY17            0x2b2b2b
#define MP_GRAY18            0x2e2e2e
#define MP_GREY18            0x2e2e2e
#define MP_GRAY19            0x303030
#define MP_GREY19            0x303030
#define MP_GRAY20            0x333333
#define MP_GREY20            0x333333
#define MP_GRAY21            0x363636
#define MP_GREY21            0x363636
#define MP_GRAY22            0x383838
#define MP_GREY22            0x383838
#define MP_GRAY23            0x3b3b3b
#define MP_GREY23            0x3b3b3b
#define MP_GRAY24            0x3d3d3d
#define MP_GREY24            0x3d3d3d
#define MP_GRAY25            0x404040
#define MP_GREY25            0x404040
#define MP_GRAY26            0x424242
#define MP_GREY26            0x424242
#define MP_GRAY27            0x454545
#define MP_GREY27            0x454545
#define MP_GRAY28            0x474747
#define MP_GREY28            0x474747
#define MP_GRAY29            0x4a4a4a
#define MP_GREY29            0x4a4a4a
#define MP_GRAY30            0x4d4d4d
#define MP_GREY30            0x4d4d4d
#define MP_GRAY31            0x4f4f4f
#define MP_GREY31            0x4f4f4f
#define MP_GRAY32            0x525252
#define MP_GREY32            0x525252
#define MP_GRAY33            0x545454
#define MP_GREY33            0x545454
#define MP_GRAY34            0x575757
#define MP_GREY34            0x575757
#define MP_GRAY35            0x595959
#define MP_GREY35            0x595959
#define MP_GRAY36            0x5c5c5c
#define MP_GREY36            0x5c5c5c
#define MP_GRAY37            0x5e5e5e
#define MP_GREY37            0x5e5e5e
#define MP_GRAY38            0x616161
#define MP_GREY38            0x616161
#define MP_GRAY39            0x636363
#define MP_GREY39            0x636363
#define MP_GRAY40            0x666666
#define MP_GREY40            0x666666
#define MP_GRAY41            0x696969
#define MP_GREY41            0x696969
#define MP_GRAY42            0x6b6b6b
#define MP_GREY42            0x6b6b6b
#define MP_GRAY43            0x6e6e6e
#define MP_GREY43            0x6e6e6e
#define MP_GRAY44            0x707070
#define MP_GREY44            0x707070
#define MP_GRAY45            0x737373
#define MP_GREY45            0x737373
#define MP_GRAY46            0x757575
#define MP_GREY46            0x757575
#define MP_GRAY47            0x787878
#define MP_GREY47            0x787878
#define MP_GRAY48            0x7a7a7a
#define MP_GREY48            0x7a7a7a
#define MP_GRAY49            0x7d7d7d
#define MP_GREY49            0x7d7d7d
#define MP_GRAY50            0x7f7f7f
#define MP_GREY50            0x7f7f7f
#define MP_GRAY51            0x828282
#define MP_GREY51            0x828282
#define MP_GRAY52            0x858585
#define MP_GREY52            0x858585
#define MP_GRAY53            0x878787
#define MP_GREY53            0x878787
#define MP_GRAY54            0x8a8a8a
#define MP_GREY54            0x8a8a8a
#define MP_GRAY55            0x8c8c8c
#define MP_GREY55            0x8c8c8c
#define MP_GRAY56            0x8f8f8f
#define MP_GREY56            0x8f8f8f
#define MP_GRAY57            0x919191
#define MP_GREY57            0x919191
#define MP_GRAY58            0x949494
#define MP_GREY58            0x949494
#define MP_GRAY59            0x969696
#define MP_GREY59            0x969696
#define MP_GRAY60            0x999999
#define MP_GREY60            0x999999
#define MP_GRAY61            0x9c9c9c
#define MP_GREY61            0x9c9c9c
#define MP_GRAY62            0x9e9e9e
#define MP_GREY62            0x9e9e9e
#define MP_GRAY63            0xa1a1a1
#define MP_GREY63            0xa1a1a1
#define MP_GRAY64            0xa3a3a3
#define MP_GREY64            0xa3a3a3
#define MP_GRAY65            0xa6a6a6
#define MP_GREY65            0xa6a6a6
#define MP_GRAY66            0xa8a8a8
#define MP_GREY66            0xa8a8a8
#define MP_GRAY67            0xababab
#define MP_GREY67            0xababab
#define MP_GRAY68            0xadadad
#define MP_GREY68            0xadadad
#define MP_GRAY69            0xb0b0b0
#define MP_GREY69            0xb0b0b0
#define MP_GRAY70            0xb3b3b3
#define MP_GREY70            0xb3b3b3
#define MP_GRAY71            0xb5b5b5
#define MP_GREY71            0xb5b5b5
#define MP_GRAY72            0xb8b8b8
#define MP_GREY72            0xb8b8b8
#define MP_GRAY73            0xbababa
#define MP_GREY73            0xbababa
#define MP_GRAY74            0xbdbdbd
#define MP_GREY74            0xbdbdbd
#define MP_GRAY75            0xbfbfbf
#define MP_GREY75            0xbfbfbf
#define MP_GRAY76            0xc2c2c2
#define MP_GREY76            0xc2c2c2
#define MP_GRAY77            0xc4c4c4
#define MP_GREY77            0xc4c4c4
#define MP_GRAY78            0xc7c7c7
#define MP_GREY78            0xc7c7c7
#define MP_GRAY79            0xc9c9c9
#define MP_GREY79            0xc9c9c9
#define MP_GRAY80            0xcccccc
#define MP_GREY80            0xcccccc
#define MP_GRAY81            0xcfcfcf
#define MP_GREY81            0xcfcfcf
#define MP_GRAY82            0xd1d1d1
#define MP_GREY82            0xd1d1d1
#define MP_GRAY83            0xd4d4d4
#define MP_GREY83            0xd4d4d4
#define MP_GRAY84            0xd6d6d6
#define MP_GREY84            0xd6d6d6
#define MP_GRAY85            0xd9d9d9
#define MP_GREY85            0xd9d9d9
#define MP_GRAY86            0xdbdbdb
#define MP_GREY86            0xdbdbdb
#define MP_GRAY87            0xdedede
#define MP_GREY87            0xdedede
#define MP_GRAY88            0xe0e0e0
#define MP_GREY88            0xe0e0e0
#define MP_GRAY89            0xe3e3e3
#define MP_GREY89            0xe3e3e3
#define MP_GRAY90            0xe5e5e5
#define MP_GREY90            0xe5e5e5
#define MP_GRAY91            0xe8e8e8
#define MP_GREY91            0xe8e8e8
#define MP_GRAY92            0xebebeb
#define MP_GREY92            0xebebeb
#define MP_GRAY93            0xededed
#define MP_GREY93            0xededed
#define MP_GRAY94            0xf0f0f0
#define MP_GREY94            0xf0f0f0
#define MP_GRAY95            0xf2f2f2
#define MP_GREY95            0xf2f2f2
#define MP_GRAY96            0xf5f5f5
#define MP_GREY96            0xf5f5f5
#define MP_GRAY97            0xf7f7f7
#define MP_GREY97            0xf7f7f7
#define MP_GRAY98            0xfafafa
#define MP_GREY98            0xfafafa
#define MP_GRAY99            0xfcfcfc
#define MP_GREY99            0xfcfcfc
#define MP_GRAY100           0xffffff
#define MP_GREY100           0xffffff
#define MP_DARKGREY          0xa9a9a9
#define MP_DARKGRAY          0xa9a9a9
#define MP_DARKBLUE          0x00008b
#define MP_DARKCYAN          0x008b8b
#define MP_DARKMAGENTA       0x8b008b
#define MP_DARKRED           0x8b0000
#define MP_LIGHTGREEN        0x90ee90

#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__) && !TARGET_OS_IPHONE
#define MICROPROFILE_CONTEXT_SWITCH_TRACE 1
#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

#ifndef MICROPROFILE_COUNTER_HISTORY
#define MICROPROFILE_COUNTER_HISTORY 1
#endif

#ifdef _WIN32
#include <basetsd.h>
typedef UINT_PTR MpSocket;
#else
typedef int MpSocket;
#endif

typedef std::thread* MicroProfileThread;

enum MicroProfileDrawMask
{
    MP_DRAW_OFF			= 0x0,
    MP_DRAW_BARS		= 0x1,
    MP_DRAW_DETAILED	= 0x2,
    MP_DRAW_COUNTERS	= 0x3,
    MP_DRAW_FRAME		= 0x4,
    MP_DRAW_HIDDEN		= 0x5,
    MP_DRAW_SIZE		= 0x6,
};

enum MicroProfileDrawBarsMask
{
    MP_DRAW_TIMERS 				= 0x1,
    MP_DRAW_AVERAGE				= 0x2,
    MP_DRAW_MAX					= 0x4,
    MP_DRAW_MIN					= 0x8,
    MP_DRAW_CALL_COUNT			= 0x10,
    MP_DRAW_TIMERS_EXCLUSIVE 	= 0x20,
    MP_DRAW_AVERAGE_EXCLUSIVE 	= 0x40,
    MP_DRAW_MAX_EXCLUSIVE		= 0x80,
    MP_DRAW_META_FIRST			= 0x100,
    MP_DRAW_ALL 				= 0xffffffff,

};

enum MicroProfileCounterFormat
{
    MICROPROFILE_COUNTER_FORMAT_DEFAULT,
    MICROPROFILE_COUNTER_FORMAT_BYTES,
};

enum MicroProfileCounterFlags
{
    MICROPROFILE_COUNTER_FLAG_NONE 			= 0,
    MICROPROFILE_COUNTER_FLAG_DETAILED		= 0x1,
    MICROPROFILE_COUNTER_FLAG_DETAILED_GRAPH= 0x2,
    //internal:
    MICROPROFILE_COUNTER_FLAG_INTERNAL_MASK = ~0x3,
    MICROPROFILE_COUNTER_FLAG_HAS_LIMIT 	= 0x4,
    MICROPROFILE_COUNTER_FLAG_CLOSED		= 0x8,
    MICROPROFILE_COUNTER_FLAG_MANUAL_SWAP	= 0x10,
    MICROPROFILE_COUNTER_FLAG_LEAF			= 0x20,
};

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 MicroProfileCounterInfo
{
    int nParent;
    int nSibling;
    int nFirstChild;
    uint16_t nNameLen;
    uint8_t nLevel;
    char* pName;
    uint32_t nFlags;
    int64_t nLimit;
    MicroProfileCounterFormat eFormat;
};

struct MicroProfileCounterHistory
{
    uint32_t nPut;
    uint64_t nHistory[MICROPROFILE_GRAPH_HISTORY];
};

struct MicroProfileGraphState
{
    int64_t nHistory[MICROPROFILE_GRAPH_HISTORY];
    MicroProfileToken nToken;
    int32_t nKey;
};

struct MicroProfileContextSwitch
{
    MicroProfileThreadIdType nThreadOut;
    MicroProfileThreadIdType nThreadIn;
    MicroProfileProcessIdType nProcessIn;
    int64_t nCpu : 8;
    int64_t nTicks : 56;
};


struct MicroProfileFrameState
{
    int64_t nFrameStartCpu;
    int64_t nFrameStartGpu;
    uint32_t nFrameStartGpuTimer;
    uint32_t nLogStart[MICROPROFILE_MAX_THREADS];
};

struct MicroProfileThreadLog
{
    MicroProfileLogEntry*	Log;
    std::atomic<uint32_t>	nPut;
    std::atomic<uint32_t>	nGet;

    MicroProfileLogEntry*	LogGpu;
    std::atomic<uint32_t>	nPutGpu;
    uint32_t				nStartGpu;
    uint32_t				bActiveGpu;
    void*					pContextGpu;

    uint32_t 				nGpu;
    MicroProfileThreadIdType nThreadId;
    uint32_t 				nLogIndex;

    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];
};

struct MicroProfileGpu
{
    void (*Shutdown)();
    uint32_t (*Flip)();
    uint32_t (*InsertTimer)(void* pContext);
    uint64_t (*GetTimeStamp)(uint32_t nIndex);
    uint64_t (*GetTicksPerSecond)();
    bool (*GetTickReference)(int64_t* pOutCpu, int64_t* pOutGpu);
};

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;

    uint32_t nForceEnable;
    uint32_t nForceMetaCounters;
    uint64_t nForceEnableGroup;
    uint64_t nForceDisableGroup;

    uint64_t nForceGroupUI;
    uint64_t nActiveGroupWanted;
    uint32_t nAllGroupsWanted;
    uint32_t nAllThreadsWanted;

    uint32_t nOverflow;

    uint64_t nGroupMask;
    uint64_t nGroupMaskGpu;
    uint32_t nRunning;
    uint32_t nToggleRunning;
    uint32_t nMaxGroupSize;
    uint32_t nDumpFileNextFrame;
    uint32_t nAutoClearFrames;
    MicroProfileDumpType eDumpType;
    uint32_t nDumpFrames;
    char DumpPath[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				AccumMinTimers[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				AggregateMin[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 				nMemUsage;

    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						bContextSwitchStart;
    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];

    MicroProfileThread			WebServerThread;

    MpSocket 					WebServerSocket;
    uint32_t					nWebServerPort;

    char						WebServerBuffer[MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE];
    uint32_t					nWebServerPut;
    uint64_t 					nWebServerDataSent;

    std::atomic<char*>			LabelBuffer;
    std::atomic<uint64_t>		nLabelPut;

    char 						CounterNames[MICROPROFILE_MAX_COUNTER_NAME_CHARS];
    MicroProfileCounterInfo 	CounterInfo[MICROPROFILE_MAX_COUNTERS];
    uint32_t					nNumCounters;
    uint32_t					nCounterNamePos;
    std::atomic<int64_t> 		Counters[MICROPROFILE_MAX_COUNTERS];

#if MICROPROFILE_COUNTER_HISTORY // uses 1kb per allocated counter. 512kb for default counter count
    uint32_t					nCounterHistoryPut;
    int64_t 					nCounterHistory[MICROPROFILE_GRAPH_HISTORY][MICROPROFILE_MAX_COUNTERS]; //flipped to make swapping cheap, drawing more expensive.
    int64_t 					nCounterMax[MICROPROFILE_MAX_COUNTERS];
    int64_t 					nCounterMin[MICROPROFILE_MAX_COUNTERS];
#endif

    uint32_t					nGpuFrameTimer;
    MicroProfileGpu		 		GPU;
};

#define MP_LOG_TICK_MASK  0x0000ffffffffffff
#define MP_LOG_INDEX_MASK 0x1fff000000000000
#define MP_LOG_BEGIN_MASK 0xe000000000000000
#define MP_LOG_LABEL_LITERAL 0x5
#define MP_LOG_GPU_EXTRA 0x4
#define MP_LOG_LABEL 0x3
#define MP_LOG_META 0x2
#define MP_LOG_ENTER 0x1
#define MP_LOG_LEAVE 0x0


inline uint64_t MicroProfileLogType(MicroProfileLogEntry Index)
{
    return ((MP_LOG_BEGIN_MASK & Index)>>61) & 0x7;
}

inline uint64_t MicroProfileLogTimerIndex(MicroProfileLogEntry Index)
{
    return (MP_LOG_INDEX_MASK & Index)>>48;
}

inline MicroProfileLogEntry MicroProfileMakeLogIndex(uint64_t nBegin, MicroProfileToken nToken, int64_t nTick)
{
    return (nBegin<<61) | (MP_LOG_INDEX_MASK&(nToken<<48)) | (MP_LOG_TICK_MASK&nTick);
}

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; }
template<typename T>
T MicroProfileClamp(T a, T min_, T max_)
{ return MicroProfileMin(max_, MicroProfileMax(min_, a));  }

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
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#if !defined(_MSC_VER) || _MSC_VER < 1900 // VS2015 includes proper snprintf
#define snprintf _snprintf
#endif

#if defined _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#endif
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 MICROPROFILE_WEBSERVER || MICROPROFILE_CONTEXT_SWITCH_TRACE
typedef void* (*MicroProfileThreadFunc)(void*);

inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
{
    *pThread = new std::thread(Func, nullptr);
}
inline void MicroProfileThreadJoin(MicroProfileThread* pThread)
{
    (*pThread)->join();
    delete *pThread;
    *pThread = nullptr;
}
#endif

#if MICROPROFILE_WEBSERVER

#ifdef _WIN32
#if defined(_WINSOCKAPI_) && !defined(_WINSOCK2API_)
#error WinSock.h has already been included; microprofile requires WinSock2
#endif
#include <WinSock2.h>
#define MP_INVALID_SOCKET(f) (f == INVALID_SOCKET)
#endif

#if defined(__APPLE__) || defined(__linux__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <fcntl.h>
#define MP_INVALID_SOCKET(f) (f < 0)
#endif

#endif

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.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;

#ifndef MP_THREAD_LOCAL
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);
MICROPROFILE_DEFINE(g_MicroProfileWebServerUpdate,"MicroProfile", "WebServerUpdate", 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)
    {
        bOnce = false;
        memset((void *)&S, 0, sizeof(S));
        S.nMemUsage = 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.nBars = MP_DRAW_AVERAGE | MP_DRAW_MAX | MP_DRAW_CALL_COUNT;
        S.nActiveGroup = 0;
        S.nActiveBars = 0;
        S.nForceEnableGroup = 0;
        S.nForceDisableGroup = 0;
        S.nAllGroupsWanted = 0;
        S.nActiveGroupWanted = 0;
        S.nAllThreadsWanted = 1;
        S.nAggregateFlip = 60;
        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;
        int64_t nTick = MP_TICK();
        for(int i = 0; i < MICROPROFILE_MAX_FRAME_HISTORY; ++i)
        {
            S.Frames[i].nFrameStartCpu = nTick;
            S.Frames[i].nFrameStartGpu = 0;
            S.Frames[i].nFrameStartGpuTimer = (uint32_t)-1;
        }

#if MICROPROFILE_COUNTER_HISTORY
        S.nCounterHistoryPut = 0;
        for(uint32_t i = 0; i < MICROPROFILE_MAX_COUNTERS; ++i)
        {
            S.nCounterMin[i] = 0x7fffffffffffffff;
            S.nCounterMax[i] = 0x8000000000000000;
        }
#endif

        S.nGpuFrameTimer = (uint32_t)-1;

        MicroProfileThreadLog* pGpu = MicroProfileCreateThreadLog("GPU");
        g_MicroProfileGpuLog = pGpu;
        MP_ASSERT(S.Pool[0] == pGpu);
        pGpu->nGpu = 1;
        pGpu->nThreadId = 0;
    }
    if(bUseLock)
        mutex.unlock();
}

void MicroProfileShutdown()
{
    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex());
    MicroProfileWebServerStop();
    MicroProfileContextSwitchTraceStop();
    MicroProfileGpuShutdown();
}

#ifndef MP_THREAD_LOCAL
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;
    uint32_t nLogIndex = 0;
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
        {
            nLogIndex = i;
            pLog = new MicroProfileThreadLog;
            S.nMemUsage += sizeof(MicroProfileThreadLog);
            S.Pool[i] = pLog;
            break;
        }
    }

    if(!pLog)
        return nullptr;

    memset((void *)pLog, 0, sizeof(*pLog));
    pLog->nLogIndex = nLogIndex;
    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();
    return pLog;
}

void MicroProfileOnThreadCreate(const char* pThreadName)
{
    g_bUseLock = true;
    MicroProfileInit();
    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex());
    if(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);

        S.Pool[nLogIndex] = 0;

        for(int i = 0; i < MICROPROFILE_MAX_FRAME_HISTORY; ++i)
        {
            S.Frames[i].nLogStart[nLogIndex] = 0;
        }

        if(pLog->Log)
        {
            delete[] pLog->Log;
            S.nMemUsage -= sizeof(MicroProfileLogEntry) * MICROPROFILE_BUFFER_SIZE;
        }

        if(pLog->LogGpu)
        {
            delete[] pLog->LogGpu;
            S.nMemUsage -= sizeof(MicroProfileLogEntry) * MICROPROFILE_GPU_BUFFER_SIZE;
        }

        delete pLog;
        S.nMemUsage -= sizeof(MicroProfileThreadLog);

        MicroProfileSetThreadLog(0);
    }
}

MicroProfileThreadLog* MicroProfileGetOrCreateThreadLog()
{
    MicroProfileThreadLog* pLog = MicroProfileGetThreadLog();

    if (!pLog)
    {
        MicroProfileOnThreadCreate(nullptr);
        pLog = MicroProfileGetThreadLog();
    }

    return pLog;
}

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 = S.nGroupCount++;
    MP_ASSERT(nGroupIndex < MICROPROFILE_MAX_GROUPS);

    size_t nLen = strlen(pGroup);
    if(nLen > MICROPROFILE_NAME_MAX_LEN-1)
        nLen = MICROPROFILE_NAME_MAX_LEN-1;
    memcpy(&S.GroupInfo[nGroupIndex].pName[0], pGroup, nLen);
    S.GroupInfo[nGroupIndex].pName[nLen] = '\0';
    S.GroupInfo[nGroupIndex].nNameLen = (uint32_t)nLen;

    S.GroupInfo[nGroupIndex].nNumTimers = 0;
    S.GroupInfo[nGroupIndex].nGroupIndex = nGroupIndex;
    S.GroupInfo[nGroupIndex].Type = Type;
    S.GroupInfo[nGroupIndex].nMaxTimerNameLen = 0;
    S.GroupInfo[nGroupIndex].nColor = 0x88888888;
    S.GroupInfo[nGroupIndex].nCategory = 0;

    S.CategoryInfo[0].nGroupMask |= 1ll << nGroupIndex;
    S.nGroupMask |= 1ll << nGroupIndex;
    S.nGroupMaskGpu |= uint64_t(Type == MicroProfileTokenTypeGpu) << nGroupIndex;

    if ((S.nRunning || S.nForceEnable) && S.nAllGroupsWanted)
        S.nActiveGroup |= 1ll << nGroupIndex;

    return nGroupIndex;
}

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;
    if(S.nTotalTimers == MICROPROFILE_MAX_TIMERS)
        return MICROPROFILE_INVALID_TOKEN;
    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);

    if(nColor == 0xffffffff)
    {
        // http://www.two4u.com/color/small-txt.html with some omissions
        static const int kDebugColors[] =
        {
            0x70DB93, 0xB5A642, 0x5F9F9F, 0xB87333, 0x4F6F4F, 0x9932CD,
            0x871F78, 0x855E42, 0x545454, 0x8E2323, 0x238E23, 0xCD7F32,
            0xDBDB70, 0x527F76, 0x9F9F5F, 0x8E236B, 0xFF2F4F, 0xCFB53B,
            0xFF7F00, 0xDB70DB, 0x5959AB, 0x8C1717, 0x238E68, 0x6B4226,
            0x8E6B23, 0x007FFF, 0x00FF7F, 0x236B8E, 0x38B0DE, 0xDB9370,
            0xCC3299, 0x99CC32,
        };

        // djb2
        unsigned int result = 5381;
        for (const char* i = pGroup; *i; ++i)
            result = result * 33 ^ *i;
        for (const char* i = pName; *i; ++i)
            result = result * 33 ^ *i;

        nColor = kDebugColors[result % (sizeof(kDebugColors) / sizeof(kDebugColors[0]))];
    }

    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 MicroProfileGetLabelToken(const char* pGroup, MicroProfileTokenType Type)
{
    MicroProfileInit();
    MicroProfileScopeLock L(MicroProfileMutex());

    uint16_t nGroupIndex = MicroProfileGetGroup(pGroup, Type);
    uint64_t nGroupMask = 1ll << nGroupIndex;
    MicroProfileToken nToken = MicroProfileMakeToken(nGroupMask, 0);

    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;
}

const char* MicroProfileNextName(const char* pName, char* pNameOut, uint32_t* nSubNameLen)
{
    uint32_t nMaxLen = MICROPROFILE_NAME_MAX_LEN-1;
    const char* pRet = 0;
    bool bDone = false;
    uint32_t nChars = 0;
    for(uint32_t i = 0; i < nMaxLen && !bDone; ++i)
    {
        char c = *pName++;
        switch(c)
        {
            case 0:
                bDone = true;
                break;
            case '\\':
            case '/':
                if(nChars)
                {
                    bDone = true;
                    pRet = pName;
                }
                break;
            default:
                nChars++;
                *pNameOut++ = c;
        }
    }
    *nSubNameLen = nChars;
    *pNameOut = '\0';
    return pRet;
}


const char* MicroProfileCounterFullName(int nCounter)
{
    static char Buffer[1024];
    int nNodes[32];
    int nIndex = 0;
    do
    {
        nNodes[nIndex++] = nCounter;
        nCounter = S.CounterInfo[nCounter].nParent;
    }while(nCounter >= 0);
    int nOffset = 0;
    while(nIndex >= 0 && nOffset < (int)sizeof(Buffer)-2)
    {
        uint32_t nLen = S.CounterInfo[nNodes[nIndex]].nNameLen + nOffset;// < sizeof(Buffer)-1
        nLen = MicroProfileMin((uint32_t)(sizeof(Buffer) - 2 - nOffset), nLen);
        memcpy(&Buffer[nOffset], S.CounterInfo[nNodes[nIndex]].pName, nLen);

        nOffset += S.CounterInfo[nNodes[nIndex]].nNameLen+1;
        if(nIndex)
        {
            Buffer[nOffset++] = '/';
        }
        nIndex--;
    }
    return &Buffer[0];
}

int MicroProfileGetCounterTokenByParent(int nParent, const char* pName)
{
    for(uint32_t i = 0; i < S.nNumCounters; ++i)
    {
        if(nParent == S.CounterInfo[i].nParent && !MP_STRCASECMP(S.CounterInfo[i].pName, pName))
        {
            return i;
        }
    }
    MicroProfileToken nResult = S.nNumCounters++;
    S.CounterInfo[nResult].nParent = nParent;
    S.CounterInfo[nResult].nSibling = -1;
    S.CounterInfo[nResult].nFirstChild = -1;
    S.CounterInfo[nResult].nFlags = 0;
    S.CounterInfo[nResult].eFormat = MICROPROFILE_COUNTER_FORMAT_DEFAULT;
    S.CounterInfo[nResult].nLimit = 0;
    int nLen = (int)strlen(pName)+1;

    MP_ASSERT(nLen + S.nCounterNamePos <= MICROPROFILE_MAX_COUNTER_NAME_CHARS);
    uint32_t nPos = S.nCounterNamePos;
    S.nCounterNamePos += nLen;
    memcpy(&S.CounterNames[nPos], pName, nLen);
    S.CounterInfo[nResult].nNameLen = nLen-1;
    S.CounterInfo[nResult].pName = &S.CounterNames[nPos];
    if(nParent >= 0)
    {
        S.CounterInfo[nResult].nSibling = S.CounterInfo[nParent].nFirstChild;
        S.CounterInfo[nResult].nLevel = S.CounterInfo[nParent].nLevel + 1;
        S.CounterInfo[nParent].nFirstChild = nResult;
    }
    else
    {
        S.CounterInfo[nResult].nLevel = 0;
    }

    return nResult;
}

MicroProfileToken MicroProfileGetCounterToken(const char* pName)
{
    MicroProfileInit();
    MicroProfileScopeLock L(MicroProfileMutex());
    char SubName[MICROPROFILE_NAME_MAX_LEN];
    int nResult = -1;
    do
    {
        uint32_t nLen = 0;
        pName = MicroProfileNextName(pName, &SubName[0], &nLen);
        if(0 == nLen)
        {
            break;
        }
        nResult = MicroProfileGetCounterTokenByParent(nResult, SubName);

    }while(pName != 0);
    S.CounterInfo[nResult].nFlags |= MICROPROFILE_COUNTER_FLAG_LEAF;

    MP_ASSERT(nResult >= 0);
    return nResult;
}

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
    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
    {
        if(!pLog->Log)
        {
            pLog->Log = new MicroProfileLogEntry[MICROPROFILE_BUFFER_SIZE];
            memset(pLog->Log, 0, sizeof(MicroProfileLogEntry) * MICROPROFILE_BUFFER_SIZE);
            S.nMemUsage += sizeof(MicroProfileLogEntry) * MICROPROFILE_BUFFER_SIZE;
        }
        pLog->Log[nPos] = MicroProfileMakeLogIndex(nBegin, nToken_, nTick);
        pLog->nPut.store(nNextPos, std::memory_order_release);
    }
}

inline void MicroProfileLogPutGpu(MicroProfileToken nToken_, uint64_t nTick, uint64_t nBegin, MicroProfileThreadLog* pLog)
{
#if MICROPROFILE_GPU_TIMERS_MULTITHREADED
    MP_ASSERT(pLog != 0); //this assert is hit if MicroProfileOnCreateThread is not called
    uint32_t nPos = pLog->nPutGpu.load(std::memory_order_relaxed);
    if(nPos >= MICROPROFILE_GPU_BUFFER_SIZE)
    {
        S.nOverflow = 100;
    }
    else
    {
        if(!pLog->LogGpu)
        {
            pLog->LogGpu = new MicroProfileLogEntry[MICROPROFILE_GPU_BUFFER_SIZE];
            memset(pLog->LogGpu, 0, sizeof(MicroProfileLogEntry) * MICROPROFILE_GPU_BUFFER_SIZE);
            S.nMemUsage += sizeof(MicroProfileLogEntry) * MICROPROFILE_GPU_BUFFER_SIZE;
        }
        pLog->LogGpu[nPos] = MicroProfileMakeLogIndex(nBegin, nToken_, nTick);
        pLog->nPutGpu.store(nPos + 1, std::memory_order_release);
    }
#else
    (void)pLog;

    MicroProfileLogPut(nToken_, nTick, nBegin, g_MicroProfileGpuLog);
#endif
}

uint64_t MicroProfileEnter(MicroProfileToken nToken_)
{
    uint64_t nGroupMask = MicroProfileGetGroupMask(nToken_);
    if(nGroupMask & S.nActiveGroup)
    {
        if (MicroProfileThreadLog* pLog = MicroProfileGetOrCreateThreadLog())
        {
            if (nGroupMask & S.nGroupMaskGpu)
            {
                uint32_t nTimer = MicroProfileGpuInsertTimer(pLog->pContextGpu);
                if (nTimer != (uint32_t)-1)
                {
                    MicroProfileLogPutGpu(nToken_, nTimer, MP_LOG_ENTER, pLog);
                    MicroProfileLogPutGpu(pLog->nLogIndex, MP_TICK(), MP_LOG_GPU_EXTRA, pLog);
                    return 0;
                }
            }
            else
            {
                uint64_t nTick = MP_TICK();
                MicroProfileLogPut(nToken_, nTick, MP_LOG_ENTER, pLog);
                return nTick;
            }
        }
    }
    return MICROPROFILE_INVALID_TICK;
}

uint64_t MicroProfileAllocateLabel(const char* pName)
{
    char* pLabelBuffer = S.LabelBuffer.load(std::memory_order_consume);
    if(!pLabelBuffer)
    {
        MicroProfileScopeLock L(MicroProfileMutex());

        pLabelBuffer = S.LabelBuffer.load(std::memory_order_consume);
        if(!pLabelBuffer)
        {
            pLabelBuffer = new char[MICROPROFILE_LABEL_BUFFER_SIZE + MICROPROFILE_LABEL_MAX_LEN];
            memset(pLabelBuffer, 0, MICROPROFILE_LABEL_BUFFER_SIZE + MICROPROFILE_LABEL_MAX_LEN);
            S.nMemUsage += MICROPROFILE_LABEL_BUFFER_SIZE + MICROPROFILE_LABEL_MAX_LEN;

            S.LabelBuffer.store(pLabelBuffer, std::memory_order_release);
        }
    }

    size_t nLen = strlen(pName);

    if(nLen > MICROPROFILE_LABEL_MAX_LEN - 1)
        nLen = MICROPROFILE_LABEL_MAX_LEN - 1;

    uint64_t nLabel = S.nLabelPut.fetch_add(nLen + 1, std::memory_order_relaxed);
    char* pLabel = &pLabelBuffer[nLabel % MICROPROFILE_LABEL_BUFFER_SIZE];

    memcpy(pLabel, pName, nLen);
    pLabel[nLen] = 0;

    return nLabel;
}

void MicroProfilePutLabel(MicroProfileToken nToken_, const char* pName)
{
    if(MicroProfileThreadLog* pLog = MicroProfileGetThreadLog())
    {
        uint64_t nLabel = MicroProfileAllocateLabel(pName);
        uint64_t nGroupMask = MicroProfileGetGroupMask(nToken_);

        if(nGroupMask & S.nGroupMaskGpu)
            MicroProfileLogPutGpu(nToken_, nLabel, MP_LOG_LABEL, pLog);
        else
            MicroProfileLogPut(nToken_, nLabel, MP_LOG_LABEL, pLog);
    }
}

void MicroProfilePutLabelLiteral(MicroProfileToken nToken_, const char* pName)
{
    if(MicroProfileThreadLog* pLog = MicroProfileGetThreadLog())
    {
        uint64_t nLabel = uint64_t(uintptr_t(pName));
        uint64_t nGroupMask = MicroProfileGetGroupMask(nToken_);

        // We can only store 48 bits for each label pointer; for 32-bit platforms the entire pointer fits as is
        // For 64-bit platforms, most platforms use 48-bit addressing.
        // In some cases, like x64, the top 16 bits are 1 or 0 based on the bit 47.
        // In some other cases, like AArch64 (I think?), the top 16 bits are all 0 in user space, even if bit 47 is 1.
        // To avoid dealing with these complications, clear out the pointer if it doesn't fit in 48 bits without modifications.
        // This will automatically safely ignore pointers that don't fit.
        nLabel = (nLabel & MP_LOG_TICK_MASK) == nLabel ? nLabel : 0;

        if(nGroupMask & S.nGroupMaskGpu)
            MicroProfileLogPutGpu(nToken_, nLabel, MP_LOG_LABEL_LITERAL, pLog);
        else
            MicroProfileLogPut(nToken_, nLabel, MP_LOG_LABEL_LITERAL, pLog);
    }
}

void MicroProfileCounterAdd(MicroProfileToken nToken, int64_t nCount)
{
    MP_ASSERT(nToken < S.nNumCounters);
    S.Counters[nToken].fetch_add(nCount);
}
void MicroProfileCounterSet(MicroProfileToken nToken, int64_t nCount)
{
    MP_ASSERT(nToken < S.nNumCounters);
    S.Counters[nToken].store(nCount);
}
void MicroProfileCounterSetLimit(MicroProfileToken nToken, int64_t nCount)
{
    MP_ASSERT(nToken < S.nNumCounters);
    S.CounterInfo[nToken].nLimit = nCount;
}

void MicroProfileCounterConfig(const char* pName, uint32_t eFormat, int64_t nLimit, uint32_t nFlags)
{
    MicroProfileToken nToken = MicroProfileGetCounterToken(pName);
    S.CounterInfo[nToken].eFormat = (MicroProfileCounterFormat)eFormat;
    S.CounterInfo[nToken].nLimit = nLimit;
    S.CounterInfo[nToken].nFlags |= (nFlags & ~MICROPROFILE_COUNTER_FLAG_INTERNAL_MASK);
}

const char* MicroProfileGetLabel(uint32_t eType, uint64_t nLabel)
{
    MP_ASSERT(eType == MP_LOG_LABEL || eType == MP_LOG_LABEL_LITERAL);
    MP_ASSERT((nLabel & MP_LOG_TICK_MASK) == nLabel);

    if(eType == MP_LOG_LABEL_LITERAL)
        return (const char*)uintptr_t(nLabel);

    char* pLabelBuffer = S.LabelBuffer.load(std::memory_order_relaxed);
    uint64_t nLabelPut = S.nLabelPut.load(std::memory_order_relaxed);

    MP_ASSERT(pLabelBuffer && nLabel < nLabelPut);

    if(nLabelPut - nLabel > MICROPROFILE_LABEL_BUFFER_SIZE)
        return 0;
    else
        return &pLabelBuffer[nLabel % MICROPROFILE_LABEL_BUFFER_SIZE];
}

void MicroProfileLabel(MicroProfileToken nToken_, const char* pName)
{
    if(MicroProfileGetGroupMask(nToken_) & S.nActiveGroup)
    {
        MicroProfilePutLabel(nToken_, pName);
    }
}

void MicroProfileLabelFormat(MicroProfileToken nToken_, const char* pName, ...)
{
    va_list args;
    va_start(args, pName);
    MicroProfileLabelFormatV(nToken_, pName, args);
    va_end(args);
}

void MicroProfileLabelFormatV(MicroProfileToken nToken_, const char* pName, va_list args)
{
    if(MicroProfileGetGroupMask(nToken_) & S.nActiveGroup)
    {
        char buffer[MICROPROFILE_LABEL_MAX_LEN];
        vsnprintf(buffer, sizeof(buffer)-1, pName, args);

        buffer[sizeof(buffer)-1] = 0;

        MicroProfilePutLabel(nToken_, buffer);
    }
}

void MicroProfileLabelLiteral(MicroProfileToken nToken_, const char* pName)
{
    if(MicroProfileGetGroupMask(nToken_) & S.nActiveGroup)
    {
        MicroProfilePutLabelLiteral(nToken_, pName);
    }
}

void MicroProfileMetaUpdate(MicroProfileToken nToken, int nCount, MicroProfileTokenType eTokenType)
{
    if((MP_DRAW_META_FIRST<<nToken) & S.nActiveBars)
    {
        if (MicroProfileThreadLog* pLog = MicroProfileGetThreadLog())
        {
            MP_ASSERT(nToken < MICROPROFILE_META_MAX);

            if (MicroProfileTokenTypeGpu == eTokenType)
                MicroProfileLogPutGpu(nToken, nCount, MP_LOG_META, pLog);
            else
                MicroProfileLogPut(nToken, nCount, MP_LOG_META, pLog);
        }
    }
}

void MicroProfileLeave(MicroProfileToken nToken_, uint64_t nTickStart)
{
    if(MICROPROFILE_INVALID_TICK != nTickStart)
    {
        if (MicroProfileThreadLog* pLog = MicroProfileGetOrCreateThreadLog())
        {
            uint64_t nGroupMask = MicroProfileGetGroupMask(nToken_);

            if (nGroupMask & S.nGroupMaskGpu)
            {
                uint32_t nTimer = MicroProfileGpuInsertTimer(pLog->pContextGpu);
                MicroProfileLogPutGpu(nToken_, nTimer, MP_LOG_LEAVE, pLog);
                MicroProfileLogPutGpu(pLog->nLogIndex, MP_TICK(), MP_LOG_GPU_EXTRA, pLog);
            }
            else
            {
                uint64_t nTick = MP_TICK();
                MicroProfileLogPut(nToken_, nTick, MP_LOG_LEAVE, pLog);
            }
        }
    }
}

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 MicroProfileDumpToFile();

void MicroProfileFlipGpu()
{
    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex());

    for (uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if (S.Pool[i])
        {
            MP_ASSERT(!S.Pool[i]->bActiveGpu);

            S.Pool[i]->nPutGpu.store(0);
        }
    }

    S.nGpuFrameTimer = MicroProfileGpuFlip();
}

void MicroProfileFlipCpu()
{
    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex());


    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.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->nFrameStartGpuTimer = S.nGpuFrameTimer;

        if(pFrameCurrent->nFrameStartGpuTimer != (uint32_t)-1)
        {
            uint64_t nTick = MicroProfileGpuGetTimeStamp(pFrameCurrent->nFrameStartGpuTimer);

            pFrameCurrent->nFrameStartGpu = (nTick == MICROPROFILE_INVALID_TICK) ? 0 : nTick;
        }

        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)
                    {
                        uint64_t nLastTick = pFrameCurrent->nFrameStartGpu;

                        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];

                                int Type = MicroProfileLogType(L);

                                if(Type == MP_LOG_ENTER || Type == MP_LOG_LEAVE)
                                {
                                    uint32_t nTimer = MicroProfileLogGetTick(L);
                                    uint64_t nTick = MicroProfileGpuGetTimeStamp(nTimer);

                                    if(nTick != MICROPROFILE_INVALID_TICK)
                                        nLastTick = nTick;

                                    pLog->Log[k] = MicroProfileLogSetTick(L, nLastTick);
                                }
                            }
                        }
                    }


                    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];
                            uint64_t 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.AccumMinTimers[i] = MicroProfileMin(S.AccumMinTimers[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.AggregateMin[0], &S.AccumMinTimers[0], sizeof(S.AggregateMin[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.AccumMinTimers[0], 0xFF, sizeof(S.AccumMinTimers[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();
        }

        #if MICROPROFILE_COUNTER_HISTORY
        int64_t* pDest = &S.nCounterHistory[S.nCounterHistoryPut][0];
        S.nCounterHistoryPut = (S.nCounterHistoryPut+1) % MICROPROFILE_GRAPH_HISTORY;
        for(uint32_t i = 0; i < S.nNumCounters; ++i)
        {
            if(0 != (S.CounterInfo[i].nFlags & MICROPROFILE_COUNTER_FLAG_DETAILED))
            {
                uint64_t nValue = S.Counters[i].load(std::memory_order_relaxed);
                pDest[i] = nValue;
                S.nCounterMin[i] = MicroProfileMin(S.nCounterMin[i], (int64_t)nValue);
                S.nCounterMax[i] = MicroProfileMax(S.nCounterMax[i], (int64_t)nValue);
            }
        }
        #endif
    }
    S.nAggregateClear = 0;

    uint64_t nNewActiveGroup = 0;
    if (S.nRunning || S.nForceEnable)
        nNewActiveGroup = S.nAllGroupsWanted ? S.nGroupMask : S.nActiveGroupWanted;
    nNewActiveGroup |= S.nForceEnableGroup;
    nNewActiveGroup |= S.nForceGroupUI;
    nNewActiveGroup &= ~S.nForceDisableGroup;
    if(S.nActiveGroup != nNewActiveGroup)
        S.nActiveGroup = nNewActiveGroup;

    uint32_t nNewActiveBars = 0;
    if (S.nRunning || S.nForceEnable)
        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 MicroProfileFlip()
{
    MICROPROFILE_SCOPE(g_MicroProfileFlip);

    MicroProfileFlipGpu();
    MicroProfileFlipCpu();
}

void MicroProfileGpuSetContext(void* pContext)
{
    if(MicroProfileThreadLog* pLog = MicroProfileGetOrCreateThreadLog())
    {
        pLog->pContextGpu = pContext;
    }
}

void MicroProfileGpuBegin(void* pContext)
{
    if(MicroProfileThreadLog* pLog = MicroProfileGetOrCreateThreadLog())
    {
        MP_ASSERT(!pLog->bActiveGpu);

        pLog->pContextGpu = pContext;
        pLog->nStartGpu = pLog->nPutGpu.load();
        pLog->bActiveGpu = 1;
    }
}

uint64_t MicroProfileGpuEnd()
{
    if(MicroProfileThreadLog* pLog = MicroProfileGetThreadLog())
    {
        MP_ASSERT(pLog->bActiveGpu);

        uint32_t nStartGpu = pLog->nStartGpu;
        uint32_t nPutGpu = pLog->nPutGpu.load();
        MP_ASSERT(nPutGpu >= nStartGpu);

        pLog->pContextGpu = 0;
        pLog->nStartGpu = nPutGpu;
        pLog->bActiveGpu = 0;

        MP_ASSERT(MICROPROFILE_GPU_BUFFER_SIZE <= 1 << 24);

        return (uint64_t(pLog->nLogIndex) << 48) | (uint64_t(nStartGpu) << 24) | uint64_t(nPutGpu);
    }
    return 0;
}

void MicroProfileGpuSubmit(uint64_t nWork)
{
    if (!nWork)
        return;

    uint32_t nLogIndex = (nWork >> 48) & 0xffff;
    uint32_t nStart = (nWork >> 24) & 0xffffff;
    uint32_t nEnd = nWork & 0xffffff;

    MP_ASSERT(nLogIndex < MICROPROFILE_MAX_THREADS);
    MP_ASSERT(nStart <= nEnd);

    MicroProfileThreadLog* pLog = S.Pool[nLogIndex];
    if (!pLog)
        return;

    MP_ASSERT(nEnd <= pLog->nStartGpu);

    for (uint32_t i = nStart; i < nEnd; ++i)
    {
        MicroProfileLogEntry LE = pLog->LogGpu[i];

        MicroProfileLogPut(MicroProfileLogTimerIndex(LE), MicroProfileLogGetTick(LE), MicroProfileLogType(LE), g_MicroProfileGpuLog);
    }
}

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.nForceEnableGroup |= (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.nForceDisableGroup |= (1ll << nGroup);
}

void MicroProfileCalcAllTimers(float* pTimers, float* pAverage, float* pMax, float* pMin, 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 fMinMs = fToMs * (S.AggregateMin[nTimer] != uint64_t(-1) ? S.AggregateMin[nTimer] : 0);
        float fMinPrc = MicroProfileMin(fMinMs * 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;
        pMin[nIdx] = fMinMs;
        pMin[nIdx + 1] = fMinPrc;
        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;
}


int MicroProfileFormatCounter(int eFormat, int64_t nCounter, char* pOut, uint32_t nBufferSize)
{
    if (!nCounter)
    {
        pOut[0] = '0';
        pOut[1] = '\0';
        return 1;
    }
    int nLen = 0;
    char* pBase = pOut;
    char* pTmp = pOut;
    char* pEnd = pOut + nBufferSize;

    switch (eFormat)
    {
    case MICROPROFILE_COUNTER_FORMAT_DEFAULT:
    {
        int nNegative = 0;
        if(nCounter < 0)
        {
            nCounter = -nCounter;
            nNegative = 1;
        }
        int nSeperate = 0;
        while (nCounter)
        {
            if (nSeperate)
            {
                *pTmp++ = ' ';
            }
            nSeperate = 1;
            for (uint32_t i = 0; nCounter && i < 3; ++i)
            {
                int nDigit = nCounter % 10;
                nCounter /= 10;
                *pTmp++ = '0' + nDigit;
            }
        }
        if(nNegative)
        {
            *pTmp++ = '-';
        }
        nLen = pTmp - pOut;
        --pTmp;
        MP_ASSERT(pTmp <= pEnd);
        while (pTmp > pOut) //reverse string
        {
            char c = *pTmp;
            *pTmp = *pOut;
            *pOut = c;
            pTmp--;
            pOut++;
        }
    }
    break;
    case MICROPROFILE_COUNTER_FORMAT_BYTES:
    {
        const char* pExt[] = { "b","kb","mb","gb","tb","pb", "eb","zb", "yb" };
        size_t nNumExt = sizeof(pExt) / sizeof(pExt[0]);
        int64_t nShift = 0;
        int64_t nDivisor = 1;
        int64_t nCountShifted = nCounter >> 10;
        while (nCountShifted)
        {
            nDivisor <<= 10;
            nCountShifted >>= 10;
            nShift++;
        }
        MP_ASSERT(nShift < (int64_t)nNumExt);
        if (nShift)
        {
            nLen = snprintf(pOut, nBufferSize - 1, "%3.2f%s", (double)nCounter / nDivisor, pExt[nShift]);
        }
        else
        {
            nLen = snprintf(pOut, nBufferSize - 1, "%" PRIi64 "%s", (int64_t)nCounter, pExt[nShift]);
        }
        nLen = strlen(pOut);
    }
    break;
    }
    pBase[nLen] = '\0';

    return nLen;
}

void MicroProfileDumpFile(const char* pPath, MicroProfileDumpType eType, uint32_t nFrames)
{
    size_t nLen = strlen(pPath);
    if(nLen > sizeof(S.DumpPath)-1)
    {
        return;
    }
    memcpy(S.DumpPath, pPath, nLen+1);
    S.nDumpFileNextFrame = 1;
    S.eDumpType = eType;
    S.nDumpFrames = nFrames;
}

MICROPROFILE_FORMAT(3, 4) void MicroProfilePrintf(MicroProfileWriteCallback CB, void* Handle, const char* pFmt, ...)
{
    char buffer[4096];
    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);
}

void MicroProfilePrintUIntComma(MicroProfileWriteCallback CB, void* Handle, uint64_t nData)
{
    char Buffer[32];

    uint32_t nOffset = sizeof(Buffer);
    Buffer[--nOffset] = ',';

    if(nData < 10)
    {
        Buffer[--nOffset] = '0' + nData;
    }
    else
    {
        do
        {
            Buffer[--nOffset] = "0123456789abcdef"[nData & 0xf];
            nData >>= 4;
        }
        while(nData);

        Buffer[--nOffset] = 'x';
        Buffer[--nOffset] = '0';
    }

    CB(Handle, sizeof(Buffer) - nOffset, &Buffer[nOffset]);
}

void MicroProfilePrintString(MicroProfileWriteCallback CB, void* Handle, const char* pData)
{
    CB(Handle, strlen(pData), pData);
}

void MicroProfileDumpCsv(MicroProfileWriteCallback CB, void* Handle, int nMaxFrames)
{
    (void)nMaxFrames;

    uint32_t nAggregateFrames = S.nAggregateFrames ? S.nAggregateFrames : 1;
    float fToMsCPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondCpu());
    float fToMsGPU = MicroProfileTickToMsMultiplier(MicroProfileTicksPerSecondGpu());

    MicroProfilePrintf(CB, Handle, "frames,%d\n", nAggregateFrames);
    MicroProfilePrintf(CB, Handle, "group,name,average,max,callaverage\n");

    uint32_t nNumTimers = S.nTotalTimers;
    uint32_t nBlockSize = 2 * nNumTimers;
    float* pTimers = (float*)alloca(nBlockSize * 9 * sizeof(float));
    float* pAverage = pTimers + nBlockSize;
    float* pMax = pTimers + 2 * nBlockSize;
    float* pMin = pTimers + 3 * nBlockSize;
    float* pCallAverage = pTimers + 4 * nBlockSize;
    float* pTimersExclusive = pTimers + 5 * nBlockSize;
    float* pAverageExclusive = pTimers + 6 * nBlockSize;
    float* pMaxExclusive = pTimers + 7 * nBlockSize;
    float* pTotal = pTimers + 8 * nBlockSize;

    MicroProfileCalcAllTimers(pTimers, pAverage, pMax, pMin, pCallAverage, pTimersExclusive, pAverageExclusive, pMaxExclusive, pTotal, nNumTimers);

    for(uint32_t i = 0; i < S.nTotalTimers; ++i)
    {
        uint32_t nIdx = i * 2;
        MicroProfilePrintf(CB, Handle, "\"%s\",\"%s\",%f,%f,%f\n", S.TimerInfo[i].pName, S.GroupInfo[S.TimerInfo[i].nGroupIndex].pName, pAverage[nIdx], pMax[nIdx], pCallAverage[nIdx]);
    }

    MicroProfilePrintf(CB, Handle, "\n\n");

    MicroProfilePrintf(CB, Handle, "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')
        {
            MicroProfilePrintf(CB, Handle, "\"%s\",%.3f,%.3f,%.3f\n", pGroupName, fToMs * S.AggregateGroup[j] / nAggregateFrames, fToMs * S.AggregateGroup[j] / nAggregateFrames, fToMs * S.AggregateGroup[j]);
        }
    }

    MicroProfilePrintf(CB, Handle, "\n\n");
    MicroProfilePrintf(CB, Handle, "group,thread,average,total\n");
    for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
    {
        for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++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;
                        MicroProfilePrintf(CB, Handle, "\"%s\",\"%s\",%.3f,%.3f\n", pGroupName, pThreadName, fTime, fTimeTotal);
                    }
                }
            }
        }
    }

    MicroProfilePrintf(CB, Handle, "\n\n");
    MicroProfilePrintf(CB, Handle, "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;
        MicroProfilePrintf(CB, Handle, "%f,", nTicks * fToMsCPU);
    }
    MicroProfilePrintf(CB, Handle, "\n");

    MicroProfilePrintf(CB, Handle, "\n\n");
    MicroProfilePrintf(CB, Handle, "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;
        MicroProfilePrintf(CB, Handle, "%f,", nTicks * fToMsGPU);
    }
    MicroProfilePrintf(CB, Handle, "\n\n");
    MicroProfilePrintf(CB, Handle, "Meta\n");//only single frame snapshot
    MicroProfilePrintf(CB, Handle, "name,average,max,total\n");
    for(int j = 0; j < MICROPROFILE_META_MAX; ++j)
    {
        if(S.MetaCounters[j].pName)
        {
            MicroProfilePrintf(CB, Handle, "\"%s\",%f,%" PRIi64 ",%" PRIi64 "\n",S.MetaCounters[j].pName, S.MetaCounters[j].nSumAggregate / (float)nAggregateFrames, (int64_t)S.MetaCounters[j].nSumAggregateMax, (int64_t)S.MetaCounters[j].nSumAggregate);
        }
    }
}

#if 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;


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 = %" PRIi64 ";\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;

    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;
        uint32_t nColor = S.TimerInfo[i].nColor;
        MicroProfilePrintf(CB, Handle, "GroupInfo[%d] = MakeGroup(%d, \"%s\", %d, %d, %d, %f, %f, %f, '#%06x');\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(nColor) & 0xff) << 16) | ((MICROPROFILE_UNPACK_GREEN(nColor) & 0xff) << 8) | (MICROPROFILE_UNPACK_BLUE(nColor) & 0xff));
    }
    //timers

    uint32_t nNumTimers = S.nTotalTimers;
    uint32_t nBlockSize = 2 * nNumTimers;
    float* pTimers = (float*)alloca(nBlockSize * 9 * sizeof(float));
    float* pAverage = pTimers + nBlockSize;
    float* pMax = pTimers + 2 * nBlockSize;
    float* pMin = pTimers + 3 * nBlockSize;
    float* pCallAverage = pTimers + 4 * nBlockSize;
    float* pTimersExclusive = pTimers + 5 * nBlockSize;
    float* pAverageExclusive = pTimers + 6 * nBlockSize;
    float* pMaxExclusive = pTimers + 7 * nBlockSize;
    float* pTotal = pTimers + 8 * nBlockSize;

    MicroProfileCalcAllTimers(pTimers, pAverage, pMax, pMin, 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);

        uint32_t nColor = S.TimerInfo[i].nColor;
        uint32_t nColorDark = (nColor >> 1) & ~0x80808080;
        MicroProfilePrintf(CB, Handle, "TimerInfo[%d] = MakeTimer(%d, \"%s\", %d, '#%06x','#%06x', %f, %f, %f, %f, %f, %f, %d, %f,\n",
            S.TimerInfo[i].nTimerIndex, S.TimerInfo[i].nTimerIndex, S.TimerInfo[i].pName, S.TimerInfo[i].nGroupIndex,
            ((MICROPROFILE_UNPACK_RED(nColor) & 0xff) << 16) | ((MICROPROFILE_UNPACK_GREEN(nColor) & 0xff) << 8) | (MICROPROFILE_UNPACK_BLUE(nColor) & 0xff),
            ((MICROPROFILE_UNPACK_RED(nColorDark) & 0xff) << 16) | ((MICROPROFILE_UNPACK_GREEN(nColorDark) & 0xff) << 8) | (MICROPROFILE_UNPACK_BLUE(nColorDark) & 0xff),
            pAverage[nIdx],
            pMax[nIdx],
            pMin[nIdx],
            pAverageExclusive[nIdx],
            pMaxExclusive[nIdx],
            pCallAverage[nIdx],
            S.Aggregate[i].nCount,
            pTotal[nIdx]);

        MicroProfilePrintString(CB, Handle, "\t[");
        for(int j = 0; j < MICROPROFILE_META_MAX; ++j)
        {
            if(S.MetaCounters[j].pName)
            {
                MicroProfilePrintUIntComma(CB, Handle, S.MetaCounters[j].nCounters[i]);
            }
        }
        MicroProfilePrintString(CB, Handle, "],[");
        for(int j = 0; j < MICROPROFILE_META_MAX; ++j)
        {
            if(S.MetaCounters[j].pName)
            {
                MicroProfilePrintUIntComma(CB, Handle, S.MetaCounters[j].nAggregate[i]);
            }
        }
        MicroProfilePrintString(CB, Handle, "],[");
        for(int j = 0; j < MICROPROFILE_META_MAX; ++j)
        {
            if(S.MetaCounters[j].pName)
            {
                MicroProfilePrintUIntComma(CB, Handle, S.MetaCounters[j].nAggregateMax[i]);
            }
        }
        MicroProfilePrintString(CB, Handle, "]);\n");
    }

    MicroProfilePrintString(CB, Handle, "\nvar ThreadNames = [");
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
            continue;
        MicroProfilePrintf(CB, Handle, "'%s',", S.Pool[i]->ThreadName);
    }
    MicroProfilePrintString(CB, Handle, "];\n\n");


    MicroProfilePrintString(CB, Handle, "\nvar ThreadIds = [");
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
            continue;
        MicroProfilePrintUIntComma(CB, Handle, S.Pool[i]->nThreadId);
    }
    MicroProfilePrintString(CB, Handle, "];\n\n");


    MicroProfilePrintString(CB, Handle, "\nvar ThreadGpu = [");
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
            continue;
        MicroProfilePrintUIntComma(CB, Handle, S.Pool[i]->nGpu);
    }
    MicroProfilePrintString(CB, Handle, "];\n\n");


    MicroProfilePrintString(CB, Handle, "\nvar ThreadGroupTimeArray = [\n");
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
            continue;
        float fToMs = S.Pool[i]->nGpu ? fToMsGPU : fToMsCPU;
        MicroProfilePrintf(CB, Handle, "MakeTimes(%e,[", fToMs);
        for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
        {
            MicroProfilePrintUIntComma(CB, Handle, S.Pool[i]->nAggregateGroupTicks[j]);
        }
        MicroProfilePrintString(CB, Handle, "]),\n");
    }
    MicroProfilePrintString(CB, Handle, "];");


    MicroProfilePrintString(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);
        }
    }
    MicroProfilePrintString(CB, Handle, "];\n\n");

    MicroProfilePrintString(CB, Handle, "\nvar CounterInfo = [");
    for(uint32_t i = 0; i < S.nNumCounters; ++i)
    {
        int64_t nCounter = S.Counters[i].load();
        int64_t nLimit = S.CounterInfo[i].nLimit;
        float fCounterPrc = 0.f;
        float fBoxPrc = 1.f;
        if(nLimit)
        {
            fCounterPrc = (float)nCounter / nLimit;
            if(fCounterPrc>1.f)
            {
                fBoxPrc = 1.f / fCounterPrc;
                fCounterPrc = 1.f;
            }
        }

        int64_t nCounterMin = 0, nCounterMax = 0;

    #if MICROPROFILE_COUNTER_HISTORY
        nCounterMin = S.nCounterMin[i];
        nCounterMax = S.nCounterMax[i];
    #endif

        char Formatted[64];
        char FormattedLimit[64];
        MicroProfileFormatCounter(S.CounterInfo[i].eFormat, nCounter, Formatted, sizeof(Formatted)-1);
        MicroProfileFormatCounter(S.CounterInfo[i].eFormat, S.CounterInfo[i].nLimit, FormattedLimit, sizeof(FormattedLimit)-1);
        MicroProfilePrintf(CB, Handle, "MakeCounter(%d, %d, %d, %d, %d, '%s', %" PRIi64 ", %" PRIi64 ", %" PRIi64 ", '%s', %" PRIi64 ", '%s', %d, %f, %f, [",
            i,
            S.CounterInfo[i].nParent,
            S.CounterInfo[i].nSibling,
            S.CounterInfo[i].nFirstChild,
            S.CounterInfo[i].nLevel,
            S.CounterInfo[i].pName,
            (int64_t)nCounter,
            (int64_t)nCounterMin,
            (int64_t)nCounterMax,
            Formatted,
            (int64_t)nLimit,
            FormattedLimit,
            S.CounterInfo[i].eFormat == MICROPROFILE_COUNTER_FORMAT_BYTES ? 1 : 0,
            fCounterPrc,
            fBoxPrc
            );

    #if MICROPROFILE_COUNTER_HISTORY
        if(0 != (S.CounterInfo[i].nFlags & MICROPROFILE_COUNTER_FLAG_DETAILED))
        {
            uint32_t nBaseIndex = S.nCounterHistoryPut;
            for(uint32_t j = 0; j < MICROPROFILE_GRAPH_HISTORY; ++j)
            {
                uint32_t nHistoryIndex = (nBaseIndex + j) % MICROPROFILE_GRAPH_HISTORY;
                int64_t nValue = MicroProfileClamp(S.nCounterHistory[nHistoryIndex][i], nCounterMin, nCounterMax);
                MicroProfilePrintUIntComma(CB, Handle, nValue - nCounterMin);
            }
        }
    #endif

        MicroProfilePrintString(CB, Handle, "]),\n");
    }
    MicroProfilePrintString(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);

    // this code removes frames from the dump that have incomplete data on any threads
    // this substantially reduces the dump size by eliminating stale data from overflowed thread logs that wrapped around
    // ideally we'd do this culling per thread so that we don't lose any valid data though; this is disabled for now
    if(0)
    {
        // figure out how many frames have valid log data
        uint32_t nNumFramesValid = 0;
        bool bCrossed = false;
        for (uint32_t i = 1; i <= nNumFrames; ++i)
        {
            const MicroProfileFrameState* pProbeFrame = &S.Frames[(S.nFrameCurrent - i + MICROPROFILE_MAX_FRAME_HISTORY) % MICROPROFILE_MAX_FRAME_HISTORY];
            const MicroProfileFrameState* pProbeFrameNext = &S.Frames[(S.nFrameCurrent - i + 1 + MICROPROFILE_MAX_FRAME_HISTORY) % MICROPROFILE_MAX_FRAME_HISTORY];
            for (uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j)
            {
                if(!S.Pool[j])
                    continue;
                uint32_t nMinIndex = pProbeFrame->nLogStart[j];
                uint32_t nMaxIndex = (pProbeFrameNext->nLogStart[j] + MICROPROFILE_BUFFER_SIZE - nMinIndex) % MICROPROFILE_BUFFER_SIZE;
                uint32_t nFencePost = (S.Pool[j]->nPut.load() + MICROPROFILE_BUFFER_SIZE - nMinIndex) % MICROPROFILE_BUFFER_SIZE;
                if (nMaxIndex > nFencePost)
                {
                    // crossed over the fencepost, can't take anymore.
                    bCrossed = true;
                    break;
                }
            }
            nNumFramesValid++;
            if(bCrossed)
                break;
        }

        nNumFrames = MicroProfileMin(nNumFrames, nNumFramesValid);
    }

    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 nTicksPerSecondCpu = MicroProfileTicksPerSecondCpu();
    int64_t nTicksPerSecondGpu = MicroProfileTicksPerSecondGpu();

    int64_t nTickReferenceCpu = 0, nTickReferenceGpu = 0;
    // Can't just call GetGpuTickReference off main thread...
    if(0 && MicroProfileGetGpuTickReference(&nTickReferenceCpu, &nTickReferenceGpu))
    {
        nTickStartGpu = (nTickStart - nTickReferenceCpu) * (double(nTicksPerSecondGpu) / double(nTicksPerSecondCpu)) + nTickReferenceGpu;
    }

#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;

        MicroProfilePrintf(CB, Handle, "var tt%d = [\n", i);
        for(uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j)
        {
            if(!S.Pool[j])
                continue;
            MicroProfileThreadLog* pLog = S.Pool[j];
            uint32_t nLogStart = S.Frames[nFrameIndex].nLogStart[j];
            uint32_t nLogEnd = S.Frames[nFrameIndexNext].nLogStart[j];

            MicroProfilePrintString(CB, Handle, "[");
            for(uint32_t k = nLogStart; 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 + 8, which is the tick part
                    nLogType = 8 + MicroProfileLogGetTick(pLog->Log[k]);
                }
                if(nLogType == MP_LOG_LABEL_LITERAL)
                {
                    // for label literals, pretend that they are stored as labels; HTML dump doesn't support efficent label literal storage yet
                    nLogType = MP_LOG_LABEL;
                }
                MicroProfilePrintUIntComma(CB, Handle, nLogType);
            }
            MicroProfilePrintString(CB, Handle, "],\n");
        }
        MicroProfilePrintString(CB, Handle, "];\n");

        MicroProfilePrintf(CB, Handle, "var ts%d = [\n", i);
        for(uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j)
        {
            if(!S.Pool[j])
                continue;
            MicroProfileThreadLog* pLog = S.Pool[j];
            uint32_t nLogStart = S.Frames[nFrameIndex].nLogStart[j];
            uint32_t nLogEnd = S.Frames[nFrameIndexNext].nLogStart[j];

            int64_t nStartTick = pLog->nGpu ? nTickStartGpu : nTickStart;
            float fToMs = pLog->nGpu ? fToMsGPU : fToMsCPU;

            if(pLog->nGpu)
                MicroProfilePrintf(CB, Handle, "MakeTimesExtra(%e,%e,tt%d[%d],[", fToMs, fToMsCPU, i, j);
            else
                MicroProfilePrintf(CB, Handle, "MakeTimes(%e,[", fToMs);
            for(uint32_t k = nLogStart; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE)
            {
                uint32_t nLogType = MicroProfileLogType(pLog->Log[k]);
                uint64_t nTick =
                    (nLogType == MP_LOG_ENTER || nLogType == MP_LOG_LEAVE)
                    ? MicroProfileLogTickDifference(nStartTick, pLog->Log[k])
                    : (nLogType == MP_LOG_GPU_EXTRA)
                    ? MicroProfileLogTickDifference(nTickStart, pLog->Log[k])
                    : 0;
                MicroProfilePrintUIntComma(CB, Handle, nTick);
            }
            MicroProfilePrintString(CB, Handle, "]),\n");
        }
        MicroProfilePrintString(CB, Handle, "];\n");

        MicroProfilePrintf(CB, Handle, "var ti%d = [\n", i);
        for(uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j)
        {
            if(!S.Pool[j])
                continue;
            MicroProfileThreadLog* pLog = S.Pool[j];
            uint32_t nLogStart = S.Frames[nFrameIndex].nLogStart[j];
            uint32_t nLogEnd = S.Frames[nFrameIndexNext].nLogStart[j];

            uint32_t nLabelIndex = 0;
            MicroProfilePrintString(CB, Handle, "[");
            for(uint32_t k = nLogStart; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE)
            {
                uint32_t nLogType = MicroProfileLogType(pLog->Log[k]);
                uint32_t nTimerIndex = (uint32_t)MicroProfileLogTimerIndex(pLog->Log[k]);
                uint32_t nIndex = (nLogType == MP_LOG_LABEL || nLogType == MP_LOG_LABEL_LITERAL) ? nLabelIndex++ : nTimerIndex;
                MicroProfilePrintUIntComma(CB, Handle, nIndex);

                if(nLogType == MP_LOG_ENTER)
                    nTimerCounter[nTimerIndex]++;
            }
            MicroProfilePrintString(CB, Handle, "],\n");
        }
        MicroProfilePrintString(CB, Handle, "];\n");

        MicroProfilePrintf(CB, Handle, "var tl%d = [\n", i);
        for(uint32_t j = 0; j < MICROPROFILE_MAX_THREADS; ++j)
        {
            if(!S.Pool[j])
                continue;
            MicroProfileThreadLog* pLog = S.Pool[j];
            uint32_t nLogStart = S.Frames[nFrameIndex].nLogStart[j];
            uint32_t nLogEnd = S.Frames[nFrameIndexNext].nLogStart[j];

            MicroProfilePrintString(CB, Handle, "[");
            for(uint32_t k = nLogStart; k != nLogEnd; k = (k+1) % MICROPROFILE_BUFFER_SIZE)
            {
                uint32_t nLogType = MicroProfileLogType(pLog->Log[k]);
                if(nLogType == MP_LOG_LABEL || nLogType == MP_LOG_LABEL_LITERAL)
                {
                    uint64_t nLabel = MicroProfileLogGetTick(pLog->Log[k]);
                    const char* pLabelName = MicroProfileGetLabel(nLogType, nLabel);

                    if(pLabelName)
                    {
                        MicroProfilePrintString(CB, Handle, "\"");
                        MicroProfilePrintString(CB, Handle, pLabelName);
                        MicroProfilePrintString(CB, Handle, "\",");
                    }
                    else
                        MicroProfilePrintString(CB, Handle, "null,");
                }
            }
            MicroProfilePrintString(CB, Handle, "],\n");
        }
        MicroProfilePrintString(CB, Handle, "];\n");

        int64_t nFrameStart = S.Frames[nFrameIndex].nFrameStartCpu;
        int64_t nFrameEnd = S.Frames[nFrameIndexNext].nFrameStartCpu;
        int64_t nFrameStartGpu = S.Frames[nFrameIndex].nFrameStartGpu;
        int64_t nFrameEndGpu = S.Frames[nFrameIndexNext].nFrameStartGpu;

        float fToMs = MicroProfileTickToMsMultiplier(nTicksPerSecondCpu);
        float fFrameMs = MicroProfileLogTickDifference(nTickStart, nFrameStart) * fToMs;
        float fFrameEndMs = MicroProfileLogTickDifference(nTickStart, nFrameEnd) * fToMs;
        float fFrameGpuMs = MicroProfileLogTickDifference(nTickStartGpu, nFrameStartGpu) * fToMsGPU;
        float fFrameGpuEndMs = MicroProfileLogTickDifference(nTickStartGpu, nFrameEndGpu) * fToMsGPU;

        MicroProfilePrintf(CB, Handle, "Frames[%d] = MakeFrame(%d, %f, %f, %f, %f, ts%d, tt%d, ti%d, tl%d);\n", i, 0, fFrameMs, fFrameEndMs, fFrameGpuMs, fFrameGpuEndMs, i, i, i, i);
    }


    uint32_t nContextSwitchStart = 0;
    uint32_t nContextSwitchEnd = 0;
    MicroProfileContextSwitchSearch(&nContextSwitchStart, &nContextSwitchEnd, nTickStart, nTickEnd);

    MicroProfilePrintString(CB, Handle, "var CSwitchThreadInOutCpu = [\n");
    for(uint32_t j = nContextSwitchStart; j != nContextSwitchEnd; j = (j+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE)
    {
        MicroProfileContextSwitch CS = S.ContextSwitch[j];
        int nCpu = CS.nCpu;
        MicroProfilePrintUIntComma(CB, Handle, CS.nThreadIn);
        MicroProfilePrintUIntComma(CB, Handle, CS.nThreadOut);
        MicroProfilePrintUIntComma(CB, Handle, nCpu);
    }
    MicroProfilePrintString(CB, Handle, "];\n");

    MicroProfilePrintString(CB, Handle, "var CSwitchTime = [\n");
    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);
    }
    MicroProfilePrintString(CB, Handle, "];\n");

    MicroProfileThreadInfo Threads[MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS];
    uint32_t nNumThreadsBase = 0;
    uint32_t nNumThreads = MicroProfileContextSwitchGatherThreads(nContextSwitchStart, nContextSwitchEnd, Threads, &nNumThreadsBase);

    MicroProfilePrintString(CB, Handle, "var CSwitchThreads = {");

    for (uint32_t i = 0; i < nNumThreads; ++i)
    {
        char Name[256];
        const char* pProcessName = MicroProfileGetProcessName(Threads[i].nProcessId, Name, sizeof(Name));

        const char* p1 = i < nNumThreadsBase && S.Pool[i] ? S.Pool[i]->ThreadName : "?";
        const char* p2 = pProcessName ? pProcessName : "?";

        MicroProfilePrintf(CB, Handle, "%" PRIi64 ":{\'tid\':%" PRIi64 ",\'pid\':%" PRIi64 ",\'t\':\'%s\',\'p\':\'%s\'},",
            (int64_t)Threads[i].nThreadId,
            (int64_t)Threads[i].nThreadId,
            (int64_t)Threads[i].nProcessId,
            p1, p2
            );
    }

    MicroProfilePrintString(CB, Handle, "};\n");

    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
}
#else
void MicroProfileDumpHtml(MicroProfileWriteCallback CB, void* Handle, int nMaxFrames, const char* pHost)
{
    MicroProfilePrintString(CB, Handle, "HTML output is disabled because MICROPROFILE_EMBED_HTML is 0\n");
}
#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());

    FILE* F = fopen(S.DumpPath, "w");
    if(F)
    {
        if(S.eDumpType == MicroProfileDumpTypeHtml)
            MicroProfileDumpHtml(MicroProfileWriteFile, F, S.nDumpFrames, 0);
        else if(S.eDumpType == MicroProfileDumpTypeCsv)
            MicroProfileDumpCsv(MicroProfileWriteFile, F, S.nDumpFrames);

        fclose(F);
    }
}

#if MICROPROFILE_WEBSERVER
uint32_t MicroProfileWebServerPort()
{
    return S.nWebServerPort;
}

void MicroProfileSendSocket(MpSocket Socket, const char* pData, size_t nSize)
{
#ifdef MSG_NOSIGNAL
    int nFlags = MSG_NOSIGNAL;
#else
    int nFlags = 0;
#endif

    send(Socket, pData, (int)nSize, nFlags);
}

void MicroProfileFlushSocket(MpSocket Socket)
{
    if(S.nWebServerPut)
    {
        MicroProfileSendSocket(Socket, &S.WebServerBuffer[0], S.nWebServerPut);
        S.nWebServerPut = 0;
    }
}

void MicroProfileWriteSocket(void* Handle, size_t nSize, const char* pData)
{
    MpSocket Socket = *(MpSocket*)Handle;
    if(nSize > MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE / 2)
    {
        MicroProfileFlushSocket(Socket);
        MicroProfileSendSocket(Socket, pData, nSize);
    }
    else
    {
        memcpy(&S.WebServerBuffer[S.nWebServerPut], pData, nSize);
        S.nWebServerPut += (uint32_t)nSize;
        if(S.nWebServerPut > MICROPROFILE_WEBSERVER_SOCKET_BUFFER_SIZE/2)
        {
            MicroProfileFlushSocket(Socket);
        }
    }

    S.nWebServerDataSent += nSize;
}

#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)
    {
        MicroProfileSendSocket(pState->Socket, (char*)pSendStart, pSendEnd - pSendStart);
        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, MZ_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;
    unsigned char* pDeflateInEnd = const_cast<unsigned char *>(Stream.next_in) + Stream.avail_in;
    unsigned char* pDeflateInStart = &pState->DeflateIn[0];
    unsigned char* pDeflateInRealEnd = &pState->DeflateIn[MICROPROFILE_COMPRESS_CHUNK];
    pState->nSize += nSize;
    if(nSize <= (size_t)(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 = const_cast<unsigned char *>(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 = const_cast<unsigned char *>(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

void* MicroProfileWebServerUpdate(void*);
void MicroProfileWebServerUpdateStop();

void MicroProfileWebServerHello(int nPort)
{
    uint32_t nInterfaces = 0;

    // getifaddrs hangs on some versions of Android so disable IP address scanning
#if (defined(__APPLE__) || defined(__linux__)) && !defined(__ANDROID__)
    struct ifaddrs* ifal;
    if(getifaddrs(&ifal) == 0 && ifal)
    {
        for(struct ifaddrs* ifa = ifal; ifa; ifa = ifa->ifa_next)
        {
            if(ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET)
            {
                void* pAddress = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
                char Ip[INET_ADDRSTRLEN];
                if(inet_ntop(AF_INET, pAddress, Ip, sizeof(Ip)))
                {
                    MICROPROFILE_PRINTF("MicroProfile: Web server started on %s:%d\n", Ip, nPort);
                    nInterfaces++;
                }
            }
        }

        freeifaddrs(ifal);
    }
#endif

    if(nInterfaces == 0)
    {
        MICROPROFILE_PRINTF("MicroProfile: Web server started on port %d\n", nPort);
    }
}

void MicroProfileWebServerStart()
{
    if(!S.WebServerThread)
    {
        MicroProfileThreadStart(&S.WebServerThread, MicroProfileWebServerUpdate);
    }
}

void MicroProfileWebServerStop()
{
    if(S.WebServerThread)
    {
        MicroProfileWebServerUpdateStop();
        MicroProfileThreadJoin(&S.WebServerThread);
    }
}

const char* MicroProfileParseHeader(char* pRequest, const char* pPrefix)
{
    size_t nRequestSize = strlen(pRequest);
    size_t nPrefixSize = strlen(pPrefix);

    for(uint32_t i = 0; i < nRequestSize; ++i)
    {
        if((i == 0 || pRequest[i-1] == '\n') && strncmp(&pRequest[i], pPrefix, nPrefixSize) == 0)
        {
            char* pResult = &pRequest[i + nPrefixSize];
            size_t nResultSize = strcspn(pResult, " \r\n");

            pResult[nResultSize] = '\0';
            return pResult;
        }
    }

    return 0;
}

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_FRAMES;
    }
}

void MicroProfileWebServerHandleRequest(MpSocket Connection)
{
    char Request[8192];
    int nReceived = recv(Connection, Request, sizeof(Request)-1, 0);
    if(nReceived <= 0)
        return;
    Request[nReceived] = 0;

    std::lock_guard<std::recursive_mutex> Lock(MicroProfileMutex());

    MICROPROFILE_SCOPE(g_MicroProfileWebServerUpdate);

#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

    const char* pUrl = MicroProfileParseHeader(Request, "GET /");
    if(!pUrl)
        return;

    int nFrames = MicroProfileParseGet(pUrl);
    if(nFrames <= 0)
        return;

    const char* pHost = MicroProfileParseHeader(Request, "Host: ");

    uint64_t nTickStart = MP_TICK();
    MicroProfileSendSocket(Connection, MICROPROFILE_HTML_HEADER, sizeof(MICROPROFILE_HTML_HEADER)-1);
    uint64_t nDataStart = S.nWebServerDataSent;
    S.nWebServerPut = 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;
    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
}

void MicroProfileWebServerCloseSocket(MpSocket Connection)
{
#ifdef _WIN32
    closesocket(Connection);
#else
    shutdown(Connection,SHUT_RDWR);
    close(Connection);
#endif
}

void* MicroProfileWebServerUpdate(void*)
{
#ifdef _WIN32
    WSADATA wsa;
    if(WSAStartup(MAKEWORD(2, 2), &wsa))
        return 0;
#endif

    S.WebServerSocket = socket(PF_INET, SOCK_STREAM, 6);
    MP_ASSERT(!MP_INVALID_SOCKET(S.WebServerSocket));

    uint32_t nPortBegin = MICROPROFILE_WEBSERVER_PORT;
    uint32_t nPortEnd = nPortBegin + 20;

    struct sockaddr_in Addr;
    Addr.sin_family = AF_INET;
    Addr.sin_addr.s_addr = INADDR_ANY;
    for(uint32_t nPort = nPortBegin; nPort < nPortEnd; ++nPort)
    {
        Addr.sin_port = htons(nPort);
        if(0 == ::bind(S.WebServerSocket, (sockaddr*)&Addr, sizeof(Addr)))
        {
            S.nWebServerPort = nPort;
            break;
        }
    }

    if(S.nWebServerPort)
    {
        MicroProfileWebServerHello(S.nWebServerPort);

        listen(S.WebServerSocket, 8);

        for (;;)
        {
            MpSocket Connection = accept(S.WebServerSocket, 0, 0);
            if(MP_INVALID_SOCKET(Connection)) break;

        #ifdef SO_NOSIGPIPE
            int nConnectionOption = 1;
            setsockopt(Connection, SOL_SOCKET, SO_NOSIGPIPE, &nConnectionOption, sizeof(nConnectionOption));
        #endif

            MicroProfileWebServerHandleRequest(Connection);

            MicroProfileWebServerCloseSocket(Connection);
        }

        S.nWebServerPort = 0;
    }
    else
    {
        MICROPROFILE_PRINTF("MicroProfile: Web server could not start: no free ports in range [%d..%d)\n", nPortBegin, nPortEnd);
    }

#ifdef _WIN32
    WSACleanup();
#endif

    return 0;
}

void MicroProfileWebServerUpdateStop()
{
    MicroProfileWebServerCloseSocket(S.WebServerSocket);
}
#else
void MicroProfileWebServerStart()
{
}

void MicroProfileWebServerStop()
{
}

uint32_t MicroProfileWebServerPort()
{
    return 0;
}
#endif


#if MICROPROFILE_CONTEXT_SWITCH_TRACE
//functions that need to be implemented per platform.
void* MicroProfileTraceThread(void*);

void MicroProfileContextSwitchTraceStart()
{
    if(!S.ContextSwitchThread)
    {
        MicroProfileThreadStart(&S.ContextSwitchThread, MicroProfileTraceThread);
    }
}

void MicroProfileContextSwitchTraceStop()
{
    if(S.ContextSwitchThread)
    {
        S.bContextSwitchStop = true;
        MicroProfileThreadJoin(&S.ContextSwitchThread);
        S.bContextSwitchStop = false;
    }
}

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;
}

uint32_t MicroProfileContextSwitchGatherThreads(uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, MicroProfileThreadInfo* Threads, uint32_t* nNumThreadsBase)
{
    MicroProfileProcessIdType nCurrentProcessId = MP_GETCURRENTPROCESSID();

    uint32_t nNumThreads = 0;
    for(uint32_t i = 0; i < MICROPROFILE_MAX_THREADS; ++i)
    {
        if(!S.Pool[i])
            continue;
        Threads[nNumThreads].nProcessId = nCurrentProcessId;
        Threads[nNumThreads].nThreadId = S.Pool[i]->nThreadId;
        nNumThreads++;
    }

    *nNumThreadsBase = nNumThreads;

    for(uint32_t i = nContextSwitchStart; i != nContextSwitchEnd; i = (i+1) % MICROPROFILE_CONTEXT_SWITCH_BUFFER_SIZE)
    {
        MicroProfileContextSwitch CS = S.ContextSwitch[i];
        MicroProfileThreadIdType nThreadId = CS.nThreadIn;
        if(nThreadId)
        {
            MicroProfileProcessIdType nProcessId = CS.nProcessIn;

            bool bSeen = false;
            for(uint32_t j = 0; j < nNumThreads; ++j)
            {
                if(Threads[j].nThreadId == nThreadId && Threads[j].nProcessId == nProcessId)
                {
                    bSeen = true;
                    break;
                }
            }
            if(!bSeen)
            {
                Threads[nNumThreads].nProcessId = nProcessId;
                Threads[nNumThreads].nThreadId = nThreadId;
                nNumThreads++;
            }
        }
        if(nNumThreads == MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS)
        {
            break;
        }
    }

    return nNumThreads;
}

#if defined(_WIN32)
#include <psapi.h>

const char* MicroProfileGetProcessName(MicroProfileProcessIdType nId, char* Buffer, uint32_t nSize)
{
    if(HANDLE Handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, nId))
    {
        DWORD nResult = GetModuleBaseNameA(Handle, nullptr, Buffer, nSize);
        CloseHandle(Handle);

        return nResult ? Buffer : nullptr;
    }
    return nullptr;
}

void* MicroProfileTraceThread(void*)
{
    while(!S.bContextSwitchStop)
    {
        FILE* pFile = fopen("\\\\.\\pipe\\microprofile-contextswitch", "rb");
        if(!pFile)
        {
            Sleep(1000);
            continue;
        }

        S.bContextSwitchRunning = true;

        MicroProfileContextSwitch Buffer[1024];

        while(!ferror(pFile) && !S.bContextSwitchStop)
        {
            size_t nCount = fread(Buffer, sizeof(MicroProfileContextSwitch), ARRAYSIZE(Buffer), pFile);

            for(size_t i = 0; i < nCount; ++i)
                MicroProfileContextSwitchPut(&Buffer[i]);
        }

        fclose(pFile);

        S.bContextSwitchRunning = false;
    }

    return 0;
}
#elif defined(__APPLE__)
#include <sys/time.h>
#include <libproc.h>

const char* MicroProfileGetProcessName(MicroProfileProcessIdType nId, char* Buffer, uint32_t nSize)
{
    char Path[PATH_MAX];
    if(proc_pidpath(nId, Path, sizeof(Path)) == 0)
        return nullptr;

    char* pSlash = strrchr(Path, '/');
    char* pName = pSlash ? pSlash + 1 : Path;

    strncpy(Buffer, pName, nSize-1);
    Buffer[nSize-1] = 0;

    return Buffer;
}

void* MicroProfileTraceThread(void*)
{
    while(!S.bContextSwitchStop)
    {
        FILE* pFile = fopen("/tmp/microprofile-contextswitch", "r");
        if(!pFile)
        {
            usleep(1000000);
            continue;
        }

        S.bContextSwitchRunning = true;

        char* pLine = 0;
        size_t cap = 0;
        size_t len = 0;

        uint32_t nLastThread[MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS] = {0};

        while((len = getline(&pLine, &cap, pFile))>0 && !S.bContextSwitchStop)
        {
            if(strncmp(pLine, "MPTD ", 5) != 0)
                continue;

            char* pos = pLine + 4;
            uint32_t cpu = strtol(pos + 1, &pos, 16);
            uint32_t pid = strtol(pos + 1, &pos, 16);
            uint32_t tid = strtol(pos + 1, &pos, 16);
            int64_t timestamp = strtoll(pos + 1, &pos, 16);

            if(cpu < MICROPROFILE_MAX_CONTEXT_SWITCH_THREADS)
            {
                MicroProfileContextSwitch Switch;

                Switch.nThreadOut = nLastThread[cpu];
                Switch.nThreadIn = tid;
                Switch.nProcessIn = pid;
                Switch.nCpu = cpu;
                Switch.nTicks = timestamp;
                MicroProfileContextSwitchPut(&Switch);

                nLastThread[cpu] = tid;
            }
        }

        fclose(pFile);

        S.bContextSwitchRunning = false;
    }

    return 0;
}
#endif
#else
void MicroProfileContextSwitchTraceStart()
{
}

void MicroProfileContextSwitchTraceStop()
{
}

void MicroProfileContextSwitchSearch(uint32_t* pContextSwitchStart, uint32_t* pContextSwitchEnd, uint64_t nBaseTicksCpu, uint64_t nBaseTicksEndCpu)
{
    (void)nBaseTicksCpu;
    (void)nBaseTicksEndCpu;

    *pContextSwitchStart = 0;
    *pContextSwitchEnd = 0;
}

uint32_t MicroProfileContextSwitchGatherThreads(uint32_t nContextSwitchStart, uint32_t nContextSwitchEnd, MicroProfileThreadInfo* Threads, uint32_t* nNumThreadsBase)
{
    (void)nContextSwitchStart;
    (void)nContextSwitchEnd;
    (void)Threads;

    *nNumThreadsBase = 0;
    return 0;
}

const char* MicroProfileGetProcessName(MicroProfileProcessIdType nId, char* Buffer, uint32_t nSize)
{
    (void)nId;
    (void)Buffer;
    (void)nSize;

    return nullptr;
}
#endif

void MicroProfileGpuShutdown()
{
    if(!S.GPU.Shutdown)
        return;

    S.GPU.Shutdown();

    memset(&S.GPU, 0, sizeof(S.GPU));
}

uint32_t MicroProfileGpuFlip()
{
    if(!S.GPU.Flip)
        return (uint32_t)-1;

    return S.GPU.Flip();
}

uint32_t MicroProfileGpuInsertTimer(void* pContext)
{
    if(!S.GPU.InsertTimer)
        return (uint32_t)-1;

    return S.GPU.InsertTimer(pContext);
}

uint64_t MicroProfileGpuGetTimeStamp(uint32_t nIndex)
{
    if(!S.GPU.GetTimeStamp)
        return MICROPROFILE_INVALID_TICK;

    if(nIndex == (uint32_t)-1)
        return MICROPROFILE_INVALID_TICK;

    return S.GPU.GetTimeStamp(nIndex);
}

uint64_t MicroProfileTicksPerSecondGpu()
{
    if (!S.GPU.GetTicksPerSecond)
        return 1000000000ll;

    return S.GPU.GetTicksPerSecond();
}

bool MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu)
{
    if(!S.GPU.GetTickReference)
        return false;

    return S.GPU.GetTickReference(pOutCpu, pOutGpu);
}

#define MICROPROFILE_GPU_STATE_DECL(API) \
    void MicroProfileGpuInitState##API(); \
    MicroProfileGpuTimerState##API g_MicroProfileGPU_##API;

#define MICROPROFILE_GPU_STATE_IMPL(API) \
    void MicroProfileGpuInitState##API() \
    { \
        MP_ASSERT(!S.GPU.Shutdown); \
        memset((void *)&g_MicroProfileGPU_##API, 0, sizeof(g_MicroProfileGPU_##API)); \
        S.GPU.Shutdown = MicroProfileGpuShutdown##API; \
        S.GPU.Flip = MicroProfileGpuFlip##API; \
        S.GPU.InsertTimer = MicroProfileGpuInsertTimer##API; \
        S.GPU.GetTimeStamp = MicroProfileGpuGetTimeStamp##API; \
        S.GPU.GetTicksPerSecond = MicroProfileTicksPerSecondGpu##API; \
        S.GPU.GetTickReference = MicroProfileGetGpuTickReference##API; \
    }

#if MICROPROFILE_GPU_TIMERS_D3D11
#ifndef D3D11_SDK_VERSION
#include <d3d11.h>
#endif

struct MicroProfileGpuTimerStateD3D11
{
    ID3D11DeviceContext* pDeviceContext;
    ID3D11Query* pQueries[MICROPROFILE_GPU_MAX_QUERIES];
    ID3D11Query* pRateQuery;
    ID3D11Query* pSyncQuery;

    uint64_t nFrame;
    std::atomic<uint32_t> nFramePut;

    uint32_t nSubmitted[MICROPROFILE_GPU_FRAMES];
    uint64_t nResults[MICROPROFILE_GPU_MAX_QUERIES];

    uint32_t nRateQueryIssue;
    uint64_t nQueryFrequency;
};

MICROPROFILE_GPU_STATE_DECL(D3D11)

void MicroProfileGpuInitD3D11(ID3D11Device* pDevice)
{
    MicroProfileGpuInitStateD3D11();

    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    pDevice->GetImmediateContext(&GPU.pDeviceContext);

    D3D11_QUERY_DESC Desc;
    Desc.MiscFlags = 0;
    Desc.Query = D3D11_QUERY_TIMESTAMP;
    for(uint32_t i = 0; i < MICROPROFILE_GPU_MAX_QUERIES; ++i)
    {
        HRESULT hr = pDevice->CreateQuery(&Desc, &GPU.pQueries[i]);
        MP_ASSERT(hr == S_OK);
    }

    HRESULT hr = pDevice->CreateQuery(&Desc, &GPU.pSyncQuery);
    MP_ASSERT(hr == S_OK);

    Desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
    hr = pDevice->CreateQuery(&Desc, &GPU.pRateQuery);
    MP_ASSERT(hr == S_OK);
}

void MicroProfileGpuShutdownD3D11()
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    for(uint32_t i = 0; i < MICROPROFILE_GPU_MAX_QUERIES; ++i)
    {
        GPU.pQueries[i]->Release();
        GPU.pQueries[i] = 0;
    }

    GPU.pRateQuery->Release();
    GPU.pRateQuery = 0;

    GPU.pSyncQuery->Release();
    GPU.pSyncQuery = 0;

    GPU.pDeviceContext->Release();
    GPU.pDeviceContext = 0;
}

uint32_t MicroProfileGpuFlipD3D11()
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    if (!GPU.pDeviceContext) return (uint32_t)-1;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    // Submit current frame
    uint32_t nFrameIndex = GPU.nFrame % MICROPROFILE_GPU_FRAMES;
    uint32_t nFramePut = MicroProfileMin(GPU.nFramePut.load(), nFrameQueries);

    GPU.nSubmitted[nFrameIndex] = nFramePut;
    GPU.nFramePut.store(0);
    GPU.nFrame++;

    // Fetch frame results
    if (GPU.nFrame >= MICROPROFILE_GPU_FRAMES)
    {
        uint64_t nPendingFrame = GPU.nFrame - MICROPROFILE_GPU_FRAMES;
        uint32_t nPendingFrameIndex = nPendingFrame % MICROPROFILE_GPU_FRAMES;

        for(uint32_t i = 0; i < GPU.nSubmitted[nPendingFrameIndex]; ++i)
        {
            uint32_t nQueryIndex = nPendingFrameIndex * nFrameQueries + i;
            MP_ASSERT(nQueryIndex < MICROPROFILE_GPU_MAX_QUERIES);

            uint64_t nResult = 0;

            HRESULT hr;
            do hr = GPU.pDeviceContext->GetData(GPU.pQueries[nQueryIndex], &nResult, sizeof(nResult), 0);
            while(hr == S_FALSE);

            GPU.nResults[nQueryIndex] = (hr == S_OK) ? nResult : MICROPROFILE_INVALID_TICK;
        }
    }

    // Update timestamp frequency
    if(GPU.nRateQueryIssue == 0)
    {
        GPU.pDeviceContext->Begin(GPU.pRateQuery);
        GPU.nRateQueryIssue = 1;
    }
    else if(GPU.nRateQueryIssue == 1)
    {
        GPU.pDeviceContext->End(GPU.pRateQuery);
        GPU.nRateQueryIssue = 2;
    }
    else
    {
        D3D11_QUERY_DATA_TIMESTAMP_DISJOINT Result;
        if(S_OK == GPU.pDeviceContext->GetData(GPU.pRateQuery, &Result, sizeof(Result), D3D11_ASYNC_GETDATA_DONOTFLUSH))
        {
            GPU.nQueryFrequency = Result.Frequency;
            GPU.nRateQueryIssue = 0;
        }
    }

    return MicroProfileGpuInsertTimer(0);
}

uint32_t MicroProfileGpuInsertTimerD3D11(void* pContext)
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    uint32_t nIndex = GPU.nFramePut.fetch_add(1);
    if(nIndex >= nFrameQueries)
        return (uint32_t)-1;

    uint32_t nQueryIndex = (GPU.nFrame % MICROPROFILE_GPU_FRAMES) * nFrameQueries + nIndex;

    GPU.pDeviceContext->End(GPU.pQueries[nQueryIndex]);

    return nQueryIndex;
}

uint64_t MicroProfileGpuGetTimeStampD3D11(uint32_t nIndex)
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    return GPU.nResults[nIndex];
}

uint64_t MicroProfileTicksPerSecondGpuD3D11()
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    return GPU.nQueryFrequency ? GPU.nQueryFrequency : 1000000000ll;
}

bool MicroProfileGetGpuTickReferenceD3D11(int64_t* pOutCpu, int64_t* pOutGpu)
{
    MicroProfileGpuTimerStateD3D11& GPU = g_MicroProfileGPU_D3D11;

    GPU.pDeviceContext->End(GPU.pSyncQuery);

    uint64_t nResult = 0;

    HRESULT hr;
    do hr = GPU.pDeviceContext->GetData(GPU.pSyncQuery, &nResult, sizeof(nResult), 0);
    while(hr == S_FALSE);

    if (hr != S_OK) return false;

    *pOutCpu = MP_TICK();
    *pOutGpu = nResult;

    return true;
}

MICROPROFILE_GPU_STATE_IMPL(D3D11)
#endif

#if MICROPROFILE_GPU_TIMERS_D3D12
#ifndef D3D12_MAJOR_VERSION
#include <d3d12.h>
#endif

struct MicroProfileGpuTimerStateD3D12
{
    ID3D12CommandQueue* pCommandQueue;
    ID3D12QueryHeap* pHeap;
    ID3D12Resource* pBuffer;
    ID3D12GraphicsCommandList* pCommandLists[MICROPROFILE_GPU_FRAMES];
    ID3D12CommandAllocator* pCommandAllocators[MICROPROFILE_GPU_FRAMES];
    ID3D12Fence* pFence;
    void* pFenceEvent;

    uint64_t nFrame;
    std::atomic<uint32_t> nFramePut;

    uint32_t nSubmitted[MICROPROFILE_GPU_FRAMES];
    uint64_t nResults[MICROPROFILE_GPU_MAX_QUERIES];
    uint64_t nQueryFrequency;
};

MICROPROFILE_GPU_STATE_DECL(D3D12)

void MicroProfileGpuInitD3D12(ID3D12Device* pDevice, ID3D12CommandQueue* pCommandQueue)
{
    MicroProfileGpuInitStateD3D12();

    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    GPU.pCommandQueue = pCommandQueue;

    HRESULT hr;

    D3D12_QUERY_HEAP_DESC HeapDesc;
    HeapDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
    HeapDesc.Count = MICROPROFILE_GPU_MAX_QUERIES;
    HeapDesc.NodeMask = 0;

    D3D12_HEAP_PROPERTIES HeapProperties;
    HeapProperties.Type = D3D12_HEAP_TYPE_READBACK;
    HeapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    HeapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
    HeapProperties.CreationNodeMask = 1;
    HeapProperties.VisibleNodeMask = 1;

    D3D12_RESOURCE_DESC ResourceDesc;
    ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    ResourceDesc.Alignment = 0;
    ResourceDesc.Width = MICROPROFILE_GPU_MAX_QUERIES * sizeof(uint64_t);
    ResourceDesc.Height = 1;
    ResourceDesc.DepthOrArraySize = 1;
    ResourceDesc.MipLevels = 1;
    ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
    ResourceDesc.SampleDesc.Count = 1;
    ResourceDesc.SampleDesc.Quality = 0;
    ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;

    hr = pDevice->CreateQueryHeap(&HeapDesc, IID_PPV_ARGS(&GPU.pHeap));
    MP_ASSERT(hr == S_OK);
    hr = pDevice->CreateCommittedResource(&HeapProperties, D3D12_HEAP_FLAG_NONE, &ResourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&GPU.pBuffer));
    MP_ASSERT(hr == S_OK);
    hr = pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&GPU.pFence));
    MP_ASSERT(hr == S_OK);
    GPU.pFenceEvent = CreateEvent(nullptr, false, false, nullptr);
    MP_ASSERT(GPU.pFenceEvent != INVALID_HANDLE_VALUE);

    for (uint32_t i = 0; i < MICROPROFILE_GPU_FRAMES; ++i)
    {
        hr = pDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&GPU.pCommandAllocators[i]));
        MP_ASSERT(hr == S_OK);
        hr = pDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, GPU.pCommandAllocators[i], nullptr, IID_PPV_ARGS(&GPU.pCommandLists[i]));
        MP_ASSERT(hr == S_OK);
        hr = GPU.pCommandLists[i]->Close();
        MP_ASSERT(hr == S_OK);
    }

    hr = pCommandQueue->GetTimestampFrequency(&GPU.nQueryFrequency);
    MP_ASSERT(hr == S_OK);
}

void MicroProfileGpuShutdownD3D12()
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    if(!GPU.pCommandQueue)
        return;

    if (GPU.nFrame > 0)
    {
        GPU.pFence->SetEventOnCompletion(GPU.nFrame, GPU.pFenceEvent);
        WaitForSingleObject(GPU.pFenceEvent, INFINITE);
    }

    for (uint32_t i = 0; i < MICROPROFILE_GPU_FRAMES; ++i)
    {
        GPU.pCommandLists[i]->Release();
        GPU.pCommandLists[i] = 0;

        GPU.pCommandAllocators[i]->Release();
        GPU.pCommandAllocators[i] = 0;
    }

    GPU.pHeap->Release();
    GPU.pHeap = 0;

    GPU.pBuffer->Release();
    GPU.pBuffer = 0;

    GPU.pFence->Release();
    GPU.pFence = 0;

    CloseHandle(GPU.pFenceEvent);
    GPU.pFenceEvent = 0;

    GPU.pCommandQueue = 0;
}

uint32_t MicroProfileGpuFlipD3D12()
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    // Submit current frame
    uint32_t nFrameIndex = GPU.nFrame % MICROPROFILE_GPU_FRAMES;
    uint32_t nFrameStart = nFrameIndex * nFrameQueries;

    ID3D12CommandAllocator* pCommandAllocator = GPU.pCommandAllocators[nFrameIndex];
    ID3D12GraphicsCommandList* pCommandList = GPU.pCommandLists[nFrameIndex];

    pCommandAllocator->Reset();
    pCommandList->Reset(pCommandAllocator, nullptr);

    uint32_t nFrameTimeStamp = MicroProfileGpuInsertTimer(pCommandList);

    uint32_t nFramePut = MicroProfileMin(GPU.nFramePut.load(), nFrameQueries);

    if (nFramePut)
        pCommandList->ResolveQueryData(GPU.pHeap, D3D12_QUERY_TYPE_TIMESTAMP, nFrameStart, nFramePut, GPU.pBuffer, nFrameStart * sizeof(int64_t));

    pCommandList->Close();

    ID3D12CommandList* pList = pCommandList;
    GPU.pCommandQueue->ExecuteCommandLists(1, &pList);
    GPU.pCommandQueue->Signal(GPU.pFence, GPU.nFrame + 1);

    GPU.nSubmitted[nFrameIndex] = nFramePut;
    GPU.nFramePut.store(0);
    GPU.nFrame++;

    // Fetch frame results
    if (GPU.nFrame >= MICROPROFILE_GPU_FRAMES)
    {
        uint64_t nPendingFrame = GPU.nFrame - MICROPROFILE_GPU_FRAMES;
        uint32_t nPendingFrameIndex = nPendingFrame % MICROPROFILE_GPU_FRAMES;

        GPU.pFence->SetEventOnCompletion(nPendingFrame + 1, GPU.pFenceEvent);
        WaitForSingleObject(GPU.pFenceEvent, INFINITE);

        uint32_t nPendingFrameStart = nPendingFrameIndex * nFrameQueries;
        uint32_t nPendingFrameCount = GPU.nSubmitted[nPendingFrameIndex];

        if (nPendingFrameCount)
        {
            void* pData = 0;
            D3D12_RANGE Range = { nPendingFrameStart * sizeof(uint64_t), (nPendingFrameStart + nPendingFrameCount) * sizeof(uint64_t) };

            HRESULT hr = GPU.pBuffer->Map(0, &Range, &pData);
            MP_ASSERT(hr == S_OK);

            memcpy(&GPU.nResults[nPendingFrameStart], (uint64_t*)pData + nPendingFrameStart, nPendingFrameCount * sizeof(uint64_t));

            GPU.pBuffer->Unmap(0, 0);
        }
    }

    return nFrameTimeStamp;
}

uint32_t MicroProfileGpuInsertTimerD3D12(void* pContext)
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    if (!pContext) return (uint32_t)-1;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    uint32_t nIndex = GPU.nFramePut.fetch_add(1);
    if(nIndex >= nFrameQueries) return (uint32_t)-1;

    uint32_t nQueryIndex = (GPU.nFrame % MICROPROFILE_GPU_FRAMES) * nFrameQueries + nIndex;

    ((ID3D12GraphicsCommandList*)pContext)->EndQuery(GPU.pHeap, D3D12_QUERY_TYPE_TIMESTAMP, nQueryIndex);

    return nQueryIndex;
}

uint64_t MicroProfileGpuGetTimeStampD3D12(uint32_t nIndex)
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    return GPU.nResults[nIndex];
}

uint64_t MicroProfileTicksPerSecondGpuD3D12()
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    return GPU.nQueryFrequency ? GPU.nQueryFrequency : 1000000000ll;
}

bool MicroProfileGetGpuTickReferenceD3D12(int64_t* pOutCpu, int64_t* pOutGpu)
{
    MicroProfileGpuTimerStateD3D12& GPU = g_MicroProfileGPU_D3D12;

    return SUCCEEDED(GPU.pCommandQueue->GetClockCalibration((uint64_t*)pOutGpu, (uint64_t*)pOutCpu));
}

MICROPROFILE_GPU_STATE_IMPL(D3D12)
#endif

#if MICROPROFILE_GPU_TIMERS_GL
#ifndef GL_TIMESTAMP
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#else
#error You must include OpenGL headers for GPU timers to work
#endif
#endif

struct MicroProfileGpuTimerStateGL
{
    int32_t nTimestampBits;
    uint32_t nQueries[MICROPROFILE_GPU_MAX_QUERIES];

    uint64_t nFrame;
    std::atomic<uint32_t> nFramePut;

    uint64_t nTimerOffset[MICROPROFILE_GPU_FRAMES];
    uint32_t nSubmitted[MICROPROFILE_GPU_FRAMES];
    uint64_t nResults[MICROPROFILE_GPU_MAX_QUERIES];
};

MICROPROFILE_GPU_STATE_DECL(GL)

void MicroProfileGpuInitGL()
{
    MicroProfileGpuInitStateGL();

    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    glGetQueryiv(GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &GPU.nTimestampBits);

#ifdef __APPLE__
    // OSX GL driver (incorrectly) issues GL_INVALID_ENUM when querying the timestamp bits
    glGetError();
#endif

    glGenQueries(MICROPROFILE_GPU_MAX_QUERIES, &GPU.nQueries[0]);
}

void MicroProfileGpuShutdownGL()
{
    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    glDeleteQueries(MICROPROFILE_GPU_MAX_QUERIES, &GPU.nQueries[0]);
}

uint32_t MicroProfileGpuFlipGL()
{
    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    // Submit current frame
    uint32_t nFrameIndex = GPU.nFrame % MICROPROFILE_GPU_FRAMES;
    uint32_t nFramePut = MicroProfileMin(GPU.nFramePut.load(), nFrameQueries);

    if(!GPU.nTimestampBits && nFramePut > 0)
        glEndQuery(GL_TIME_ELAPSED);

    GPU.nTimerOffset[nFrameIndex] = MP_TICK() * (double(MicroProfileTicksPerSecondGpu()) / double(MicroProfileTicksPerSecondCpu()));
    GPU.nSubmitted[nFrameIndex] = nFramePut;
    GPU.nFramePut.store(0);
    GPU.nFrame++;

    // Fetch frame results
    if (GPU.nFrame >= MICROPROFILE_GPU_FRAMES)
    {
        uint64_t nPendingFrame = GPU.nFrame - MICROPROFILE_GPU_FRAMES;
        uint32_t nPendingFrameIndex = nPendingFrame % MICROPROFILE_GPU_FRAMES;
        uint64_t nTimerOffset = GPU.nTimerOffset[nPendingFrameIndex];

        for(uint32_t i = 0; i < GPU.nSubmitted[nPendingFrameIndex]; ++i)
        {
            uint32_t nQueryIndex = nPendingFrameIndex * nFrameQueries + i;
            MP_ASSERT(nQueryIndex < MICROPROFILE_GPU_MAX_QUERIES);

            uint64_t nResult = 0;
            glGetQueryObjectui64v(GPU.nQueries[nQueryIndex], GL_QUERY_RESULT, &nResult);

            if(GPU.nTimestampBits)
            {
                GPU.nResults[nQueryIndex] = nResult;
            }
            else
            {
                GPU.nResults[nQueryIndex] = nTimerOffset;
                nTimerOffset += nResult;
            }
        }
    }

    return MicroProfileGpuInsertTimer(0);
}

uint32_t MicroProfileGpuInsertTimerGL(void* pContext)
{
    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    (void)pContext;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    uint32_t nIndex = GPU.nFramePut.fetch_add(1);
    if(nIndex >= nFrameQueries) return (uint32_t)-1;

    uint32_t nQueryIndex = (GPU.nFrame % MICROPROFILE_GPU_FRAMES) * nFrameQueries + nIndex;

    if(!GPU.nTimestampBits && nIndex > 0)
        glEndQuery(GL_TIME_ELAPSED);

    if(GPU.nTimestampBits)
        glQueryCounter(GPU.nQueries[nQueryIndex], GL_TIMESTAMP);
    else
        glBeginQuery(GL_TIME_ELAPSED, GPU.nQueries[nQueryIndex]);

    return nQueryIndex;
}

uint64_t MicroProfileGpuGetTimeStampGL(uint32_t nIndex)
{
    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    return GPU.nResults[nIndex];
}

uint64_t MicroProfileTicksPerSecondGpuGL()
{
    return 1000000000ll;
}

bool MicroProfileGetGpuTickReferenceGL(int64_t* pOutCpu, int64_t* pOutGpu)
{
    MicroProfileGpuTimerStateGL& GPU = g_MicroProfileGPU_GL;

    if(GPU.nTimestampBits)
    {
        int64_t nGpuTimeStamp = 0;
        glGetInteger64v(GL_TIMESTAMP, &nGpuTimeStamp);

        if(nGpuTimeStamp)
        {
            *pOutCpu = MP_TICK();
            *pOutGpu = nGpuTimeStamp;
            return true;
        }

        return false;
    }
    else
    {
        *pOutCpu = MP_TICK();
        *pOutGpu = MP_TICK() * (double(MicroProfileTicksPerSecondGpu()) / double(MicroProfileTicksPerSecondCpu()));
        return true;
    }
}

MICROPROFILE_GPU_STATE_IMPL(GL)
#endif

#if MICROPROFILE_GPU_TIMERS_VK
#ifndef VK_HEADER_VERSION
#include <vulkan/vulkan.h>
#endif

struct MicroProfileGpuTimerStateVK
{
    VkDevice pDevice;
    VkQueue pQueue;
    VkQueryPool pQueryPool;
    VkCommandPool pCommandPool;
    VkCommandBuffer pCommandBuffers[MICROPROFILE_GPU_FRAMES];
    VkFence pFences[MICROPROFILE_GPU_FRAMES];

    VkCommandBuffer pReferenceCommandBuffer;
    uint32_t nReferenceQuery;

    uint64_t nFrame;
    std::atomic<uint32_t> nFramePut;

    uint32_t nSubmitted[MICROPROFILE_GPU_FRAMES];
    uint64_t nResults[MICROPROFILE_GPU_MAX_QUERIES];
    uint64_t nQueryFrequency;
};

MICROPROFILE_GPU_STATE_DECL(VK)

void MicroProfileGpuInitVK(VkDevice pDevice, VkPhysicalDevice pPhysicalDevice, VkQueue pQueue)
{
    MicroProfileGpuInitStateVK();

    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    VkPhysicalDeviceProperties Properties;
    vkGetPhysicalDeviceProperties(pPhysicalDevice, &Properties);

    GPU.pDevice = pDevice;
    GPU.pQueue = pQueue;

    VkQueryPoolCreateInfo queryPoolInfo = {};
    queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
    queryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
    queryPoolInfo.queryCount = MICROPROFILE_GPU_MAX_QUERIES + 1; // reference query

    VkResult res = vkCreateQueryPool(pDevice, &queryPoolInfo, nullptr, &GPU.pQueryPool);
    MP_ASSERT(res == VK_SUCCESS);

    VkCommandPoolCreateInfo commandPoolInfo = {};
    commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    commandPoolInfo.queueFamilyIndex = 0;

    res = vkCreateCommandPool(pDevice, &commandPoolInfo, nullptr, &GPU.pCommandPool);
    MP_ASSERT(res == VK_SUCCESS);

    VkCommandBuffer pCommandBuffers[MICROPROFILE_GPU_FRAMES + 1] = {};

    VkCommandBufferAllocateInfo commandBufferInfo = {};
    commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    commandBufferInfo.commandPool = GPU.pCommandPool;
    commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    commandBufferInfo.commandBufferCount = sizeof(pCommandBuffers) / sizeof(pCommandBuffers[0]);

    res = vkAllocateCommandBuffers(pDevice, &commandBufferInfo, pCommandBuffers);
    MP_ASSERT(res == VK_SUCCESS);

    VkFenceCreateInfo fenceInfo = {};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;

    for (uint32_t i = 0; i < MICROPROFILE_GPU_FRAMES; ++i)
    {
        GPU.pCommandBuffers[i] = pCommandBuffers[i];

        res = vkCreateFence(pDevice, &fenceInfo, nullptr, &GPU.pFences[i]);
        MP_ASSERT(res == VK_SUCCESS);
    }

    GPU.pReferenceCommandBuffer = pCommandBuffers[MICROPROFILE_GPU_FRAMES];
    GPU.nReferenceQuery = MICROPROFILE_GPU_MAX_QUERIES; // reference query

    GPU.nQueryFrequency = 1e9 / Properties.limits.timestampPeriod;
}

void MicroProfileGpuShutdownVK()
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    if (GPU.nFrame > 0)
    {
        uint32_t nFrameIndex = (GPU.nFrame - 1) % MICROPROFILE_GPU_FRAMES;

        VkResult res = vkWaitForFences(GPU.pDevice, 1, &GPU.pFences[nFrameIndex], VK_TRUE, UINT64_MAX);
        MP_ASSERT(res == VK_SUCCESS);
    }

    for (uint32_t i = 0; i < MICROPROFILE_GPU_FRAMES; ++i)
    {
        vkDestroyFence(GPU.pDevice, GPU.pFences[i], nullptr);
        GPU.pFences[i] = 0;
    }

    vkDestroyCommandPool(GPU.pDevice, GPU.pCommandPool, nullptr);
    memset(GPU.pCommandBuffers, 0, sizeof(GPU.pCommandBuffers));
    GPU.pCommandPool = 0;

    vkDestroyQueryPool(GPU.pDevice, GPU.pQueryPool, nullptr);
    GPU.pQueryPool = 0;

    GPU.pQueue = 0;
    GPU.pDevice = 0;
}

uint32_t MicroProfileGpuFlipVK()
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    // Submit current frame
    uint32_t nFrameIndex = GPU.nFrame % MICROPROFILE_GPU_FRAMES;
    uint32_t nFrameStart = nFrameIndex * nFrameQueries;

    VkCommandBuffer pCommandBuffer = GPU.pCommandBuffers[nFrameIndex];

    VkCommandBufferBeginInfo commandBufferBeginInfo = {};
    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    VkResult res = vkBeginCommandBuffer(pCommandBuffer, &commandBufferBeginInfo);
    MP_ASSERT(res == VK_SUCCESS);

    uint32_t nFrameTimeStamp = MicroProfileGpuInsertTimer(pCommandBuffer);
    uint32_t nFramePut = MicroProfileMin(GPU.nFramePut.load(), nFrameQueries);

    res = vkEndCommandBuffer(pCommandBuffer);
    MP_ASSERT(res == VK_SUCCESS);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &pCommandBuffer;

    res = vkQueueSubmit(GPU.pQueue, 1, &submitInfo, GPU.pFences[nFrameIndex]);
    MP_ASSERT(res == VK_SUCCESS);

    GPU.nSubmitted[nFrameIndex] = nFramePut;
    GPU.nFramePut.store(0);
    GPU.nFrame++;

    // Fetch frame results
    if (GPU.nFrame >= MICROPROFILE_GPU_FRAMES)
    {
        uint64_t nPendingFrame = GPU.nFrame - MICROPROFILE_GPU_FRAMES;
        uint32_t nPendingFrameIndex = nPendingFrame % MICROPROFILE_GPU_FRAMES;

        res = vkWaitForFences(GPU.pDevice, 1, &GPU.pFences[nPendingFrameIndex], VK_TRUE, UINT64_MAX);
        MP_ASSERT(res == VK_SUCCESS);

        res = vkResetFences(GPU.pDevice, 1, &GPU.pFences[nPendingFrameIndex]);
        MP_ASSERT(res == VK_SUCCESS);

        uint32_t nPendingFrameStart = nPendingFrameIndex * nFrameQueries;
        uint32_t nPendingFrameCount = GPU.nSubmitted[nPendingFrameIndex];

        if (nPendingFrameCount)
        {
            res = vkGetQueryPoolResults(GPU.pDevice, GPU.pQueryPool,
                nPendingFrameStart, nPendingFrameCount,
                nPendingFrameCount * sizeof(uint64_t), &GPU.nResults[nPendingFrameStart],
                sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
            MP_ASSERT(res == VK_SUCCESS);
        }
    }

    return nFrameTimeStamp;
}

uint32_t MicroProfileGpuInsertTimerVK(void* pContext)
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    uint32_t nFrameQueries = MICROPROFILE_GPU_MAX_QUERIES / MICROPROFILE_GPU_FRAMES;

    uint32_t nIndex = GPU.nFramePut.fetch_add(1);
    if(nIndex >= nFrameQueries) return (uint32_t)-1;

    uint32_t nQueryIndex = (GPU.nFrame % MICROPROFILE_GPU_FRAMES) * nFrameQueries + nIndex;

    vkCmdWriteTimestamp((VkCommandBuffer)pContext, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, GPU.pQueryPool, nQueryIndex);

    return nQueryIndex;
}

uint64_t MicroProfileGpuGetTimeStampVK(uint32_t nIndex)
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    return GPU.nResults[nIndex];
}

uint64_t MicroProfileTicksPerSecondGpuVK()
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    return GPU.nQueryFrequency ? GPU.nQueryFrequency : 1000000000ll;
}

bool MicroProfileGetGpuTickReferenceVK(int64_t* pOutCpu, int64_t* pOutGpu)
{
    MicroProfileGpuTimerStateVK& GPU = g_MicroProfileGPU_VK;

    VkCommandBuffer pCommandBuffer = GPU.pReferenceCommandBuffer;
    uint32_t nQueryIndex = GPU.nReferenceQuery;

    VkCommandBufferBeginInfo commandBufferBeginInfo = {};
    commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    VkResult res = vkBeginCommandBuffer(pCommandBuffer, &commandBufferBeginInfo);
    MP_ASSERT(res == VK_SUCCESS);

    vkCmdWriteTimestamp(pCommandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, GPU.pQueryPool, nQueryIndex);

    res = vkEndCommandBuffer(pCommandBuffer);
    MP_ASSERT(res == VK_SUCCESS);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &pCommandBuffer;

    res = vkQueueSubmit(GPU.pQueue, 1, &submitInfo, VK_NULL_HANDLE);
    MP_ASSERT(res == VK_SUCCESS);

    res = vkQueueWaitIdle(GPU.pQueue);
    MP_ASSERT(res == VK_SUCCESS);

    *pOutCpu = MP_TICK();

    res = vkGetQueryPoolResults(GPU.pDevice, GPU.pQueryPool, nQueryIndex, 1, sizeof(uint64_t), pOutGpu, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
    MP_ASSERT(res == VK_SUCCESS);

    return true;
}

MICROPROFILE_GPU_STATE_IMPL(VK)
#endif

#undef S

#if defined _MSC_VER
#pragma warning(pop)
#endif

#if MICROPROFILE_EMBED_HTML
#include "microprofilehtml.h"
#endif //MICROPROFILE_EMBED_HTML

#endif
#endif
