diff --git a/Tracy.hpp b/Tracy.hpp
index d8ed519b..bde4f7f1 100644
--- a/Tracy.hpp
+++ b/Tracy.hpp
@@ -29,6 +29,9 @@
#define TracyMessage(x,y)
#define TracyMessageL(x)
+#define TracyAlloc(x,y)
+#define TracyFree(x)
+
#else
#include "client/TracyLock.hpp"
@@ -57,6 +60,9 @@
#define TracyMessage( txt, size ) tracy::Profiler::Message( txt, size );
#define TracyMessageL( txt ) tracy::Profiler::Message( txt );
+#define TracyAlloc( ptr, size ) tracy::Profiler::MemAlloc( ptr, size );
+#define TracyFree( ptr ) tracy::Profiler::MemFree( ptr );
+
#endif
#endif
diff --git a/capture/build/win32/capture.vcxproj b/capture/build/win32/capture.vcxproj
index 23761818..aef83464 100644
--- a/capture/build/win32/capture.vcxproj
+++ b/capture/build/win32/capture.vcxproj
@@ -144,7 +144,9 @@
+
+
@@ -153,9 +155,7 @@
-
-
diff --git a/capture/build/win32/capture.vcxproj.filters b/capture/build/win32/capture.vcxproj.filters
index e9183070..01444809 100644
--- a/capture/build/win32/capture.vcxproj.filters
+++ b/capture/build/win32/capture.vcxproj.filters
@@ -59,15 +59,9 @@
common
-
- server
-
server
-
- server
-
server
@@ -98,5 +92,11 @@
common
+
+ common
+
+
+ common
+
\ No newline at end of file
diff --git a/capture/src/capture.cpp b/capture/src/capture.cpp
index 17e673ad..46dff6ca 100644
--- a/capture/src/capture.cpp
+++ b/capture/src/capture.cpp
@@ -9,7 +9,6 @@
#include
#include
-#include "../../server/tracy_benaphore.h"
#include "../../server/TracyFileWrite.hpp"
#include "../../server/TracyMemory.hpp"
#include "../../server/TracyWorker.hpp"
diff --git a/client/TracyFastVector.hpp b/client/TracyFastVector.hpp
new file mode 100644
index 00000000..f0757dfe
--- /dev/null
+++ b/client/TracyFastVector.hpp
@@ -0,0 +1,88 @@
+#ifndef __TRACYFASTVECTOR_HPP__
+#define __TRACYFASTVECTOR_HPP__
+
+#include
+
+#include "../common/TracyAlloc.hpp"
+#include "../common/TracyForceInline.hpp"
+
+namespace tracy
+{
+
+template
+class FastVector
+{
+public:
+ using iterator = T*;
+ using const_iterator = const T*;
+
+ FastVector( size_t capacity )
+ : m_ptr( (T*)tracy_malloc( sizeof( T ) * capacity ) )
+ , m_size( 0 )
+ , m_capacity( capacity )
+ {
+ }
+
+ FastVector( const FastVector& ) = delete;
+ FastVector( FastVector&& ) = delete;
+
+ ~FastVector()
+ {
+ tracy_free( m_ptr );
+ }
+
+ FastVector& operator=( const FastVector& ) = delete;
+ FastVector& operator=( FastVector&& ) = delete;
+
+ bool empty() const { return m_size == 0; }
+ size_t size() const { return m_size; }
+
+ T* data() { return m_ptr; }
+ const T* data() const { return m_ptr; };
+
+ T* begin() { return m_ptr; }
+ const T* begin() const { return m_ptr; }
+ T* end() { return m_ptr + m_size; }
+ const T* end() const { return m_ptr + m_size; }
+
+ T& front() { assert( m_size > 0 ); return m_ptr[0]; }
+ const T& front() const { assert( m_size > 0 ); return m_ptr[0]; }
+
+ T& back() { assert( m_size > 0 ); return m_ptr[m_size - 1]; }
+ const T& back() const { assert( m_size > 0 ); return m_ptr[m_size - 1]; }
+
+ T& operator[]( size_t idx ) { return m_ptr[idx]; }
+ const T& operator[]( size_t idx ) const { return m_ptr[idx]; }
+
+ T* push_next()
+ {
+ T* ret;
+ if( m_size == m_capacity ) AllocMore();
+ ret = m_ptr + m_size;
+ m_size++;
+ return ret;
+ }
+
+ void clear()
+ {
+ m_size = 0;
+ }
+
+private:
+ tracy_no_inline void AllocMore()
+ {
+ m_capacity *= 2;
+ T* ptr = (T*)tracy_malloc( sizeof( T ) * m_capacity );
+ memcpy( ptr, m_ptr, m_size * sizeof( T ) );
+ tracy_free( m_ptr );
+ m_ptr = ptr;
+ }
+
+ T* m_ptr;
+ size_t m_size;
+ size_t m_capacity;
+};
+
+}
+
+#endif
diff --git a/client/TracyProfiler.cpp b/client/TracyProfiler.cpp
index 24953a16..173cdc74 100644
--- a/client/TracyProfiler.cpp
+++ b/client/TracyProfiler.cpp
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
#include
@@ -108,7 +109,7 @@ struct ThreadNameData;
std::atomic init_order(104) s_threadNameData( nullptr );
#endif
-static Profiler init_order(105) s_profiler;
+Profiler init_order(105) s_profiler;
enum { BulkSize = TargetFrameSize / QueueItemSize };
@@ -125,6 +126,7 @@ Profiler::Profiler()
, m_bufferStart( 0 )
, m_itemBuf( (QueueItem*)tracy_malloc( sizeof( QueueItem ) * BulkSize ) )
, m_lz4Buf( (char*)tracy_malloc( LZ4Size + sizeof( lz4sz_t ) ) )
+ , m_serialQueue( 1024*1024 )
{
assert( !s_instance );
s_instance = this;
@@ -212,11 +214,12 @@ void Profiler::Worker()
for(;;)
{
const auto status = Dequeue( token );
- if( status == ConnectionLost )
+ const auto serialStatus = DequeueSerial();
+ if( status == ConnectionLost || serialStatus == ConnectionLost )
{
break;
}
- else if( status == QueueEmpty )
+ else if( status == QueueEmpty && serialStatus == QueueEmpty )
{
if( ShouldExit() ) break;
if( m_bufferOffset != m_bufferStart ) CommitData();
@@ -234,11 +237,12 @@ void Profiler::Worker()
for(;;)
{
const auto status = Dequeue( token );
- if( status == ConnectionLost )
+ const auto serialStatus = DequeueSerial();
+ if( status == ConnectionLost || serialStatus == ConnectionLost )
{
break;
}
- else if( status == QueueEmpty )
+ else if( status == QueueEmpty && serialStatus == QueueEmpty )
{
if( m_bufferOffset != m_bufferStart ) CommitData();
break;
@@ -266,6 +270,7 @@ void Profiler::Worker()
}
}
while( Dequeue( token ) == Success ) {}
+ while( DequeueSerial() == Success ) {}
if( m_bufferOffset != m_bufferStart )
{
if( !CommitData() ) return;
@@ -325,6 +330,29 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token )
return Success;
}
+Profiler::DequeueStatus Profiler::DequeueSerial()
+{
+ std::lock_guard lock( m_serialLock );
+ const auto sz = m_serialQueue.size();
+ if( sz > 0 )
+ {
+ auto item = m_serialQueue.data();
+ auto end = item + sz;
+ while( item != end )
+ {
+ const auto idx = MemRead( &item->hdr.idx );
+ if( !AppendData( item, QueueDataSize[idx] ) ) return ConnectionLost;
+ item++;
+ }
+ m_serialQueue.clear();
+ }
+ else
+ {
+ return QueueEmpty;
+ }
+ return Success;
+}
+
bool Profiler::AppendData( const void* data, size_t len )
{
auto ret = true;
diff --git a/client/TracyProfiler.hpp b/client/TracyProfiler.hpp
index 1666d0c9..9d254eac 100644
--- a/client/TracyProfiler.hpp
+++ b/client/TracyProfiler.hpp
@@ -7,7 +7,9 @@
#include
#include "concurrentqueue.h"
+#include "TracyFastVector.hpp"
#include "../common/tracy_lz4.hpp"
+#include "../common/tracy_benaphore.h"
#include "../common/TracyQueue.hpp"
#include "../common/TracyAlign.hpp"
#include "../common/TracyAlloc.hpp"
@@ -50,6 +52,9 @@ struct GpuCtxWrapper
using Magic = moodycamel::ConcurrentQueueDefaultTraits::index_t;
+class Profiler;
+extern Profiler s_profiler;
+
class Profiler
{
public:
@@ -186,6 +191,33 @@ public:
tail.store( magic + 1, std::memory_order_release );
}
+ static tracy_force_inline void MemAlloc( const void* ptr, size_t size )
+ {
+ const auto thread = GetThreadHandle();
+
+ s_profiler.m_serialLock.lock();
+ auto item = s_profiler.m_serialQueue.push_next();
+ MemWrite( &item->hdr.type, QueueType::MemAlloc );
+ MemWrite( &item->memAlloc.time, GetTime() );
+ MemWrite( &item->memAlloc.thread, thread );
+ MemWrite( &item->memAlloc.ptr, (uint64_t)ptr );
+ memcpy( &item->memAlloc.size, &size, 6 );
+ s_profiler.m_serialLock.unlock();
+ }
+
+ static tracy_force_inline void MemFree( const void* ptr )
+ {
+ const auto thread = GetThreadHandle();
+
+ s_profiler.m_serialLock.lock();
+ auto item = s_profiler.m_serialQueue.push_next();
+ MemWrite( &item->hdr.type, QueueType::MemFree );
+ MemWrite( &item->memFree.time, GetTime() );
+ MemWrite( &item->memFree.thread, thread );
+ MemWrite( &item->memFree.ptr, (uint64_t)ptr );
+ s_profiler.m_serialLock.unlock();
+ }
+
static bool ShouldExit();
private:
@@ -195,6 +227,7 @@ private:
void Worker();
DequeueStatus Dequeue( moodycamel::ConsumerToken& token );
+ DequeueStatus DequeueSerial();
bool AppendData( const void* data, size_t len );
bool CommitData();
bool NeedDataSize( size_t len );
@@ -225,6 +258,9 @@ private:
QueueItem* m_itemBuf;
char* m_lz4Buf;
+
+ FastVector m_serialQueue;
+ NonRecursiveBenaphore m_serialLock;
};
};
diff --git a/common/TracyQueue.hpp b/common/TracyQueue.hpp
index 0762f39c..69c0f6b6 100644
--- a/common/TracyQueue.hpp
+++ b/common/TracyQueue.hpp
@@ -31,6 +31,8 @@ enum class QueueType : uint8_t
GpuZoneEnd,
GpuTime,
GpuResync,
+ MemAlloc,
+ MemFree,
StringData,
ThreadName,
CustomStringData,
@@ -187,6 +189,21 @@ struct QueueGpuResync
uint16_t context;
};
+struct QueueMemAlloc
+{
+ int64_t time;
+ uint64_t thread;
+ uint64_t ptr;
+ char size[6];
+};
+
+struct QueueMemFree
+{
+ int64_t time;
+ uint64_t thread;
+ uint64_t ptr;
+};
+
struct QueueHeader
{
union
@@ -219,6 +236,8 @@ struct QueueItem
QueueGpuZoneEnd gpuZoneEnd;
QueueGpuTime gpuTime;
QueueGpuResync gpuResync;
+ QueueMemAlloc memAlloc;
+ QueueMemFree memFree;
};
};
@@ -251,6 +270,8 @@ static const size_t QueueDataSize[] = {
sizeof( QueueHeader ) + sizeof( QueueGpuZoneEnd ),
sizeof( QueueHeader ) + sizeof( QueueGpuTime ),
sizeof( QueueHeader ) + sizeof( QueueGpuResync ),
+ sizeof( QueueHeader ) + sizeof( QueueMemAlloc ),
+ sizeof( QueueHeader ) + sizeof( QueueMemFree ),
// keep all QueueStringTransfer below
sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // string data
sizeof( QueueHeader ) + sizeof( QueueStringTransfer ), // thread name
diff --git a/server/tracy_benaphore.h b/common/tracy_benaphore.h
similarity index 100%
rename from server/tracy_benaphore.h
rename to common/tracy_benaphore.h
diff --git a/server/tracy_sema.h b/common/tracy_sema.h
similarity index 91%
rename from server/tracy_sema.h
rename to common/tracy_sema.h
index da3f088a..0b76e10c 100644
--- a/server/tracy_sema.h
+++ b/common/tracy_sema.h
@@ -30,9 +30,16 @@ namespace tracy
// Semaphore (Windows)
//---------------------------------------------------------
-#include
-#undef min
-#undef max
+#ifndef MAXLONG
+enum { MAXLONG = 0x7fffffff };
+enum { INFINITE = 0xFFFFFFFF };
+typedef void* HANDLE;
+
+extern "C" __declspec(dllimport) HANDLE __stdcall CreateSemaphoreA( void*, long, long, const char* );
+extern "C" __declspec(dllimport) int __stdcall CloseHandle( HANDLE );
+extern "C" __declspec(dllimport) unsigned long __stdcall WaitForSingleObject( HANDLE, unsigned long );
+extern "C" __declspec(dllimport) int __stdcall ReleaseSemaphore( HANDLE, long, long* );
+#endif
class Semaphore
{
@@ -46,7 +53,7 @@ public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
- m_hSema = CreateSemaphore(NULL, initialCount, MAXLONG, NULL);
+ m_hSema = CreateSemaphoreA(NULL, initialCount, MAXLONG, NULL);
}
~Semaphore()
diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp
index 463bd1ff..7819f846 100644
--- a/server/TracyEvent.hpp
+++ b/server/TracyEvent.hpp
@@ -137,6 +137,20 @@ struct GpuEvent
enum { GpuEventSize = sizeof( GpuEvent ) };
static_assert( std::is_standard_layout::value, "GpuEvent is not standard layout" );
+
+struct MemEvent
+{
+ uint64_t ptr;
+ uint64_t size;
+ int64_t timeAlloc;
+ uint16_t threadAlloc;
+ int64_t timeFree;
+ uint16_t threadFree;
+};
+
+enum { MemEventSize = sizeof( MemEvent ) };
+static_assert( std::is_standard_layout::value, "MemEvent is not standard layout" );
+
#pragma pack()
@@ -208,6 +222,15 @@ struct PlotData
uint64_t postponeTime;
};
+struct MemData
+{
+ Vector data;
+ flat_hash_map> active;
+ uint64_t high = std::numeric_limits::min();
+ uint64_t low = std::numeric_limits::max();
+ uint64_t usage = 0;
+};
+
struct StringLocation
{
const char* ptr;
diff --git a/server/TracyFileRead.hpp b/server/TracyFileRead.hpp
index 52c02849..a53342b5 100644
--- a/server/TracyFileRead.hpp
+++ b/server/TracyFileRead.hpp
@@ -38,12 +38,24 @@ public:
}
}
+ bool IsEOF()
+ {
+ if( m_lastBlock != BufSize && m_offset == m_lastBlock ) return true;
+ if( m_offset == BufSize )
+ {
+ if( fseek( m_file, 1, SEEK_CUR ) != 0 ) return true;
+ fseek( m_file, -1, SEEK_CUR );
+ }
+ return false;
+ }
+
private:
FileRead( FILE* f )
: m_stream( LZ4_createStreamDecode() )
, m_file( f )
, m_offset( BufSize )
, m_active( 1 )
+ , m_lastBlock( 0 )
{}
tracy_force_inline void ReadSmall( void* ptr, size_t size )
@@ -65,7 +77,7 @@ private:
uint32_t sz;
fread( &sz, 1, sizeof( sz ), m_file );
fread( m_lz4buf, 1, sz, m_file );
- LZ4_decompress_safe_continue( m_stream, m_lz4buf, m_buf[m_active], sz, BufSize );
+ m_lastBlock = LZ4_decompress_safe_continue( m_stream, m_lz4buf, m_buf[m_active], sz, BufSize );
}
const auto sz = std::min( size, BufSize - m_offset );
@@ -84,6 +96,7 @@ private:
char m_buf[2][BufSize];
size_t m_offset;
uint8_t m_active;
+ int m_lastBlock;
};
}
diff --git a/server/TracyVector.hpp b/server/TracyVector.hpp
index db2474f8..c96c251d 100644
--- a/server/TracyVector.hpp
+++ b/server/TracyVector.hpp
@@ -94,6 +94,12 @@ public:
m_ptr[m_size++] = std::move( v );
}
+ T& push_next()
+ {
+ if( m_size == Capacity() ) AllocMore();
+ return m_ptr[m_size++];
+ }
+
T* insert( T* it, const T& v )
{
assert( it >= m_ptr && it <= m_ptr + m_size );
diff --git a/server/TracyView.cpp b/server/TracyView.cpp
index 730daaf0..643e8043 100644
--- a/server/TracyView.cpp
+++ b/server/TracyView.cpp
@@ -307,6 +307,8 @@ void View::DrawImpl()
ImGui::SameLine();
if( ImGui::Button( "Statistics", ImVec2( bw, 0 ) ) ) m_showStatistics = true;
ImGui::SameLine();
+ if( ImGui::Button( "Memory", ImVec2( bw, 0 ) ) ) m_memInfo.show = true;
+ ImGui::SameLine();
ImGui::Text( "Frames: %-7" PRIu64 " Time span: %-10s View span: %-10s Zones: %-13s Queue delay: %s Timer resolution: %s", m_worker.GetFrameCount(), TimeToString( m_worker.GetLastTime() - m_worker.GetFrameBegin( 0 ) ), TimeToString( m_zvEnd - m_zvStart ), RealToString( m_worker.GetZoneCount(), true ), TimeToString( m_worker.GetDelay() ), TimeToString( m_worker.GetResolution() ) );
DrawFrames();
DrawZones();
@@ -321,6 +323,7 @@ void View::DrawImpl()
if( m_showMessages ) DrawMessages();
if( m_findZone.show ) DrawFindZone();
if( m_showStatistics ) DrawStatistics();
+ if( m_memInfo.show ) DrawMemory();
if( m_zoomAnim.active )
{
@@ -1089,6 +1092,13 @@ void View::DrawZones()
auto& io = ImGui::GetIO();
draw->AddLine( ImVec2( io.MousePos.x, linepos.y ), ImVec2( io.MousePos.x, linepos.y + lineh ), 0x33FFFFFF );
}
+
+ if( m_memInfo.show && m_memInfo.restrictTime )
+ {
+ const auto zvMid = ( m_zvEnd - m_zvStart ) / 2;
+ auto& io = ImGui::GetIO();
+ draw->AddLine( ImVec2( wpos.x + zvMid * pxns, linepos.y ), ImVec2( wpos.x + zvMid * pxns, linepos.y + lineh ), 0x88FF44FF );
+ }
}
int View::DrawZoneLevel( const Vector& vec, bool hover, double pxns, const ImVec2& wpos, int _offset, int depth )
@@ -2508,7 +2518,7 @@ void View::DrawZoneInfoWindow()
cti[i] = uint32_t( i );
}
- std::sort( cti.get(), cti.get() + ev.child.size(), [&ctt] ( const auto& lhs, const auto& rhs ) { return ctt[lhs] > ctt[rhs]; } );
+ pdqsort_branchless( cti.get(), cti.get() + ev.child.size(), [&ctt] ( const auto& lhs, const auto& rhs ) { return ctt[lhs] > ctt[rhs]; } );
if( !ev.child.empty() )
{
@@ -2667,7 +2677,7 @@ void View::DrawGpuInfoWindow()
cti[i] = uint32_t( i );
}
- std::sort( cti.get(), cti.get() + ev.child.size(), [&ctt] ( const auto& lhs, const auto& rhs ) { return ctt[lhs] > ctt[rhs]; } );
+ pdqsort_branchless( cti.get(), cti.get() + ev.child.size(), [&ctt] ( const auto& lhs, const auto& rhs ) { return ctt[lhs] > ctt[rhs]; } );
if( !ev.child.empty() )
{
@@ -3481,7 +3491,7 @@ void View::DrawFindZone()
}
if( m_findZone.sortByCounts )
{
- std::sort( threads.begin(), threads.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.size() > rhs->second.size(); } );
+ pdqsort_branchless( threads.begin(), threads.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second.size() > rhs->second.size(); } );
}
ImGui::BeginChild( "##zonesScroll", ImVec2( ImGui::GetWindowContentRegionWidth(), std::max( 200.f, ImGui::GetContentRegionAvail().y ) ) );
@@ -3647,6 +3657,481 @@ void View::DrawStatistics()
ImGui::End();
}
+template
+void View::ListMemData( T ptr, T end, std::function DrawAddress )
+{
+ ImGui::BeginChild( "##memScroll", ImVec2( 0, std::max( 200.f, ImGui::GetContentRegionAvail().y ) ) );
+ ImGui::Columns( 7 );
+ ImGui::Text( "Address" );
+ ImGui::NextColumn();
+ ImGui::Text( "Size" );
+ ImGui::NextColumn();
+ ImGui::Text( "Appeared at" );
+ ImGui::NextColumn();
+ ImGui::Text( "Duration" );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(?)" );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "Active allocations are displayed using green color." );
+ ImGui::EndTooltip();
+ }
+
+ ImGui::NextColumn();
+ ImGui::Text( "Thread" );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(?)" );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "Shows one thread if alloc and free was performed on the same thread." );
+ ImGui::Text( "Otherwise two threads are displayed in order: alloc, free." );
+ ImGui::EndTooltip();
+ }
+ ImGui::NextColumn();
+ ImGui::Text( "Zone alloc" );
+ ImGui::NextColumn();
+ ImGui::Text( "Zone free" );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(?)" );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "If alloc and free is performed in the same zone, it is displayed in yellow color." );
+ ImGui::EndTooltip();
+ }
+ ImGui::NextColumn();
+ ImGui::Separator();
+ int idx = 0;
+ while( ptr != end )
+ {
+ auto v = DrawAddress( ptr );
+ ImGui::NextColumn();
+ ImGui::Text( "%s", RealToString( v->size, true ) );
+ ImGui::NextColumn();
+ ImGui::Text( "%s", TimeToString( v->timeAlloc - m_worker.GetFrameBegin( 0 ) ) );
+ ImGui::NextColumn();
+ if( v->timeFree < 0 )
+ {
+ ImGui::TextColored( ImVec4( 0.6f, 1.f, 0.6f, 1.f ), "%s", TimeToString( m_worker.GetLastTime() - v->timeAlloc ) );
+ ImGui::NextColumn();
+ ImGui::Text( "%s", m_worker.GetThreadString( m_worker.DecompressThread( v->threadAlloc ) ) );
+ }
+ else
+ {
+ ImGui::Text( "%s", TimeToString( v->timeFree - v->timeAlloc ) );
+ ImGui::NextColumn();
+ ImGui::Text( "%s", m_worker.GetThreadString( m_worker.DecompressThread( v->threadAlloc ) ) );
+ if( v->threadAlloc != v->threadFree )
+ {
+ ImGui::Text( "%s", m_worker.GetThreadString( m_worker.DecompressThread( v->threadFree ) ) );
+ }
+ }
+ ImGui::NextColumn();
+ auto zone = FindZoneAtTime( m_worker.DecompressThread( v->threadAlloc ), v->timeAlloc );
+ if( !zone )
+ {
+ ImGui::Text( "-" );
+ }
+ else
+ {
+ const auto& srcloc = m_worker.GetSourceLocation( zone->srcloc );
+ const auto txt = srcloc.name.active ? m_worker.GetString( srcloc.name ) : m_worker.GetString( srcloc.function );
+ ImGui::PushID( idx++ );
+ auto sel = ImGui::Selectable( txt, m_zoneInfoWindow == zone );
+ auto hover = ImGui::IsItemHovered();
+ ImGui::PopID();
+ if( sel )
+ {
+ m_zoneInfoWindow = zone;
+ }
+ if( hover )
+ {
+ m_zoneHighlight = zone;
+ if( ImGui::IsMouseClicked( 2 ) )
+ {
+ ZoomToZone( *zone );
+ }
+ ZoneTooltip( *zone );
+ }
+ }
+ ImGui::NextColumn();
+ if( v->timeFree < 0 )
+ {
+ ImGui::TextColored( ImVec4( 0.6f, 1.f, 0.6f, 1.f ), "active" );
+ }
+ else
+ {
+ auto zoneFree = FindZoneAtTime( m_worker.DecompressThread( v->threadFree ), v->timeFree );
+ if( !zoneFree )
+ {
+ ImGui::Text( "-" );
+ }
+ else
+ {
+ const auto& srcloc = m_worker.GetSourceLocation( zoneFree->srcloc );
+ const auto txt = srcloc.name.active ? m_worker.GetString( srcloc.name ) : m_worker.GetString( srcloc.function );
+ ImGui::PushID( idx++ );
+ bool sel;
+ if( zoneFree == zone )
+ {
+ sel = ImGui::Selectable( "", m_zoneInfoWindow == zoneFree );
+ ImGui::SameLine();
+ ImGui::TextColored( ImVec4( 1.f, 1.f, 0.6f, 1.f ), txt );
+ }
+ else
+ {
+ sel = ImGui::Selectable( txt, m_zoneInfoWindow == zoneFree );
+ }
+ auto hover = ImGui::IsItemHovered();
+ ImGui::PopID();
+ if( sel )
+ {
+ m_zoneInfoWindow = zoneFree;
+ }
+ if( hover )
+ {
+ m_zoneHighlight = zoneFree;
+ if( ImGui::IsMouseClicked( 2 ) )
+ {
+ ZoomToZone( *zoneFree );
+ }
+ ZoneTooltip( *zoneFree );
+ }
+ }
+ }
+ ImGui::NextColumn();
+ ptr++;
+ }
+ ImGui::EndColumns();
+ ImGui::EndChild();
+}
+
+enum { ChunkBits = 10 };
+enum { PageBits = 10 };
+enum { PageSize = 1 << PageBits };
+enum { PageChunkBits = ChunkBits + PageBits };
+enum { PageChunkSize = 1 << PageChunkBits };
+
+uint32_t MemDecayColor[256] = {
+ 0x0, 0xFF077F07, 0xFF078007, 0xFF078207, 0xFF078307, 0xFF078507, 0xFF078707, 0xFF078807,
+ 0xFF078A07, 0xFF078B07, 0xFF078D07, 0xFF078F07, 0xFF079007, 0xFF089208, 0xFF089308, 0xFF089508,
+ 0xFF089708, 0xFF089808, 0xFF089A08, 0xFF089B08, 0xFF089D08, 0xFF089F08, 0xFF08A008, 0xFF08A208,
+ 0xFF09A309, 0xFF09A509, 0xFF09A709, 0xFF09A809, 0xFF09AA09, 0xFF09AB09, 0xFF09AD09, 0xFF09AF09,
+ 0xFF09B009, 0xFF09B209, 0xFF09B309, 0xFF09B509, 0xFF0AB70A, 0xFF0AB80A, 0xFF0ABA0A, 0xFF0ABB0A,
+ 0xFF0ABD0A, 0xFF0ABF0A, 0xFF0AC00A, 0xFF0AC20A, 0xFF0AC30A, 0xFF0AC50A, 0xFF0AC70A, 0xFF0BC80B,
+ 0xFF0BCA0B, 0xFF0BCB0B, 0xFF0BCD0B, 0xFF0BCF0B, 0xFF0BD00B, 0xFF0BD20B, 0xFF0BD30B, 0xFF0BD50B,
+ 0xFF0BD70B, 0xFF0BD80B, 0xFF0BDA0B, 0xFF0CDB0C, 0xFF0CDD0C, 0xFF0CDF0C, 0xFF0CE00C, 0xFF0CE20C,
+ 0xFF0CE30C, 0xFF0CE50C, 0xFF0CE70C, 0xFF0CE80C, 0xFF0CEA0C, 0xFF0CEB0C, 0xFF0DED0D, 0xFF0DEF0D,
+ 0xFF0DF00D, 0xFF0DF20D, 0xFF0DF30D, 0xFF0DF50D, 0xFF0DF70D, 0xFF0DF80D, 0xFF0DFA0D, 0xFF0DFB0D,
+ 0xFF0DFD0D, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E,
+ 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0EFF0E, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F,
+ 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F, 0xFF0FFF0F,
+ 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10,
+ 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF10FF10, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11,
+ 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF11FF11, 0xFF12FF12,
+ 0x0, 0xFF1212FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF,
+ 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1111FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF,
+ 0xFF1010FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF, 0xFF1010FF,
+ 0xFF1010FF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF,
+ 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0F0FFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF,
+ 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF, 0xFF0E0EFF,
+ 0xFF0D0DFD, 0xFF0D0DFB, 0xFF0D0DFA, 0xFF0D0DF8, 0xFF0D0DF7, 0xFF0D0DF5, 0xFF0D0DF3, 0xFF0D0DF2,
+ 0xFF0D0DF0, 0xFF0D0DEF, 0xFF0D0DED, 0xFF0C0CEB, 0xFF0C0CEA, 0xFF0C0CE8, 0xFF0C0CE7, 0xFF0C0CE5,
+ 0xFF0C0CE3, 0xFF0C0CE2, 0xFF0C0CE0, 0xFF0C0CDF, 0xFF0C0CDD, 0xFF0C0CDB, 0xFF0B0BDA, 0xFF0B0BD8,
+ 0xFF0B0BD7, 0xFF0B0BD5, 0xFF0B0BD3, 0xFF0B0BD2, 0xFF0B0BD0, 0xFF0B0BCF, 0xFF0B0BCD, 0xFF0B0BCB,
+ 0xFF0B0BCA, 0xFF0B0BC8, 0xFF0A0AC7, 0xFF0A0AC5, 0xFF0A0AC3, 0xFF0A0AC2, 0xFF0A0AC0, 0xFF0A0ABF,
+ 0xFF0A0ABD, 0xFF0A0ABB, 0xFF0A0ABA, 0xFF0A0AB8, 0xFF0A0AB7, 0xFF0909B5, 0xFF0909B3, 0xFF0909B2,
+ 0xFF0909B0, 0xFF0909AF, 0xFF0909AD, 0xFF0909AB, 0xFF0909AA, 0xFF0909A8, 0xFF0909A7, 0xFF0909A5,
+ 0xFF0909A3, 0xFF0808A2, 0xFF0808A0, 0xFF08089F, 0xFF08089D, 0xFF08089B, 0xFF08089A, 0xFF080898,
+ 0xFF080897, 0xFF080895, 0xFF080893, 0xFF080892, 0xFF070790, 0xFF07078F, 0xFF07078D, 0xFF07078B,
+ 0xFF07078A, 0xFF070788, 0xFF070787, 0xFF070785, 0xFF070783, 0xFF070782, 0xFF070780, 0xFF07077F,
+};
+
+void View::DrawMemory()
+{
+ auto& mem = m_worker.GetMemData();
+
+ ImGui::Begin( "Memory", &m_memInfo.show );
+
+ ImGui::Text( "Total allocations: %-15s Active allocations: %-15s Memory usage: %-15s Memory span: %s",
+ RealToString( mem.data.size(), true ),
+ RealToString( mem.active.size(), true ),
+ RealToString( mem.usage, true ),
+ RealToString( mem.high - mem.low, true ) );
+
+ ImGui::InputText( "", m_memInfo.pattern, 1024 );
+ ImGui::SameLine();
+
+ if( ImGui::Button( "Find" ) )
+ {
+ m_memInfo.ptrFind = strtoull( m_memInfo.pattern, nullptr, 0 );
+ }
+ ImGui::SameLine();
+ if( ImGui::Button( "Clear" ) )
+ {
+ m_memInfo.ptrFind = 0;
+ m_memInfo.pattern[0] = '\0';
+ }
+ ImGui::SameLine();
+ ImGui::Checkbox( "Restrict time", &m_memInfo.restrictTime );
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(?)" );
+ if( ImGui::IsItemHovered() )
+ {
+ ImGui::BeginTooltip();
+ ImGui::Text( "Don't show allocations beyond the middle of timeline display." );
+ ImGui::EndTooltip();
+ }
+
+ const auto zvMid = m_zvStart + ( m_zvEnd - m_zvStart ) / 2;
+
+ ImGui::Separator();
+ if( ImGui::TreeNodeEx( "Allocations", ImGuiTreeNodeFlags_DefaultOpen ) )
+ {
+ if( m_memInfo.ptrFind != 0 )
+ {
+ std::vector match;
+ match.reserve( mem.active.size() ); // heuristic
+ if( m_memInfo.restrictTime )
+ {
+ for( auto& v : mem.data )
+ {
+ if( v.ptr <= m_memInfo.ptrFind && v.ptr + v.size > m_memInfo.ptrFind && v.timeAlloc < zvMid )
+ {
+ match.emplace_back( &v );
+ }
+ }
+ }
+ else
+ {
+ for( auto& v : mem.data )
+ {
+ if( v.ptr <= m_memInfo.ptrFind && v.ptr + v.size > m_memInfo.ptrFind )
+ {
+ match.emplace_back( &v );
+ }
+ }
+ }
+
+ if( match.empty() )
+ {
+ ImGui::Text( "Found no allocations at given address" );
+ }
+ else
+ {
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", RealToString( match.size(), true ) );
+ ListMemData( match.begin(), match.end(), [this]( auto& it ) {
+ auto& v = *it;
+ if( v->ptr == m_memInfo.ptrFind )
+ {
+ ImGui::Text( "0x%" PRIx64, m_memInfo.ptrFind );
+ }
+ else
+ {
+ ImGui::Text( "0x%" PRIx64 "+%" PRIu64, v->ptr, m_memInfo.ptrFind - v->ptr );
+ }
+ return v;
+ } );
+ }
+ }
+ ImGui::TreePop();
+ }
+
+ ImGui::Separator();
+ if( ImGui::TreeNode( "Active allocations" ) )
+ {
+ uint64_t total = 0;
+ std::vector items;
+ items.reserve( mem.active.size() );
+ if( m_memInfo.restrictTime )
+ {
+ for( auto& v : mem.data )
+ {
+ if( v.timeAlloc < zvMid && ( v.timeFree > zvMid || v.timeFree < 0 ) )
+ {
+ items.emplace_back( &v );
+ total += v.size;
+ }
+ }
+ }
+ else
+ {
+ auto ptr = mem.data.data();
+ for( auto& v : mem.active )
+ {
+ items.emplace_back( ptr + v.second );
+ }
+ pdqsort_branchless( items.begin(), items.end(), []( const auto& lhs, const auto& rhs ) { return lhs->timeAlloc < rhs->timeAlloc; } );
+ total = mem.usage;
+ }
+
+ ImGui::SameLine();
+ ImGui::TextDisabled( "(%s)", RealToString( items.size(), true ) );
+ ImGui::Text( "Memory usage: %s", RealToString( total, true ) );
+
+ ListMemData( items.begin(), items.end(), []( auto& v ) {
+ ImGui::Text( "0x%" PRIx64, (*v)->ptr );
+ return *v;
+ } );
+ ImGui::TreePop();
+ }
+
+ ImGui::Separator();
+ if( ImGui::TreeNode( "Memory map" ) )
+ {
+ ImGui::Text( "Single pixel: %s KB Single line: %s KB", RealToString( ( 1 << ChunkBits ) / 1024, true ), RealToString( PageChunkSize / 1024, true ) );
+
+ auto pages = GetMemoryPages();
+
+ const int8_t empty[PageSize] = {};
+ const auto sz = pages.size() / PageSize;
+ auto pgptr = pages.data();
+ const auto end = pgptr + sz * PageSize;
+ size_t lines = sz;
+ while( pgptr != end )
+ {
+ if( memcmp( empty, pgptr, PageSize ) == 0 )
+ {
+ pgptr += PageSize;
+ while( pgptr != end && memcmp( empty, pgptr, PageSize ) == 0 )
+ {
+ lines--;
+ pgptr += PageSize;
+ }
+ }
+ else
+ {
+ pgptr += PageSize;
+ }
+ }
+
+ ImGui::BeginChild( "##memMap", ImVec2( PageSize + 2, lines + 2 ), false );
+ auto draw = ImGui::GetWindowDrawList();
+ const auto wpos = ImGui::GetCursorScreenPos() + ImVec2( 1, 1 );
+ draw->AddRect( wpos - ImVec2( 1, 1 ), wpos + ImVec2( PageSize + 1, lines + 1 ), 0xFF666666 );
+ draw->AddRectFilled( wpos, wpos + ImVec2( PageSize, lines ), 0xFF444444 );
+
+ size_t line = 0;
+ pgptr = pages.data();
+ while( pgptr != end )
+ {
+ if( memcmp( empty, pgptr, PageSize ) == 0 )
+ {
+ pgptr += PageSize;
+ draw->AddLine( wpos + ImVec2( 0, line ), wpos + ImVec2( PageSize, line ), 0x11000000 );
+ line++;
+ while( pgptr != end && memcmp( empty, pgptr, PageSize ) == 0 ) pgptr += PageSize;
+ }
+ else
+ {
+ size_t idx = 0;
+ while( idx < PageSize )
+ {
+ if( pgptr[idx] == 0 )
+ {
+ do
+ {
+ idx++;
+ }
+ while( idx < PageSize && pgptr[idx] == 0 );
+ }
+ else
+ {
+ auto val = pgptr[idx];
+ const auto i0 = idx;
+ do
+ {
+ idx++;
+ }
+ while( idx < PageSize && pgptr[idx] == val );
+ draw->AddLine( wpos + ImVec2( i0, line ), wpos + ImVec2( idx, line ), MemDecayColor[(uint8_t)val] );
+ }
+ }
+ line++;
+ pgptr += PageSize;
+ }
+ }
+
+ ImGui::EndChild();
+ ImGui::TreePop();
+ }
+
+ ImGui::End();
+}
+
+Vector View::GetMemoryPages() const
+{
+ Vector ret;
+
+ const auto& mem = m_worker.GetMemData();
+ const auto span = mem.high - mem.low;
+ const auto pages = ( span / PageChunkSize ) + 1;
+
+ ret.reserve_and_use( pages * PageSize );
+ auto pgptr = ret.data();
+ memset( pgptr, 0, pages * PageSize );
+
+ const auto memlow = mem.low;
+
+ if( m_memInfo.restrictTime )
+ {
+ const auto zvMid = m_zvStart + ( m_zvEnd - m_zvStart ) / 2;
+ for( auto& alloc : mem.data )
+ {
+ if( m_memInfo.restrictTime && alloc.timeAlloc > zvMid ) break;
+
+ const auto a0 = alloc.ptr - memlow;
+ const auto a1 = a0 + alloc.size;
+ int8_t val = alloc.timeFree < 0 ?
+ int8_t( std::max( int64_t( 1 ), 127 - ( ( zvMid - alloc.timeAlloc ) >> 24 ) ) ) :
+ ( alloc.timeFree > zvMid ?
+ int8_t( std::max( int64_t( 1 ), 127 - ( ( zvMid - alloc.timeAlloc ) >> 24 ) ) ) :
+ int8_t( -std::max( int64_t( 1 ), 127 - ( ( zvMid - alloc.timeFree ) >> 24 ) ) ) );
+
+ const auto c0 = a0 >> ChunkBits;
+ const auto c1 = a1 >> ChunkBits;
+
+ if( c0 == c1 )
+ {
+ pgptr[c0] = val;
+ }
+ else
+ {
+ memset( pgptr + c0, val, c1 - c0 + 1 );
+ }
+ }
+ }
+ else
+ {
+ const auto lastTime = m_worker.GetLastTime();
+ for( auto& alloc : mem.data )
+ {
+ const auto a0 = alloc.ptr - memlow;
+ const auto a1 = a0 + alloc.size;
+ const int8_t val = alloc.timeFree < 0 ?
+ int8_t( std::max( int64_t( 1 ), 127 - ( ( lastTime - std::min( lastTime, alloc.timeAlloc ) ) >> 24 ) ) ) :
+ int8_t( -std::max( int64_t( 1 ), 127 - ( ( lastTime - std::min( lastTime, alloc.timeFree ) ) >> 24 ) ) );
+
+ const auto c0 = a0 >> ChunkBits;
+ const auto c1 = a1 >> ChunkBits;
+
+ if( c0 == c1 )
+ {
+ pgptr[c0] = val;
+ }
+ else
+ {
+ memset( pgptr + c0, val, c1 - c0 + 1 );
+ }
+ }
+ }
+
+ return ret;
+}
+
uint32_t View::GetZoneColor( const ZoneEvent& ev )
{
const auto& srcloc = m_worker.GetSourceLocation( ev.srcloc );
@@ -3928,6 +4413,34 @@ uint64_t View::GetZoneThread( const GpuEvent& zone ) const
return 0;
}
+const ZoneEvent* View::FindZoneAtTime( uint64_t thread, int64_t time ) const
+{
+ // TODO add thread rev-map
+ ThreadData* td = nullptr;
+ for( const auto& t : m_worker.GetThreadData() )
+ {
+ if( t->id == thread )
+ {
+ td = t;
+ break;
+ }
+ }
+ if( !td ) return nullptr;
+
+ const Vector* timeline = &td->timeline;
+ if( timeline->empty() ) return nullptr;
+ ZoneEvent* ret = nullptr;
+ for(;;)
+ {
+ auto it = std::upper_bound( timeline->begin(), timeline->end(), time, [] ( const auto& l, const auto& r ) { return l < r->start; } );
+ if( it != timeline->begin() ) --it;
+ if( (*it)->start > time || ( (*it)->end >= 0 && (*it)->end < time ) ) return ret;
+ ret = *it;
+ if( (*it)->child.empty() ) return ret;
+ timeline = &(*it)->child;
+ }
+}
+
#ifndef TRACY_NO_STATISTICS
void View::FindZones()
{
diff --git a/server/TracyView.hpp b/server/TracyView.hpp
index 6b930e32..fd093299 100644
--- a/server/TracyView.hpp
+++ b/server/TracyView.hpp
@@ -2,14 +2,15 @@
#define __TRACYVIEW_HPP__
#include
+#include
#include