update tracy from 11.0 to 13.1 and fix build with tracy enabled

This commit is contained in:
Sven Balzer
2026-05-01 18:24:04 +02:00
parent 7fa5294e02
commit 2adf75973a
304 changed files with 20579 additions and 170182 deletions
@@ -0,0 +1,341 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <emscripten/html5.h>
#include <backends/imgui_impl_opengl3.h>
#include "Backend.hpp"
#include "RunQueue.hpp"
#include "profiler/TracyImGui.hpp"
static std::function<void()> s_redraw;
static std::function<void(float)> s_scaleChanged;
static std::function<int(void)> s_isBusy;
static RunQueue* s_mainThreadTasks;
static EGLDisplay s_eglDpy;
static EGLContext s_eglCtx;
static EGLSurface s_eglSurf;
static float s_prevScale = -1;
static int s_width, s_height;
static uint64_t s_time;
static const char* s_prevCursor = nullptr;
static ImGuiKey TranslateKeyCode( const char* code )
{
if( strcmp( code, "Backquote" ) == 0 ) return ImGuiKey_GraveAccent;
if( strcmp( code, "Backslash" ) == 0 ) return ImGuiKey_Backslash;
if( strcmp( code, "BracketLeft" ) == 0 ) return ImGuiKey_LeftBracket;
if( strcmp( code, "BracketRight" ) == 0 ) return ImGuiKey_RightBracket;
if( strcmp( code, "Comma" ) == 0 ) return ImGuiKey_Comma;
if( strcmp( code, "Digit0" ) == 0 ) return ImGuiKey_0;
if( strcmp( code, "Digit1" ) == 0 ) return ImGuiKey_1;
if( strcmp( code, "Digit2" ) == 0 ) return ImGuiKey_2;
if( strcmp( code, "Digit3" ) == 0 ) return ImGuiKey_3;
if( strcmp( code, "Digit4" ) == 0 ) return ImGuiKey_4;
if( strcmp( code, "Digit5" ) == 0 ) return ImGuiKey_5;
if( strcmp( code, "Digit6" ) == 0 ) return ImGuiKey_6;
if( strcmp( code, "Digit7" ) == 0 ) return ImGuiKey_7;
if( strcmp( code, "Digit8" ) == 0 ) return ImGuiKey_8;
if( strcmp( code, "Digit9" ) == 0 ) return ImGuiKey_9;
if( strcmp( code, "Equal" ) == 0 ) return ImGuiKey_Equal;
if( strcmp( code, "IntlBackslash" ) == 0 ) return ImGuiKey_Backslash;
if( strcmp( code, "IntlRo" ) == 0 ) return ImGuiKey_Backslash;
if( strcmp( code, "IntlYen" ) == 0 ) return ImGuiKey_Backslash;
if( strcmp( code, "KeyA" ) == 0 ) return ImGuiKey_A;
if( strcmp( code, "KeyB" ) == 0 ) return ImGuiKey_B;
if( strcmp( code, "KeyC" ) == 0 ) return ImGuiKey_C;
if( strcmp( code, "KeyD" ) == 0 ) return ImGuiKey_D;
if( strcmp( code, "KeyE" ) == 0 ) return ImGuiKey_E;
if( strcmp( code, "KeyF" ) == 0 ) return ImGuiKey_F;
if( strcmp( code, "KeyG" ) == 0 ) return ImGuiKey_G;
if( strcmp( code, "KeyH" ) == 0 ) return ImGuiKey_H;
if( strcmp( code, "KeyI" ) == 0 ) return ImGuiKey_I;
if( strcmp( code, "KeyJ" ) == 0 ) return ImGuiKey_J;
if( strcmp( code, "KeyK" ) == 0 ) return ImGuiKey_K;
if( strcmp( code, "KeyL" ) == 0 ) return ImGuiKey_L;
if( strcmp( code, "KeyM" ) == 0 ) return ImGuiKey_M;
if( strcmp( code, "KeyN" ) == 0 ) return ImGuiKey_N;
if( strcmp( code, "KeyO" ) == 0 ) return ImGuiKey_O;
if( strcmp( code, "KeyP" ) == 0 ) return ImGuiKey_P;
if( strcmp( code, "KeyQ" ) == 0 ) return ImGuiKey_Q;
if( strcmp( code, "KeyR" ) == 0 ) return ImGuiKey_R;
if( strcmp( code, "KeyS" ) == 0 ) return ImGuiKey_S;
if( strcmp( code, "KeyT" ) == 0 ) return ImGuiKey_T;
if( strcmp( code, "KeyU" ) == 0 ) return ImGuiKey_U;
if( strcmp( code, "KeyV" ) == 0 ) return ImGuiKey_V;
if( strcmp( code, "KeyW" ) == 0 ) return ImGuiKey_W;
if( strcmp( code, "KeyX" ) == 0 ) return ImGuiKey_X;
if( strcmp( code, "KeyY" ) == 0 ) return ImGuiKey_Y;
if( strcmp( code, "KeyZ" ) == 0 ) return ImGuiKey_Z;
if( strcmp( code, "Minus" ) == 0 ) return ImGuiKey_Minus;
if( strcmp( code, "Period" ) == 0 ) return ImGuiKey_Period;
if( strcmp( code, "Quote" ) == 0 ) return ImGuiKey_Apostrophe;
if( strcmp( code, "Semicolon" ) == 0 ) return ImGuiKey_Semicolon;
if( strcmp( code, "Slash" ) == 0 ) return ImGuiKey_Slash;
if( strcmp( code, "AltLeft" ) == 0 ) return ImGuiKey_LeftAlt;
if( strcmp( code, "AltRight" ) == 0 ) return ImGuiKey_RightAlt;
if( strcmp( code, "Backspace" ) == 0 ) return ImGuiKey_Backspace;
if( strcmp( code, "CapsLock" ) == 0 ) return ImGuiKey_CapsLock;
if( strcmp( code, "ContextMenu" ) == 0 ) return ImGuiKey_Menu;
if( strcmp( code, "ControlLeft" ) == 0 ) return ImGuiKey_LeftCtrl;
if( strcmp( code, "ControlRight" ) == 0 ) return ImGuiKey_RightCtrl;
if( strcmp( code, "Enter" ) == 0 ) return ImGuiKey_Enter;
if( strcmp( code, "MetaLeft" ) == 0 ) return ImGuiKey_LeftSuper;
if( strcmp( code, "MetaRight" ) == 0 ) return ImGuiKey_RightSuper;
if( strcmp( code, "ShiftLeft" ) == 0 ) return ImGuiKey_LeftShift;
if( strcmp( code, "ShiftRight" ) == 0 ) return ImGuiKey_RightShift;
if( strcmp( code, "Space" ) == 0 ) return ImGuiKey_Space;
if( strcmp( code, "Tab" ) == 0 ) return ImGuiKey_Tab;
if( strcmp( code, "Delete" ) == 0 ) return ImGuiKey_Delete;
if( strcmp( code, "End" ) == 0 ) return ImGuiKey_End;
if( strcmp( code, "Home" ) == 0 ) return ImGuiKey_Home;
if( strcmp( code, "Insert" ) == 0 ) return ImGuiKey_Insert;
if( strcmp( code, "PageDown" ) == 0 ) return ImGuiKey_PageDown;
if( strcmp( code, "PageUp" ) == 0 ) return ImGuiKey_PageUp;
if( strcmp( code, "ArrowDown" ) == 0 ) return ImGuiKey_DownArrow;
if( strcmp( code, "ArrowLeft" ) == 0 ) return ImGuiKey_LeftArrow;
if( strcmp( code, "ArrowRight" ) == 0 ) return ImGuiKey_RightArrow;
if( strcmp( code, "ArrowUp" ) == 0 ) return ImGuiKey_UpArrow;
if( strcmp( code, "NumLock" ) == 0 ) return ImGuiKey_NumLock;
if( strcmp( code, "Numpad0" ) == 0 ) return ImGuiKey_Keypad0;
if( strcmp( code, "Numpad1" ) == 0 ) return ImGuiKey_Keypad1;
if( strcmp( code, "Numpad2" ) == 0 ) return ImGuiKey_Keypad2;
if( strcmp( code, "Numpad3" ) == 0 ) return ImGuiKey_Keypad3;
if( strcmp( code, "Numpad4" ) == 0 ) return ImGuiKey_Keypad4;
if( strcmp( code, "Numpad5" ) == 0 ) return ImGuiKey_Keypad5;
if( strcmp( code, "Numpad6" ) == 0 ) return ImGuiKey_Keypad6;
if( strcmp( code, "Numpad7" ) == 0 ) return ImGuiKey_Keypad7;
if( strcmp( code, "Numpad8" ) == 0 ) return ImGuiKey_Keypad8;
if( strcmp( code, "Numpad9" ) == 0 ) return ImGuiKey_Keypad9;
if( strcmp( code, "NumpadAdd" ) == 0 ) return ImGuiKey_KeypadAdd;
if( strcmp( code, "NumpadBackspace" ) == 0 ) return ImGuiKey_Backspace;
if( strcmp( code, "NumpadComma" ) == 0 ) return ImGuiKey_KeypadDecimal;
if( strcmp( code, "NumpadDecimal" ) == 0 ) return ImGuiKey_KeypadDecimal;
if( strcmp( code, "NumpadDivide" ) == 0 ) return ImGuiKey_KeypadDivide;
if( strcmp( code, "NumpadEnter" ) == 0 ) return ImGuiKey_KeypadEnter;
if( strcmp( code, "NumpadEqual" ) == 0 ) return ImGuiKey_KeypadEqual;
if( strcmp( code, "NumpadMultiply" ) == 0 ) return ImGuiKey_KeypadMultiply;
if( strcmp( code, "NumpadSubtract" ) == 0 ) return ImGuiKey_KeypadSubtract;
if( strcmp( code, "Escape" ) == 0 ) return ImGuiKey_Escape;
if( strcmp( code, "F1" ) == 0 ) return ImGuiKey_F1;
if( strcmp( code, "F2" ) == 0 ) return ImGuiKey_F2;
if( strcmp( code, "F3" ) == 0 ) return ImGuiKey_F3;
if( strcmp( code, "F4" ) == 0 ) return ImGuiKey_F4;
if( strcmp( code, "F5" ) == 0 ) return ImGuiKey_F5;
if( strcmp( code, "F6" ) == 0 ) return ImGuiKey_F6;
if( strcmp( code, "F7" ) == 0 ) return ImGuiKey_F7;
if( strcmp( code, "F8" ) == 0 ) return ImGuiKey_F8;
if( strcmp( code, "F9" ) == 0 ) return ImGuiKey_F9;
if( strcmp( code, "F10" ) == 0 ) return ImGuiKey_F10;
// F11 is browser fullscreen, F12 is browser dev tools, omitting them
if( strcmp( code, "ScrollLock" ) == 0 ) return ImGuiKey_ScrollLock;
if( strcmp( code, "Pause" ) == 0 ) return ImGuiKey_Pause;
return ImGuiKey_None;
}
Backend::Backend( const char* title, const std::function<void()>& redraw, const std::function<void(float)>& scaleChanged, const std::function<int(void)>& isBusy, RunQueue* mainThreadTasks )
{
constexpr EGLint eglConfigAttrib[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE
};
s_eglDpy = eglGetDisplay( EGL_DEFAULT_DISPLAY );
EGLBoolean res;
res = eglInitialize( s_eglDpy, nullptr, nullptr );
if( res != EGL_TRUE ) { fprintf( stderr, "Cannot initialize EGL!\n" ); exit( 1 ); }
EGLint count;
EGLConfig eglConfig;
res = eglChooseConfig( s_eglDpy, eglConfigAttrib, &eglConfig, 1, &count );
if( res != EGL_TRUE || count != 1 ) { fprintf( stderr, "No suitable EGL config found!\n" ); exit( 1 ); }
s_eglSurf = eglCreateWindowSurface( s_eglDpy, eglConfig, 0, nullptr );
constexpr EGLint eglCtxAttrib[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
s_eglCtx = eglCreateContext( s_eglDpy, eglConfig, EGL_NO_CONTEXT, eglCtxAttrib );
if( !s_eglCtx ) { fprintf( stderr, "Cannot create OpenGL 3.2 Core Profile context!\n" ); exit( 1 ); }
res = eglMakeCurrent( s_eglDpy, s_eglSurf, s_eglSurf, s_eglCtx );
if( res != EGL_TRUE ) { fprintf( stderr, "Cannot make EGL context current!\n" ); exit( 1 ); }
ImGui_ImplOpenGL3_Init( "#version 100" );
EM_ASM( document.title = UTF8ToString($0), title );
s_redraw = redraw;
s_scaleChanged = scaleChanged;
s_isBusy = isBusy;
s_mainThreadTasks = mainThreadTasks;
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "wasm (tracy profiler)";
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_mouseup_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, false );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_mousemove_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
const auto scale = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
ImGui::GetIO().AddMousePosEvent( e->targetX * scale, e->targetY * scale );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( false );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( true );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_wheel_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenWheelEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseWheelEvent( e->deltaX * -0.05, e->deltaY * -0.05 );
tracy::s_wasActive = true;
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, true );
if( e->key[0] && !e->key[1] ) ImGui::GetIO().AddInputCharacter( *e->key );
return EM_TRUE;
} );
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, false );
return EM_TRUE;
} );
s_time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
Backend::~Backend()
{
}
void Backend::Show()
{
}
void Backend::Run()
{
emscripten_set_main_loop( []() {
s_redraw();
s_mainThreadTasks->Run();
}, 0, 1 );
}
void Backend::Attention()
{
}
void Backend::NewFrame( int& w, int& h )
{
const auto scale = GetDpiScale();
if( scale != s_prevScale )
{
s_prevScale = scale;
s_scaleChanged( scale );
}
w = EM_ASM_INT( { return window.innerWidth; } ) * scale;
h = EM_ASM_INT( { return window.innerHeight; } ) * scale;
if( s_width != w || s_height != h )
{
EM_ASM( Module.canvas.style.width = window.innerWidth + 'px'; Module.canvas.style.height = window.innerHeight + 'px' );
EM_ASM( Module.canvas.width = $0; Module.canvas.height = $1, w, h );
s_width = w;
s_height = h;
glViewport( 0, 0, s_width, s_height );
tracy::s_wasActive = true;
}
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2( w, h );
io.DisplayFramebufferScale = ImVec2( 1, 1 );
ImGui_ImplOpenGL3_NewFrame();
ImGuiMouseCursor cursor = ImGui::GetMouseCursor();
const char* cursorName;
switch( cursor )
{
case ImGuiMouseCursor_None: cursorName = "none"; break;
case ImGuiMouseCursor_Arrow:
switch( s_isBusy() )
{
default:
case 0: cursorName = "default"; break;
case 1: cursorName = "progress"; break;
case 2: cursorName = "wait"; break;
}
break;
case ImGuiMouseCursor_TextInput: cursorName = "text"; break;
case ImGuiMouseCursor_ResizeAll: cursorName = "move"; break;
case ImGuiMouseCursor_ResizeNS: cursorName = "ns-resize"; break;
case ImGuiMouseCursor_ResizeEW: cursorName = "ew-resize"; break;
case ImGuiMouseCursor_ResizeNESW: cursorName = "nesw-resize"; break;
case ImGuiMouseCursor_ResizeNWSE: cursorName = "nwse-resize"; break;
case ImGuiMouseCursor_Hand: cursorName = "pointer"; break;
case ImGuiMouseCursor_NotAllowed: cursorName = "not-allowed"; break;
default: cursorName = "auto"; break;
};
if( s_prevCursor != cursorName )
{
s_prevCursor = cursorName;
EM_ASM_INT( { document.getElementById('canvas').style.cursor = UTF8ToString($0); }, cursorName );
}
uint64_t time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
io.DeltaTime = std::min( 0.1f, ( time - s_time ) / 1000000.f );
s_time = time;
}
void Backend::EndFrame()
{
const ImVec4 clear_color = ImColor( 20, 20, 17 );
ImGui::Render();
glClearColor( clear_color.x, clear_color.y, clear_color.z, clear_color.w );
glClear( GL_COLOR_BUFFER_BIT );
ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() );
}
void Backend::SetIcon( uint8_t* data, int w, int h )
{
}
void Backend::SetTitle( const char* title )
{
EM_ASM( document.title = UTF8ToString($0), title );
}
float Backend::GetDpiScale()
{
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
}
+86 -42
View File
@@ -1,11 +1,6 @@
#include "imgui/imgui_impl_glfw.h"
#include "imgui/imgui_impl_opengl3.h"
#ifdef __EMSCRIPTEN__
# include <GLES2/gl2.h>
# include <emscripten/html5.h>
#else
# include "imgui/imgui_impl_opengl3_loader.h"
#endif
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_opengl3.h>
#include <backends/imgui_impl_opengl3_loader.h>
#include <chrono>
#include <GLFW/glfw3.h>
@@ -19,14 +14,65 @@
#include "Backend.hpp"
#include "RunQueue.hpp"
#ifdef __APPLE__
#include <objc/objc.h>
#include <objc/message.h>
#include <objc/runtime.h>
#include "icon.hpp"
#endif
static GLFWwindow* s_window;
static std::function<void()> s_redraw;
static std::function<void(float)> s_scaleChanged;
static RunQueue* s_mainThreadTasks;
static WindowPosition* s_winPos;
static bool s_iconified;
static float s_prevScale = -1;
extern tracy::Config s_config;
#ifdef __APPLE__
typedef long NSInteger;
typedef unsigned long NSUInteger;
namespace
{
static void EnsureMacAppRegistration()
{
static bool initialized = false;
if( initialized ) return;
initialized = true;
id pool = ((id (*)(Class, SEL))objc_msgSend)((Class)objc_getClass("NSAutoreleasePool"), sel_getUid("alloc"));
pool = ((id (*)(id, SEL))objc_msgSend)(pool, sel_getUid("init"));
id app = ((id (*)(Class, SEL))objc_msgSend)((Class)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));
((void (*)(id, SEL, NSInteger))objc_msgSend)(app, sel_getUid("setActivationPolicy:"), (NSInteger)0);
((void (*)(id, SEL, BOOL))objc_msgSend)(app, sel_getUid("activateIgnoringOtherApps:"), (BOOL)1);
((void (*)(id, SEL))objc_msgSend)(pool, sel_getUid("release"));
}
static void SetMacAppIcon()
{
id pool = ((id (*)(Class, SEL))objc_msgSend)((Class)objc_getClass("NSAutoreleasePool"), sel_getUid("alloc"));
pool = ((id (*)(id, SEL))objc_msgSend)(pool, sel_getUid("init"));
id data = ((id (*)(Class, SEL, const void*, NSUInteger))objc_msgSend)((Class)objc_getClass("NSData"), sel_getUid("dataWithBytes:length:"), (const void*)Icon_data, (NSUInteger)Icon_size);
id image = ((id (*)(Class, SEL))objc_msgSend)((Class)objc_getClass("NSImage"), sel_getUid("alloc"));
image = ((id (*)(id, SEL, id))objc_msgSend)(image, sel_getUid("initWithData:"), data);
if( image )
{
id app = ((id (*)(Class, SEL))objc_msgSend)((Class)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));
((void (*)(id, SEL, id))objc_msgSend)(app, sel_getUid("setApplicationIconImage:"), image);
((void (*)(id, SEL))objc_msgSend)(image, sel_getUid("release"));
}
((void (*)(id, SEL))objc_msgSend)(pool, sel_getUid("release"));
}
}
#endif
static void glfw_error_callback( int error, const char* description )
@@ -83,6 +129,9 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
# if GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4 )
glfwWindowHint( GLFW_WIN32_KEYBOARD_MENU, 1 );
# endif
# if GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3 )
glfwWindowHint( GLFW_SCALE_TO_MONITOR, 1 );
# endif
#endif
s_window = glfwCreateWindow( m_winPos.w, m_winPos.h, title, NULL, NULL );
if( !s_window ) exit( 1 );
@@ -97,13 +146,10 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
glfwSetWindowRefreshCallback( s_window, []( GLFWwindow* ) { tracy::s_wasActive = true; s_redraw(); } );
ImGui_ImplGlfw_InitForOpenGL( s_window, true );
#ifdef __EMSCRIPTEN__
ImGui_ImplOpenGL3_Init( "#version 100" );
#else
ImGui_ImplOpenGL3_Init( "#version 150" );
#endif
s_redraw = redraw;
s_scaleChanged = scaleChanged;
s_mainThreadTasks = mainThreadTasks;
s_winPos = &m_winPos;
s_iconified = false;
@@ -114,6 +160,10 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
glfwSetWindowMaximizeCallback( s_window, glfw_window_maximize_callback );
#endif
glfwSetWindowIconifyCallback( s_window, glfw_window_iconify_callback );
#ifdef __APPLE__
EnsureMacAppRegistration();
#endif
}
Backend::~Backend()
@@ -133,13 +183,6 @@ void Backend::Show()
void Backend::Run()
{
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop( []() {
glfwPollEvents();
s_redraw();
s_mainThreadTasks->Run();
}, 0, 1 );
#else
while( !glfwWindowShouldClose( s_window ) )
{
if( s_iconified )
@@ -150,11 +193,10 @@ void Backend::Run()
{
glfwPollEvents();
s_redraw();
if( s_config.focusLostLimit && !glfwGetWindowAttrib( s_window, GLFW_FOCUSED ) ) std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
if( tracy::s_config.focusLostLimit && !glfwGetWindowAttrib( s_window, GLFW_FOCUSED ) ) std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
s_mainThreadTasks->Run();
}
}
#endif
}
void Backend::Attention()
@@ -169,7 +211,18 @@ void Backend::Attention()
void Backend::NewFrame( int& w, int& h )
{
const auto scale = GetDpiScale();
if( scale != s_prevScale )
{
s_prevScale = scale;
s_scaleChanged( scale );
}
glfwGetFramebufferSize( s_window, &w, &h );
#if defined( __APPLE__ )
w = static_cast<int>( w / scale );
h = static_cast<int>( h / scale );
#endif
m_w = w;
m_h = h;
@@ -192,11 +245,16 @@ void Backend::EndFrame()
void Backend::SetIcon( uint8_t* data, int w, int h )
{
#ifdef __APPLE__
EnsureMacAppRegistration();
SetMacAppIcon();
#else
GLFWimage icon;
icon.width = w;
icon.height = h;
icon.pixels = data;
glfwSetWindowIcon( s_window, 1, &icon );
#endif
}
void Backend::SetTitle( const char* title )
@@ -206,25 +264,11 @@ void Backend::SetTitle( const char* title )
float Backend::GetDpiScale()
{
#ifdef __EMSCRIPTEN__
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
#elif GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3 )
auto monitor = glfwGetWindowMonitor( s_window );
if( !monitor ) monitor = glfwGetPrimaryMonitor();
if( monitor )
{
float x, y;
glfwGetMonitorContentScale( monitor, &x, &y );
return x;
}
#endif
#if GLFW_VERSION_MAJOR > 3 || ( GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 3 )
float x, y;
glfwGetWindowContentScale( s_window, &x, &y );
return x;
#else
return 1;
}
#ifdef __EMSCRIPTEN__
extern "C" int nativeResize( int width, int height )
{
glfwSetWindowSize( s_window, width, height );
return 0;
}
#endif
}
+356 -23
View File
@@ -1,14 +1,14 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "imgui/imgui_impl_opengl3.h"
#include "imgui/imgui_impl_opengl3_loader.h"
#include <backends/imgui_impl_opengl3.h>
#include <backends/imgui_impl_opengl3_loader.h>
#include <chrono>
#include <linux/input-event-codes.h>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -25,8 +25,10 @@
#include "wayland-fractional-scale-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-cursor-shape-client-protocol.h"
#include "wayland-xdg-toplevel-icon-client-protocol.h"
#include "profiler/TracyImGui.hpp"
#include "stb_image_resize.h"
#include "Backend.hpp"
#include "RunQueue.hpp"
@@ -205,6 +207,18 @@ static xkb_mod_index_t s_xkbCtrl, s_xkbAlt, s_xkbShift, s_xkbSuper;
static wp_cursor_shape_device_v1_shape s_mouseCursor;
static uint32_t s_mouseCursorSerial;
static bool s_hasFocus = false;
static struct wl_data_device_manager* s_dataDevMgr;
static struct wl_data_device* s_dataDev;
static struct wl_data_source* s_dataSource;
static uint32_t s_dataSerial;
static std::string s_clipboard, s_clipboardIncoming;
static struct wl_data_offer* s_dataOffer;
static struct wl_data_offer* s_newDataOffer;
static bool s_newDataOfferValid;
static struct xdg_toplevel_icon_manager_v1* s_iconMgr;
static std::vector<int> s_iconSizes;
static int s_keyRepeatRate = 0;
static int s_keyRepeatDelay = 0;
struct Output
{
@@ -214,7 +228,7 @@ struct Output
};
static std::unordered_map<uint32_t, std::unique_ptr<Output>> s_output;
static int s_maxScale = 120;
static int s_prevScale = 120;
static int s_prevScale = -1;
static bool s_running = true;
static int s_width, s_height;
@@ -225,7 +239,15 @@ static uint64_t s_time;
static wl_fixed_t s_wheelAxisX, s_wheelAxisY;
static bool s_wheel;
extern tracy::Config s_config;
struct KeyRepeat
{
bool active;
bool first;
ImGuiKey key;
char txt[8];
uint64_t time;
};
static KeyRepeat s_keyRepeat;
static void RecomputeScale()
@@ -233,7 +255,7 @@ static void RecomputeScale()
if( s_fracSurf ) return;
// On wl_compositor >= 6 the scale is sent explicitly via wl_surface.preferred_buffer_scale.
if ( s_comp_version >= 6 ) return;
if( s_comp_version >= 6 ) return;
int max = 1;
for( auto& out : s_output )
@@ -391,6 +413,12 @@ static void KeyboardEnter( void*, struct wl_keyboard* kbd, uint32_t serial, stru
static void KeyboardLeave( void*, struct wl_keyboard* kbd, uint32_t serial, struct wl_surface* surf )
{
if( s_dataOffer )
{
wl_data_offer_destroy( s_dataOffer );
s_dataOffer = nullptr;
}
ImGui::GetIO().AddFocusEvent( false );
s_hasFocus = false;
}
@@ -418,6 +446,12 @@ static void KeyboardKey( void*, struct wl_keyboard* kbd, uint32_t serial, uint32
if( key < ( sizeof( s_keyTable ) / sizeof( *s_keyTable ) ) )
{
io.AddKeyEvent( s_keyTable[key], state == WL_KEYBOARD_KEY_STATE_PRESSED );
*s_keyRepeat.txt = 0;
s_keyRepeat.key = s_keyTable[key];
s_keyRepeat.active = true;
s_keyRepeat.first = true;
s_keyRepeat.time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
if( state == WL_KEYBOARD_KEY_STATE_PRESSED )
@@ -430,8 +464,18 @@ static void KeyboardKey( void*, struct wl_keyboard* kbd, uint32_t serial, uint32
if( xkb_keysym_to_utf8( sym, txt, sizeof( txt ) ) > 0 )
{
ImGui::GetIO().AddInputCharactersUTF8( txt );
memcpy( s_keyRepeat.txt, txt, sizeof( s_keyRepeat.txt ) );
s_keyRepeat.active = true;
s_keyRepeat.first = true;
s_keyRepeat.time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
}
s_dataSerial = serial;
}
else
{
s_keyRepeat.active = false;
}
}
@@ -449,6 +493,8 @@ static void KeyboardModifiers( void*, struct wl_keyboard* kbd, uint32_t serial,
static void KeyboardRepeatInfo( void*, struct wl_keyboard* kbd, int32_t rate, int32_t delay )
{
s_keyRepeatRate = 1000000 / rate;
s_keyRepeatDelay = delay * 1000;
}
constexpr struct wl_keyboard_listener keyboardListener = {
@@ -551,6 +597,21 @@ constexpr struct zxdg_toplevel_decoration_v1_listener decorationListener = {
};
static void IconMgrSize( void*, struct xdg_toplevel_icon_manager_v1*, int32_t size )
{
s_iconSizes.push_back( size );
}
static void IconMgrDone( void*, struct xdg_toplevel_icon_manager_v1* )
{
}
constexpr struct xdg_toplevel_icon_manager_v1_listener iconMgrListener = {
.icon_size = IconMgrSize,
.done = IconMgrDone
};
static void RegistryGlobal( void*, struct wl_registry* reg, uint32_t name, const char* interface, uint32_t version )
{
if( strcmp( interface, wl_compositor_interface.name ) == 0 )
@@ -600,6 +661,15 @@ static void RegistryGlobal( void*, struct wl_registry* reg, uint32_t name, const
s_cursorShape = (wp_cursor_shape_manager_v1*)wl_registry_bind( reg, name, &wp_cursor_shape_manager_v1_interface, 1 );
if( s_pointer ) s_cursorShapeDev = wp_cursor_shape_manager_v1_get_pointer( s_cursorShape, s_pointer );
}
else if( strcmp( interface, wl_data_device_manager_interface.name ) == 0 )
{
s_dataDevMgr = (wl_data_device_manager*)wl_registry_bind( reg, name, &wl_data_device_manager_interface, 2 );
}
else if( strcmp( interface, xdg_toplevel_icon_manager_v1_interface.name ) == 0 )
{
s_iconMgr = (xdg_toplevel_icon_manager_v1*)wl_registry_bind( reg, name, &xdg_toplevel_icon_manager_v1_interface, 1 );
xdg_toplevel_icon_manager_v1_add_listener( s_iconMgr, &iconMgrListener, nullptr );
}
}
static void RegistryGlobalRemove( void*, struct wl_registry* reg, uint32_t name )
@@ -617,10 +687,13 @@ constexpr struct wl_registry_listener registryListener = {
};
static bool s_configureAcked = false;
static void XdgSurfaceConfigure( void*, struct xdg_surface* surf, uint32_t serial )
{
tracy::s_wasActive = true;
xdg_surface_ack_configure( surf, serial );
s_configureAcked = true;
}
constexpr struct xdg_surface_listener xdgSurfaceListener = {
@@ -698,8 +771,10 @@ static void SurfacePreferredBufferTransform( void*, struct wl_surface* surface,
constexpr struct wl_surface_listener surfaceListener = {
.enter = SurfaceEnter,
.leave = SurfaceLeave,
#ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION
.preferred_buffer_scale = SurfacePreferredBufferScale,
.preferred_buffer_transform = SurfacePreferredBufferTransform
#endif
};
static void FractionalPreferredScale( void*, struct wp_fractional_scale_v1* frac, uint32_t scale )
@@ -713,6 +788,126 @@ constexpr struct wp_fractional_scale_v1_listener fractionalListener = {
};
static void DataOfferOffer( void*, struct wl_data_offer* offer, const char* mimeType )
{
assert( s_newDataOffer == offer );
if( strcmp( mimeType, "text/plain" ) == 0 )
{
wl_data_offer_accept( offer, 0, mimeType );
s_newDataOfferValid = true;
}
else
{
wl_data_offer_accept( offer, 0, nullptr );
}
}
static void DataOfferSourceActions( void*, struct wl_data_offer* offer, uint32_t sourceActions )
{
}
static void DataOfferAction( void*, struct wl_data_offer* offer, uint32_t dndAction )
{
}
constexpr struct wl_data_offer_listener dataOfferListener = {
.offer = DataOfferOffer,
.source_actions = DataOfferSourceActions,
.action = DataOfferAction
};
static void DataDeviceDataOffer( void*, struct wl_data_device* dataDevice, struct wl_data_offer* offer )
{
s_newDataOffer = offer;
wl_data_offer_add_listener( offer, &dataOfferListener, nullptr );
s_newDataOfferValid = false;
}
static void DataDeviceEnter( void*, struct wl_data_device* dataDevice, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* offer )
{
if( s_newDataOffer )
{
wl_data_offer_destroy( s_newDataOffer );
s_newDataOffer = nullptr;
}
}
static void DataDeviceLeave( void*, struct wl_data_device* dataDevice )
{
}
static void DataDeviceMotion( void*, struct wl_data_device* dataDevice, uint32_t time, wl_fixed_t x, wl_fixed_t y )
{
}
static void DataDeviceSelection( void*, struct wl_data_device* dataDevice, struct wl_data_offer* offer )
{
if( s_dataOffer ) wl_data_offer_destroy( s_dataOffer );
if( offer )
{
if( s_newDataOfferValid )
{
s_dataOffer = s_newDataOffer;
}
else
{
if( s_newDataOffer ) wl_data_offer_destroy( s_newDataOffer );
s_dataOffer = nullptr;
}
s_newDataOffer = nullptr;
}
else
{
s_dataOffer = nullptr;
}
}
constexpr struct wl_data_device_listener dataDeviceListener = {
.data_offer = DataDeviceDataOffer,
.enter = DataDeviceEnter,
.leave = DataDeviceLeave,
.motion = DataDeviceMotion,
.selection = DataDeviceSelection
};
void DataSourceTarget( void*, struct wl_data_source* dataSource, const char* mimeType )
{
}
void DataSourceSend( void*, struct wl_data_source* dataSource, const char* mimeType, int32_t fd )
{
if( !s_clipboard.empty() )
{
auto len = s_clipboard.size();
auto ptr = s_clipboard.data();
while( len > 0 )
{
auto sz = write( fd, ptr, len );
if( sz < 0 ) break;
len -= sz;
ptr += sz;
}
}
close( fd );
}
void DataSourceCancelled( void*, struct wl_data_source* dataSource )
{
s_clipboard.clear();
wl_data_source_destroy( s_dataSource );
s_dataSource = nullptr;
}
constexpr struct wl_data_source_listener dataSourceListener = {
.target = DataSourceTarget,
.send = DataSourceSend,
.cancelled = DataSourceCancelled
};
static void SetupCursor()
{
if( s_cursorShape ) return;
@@ -736,6 +931,39 @@ static void SetupCursor()
s_cursorY = cursor->images[0]->hotspot_y * 120 / s_maxScale;
}
static void SetClipboard( ImGuiContext*, const char* text )
{
s_clipboard = text;
if( s_dataSource ) wl_data_source_destroy( s_dataSource );
s_dataSource = wl_data_device_manager_create_data_source( s_dataDevMgr );
wl_data_source_add_listener( s_dataSource, &dataSourceListener, nullptr );
wl_data_source_offer( s_dataSource, "text/plain" );
wl_data_device_set_selection( s_dataDev, s_dataSource, s_dataSerial );
}
static const char* GetClipboard( ImGuiContext* )
{
if( !s_dataOffer ) return nullptr;
int fd[2];
if( pipe( fd ) != 0 ) return nullptr;
wl_data_offer_receive( s_dataOffer, "text/plain", fd[1] );
close( fd[1] );
wl_display_roundtrip( s_dpy );
s_clipboardIncoming.clear();
char buf[4096];
while( true )
{
auto len = read( fd[0], buf, sizeof( buf ) );
if( len <= 0 ) break;
s_clipboardIncoming.append( buf, len );
}
close( fd[0] );
return s_clipboardIncoming.c_str();
}
Backend::Backend( const char* title, const std::function<void()>& redraw, const std::function<void(float)>& scaleChanged, const std::function<int(void)>& isBusy, RunQueue* mainThreadTasks )
{
s_redraw = redraw;
@@ -761,7 +989,6 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
s_surf = wl_compositor_create_surface( s_comp );
wl_surface_add_listener( s_surf, &surfaceListener, nullptr );
s_eglWin = wl_egl_window_create( s_surf, m_winPos.w, m_winPos.h );
s_xdgSurf = xdg_wm_base_get_xdg_surface( s_wm, s_surf );
xdg_surface_add_listener( s_xdgSurf, &xdgSurfaceListener, nullptr );
@@ -796,6 +1023,15 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
res = eglBindAPI( EGL_OPENGL_API );
if( res != EGL_TRUE ) { fprintf( stderr, "Cannot use OpenGL through EGL!\n" ); exit( 1 ); }
wl_display_roundtrip( s_dpy );
s_toplevel = xdg_surface_get_toplevel( s_xdgSurf );
xdg_toplevel_add_listener( s_toplevel, &toplevelListener, nullptr );
xdg_toplevel_set_title( s_toplevel, title );
xdg_toplevel_set_app_id( s_toplevel, "tracy" );
wl_surface_commit( s_surf );
while( !s_configureAcked ) wl_display_roundtrip( s_dpy );
s_eglWin = wl_egl_window_create( s_surf, int( round( s_width * s_maxScale / 120.f ) ), int( round( s_height * s_maxScale / 120.f ) ) );
s_eglSurf = eglCreatePlatformWindowSurface( s_eglDpy, eglConfig, s_eglWin, nullptr );
constexpr EGLint eglCtxAttrib[] = {
@@ -812,11 +1048,15 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
ImGui_ImplOpenGL3_Init( "#version 150" );
wl_display_roundtrip( s_dpy );
s_toplevel = xdg_surface_get_toplevel( s_xdgSurf );
xdg_toplevel_add_listener( s_toplevel, &toplevelListener, nullptr );
xdg_toplevel_set_title( s_toplevel, title );
xdg_toplevel_set_app_id( s_toplevel, "tracy" );
if( s_activation )
{
const char* token = getenv( "XDG_ACTIVATION_TOKEN" );
if( token )
{
xdg_activation_v1_activate( s_activation, token, s_surf );
unsetenv( "XDG_ACTIVATION_TOKEN" );
}
}
if( s_decoration )
{
@@ -828,6 +1068,17 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "wayland (tracy profiler)";
if( s_dataDevMgr )
{
s_dataDev = wl_data_device_manager_get_data_device( s_dataDevMgr, s_seat );
wl_data_device_add_listener( s_dataDev, &dataDeviceListener, nullptr );
auto& platform = ImGui::GetPlatformIO();
platform.Platform_SetClipboardTextFn = SetClipboard;
platform.Platform_GetClipboardTextFn = GetClipboard;
}
s_time = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();
}
@@ -835,6 +1086,11 @@ Backend::~Backend()
{
ImGui_ImplOpenGL3_Shutdown();
if( s_iconMgr ) xdg_toplevel_icon_manager_v1_destroy( s_iconMgr );
if( s_dataOffer ) wl_data_offer_destroy( s_dataOffer );
if( s_dataSource ) wl_data_source_destroy( s_dataSource );
if( s_dataDev ) wl_data_device_destroy( s_dataDev );
if( s_dataDevMgr ) wl_data_device_manager_destroy( s_dataDevMgr );
if( s_cursorShapeDev ) wp_cursor_shape_device_v1_destroy( s_cursorShapeDev );
if( s_cursorShape ) wp_cursor_shape_manager_v1_destroy( s_cursorShape );
if( s_viewport ) wp_viewport_destroy( s_viewport );
@@ -878,9 +1134,10 @@ void Backend::Show()
void Backend::Run()
{
while( s_running && wl_display_dispatch( s_dpy ) != -1 )
timespec zero = {};
while( s_running && wl_display_dispatch_timeout( s_dpy, &zero ) != -1 )
{
if( s_config.focusLostLimit && !s_hasFocus ) std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
if( tracy::s_config.focusLostLimit && !s_hasFocus ) std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
s_redraw();
s_mainThreadTasks->Run();
}
@@ -915,12 +1172,8 @@ void Backend::NewFrame( int& w, int& h )
{
s_prevWidth = s_width;
s_prevHeight = s_height;
wl_egl_window_resize( s_eglWin, s_width * s_maxScale / 120, s_height * s_maxScale / 120, 0, 0 );
if( s_fracSurf )
{
wp_viewport_set_source( s_viewport, 0, 0, wl_fixed_from_double( s_width * s_maxScale / 120. ), wl_fixed_from_double( s_height * s_maxScale / 120. ) );
wp_viewport_set_destination( s_viewport, s_width, s_height );
}
wl_egl_window_resize( s_eglWin, int( round( s_width * s_maxScale / 120.f ) ), int( round( s_height * s_maxScale / 120.f ) ), 0, 0 );
if( s_fracSurf ) wp_viewport_set_destination( s_viewport, s_width, s_height );
}
if( s_prevScale != s_maxScale )
@@ -938,8 +1191,8 @@ void Backend::NewFrame( int& w, int& h )
m_winPos.h = s_height;
}
w = s_width * s_maxScale / 120;
h = s_height * s_maxScale / 120;
w = int( round ( s_width * s_maxScale / 120.f ) );
h = int( round ( s_height * s_maxScale / 120.f ) );
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2( w, h );
@@ -951,6 +1204,26 @@ void Backend::NewFrame( int& w, int& h )
io.DeltaTime = std::min( 0.1f, ( time - s_time ) / 1000000.f );
s_time = time;
if( s_keyRepeat.active )
{
tracy::s_wasActive = true;
const auto delta = s_time - s_keyRepeat.time;
if( ( s_keyRepeat.first && delta >= s_keyRepeatDelay ) ||
( !s_keyRepeat.first && delta >= s_keyRepeatRate ) )
{
s_keyRepeat.first = false;
s_keyRepeat.time = s_time;
if( *s_keyRepeat.txt )
{
ImGui::GetIO().AddInputCharactersUTF8( s_keyRepeat.txt );
}
else
{
io.AddKeyEvent( s_keyRepeat.key, true );
}
}
}
if( s_cursorShapeDev )
{
ImGuiMouseCursor cursor = ImGui::GetMouseCursor();
@@ -994,7 +1267,11 @@ void Backend::NewFrame( int& w, int& h )
case ImGuiMouseCursor_NotAllowed:
shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED;
break;
case ImGuiMouseCursor_Hand:
shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
break;
default:
shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
break;
};
@@ -1018,7 +1295,7 @@ void Backend::EndFrame()
const ImVec4 clear_color = ImColor( 20, 20, 17 );
ImGui::Render();
glViewport( 0, 0, s_width * s_maxScale / 120, s_height * s_maxScale / 120 );
glViewport( 0, 0, GLsizei( round( s_width * s_maxScale / 120.f ) ), GLsizei( round ( s_height * s_maxScale / 120.f ) ) );
glClearColor( clear_color.x, clear_color.y, clear_color.z, clear_color.w );
glClear( GL_COLOR_BUFFER_BIT );
ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() );
@@ -1028,6 +1305,62 @@ void Backend::EndFrame()
void Backend::SetIcon( uint8_t* data, int w, int h )
{
if( !s_iconMgr ) return;
if( s_iconSizes.empty() ) return;
size_t size = 0;
for( auto sz : s_iconSizes )
{
size += sz * sz;
}
size *= 4;
auto path = getenv( "XDG_RUNTIME_DIR" );
if( !path ) return;
std::string shmPath = path;
shmPath += "/tracy_icon-XXXXXX";
int fd = mkstemp( shmPath.data() );
if( fd < 0 ) return;
unlink( shmPath.data() );
ftruncate( fd, size );
auto membuf = (char*)mmap( nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
if( membuf == MAP_FAILED )
{
close( fd );
return;
}
auto pool = wl_shm_create_pool( s_shm, fd, size );
close( fd );
auto icon = xdg_toplevel_icon_manager_v1_create_icon( s_iconMgr );
auto rgb = new uint32_t[w * h];
auto bgr = (uint32_t*)data;
for( int i=0; i<w*h; i++ )
{
rgb[i] = ( bgr[i] & 0xff00ff00 ) | ( ( bgr[i] & 0xff ) << 16 ) | ( ( bgr[i] >> 16 ) & 0xff );
}
std::vector<wl_buffer*> bufs;
int32_t offset = 0;
for( auto sz : s_iconSizes )
{
auto buffer = wl_shm_pool_create_buffer( pool, offset, sz, sz, sz * 4, WL_SHM_FORMAT_ARGB8888 );
bufs.push_back( buffer );
auto ptr = membuf + offset;
offset += sz * sz * 4;
stbir_resize_uint8( (uint8_t*)rgb, w, h, 0, (uint8_t*)ptr, sz, sz, 0, 4 );
xdg_toplevel_icon_v1_add_buffer( icon, buffer, sz );
}
xdg_toplevel_icon_manager_v1_set_icon( s_iconMgr, s_toplevel, icon );
xdg_toplevel_icon_v1_destroy( icon );
for( auto buf : bufs ) wl_buffer_destroy( buf );
munmap( membuf, size );
wl_shm_pool_destroy( pool );
}
void Backend::SetTitle( const char* title )
@@ -65,17 +65,16 @@ void ConnectionHistory::Rebuild()
std::swap( m_connHistVec, vec );
}
void ConnectionHistory::Count( const char* name )
void ConnectionHistory::Count( const std::string& name )
{
std::string addr( name );
auto it = m_connHistMap.find( addr );
auto it = m_connHistMap.find( name );
if( it != m_connHistMap.end() )
{
it->second++;
}
else
{
m_connHistMap.emplace( std::move( addr ), 1 );
m_connHistMap.emplace( name, 1 );
}
Rebuild();
}
@@ -14,7 +14,7 @@ public:
const std::string& Name( size_t idx ) const { return m_connHistVec[idx]->first; }
void Count( const char* name );
void Count( const std::string& name );
void Erase( size_t idx );
bool empty() const { return m_connHistVec.empty(); }
+44 -39
View File
@@ -1,59 +1,64 @@
#include <imgui.h>
#include <math.h>
#include <backends/imgui_impl_opengl3.h>
#include <misc/freetype/imgui_freetype.h>
#include "Fonts.hpp"
#include "misc/freetype/imgui_freetype.h"
#include "imgui/imgui_impl_opengl3.h"
#include "profiler/IconsFontAwesome6.h"
#include "profiler/TracyEmbed.hpp"
#include "font/DroidSans.hpp"
#include "font/FiraCodeRetina.hpp"
#include "font/FontAwesomeSolid.hpp"
#include "data/FontFixed.hpp"
#include "data/FontIcons.hpp"
#include "data/FontNormal.hpp"
#include "data/FontBold.hpp"
#include "data/FontBoldItalic.hpp"
#include "data/FontItalic.hpp"
ImFont* s_bigFont;
ImFont* s_smallFont;
ImFont* s_fixedWidth;
FontData g_fonts;
float FontNormal, FontSmall, FontBig;
void LoadFonts( float scale )
{
static const ImWchar rangesBasic[] = {
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x03BC, 0x03BC, // micro
0x03C3, 0x03C3, // small sigma
0x2013, 0x2013, // en dash
0x2026, 0x2026, // ellipsis
0x2264, 0x2264, // less-than or equal to
0,
};
static const ImWchar rangesIcons[] = {
ICON_MIN_FA, ICON_MAX_FA,
0
};
static const ImWchar rangesFixed[] = {
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x2026, 0x2026, // ellipsis
0
};
ImGuiIO& io = ImGui::GetIO();
ImFontConfig configBasic;
configBasic.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LightHinting;
configBasic.FontLoaderFlags = ImGuiFreeTypeLoaderFlags_LightHinting;
configBasic.FontDataOwnedByAtlas = false;
ImFontConfig configMerge;
configMerge.MergeMode = true;
configMerge.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LightHinting;
configMerge.FontLoaderFlags = ImGuiFreeTypeLoaderFlags_LightHinting;
configMerge.FontDataOwnedByAtlas = false;
ImFontConfig configFixed;
configFixed.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LightHinting;
configFixed.GlyphExtraSpacing.x = -1;
configFixed.FontLoaderFlags = ImGuiFreeTypeLoaderFlags_LightHinting;
configFixed.GlyphExtraAdvanceX = -1;
configFixed.FontDataOwnedByAtlas = false;
auto fontFixed = Unembed( FontFixed );
auto fontIcons = Unembed( FontIcons );
auto fontNormal = Unembed( FontNormal );
auto fontBold = Unembed( FontBold );
auto fontBoldItalic = Unembed( FontBoldItalic );
auto fontItalic = Unembed( FontItalic );
io.Fonts->Clear();
io.Fonts->AddFontFromMemoryCompressedTTF( tracy::DroidSans_compressed_data, tracy::DroidSans_compressed_size, round( 15.0f * scale ), &configBasic, rangesBasic );
io.Fonts->AddFontFromMemoryCompressedTTF( tracy::FontAwesomeSolid_compressed_data, tracy::FontAwesomeSolid_compressed_size, round( 14.0f * scale ), &configMerge, rangesIcons );
s_fixedWidth = io.Fonts->AddFontFromMemoryCompressedTTF( tracy::FiraCodeRetina_compressed_data, tracy::FiraCodeRetina_compressed_size, round( 15.0f * scale ), &configFixed, rangesFixed );
s_bigFont = io.Fonts->AddFontFromMemoryCompressedTTF( tracy::DroidSans_compressed_data, tracy::DroidSans_compressed_size, round( 21.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryCompressedTTF( tracy::FontAwesomeSolid_compressed_data, tracy::FontAwesomeSolid_compressed_size, round( 20.0f * scale ), &configMerge, rangesIcons );
s_smallFont = io.Fonts->AddFontFromMemoryCompressedTTF( tracy::DroidSans_compressed_data, tracy::DroidSans_compressed_size, round( 10.0f * scale ), &configBasic );
ImGui_ImplOpenGL3_DestroyFontsTexture();
ImGui_ImplOpenGL3_CreateFontsTexture();
g_fonts.normal = io.Fonts->AddFontFromMemoryTTF( (void*)fontNormal->data(), fontNormal->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
g_fonts.mono = io.Fonts->AddFontFromMemoryTTF( (void*)fontFixed->data(), fontFixed->size(), round( 15.0f * scale ), &configFixed );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
g_fonts.bold = io.Fonts->AddFontFromMemoryTTF( (void*)fontBold->data(), fontBold->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
g_fonts.boldItalic = io.Fonts->AddFontFromMemoryTTF( (void*)fontBoldItalic->data(), fontBoldItalic->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
g_fonts.italic = io.Fonts->AddFontFromMemoryTTF( (void*)fontItalic->data(), fontItalic->size(), round( 15.0f * scale ), &configBasic );
io.Fonts->AddFontFromMemoryTTF( (void*)fontIcons->data(), fontIcons->size(), round( 14.0f * scale ), &configMerge );
FontNormal = round( scale * 15.f );
FontSmall = round( scale * 15 * 2.f / 3.f );
FontBig = round( scale * 15 * 1.4f );
}
+11 -3
View File
@@ -3,9 +3,17 @@
struct ImFont;
extern ImFont* s_bigFont;
extern ImFont* s_smallFont;
extern ImFont* s_fixedWidth;
struct FontData
{
ImFont* normal;
ImFont* mono;
ImFont* bold;
ImFont* boldItalic;
ImFont* italic;
};
extern FontData g_fonts;
extern float FontNormal, FontSmall, FontBig;
void LoadFonts( float scale );
+14 -2
View File
@@ -5,11 +5,14 @@
#include "../public/common/TracySocket.hpp"
#include "../public/common/TracyVersion.hpp"
#include "GitRef.hpp"
#include "HttpRequest.hpp"
#if defined _WIN32
# include <windows.h>
extern "C" typedef LONG (WINAPI *t_RtlGetVersion)( PRTL_OSVERSIONINFOW );
extern "C" typedef char* (WINAPI *t_WineGetVersion)();
extern "C" typedef char* (WINAPI *t_WineGetBuildId)();
#elif defined __linux__
# include <sys/utsname.h>
#elif defined __APPLE__
@@ -39,7 +42,16 @@ static const char* GetOsInfo()
# ifdef __MINGW32__
sprintf( buf, "Windows %i.%i.%i (MingW)", (int)ver.dwMajorVersion, (int)ver.dwMinorVersion, (int)ver.dwBuildNumber );
# else
sprintf( buf, "Windows %i.%i.%i", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber );
auto WineGetVersion = (t_WineGetVersion)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "wine_get_version" );
auto WineGetBuildId = (t_WineGetBuildId)GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "wine_get_build_id" );
if( WineGetVersion && WineGetBuildId )
{
sprintf( buf, "Windows %i.%i.%i (Wine %s [%s])", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, WineGetVersion(), WineGetBuildId() );
}
else
{
sprintf( buf, "Windows %i.%i.%i", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber );
}
# endif
}
#elif defined __linux__
@@ -79,7 +91,7 @@ void HttpRequest( const char* server, const char* resource, int port, const std:
tracy::Socket sock;
if( !sock.ConnectBlocking( server, port ) ) return;
char request[4096];
const auto len = sprintf( request, "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: Tracy Profiler %i.%i.%i (%s)\r\nConnection: close\r\nCache-Control: no-cache, no-store, must-revalidate\r\n\r\n", resource, server, tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, GetOsInfo() );
const auto len = sprintf( request, "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: Tracy Profiler %i.%i.%i (%s) [%s]\r\nConnection: close\r\nCache-Control: no-cache, no-store, must-revalidate\r\n\r\n", resource, server, tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, GetOsInfo(), tracy::GitRef );
sock.Send( request, len );
char response[4096];
const auto sz = sock.ReadUpTo( response, 4096 );
+1
View File
@@ -11,6 +11,7 @@ ImGuiTracyContext::ImGuiTracyContext()
io.IniFilename = m_iniFilename.c_str();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable;
io.ConfigInputTextCursorBlink = false;
io.ConfigScrollbarScrollByPage = false;
}
ImGuiTracyContext::~ImGuiTracyContext()
@@ -14,22 +14,28 @@
ResolvService::ResolvService( uint16_t port )
: m_exit( false )
, m_port( port )
#ifndef __EMSCRIPTEN__
, m_thread( [this] { Worker(); } )
#endif
{
}
ResolvService::~ResolvService()
{
#ifndef __EMSCRIPTEN__
m_exit.store( true, std::memory_order_relaxed );
m_cv.notify_one();
m_thread.join();
#endif
}
void ResolvService::Query( uint32_t ip, const std::function<void(std::string&&)>& callback )
{
#ifndef __EMSCRIPTEN__
std::lock_guard<std::mutex> lock( m_lock );
m_queue.emplace_back( QueueItem { ip, callback } );
m_cv.notify_one();
#endif
}
void ResolvService::Worker()
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -1,62 +0,0 @@
// dear imgui: Platform Backend for GLFW
// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
// (Requires: GLFW 3.1+. Prefer GLFW 3.3+ for full feature support.)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only).
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// Issues:
// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct GLFWwindow;
struct GLFWmonitor;
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown();
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame();
// Emscripten related initialization phase methods
#ifdef __EMSCRIPTEN__
IMGUI_IMPL_API void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector);
#endif
// GLFW callbacks install
// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any.
// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks.
IMGUI_IMPL_API void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window);
IMGUI_IMPL_API void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window);
// GFLW callbacks options:
// - Set 'chain_for_all_windows=true' to enable chaining callbacks for all windows (including secondary viewports created by backends or by user)
IMGUI_IMPL_API void ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows);
// GLFW callbacks (individual callbacks to call yourself if you didn't install callbacks)
IMGUI_IMPL_API void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused); // Since 1.84
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); // Since 1.84
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y); // Since 1.87
IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);
#endif // #ifndef IMGUI_DISABLE
@@ -1,996 +0,0 @@
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES:
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
// - This is done automatically on iOS, Android and Emscripten targets.
// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562)
// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447)
// 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
// 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983)
// 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)
// 2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)
// 2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)
// 2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)
// 2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224)
// 2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530)
// 2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)
// 2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.
// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).
// 2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.
// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.
// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.
// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version.
// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader.
// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.
// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.
// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.
// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.
// 2017-05-01: OpenGL: Fixed save and restore of current blend func state.
// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.
// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.
// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)
//----------------------------------------
// OpenGL GLSL GLSL
// version version string
//----------------------------------------
// 2.0 110 "#version 110"
// 2.1 120 "#version 120"
// 3.0 130 "#version 130"
// 3.1 140 "#version 140"
// 3.2 150 "#version 150"
// 3.3 330 "#version 330 core"
// 4.0 400 "#version 400 core"
// 4.1 410 "#version 410 core"
// 4.2 420 "#version 410 core"
// 4.3 430 "#version 430 core"
// ES 2.0 100 "#version 100" = WebGL 1.0
// ES 3.0 300 "#version 300 es" = WebGL 2.0
//----------------------------------------
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <stdint.h> // intptr_t
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used
#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
#pragma clang diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader)
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx'
#pragma GCC diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader)
#endif
// GL includes
#if defined(IMGUI_IMPL_OPENGL_ES2)
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
#include <OpenGLES/ES2/gl.h> // Use GL ES 2
#else
#include <GLES2/gl2.h> // Use GL ES 2
#endif
#if defined(__EMSCRIPTEN__)
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#endif
#include <GLES2/gl2ext.h>
#endif
#elif defined(IMGUI_IMPL_OPENGL_ES3)
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))
#include <OpenGLES/ES3/gl.h> // Use GL ES 3
#else
#include <GLES3/gl3.h> // Use GL ES 3
#endif
#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.
// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w.
// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.).
// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp):
// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped
// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases
// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version.
#define IMGL3W_IMPL
#include "imgui_impl_opengl3_loader.h"
#endif
// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension
#ifndef IMGUI_IMPL_OPENGL_ES2
#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
#elif defined(__EMSCRIPTEN__)
#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
#define glBindVertexArray glBindVertexArrayOES
#define glGenVertexArrays glGenVertexArraysOES
#define glDeleteVertexArrays glDeleteVertexArraysOES
#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES
#endif
// Desktop GL 2.0+ has extension and glPolygonMode() which GL ES and WebGL don't have..
// A desktop ES context can technically compile fine with our loader, so we also perform a runtime checks
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)
#define IMGUI_IMPL_OPENGL_HAS_EXTENSIONS // has glGetIntegerv(GL_NUM_EXTENSIONS)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE // may have glPolygonMode()
#endif
// Desktop GL 2.1+ and GL ES 3.0+ have glBindBuffer() with GL_PIXEL_UNPACK_BUFFER target.
#if !defined(IMGUI_IMPL_OPENGL_ES2)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK
#endif
// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
#endif
// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2)
#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
#endif
// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler()
#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3))
#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
#endif
// [Debugging]
//#define IMGUI_IMPL_OPENGL_DEBUG
#ifdef IMGUI_IMPL_OPENGL_DEBUG
#include <stdio.h>
#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check
#else
#define GL_CALL(_CALL) _CALL // Call without error check
#endif
// OpenGL Data
struct ImGui_ImplOpenGL3_Data
{
GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2)
char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings.
bool GlProfileIsES2;
bool GlProfileIsES3;
bool GlProfileIsCompat;
GLint GlProfileMask;
GLuint FontTexture;
GLuint ShaderHandle;
GLint AttribLocationTex; // Uniforms location
GLint AttribLocationProjMtx;
GLuint AttribLocationVtxPos; // Vertex attributes location
GLuint AttribLocationVtxUV;
GLuint AttribLocationVtxColor;
unsigned int VboHandle, ElementsHandle;
GLsizeiptr VertexBufferSize;
GLsizeiptr IndexBufferSize;
bool HasPolygonMode;
bool HasClipOrigin;
bool UseBufferSubData;
ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Forward Declarations
static void ImGui_ImplOpenGL3_InitPlatformInterface();
static void ImGui_ImplOpenGL3_ShutdownPlatformInterface();
// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)
#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
struct ImGui_ImplOpenGL3_VtxAttribState
{
GLint Enabled, Size, Type, Normalized, Stride;
GLvoid* Ptr;
void GetState(GLint index)
{
glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled);
glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size);
glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type);
glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized);
glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride);
glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr);
}
void SetState(GLint index)
{
glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr);
if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index);
}
};
#endif
// Functions
bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Initialize our loader
#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
if (imgl3wInit() != 0)
{
fprintf(stderr, "Failed to initialize OpenGL loader!\n");
return false;
}
#endif
// Setup backend capabilities flags
ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_opengl3";
// Query for GL version (e.g. 320 for GL 3.2)
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GLES 2
bd->GlVersion = 200;
bd->GlProfileIsES2 = true;
#else
// Desktop or GLES 3
const char* gl_version_str = (const char*)glGetString(GL_VERSION);
GLint major = 0;
GLint minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
if (major == 0 && minor == 0)
sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
bd->GlVersion = (GLuint)(major * 100 + minor * 10);
#if defined(GL_CONTEXT_PROFILE_MASK)
if (bd->GlVersion >= 320)
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
#endif
#if defined(IMGUI_IMPL_OPENGL_ES3)
bd->GlProfileIsES3 = true;
#else
if (strncmp(gl_version_str, "OpenGL ES 3", 11) == 0)
bd->GlProfileIsES3 = true;
#endif
bd->UseBufferSubData = false;
/*
// Query vendor to enable glBufferSubData kludge
#ifdef _WIN32
if (const char* vendor = (const char*)glGetString(GL_VENDOR))
if (strncmp(vendor, "Intel", 5) == 0)
bd->UseBufferSubData = true;
#endif
*/
#endif
#ifdef IMGUI_IMPL_OPENGL_DEBUG
printf("GlVersion = %d, \"%s\"\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, gl_version_str, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG]
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
if (bd->GlVersion >= 320)
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
#endif
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// Store GLSL version string so we can refer to it later in case we recreate shaders.
// Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
if (glsl_version == nullptr)
{
#if defined(IMGUI_IMPL_OPENGL_ES2)
glsl_version = "#version 100";
#elif defined(IMGUI_IMPL_OPENGL_ES3)
glsl_version = "#version 300 es";
#elif defined(__APPLE__)
glsl_version = "#version 150";
#else
glsl_version = "#version 130";
#endif
}
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
strcpy(bd->GlslVersionString, glsl_version);
strcat(bd->GlslVersionString, "\n");
// Make an arbitrary GL call (we don't actually need the result)
// IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know!
GLint current_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);
// Detect extensions we support
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3);
#endif
bd->HasClipOrigin = (bd->GlVersion >= 450);
#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS
GLint num_extensions = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
for (GLint i = 0; i < num_extensions; i++)
{
const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0)
bd->HasClipOrigin = true;
}
#endif
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
ImGui_ImplOpenGL3_InitPlatformInterface();
return true;
}
void ImGui_ImplOpenGL3_Shutdown()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplOpenGL3_ShutdownPlatformInterface();
ImGui_ImplOpenGL3_DestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd);
}
void ImGui_ImplOpenGL3_NewFrame()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL3_Init()?");
if (!bd->ShaderHandle)
ImGui_ImplOpenGL3_CreateDeviceObjects();
}
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (bd->GlVersion >= 310)
glDisable(GL_PRIMITIVE_RESTART);
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
if (bd->HasPolygonMode)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
#if defined(GL_CLIP_ORIGIN)
bool clip_origin_lower_left = true;
if (bd->HasClipOrigin)
{
GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
if (current_clip_origin == GL_UPPER_LEFT)
clip_origin_lower_left = false;
}
#endif
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height));
float L = draw_data->DisplayPos.x;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = draw_data->DisplayPos.y;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
#if defined(GL_CLIP_ORIGIN)
if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
#endif
const float ortho_projection[4][4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, -1.0f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
};
glUseProgram(bd->ShaderHandle);
glUniform1i(bd->AttribLocationTex, 0);
glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise.
#endif
(void)vertex_array_object;
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
glBindVertexArray(vertex_array_object);
#endif
// Bind vertex/index buffers and setup attributes for ImDrawVert
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle));
GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle));
GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos));
GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV));
GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor));
GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos)));
GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv)));
GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col)));
}
// OpenGL3 Render function.
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly.
// This is in order to be able to run within an OpenGL engine that doesn't do so.
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0)
return;
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Backup GL state
GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
glActiveTexture(GL_TEXTURE0);
GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program);
GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; }
#endif
GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer);
#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
// This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+.
GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos);
ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV);
ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor);
#endif
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object);
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
GLint last_polygon_mode[2]; if (bd->HasPolygonMode) { glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); }
#endif
GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);
GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);
GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);
GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);
GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);
GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);
GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
#endif
// Setup desired GL state
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
GLuint vertex_array_object = 0;
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
GL_CALL(glGenVertexArrays(1, &vertex_array_object));
#endif
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
// Upload vertex/index buffers
// - OpenGL drivers are in a very sorry state nowadays....
// During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports
// of leaks on Intel GPU when using multi-viewports on Windows.
// - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel.
// - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code.
// We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path.
// - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues.
const GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert);
const GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx);
if (bd->UseBufferSubData)
{
if (bd->VertexBufferSize < vtx_buffer_size)
{
bd->VertexBufferSize = vtx_buffer_size;
GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW));
}
if (bd->IndexBufferSize < idx_buffer_size)
{
bd->IndexBufferSize = idx_buffer_size;
GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW));
}
GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data));
GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data));
}
else
{
GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW));
GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW));
}
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
else
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle (Y is inverted in OpenGL)
GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)));
// Bind texture, Draw
GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()));
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
if (bd->GlVersion >= 320)
GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset));
else
#endif
GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))));
}
}
}
// Destroy the temporary VAO
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
GL_CALL(glDeleteVertexArrays(1, &vertex_array_object));
#endif
// Restore modified GL state
// This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220.
if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program);
glBindTexture(GL_TEXTURE_2D, last_texture);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
if (bd->GlVersion >= 330 || bd->GlProfileIsES3)
glBindSampler(0, last_sampler);
#endif
glActiveTexture(last_active_texture);
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
glBindVertexArray(last_vertex_array_object);
#endif
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos);
last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV);
last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor);
#endif
glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);
if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
// Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons
if (bd->HasPolygonMode) { if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) { glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); } else { glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); } }
#endif // IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
(void)bd; // Not all compilation paths use this
}
bool ImGui_ImplOpenGL3_CreateFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Build texture atlas
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
// Upload texture to graphics system
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
GLint last_texture;
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
GL_CALL(glGenTextures(1, &bd->FontTexture));
GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
#endif
GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
// Store our identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
// Restore state
GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
return true;
}
void ImGui_ImplOpenGL3_DestroyFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->FontTexture)
{
glDeleteTextures(1, &bd->FontTexture);
io.Fonts->SetTexID(0);
bd->FontTexture = 0;
}
}
// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
static bool CheckShader(GLuint handle, const char* desc)
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
GLint status = 0, log_length = 0;
glGetShaderiv(handle, GL_COMPILE_STATUS, &status);
glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);
if ((GLboolean)status == GL_FALSE)
fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString);
if (log_length > 1)
{
ImVector<char> buf;
buf.resize((int)(log_length + 1));
glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
fprintf(stderr, "%s\n", buf.begin());
}
return (GLboolean)status == GL_TRUE;
}
// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
static bool CheckProgram(GLuint handle, const char* desc)
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
GLint status = 0, log_length = 0;
glGetProgramiv(handle, GL_LINK_STATUS, &status);
glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
if ((GLboolean)status == GL_FALSE)
fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString);
if (log_length > 1)
{
ImVector<char> buf;
buf.resize((int)(log_length + 1));
glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin());
fprintf(stderr, "%s\n", buf.begin());
}
return (GLboolean)status == GL_TRUE;
}
bool ImGui_ImplOpenGL3_CreateDeviceObjects()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Backup GL state
GLint last_texture, last_array_buffer;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK
GLint last_pixel_unpack_buffer = 0;
if (bd->GlVersion >= 210) { glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &last_pixel_unpack_buffer); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); }
#endif
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
GLint last_vertex_array;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
#endif
// Parse GLSL version string
int glsl_version = 130;
sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
const GLchar* vertex_shader_glsl_120 =
"uniform mat4 ProjMtx;\n"
"attribute vec2 Position;\n"
"attribute vec2 UV;\n"
"attribute vec4 Color;\n"
"varying vec2 Frag_UV;\n"
"varying vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_130 =
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 UV;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_300_es =
"precision highp float;\n"
"layout (location = 0) in vec2 Position;\n"
"layout (location = 1) in vec2 UV;\n"
"layout (location = 2) in vec4 Color;\n"
"uniform mat4 ProjMtx;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* vertex_shader_glsl_410_core =
"layout (location = 0) in vec2 Position;\n"
"layout (location = 1) in vec2 UV;\n"
"layout (location = 2) in vec4 Color;\n"
"uniform mat4 ProjMtx;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
"}\n";
const GLchar* fragment_shader_glsl_120 =
"#ifdef GL_ES\n"
" precision mediump float;\n"
"#endif\n"
"uniform sampler2D Texture;\n"
"varying vec2 Frag_UV;\n"
"varying vec4 Frag_Color;\n"
"void main()\n"
"{\n"
" gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_130 =
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_300_es =
"precision mediump float;\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"layout (location = 0) out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
const GLchar* fragment_shader_glsl_410_core =
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"uniform sampler2D Texture;\n"
"layout (location = 0) out vec4 Out_Color;\n"
"void main()\n"
"{\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
// Select shaders matching our GLSL versions
const GLchar* vertex_shader = nullptr;
const GLchar* fragment_shader = nullptr;
if (glsl_version < 130)
{
vertex_shader = vertex_shader_glsl_120;
fragment_shader = fragment_shader_glsl_120;
}
else if (glsl_version >= 410)
{
vertex_shader = vertex_shader_glsl_410_core;
fragment_shader = fragment_shader_glsl_410_core;
}
else if (glsl_version == 300)
{
vertex_shader = vertex_shader_glsl_300_es;
fragment_shader = fragment_shader_glsl_300_es;
}
else
{
vertex_shader = vertex_shader_glsl_130;
fragment_shader = fragment_shader_glsl_130;
}
// Create shaders
const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
glCompileShader(vert_handle);
CheckShader(vert_handle, "vertex shader");
const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
glCompileShader(frag_handle);
CheckShader(frag_handle, "fragment shader");
// Link
bd->ShaderHandle = glCreateProgram();
glAttachShader(bd->ShaderHandle, vert_handle);
glAttachShader(bd->ShaderHandle, frag_handle);
glLinkProgram(bd->ShaderHandle);
CheckProgram(bd->ShaderHandle, "shader program");
glDetachShader(bd->ShaderHandle, vert_handle);
glDetachShader(bd->ShaderHandle, frag_handle);
glDeleteShader(vert_handle);
glDeleteShader(frag_handle);
bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
// Create buffers
glGenBuffers(1, &bd->VboHandle);
glGenBuffers(1, &bd->ElementsHandle);
ImGui_ImplOpenGL3_CreateFontsTexture();
// Restore modified GL state
glBindTexture(GL_TEXTURE_2D, last_texture);
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK
if (bd->GlVersion >= 210) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, last_pixel_unpack_buffer); }
#endif
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
glBindVertexArray(last_vertex_array);
#endif
return true;
}
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
ImGui_ImplOpenGL3_DestroyFontsTexture();
}
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
static void ImGui_ImplOpenGL3_RenderWindow(ImGuiViewport* viewport, void*)
{
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
{
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
}
ImGui_ImplOpenGL3_RenderDrawData(viewport->DrawData);
}
static void ImGui_ImplOpenGL3_InitPlatformInterface()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_RenderWindow = ImGui_ImplOpenGL3_RenderWindow;
}
static void ImGui_ImplOpenGL3_ShutdownPlatformInterface()
{
ImGui::DestroyPlatformWindows();
}
//-----------------------------------------------------------------------------
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE
@@ -1,67 +0,0 @@
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES:
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
// - This is done automatically on iOS, Android and Emscripten targets.
// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// About GLSL version:
// The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string.
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
// Backend API
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// Configuration flags to add in your imconfig file:
//#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten)
//#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android)
// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
#if !defined(IMGUI_IMPL_OPENGL_ES2) \
&& !defined(IMGUI_IMPL_OPENGL_ES3)
// Try to detect GLES on matching platforms
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es"
#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100"
#else
// Otherwise imgui_impl_opengl3_loader.h will be used.
#endif
#endif
#endif // #ifndef IMGUI_DISABLE
@@ -1,922 +0,0 @@
//-----------------------------------------------------------------------------
// About imgui_impl_opengl3_loader.h:
//
// We embed our own OpenGL loader to not require user to provide their own or to have to use ours,
// which proved to be endless problems for users.
// Our loader is custom-generated, based on gl3w but automatically filtered to only include
// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small.
//
// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY.
// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE.
//
// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions):
// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCUDING 'imgui_impl_opengl3_loader.h'
// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER.
// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS)
// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT.
// BUT IF YOU REALLY WANT TO, you can '#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM' and imgui_impl_opengl3.cpp
// WILL NOT BE USING OUR LOADER, AND INSTEAD EXPECT ANOTHER/YOUR LOADER TO BE AVAILABLE IN THE COMPILATION UNIT.
//
// Regenerate with:
// python3 gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt
//
// More info:
// https://github.com/dearimgui/gl3w_stripped
// https://github.com/ocornut/imgui/issues/4445
//-----------------------------------------------------------------------------
/*
* This file was generated with gl3w_gen.py, part of imgl3w
* (hosted at https://github.com/dearimgui/gl3w_stripped)
*
* 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.
*/
#ifndef __gl3w_h_
#define __gl3w_h_
// Adapted from KHR/khrplatform.h to avoid including entire file.
#ifndef __khrplatform_h_
typedef float khronos_float_t;
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
#ifdef _WIN64
typedef signed long long int khronos_intptr_t;
typedef signed long long int khronos_ssize_t;
#else
typedef signed long int khronos_intptr_t;
typedef signed long int khronos_ssize_t;
#endif
#if defined(_MSC_VER) && !defined(__clang__)
typedef signed __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100)
#include <stdint.h>
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#else
typedef signed long long khronos_int64_t;
typedef unsigned long long khronos_uint64_t;
#endif
#endif // __khrplatform_h_
#ifndef __gl_glcorearb_h_
#define __gl_glcorearb_h_ 1
#ifdef __cplusplus
extern "C" {
#endif
/*
** Copyright 2013-2020 The Khronos Group Inc.
** SPDX-License-Identifier: MIT
**
** This header is generated from the Khronos OpenGL / OpenGL ES XML
** API Registry. The current version of the Registry, generator scripts
** used to make the header, and the header can be found at
** https://github.com/KhronosGroup/OpenGL-Registry
*/
#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
#endif
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif
#ifndef GLAPI
#define GLAPI extern
#endif
/* glcorearb.h is for use with OpenGL core profile implementations.
** It should should be placed in the same directory as gl.h and
** included as <GL/glcorearb.h>.
**
** glcorearb.h includes only APIs in the latest OpenGL core profile
** implementation together with APIs in newer ARB extensions which
** can be supported by the core profile. It does not, and never will
** include functionality removed from the core profile, such as
** fixed-function vertex and fragment processing.
**
** Do not #include both <GL/glcorearb.h> and either of <GL/gl.h> or
** <GL/glext.h> in the same source file.
*/
/* Generated C header for:
* API: gl
* Profile: core
* Versions considered: .*
* Versions emitted: .*
* Default extensions included: glcore
* Additional extensions included: _nomatch_^
* Extensions removed: _nomatch_^
*/
#ifndef GL_VERSION_1_0
typedef void GLvoid;
typedef unsigned int GLenum;
typedef khronos_float_t GLfloat;
typedef int GLint;
typedef int GLsizei;
typedef unsigned int GLbitfield;
typedef double GLdouble;
typedef unsigned int GLuint;
typedef unsigned char GLboolean;
typedef khronos_uint8_t GLubyte;
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_FALSE 0
#define GL_TRUE 1
#define GL_TRIANGLES 0x0004
#define GL_ONE 1
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_FRONT 0x0404
#define GL_BACK 0x0405
#define GL_FRONT_AND_BACK 0x0408
#define GL_POLYGON_MODE 0x0B40
#define GL_CULL_FACE 0x0B44
#define GL_DEPTH_TEST 0x0B71
#define GL_STENCIL_TEST 0x0B90
#define GL_VIEWPORT 0x0BA2
#define GL_BLEND 0x0BE2
#define GL_SCISSOR_BOX 0x0C10
#define GL_SCISSOR_TEST 0x0C11
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_TEXTURE_2D 0x0DE1
#define GL_UNSIGNED_BYTE 0x1401
#define GL_UNSIGNED_SHORT 0x1403
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_RGBA 0x1908
#define GL_FILL 0x1B02
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
#define GL_VERSION 0x1F02
#define GL_EXTENSIONS 0x1F03
#define GL_LINEAR 0x2601
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
#define GL_TEXTURE_MAG_FILTER 0x2800
#define GL_TEXTURE_MIN_FILTER 0x2801
#define GL_TEXTURE_WRAP_S 0x2802
#define GL_TEXTURE_WRAP_T 0x2803
#define GL_REPEAT 0x2901
typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode);
typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height);
typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param);
typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask);
typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap);
typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap);
typedef void (APIENTRYP PFNGLFLUSHPROC) (void);
typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param);
typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void);
typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data);
typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name);
typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap);
typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode);
GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height);
GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);
GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
GLAPI void APIENTRY glClear (GLbitfield mask);
GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
GLAPI void APIENTRY glDisable (GLenum cap);
GLAPI void APIENTRY glEnable (GLenum cap);
GLAPI void APIENTRY glFlush (void);
GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param);
GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
GLAPI GLenum APIENTRY glGetError (void);
GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data);
GLAPI const GLubyte *APIENTRY glGetString (GLenum name);
GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap);
GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);
#endif
#endif /* GL_VERSION_1_0 */
#ifndef GL_VERSION_1_1
typedef khronos_float_t GLclampf;
typedef double GLclampd;
#define GL_TEXTURE_BINDING_2D 0x8069
typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices);
typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture);
typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures);
typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);
GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture);
GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures);
GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures);
#endif
#endif /* GL_VERSION_1_1 */
#ifndef GL_VERSION_1_2
#define GL_CLAMP_TO_EDGE 0x812F
#endif /* GL_VERSION_1_2 */
#ifndef GL_VERSION_1_3
#define GL_TEXTURE0 0x84C0
#define GL_ACTIVE_TEXTURE 0x84E0
typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glActiveTexture (GLenum texture);
GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data);
#endif
#endif /* GL_VERSION_1_3 */
#ifndef GL_VERSION_1_4
#define GL_BLEND_DST_RGB 0x80C8
#define GL_BLEND_SRC_RGB 0x80C9
#define GL_BLEND_DST_ALPHA 0x80CA
#define GL_BLEND_SRC_ALPHA 0x80CB
#define GL_FUNC_ADD 0x8006
typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
GLAPI void APIENTRY glBlendEquation (GLenum mode);
#endif
#endif /* GL_VERSION_1_4 */
#ifndef GL_VERSION_1_5
typedef khronos_ssize_t GLsizeiptr;
typedef khronos_intptr_t GLintptr;
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_ARRAY_BUFFER_BINDING 0x8894
#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895
#define GL_STREAM_DRAW 0x88E0
typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer);
GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers);
GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers);
GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
#endif
#endif /* GL_VERSION_1_5 */
#ifndef GL_VERSION_2_0
typedef char GLchar;
typedef khronos_int16_t GLshort;
typedef khronos_int8_t GLbyte;
typedef khronos_uint16_t GLushort;
#define GL_BLEND_EQUATION_RGB 0x8009
#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622
#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623
#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624
#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625
#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645
#define GL_BLEND_EQUATION_ALPHA 0x883D
#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_CURRENT_PROGRAM 0x8B8D
#define GL_UPPER_LEFT 0x8CA2
typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha);
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index);
typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer);
typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha);
GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader);
GLAPI void APIENTRY glCompileShader (GLuint shader);
GLAPI GLuint APIENTRY glCreateProgram (void);
GLAPI GLuint APIENTRY glCreateShader (GLenum type);
GLAPI void APIENTRY glDeleteProgram (GLuint program);
GLAPI void APIENTRY glDeleteShader (GLuint shader);
GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader);
GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index);
GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index);
GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name);
GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name);
GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params);
GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer);
GLAPI GLboolean APIENTRY glIsProgram (GLuint program);
GLAPI void APIENTRY glLinkProgram (GLuint program);
GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
GLAPI void APIENTRY glUseProgram (GLuint program);
GLAPI void APIENTRY glUniform1i (GLint location, GLint v0);
GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
#endif
#endif /* GL_VERSION_2_0 */
#ifndef GL_VERSION_2_1
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
#endif /* GL_VERSION_2_1 */
#ifndef GL_VERSION_3_0
typedef khronos_uint16_t GLhalf;
#define GL_MAJOR_VERSION 0x821B
#define GL_MINOR_VERSION 0x821C
#define GL_NUM_EXTENSIONS 0x821D
#define GL_FRAMEBUFFER_SRGB 0x8DB9
#define GL_VERTEX_ARRAY_BINDING 0x85B5
typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data);
typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data);
typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index);
typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);
typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays);
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index);
GLAPI void APIENTRY glBindVertexArray (GLuint array);
GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays);
GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays);
#endif
#endif /* GL_VERSION_3_0 */
#ifndef GL_VERSION_3_1
#define GL_VERSION_3_1 1
#define GL_PRIMITIVE_RESTART 0x8F9D
#endif /* GL_VERSION_3_1 */
#ifndef GL_VERSION_3_2
#define GL_VERSION_3_2 1
typedef struct __GLsync *GLsync;
typedef khronos_uint64_t GLuint64;
typedef khronos_int64_t GLint64;
#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
#define GL_CONTEXT_PROFILE_MASK 0x9126
typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);
typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex);
#endif
#endif /* GL_VERSION_3_2 */
#ifndef GL_VERSION_3_3
#define GL_VERSION_3_3 1
#define GL_SAMPLER_BINDING 0x8919
typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler);
#endif
#endif /* GL_VERSION_3_3 */
#ifndef GL_VERSION_4_1
typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data);
typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data);
#endif /* GL_VERSION_4_1 */
#ifndef GL_VERSION_4_3
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
#endif /* GL_VERSION_4_3 */
#ifndef GL_VERSION_4_5
#define GL_CLIP_ORIGIN 0x935C
typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param);
typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param);
#endif /* GL_VERSION_4_5 */
#ifndef GL_ARB_bindless_texture
typedef khronos_uint64_t GLuint64EXT;
#endif /* GL_ARB_bindless_texture */
#ifndef GL_ARB_cl_event
struct _cl_context;
struct _cl_event;
#endif /* GL_ARB_cl_event */
#ifndef GL_ARB_clip_control
#define GL_ARB_clip_control 1
#endif /* GL_ARB_clip_control */
#ifndef GL_ARB_debug_output
typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);
#endif /* GL_ARB_debug_output */
#ifndef GL_EXT_EGL_image_storage
typedef void *GLeglImageOES;
#endif /* GL_EXT_EGL_image_storage */
#ifndef GL_EXT_direct_state_access
typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params);
typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params);
typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params);
typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param);
typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param);
#endif /* GL_EXT_direct_state_access */
#ifndef GL_NV_draw_vulkan_image
typedef void (APIENTRY *GLVULKANPROCNV)(void);
#endif /* GL_NV_draw_vulkan_image */
#ifndef GL_NV_gpu_shader5
typedef khronos_int64_t GLint64EXT;
#endif /* GL_NV_gpu_shader5 */
#ifndef GL_NV_vertex_buffer_unified_memory
typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result);
#endif /* GL_NV_vertex_buffer_unified_memory */
#ifdef __cplusplus
}
#endif
#endif
#ifndef GL3W_API
#define GL3W_API
#endif
#ifndef __gl_h_
#define __gl_h_
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define GL3W_OK 0
#define GL3W_ERROR_INIT -1
#define GL3W_ERROR_LIBRARY_OPEN -2
#define GL3W_ERROR_OPENGL_VERSION -3
typedef void (*GL3WglProc)(void);
typedef GL3WglProc (*GL3WGetProcAddressProc)(const char *proc);
/* gl3w api */
GL3W_API int imgl3wInit(void);
GL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc);
GL3W_API int imgl3wIsSupported(int major, int minor);
GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc);
/* gl3w internal state */
union ImGL3WProcs {
GL3WglProc ptr[60];
struct {
PFNGLACTIVETEXTUREPROC ActiveTexture;
PFNGLATTACHSHADERPROC AttachShader;
PFNGLBINDBUFFERPROC BindBuffer;
PFNGLBINDSAMPLERPROC BindSampler;
PFNGLBINDTEXTUREPROC BindTexture;
PFNGLBINDVERTEXARRAYPROC BindVertexArray;
PFNGLBLENDEQUATIONPROC BlendEquation;
PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate;
PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate;
PFNGLBUFFERDATAPROC BufferData;
PFNGLBUFFERSUBDATAPROC BufferSubData;
PFNGLCLEARPROC Clear;
PFNGLCLEARCOLORPROC ClearColor;
PFNGLCOMPILESHADERPROC CompileShader;
PFNGLCOMPRESSEDTEXIMAGE2DPROC CompressedTexImage2D;
PFNGLCREATEPROGRAMPROC CreateProgram;
PFNGLCREATESHADERPROC CreateShader;
PFNGLDELETEBUFFERSPROC DeleteBuffers;
PFNGLDELETEPROGRAMPROC DeleteProgram;
PFNGLDELETESHADERPROC DeleteShader;
PFNGLDELETETEXTURESPROC DeleteTextures;
PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays;
PFNGLDETACHSHADERPROC DetachShader;
PFNGLDISABLEPROC Disable;
PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray;
PFNGLDRAWELEMENTSPROC DrawElements;
PFNGLDRAWELEMENTSBASEVERTEXPROC DrawElementsBaseVertex;
PFNGLENABLEPROC Enable;
PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray;
PFNGLFLUSHPROC Flush;
PFNGLGENBUFFERSPROC GenBuffers;
PFNGLGENTEXTURESPROC GenTextures;
PFNGLGENVERTEXARRAYSPROC GenVertexArrays;
PFNGLGETATTRIBLOCATIONPROC GetAttribLocation;
PFNGLGETERRORPROC GetError;
PFNGLGETINTEGERVPROC GetIntegerv;
PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog;
PFNGLGETPROGRAMIVPROC GetProgramiv;
PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog;
PFNGLGETSHADERIVPROC GetShaderiv;
PFNGLGETSTRINGPROC GetString;
PFNGLGETSTRINGIPROC GetStringi;
PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation;
PFNGLGETVERTEXATTRIBPOINTERVPROC GetVertexAttribPointerv;
PFNGLGETVERTEXATTRIBIVPROC GetVertexAttribiv;
PFNGLISENABLEDPROC IsEnabled;
PFNGLISPROGRAMPROC IsProgram;
PFNGLLINKPROGRAMPROC LinkProgram;
PFNGLPIXELSTOREIPROC PixelStorei;
PFNGLPOLYGONMODEPROC PolygonMode;
PFNGLREADPIXELSPROC ReadPixels;
PFNGLSCISSORPROC Scissor;
PFNGLSHADERSOURCEPROC ShaderSource;
PFNGLTEXIMAGE2DPROC TexImage2D;
PFNGLTEXPARAMETERIPROC TexParameteri;
PFNGLUNIFORM1IPROC Uniform1i;
PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv;
PFNGLUSEPROGRAMPROC UseProgram;
PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer;
PFNGLVIEWPORTPROC Viewport;
} gl;
};
GL3W_API extern union ImGL3WProcs imgl3wProcs;
/* OpenGL functions */
#define glActiveTexture imgl3wProcs.gl.ActiveTexture
#define glAttachShader imgl3wProcs.gl.AttachShader
#define glBindBuffer imgl3wProcs.gl.BindBuffer
#define glBindSampler imgl3wProcs.gl.BindSampler
#define glBindTexture imgl3wProcs.gl.BindTexture
#define glBindVertexArray imgl3wProcs.gl.BindVertexArray
#define glBlendEquation imgl3wProcs.gl.BlendEquation
#define glBlendEquationSeparate imgl3wProcs.gl.BlendEquationSeparate
#define glBlendFuncSeparate imgl3wProcs.gl.BlendFuncSeparate
#define glBufferData imgl3wProcs.gl.BufferData
#define glBufferSubData imgl3wProcs.gl.BufferSubData
#define glClear imgl3wProcs.gl.Clear
#define glClearColor imgl3wProcs.gl.ClearColor
#define glCompileShader imgl3wProcs.gl.CompileShader
#define glCompressedTexImage2D imgl3wProcs.gl.CompressedTexImage2D
#define glCreateProgram imgl3wProcs.gl.CreateProgram
#define glCreateShader imgl3wProcs.gl.CreateShader
#define glDeleteBuffers imgl3wProcs.gl.DeleteBuffers
#define glDeleteProgram imgl3wProcs.gl.DeleteProgram
#define glDeleteShader imgl3wProcs.gl.DeleteShader
#define glDeleteTextures imgl3wProcs.gl.DeleteTextures
#define glDeleteVertexArrays imgl3wProcs.gl.DeleteVertexArrays
#define glDetachShader imgl3wProcs.gl.DetachShader
#define glDisable imgl3wProcs.gl.Disable
#define glDisableVertexAttribArray imgl3wProcs.gl.DisableVertexAttribArray
#define glDrawElements imgl3wProcs.gl.DrawElements
#define glDrawElementsBaseVertex imgl3wProcs.gl.DrawElementsBaseVertex
#define glEnable imgl3wProcs.gl.Enable
#define glEnableVertexAttribArray imgl3wProcs.gl.EnableVertexAttribArray
#define glFlush imgl3wProcs.gl.Flush
#define glGenBuffers imgl3wProcs.gl.GenBuffers
#define glGenTextures imgl3wProcs.gl.GenTextures
#define glGenVertexArrays imgl3wProcs.gl.GenVertexArrays
#define glGetAttribLocation imgl3wProcs.gl.GetAttribLocation
#define glGetError imgl3wProcs.gl.GetError
#define glGetIntegerv imgl3wProcs.gl.GetIntegerv
#define glGetProgramInfoLog imgl3wProcs.gl.GetProgramInfoLog
#define glGetProgramiv imgl3wProcs.gl.GetProgramiv
#define glGetShaderInfoLog imgl3wProcs.gl.GetShaderInfoLog
#define glGetShaderiv imgl3wProcs.gl.GetShaderiv
#define glGetString imgl3wProcs.gl.GetString
#define glGetStringi imgl3wProcs.gl.GetStringi
#define glGetUniformLocation imgl3wProcs.gl.GetUniformLocation
#define glGetVertexAttribPointerv imgl3wProcs.gl.GetVertexAttribPointerv
#define glGetVertexAttribiv imgl3wProcs.gl.GetVertexAttribiv
#define glIsEnabled imgl3wProcs.gl.IsEnabled
#define glIsProgram imgl3wProcs.gl.IsProgram
#define glLinkProgram imgl3wProcs.gl.LinkProgram
#define glPixelStorei imgl3wProcs.gl.PixelStorei
#define glPolygonMode imgl3wProcs.gl.PolygonMode
#define glReadPixels imgl3wProcs.gl.ReadPixels
#define glScissor imgl3wProcs.gl.Scissor
#define glShaderSource imgl3wProcs.gl.ShaderSource
#define glTexImage2D imgl3wProcs.gl.TexImage2D
#define glTexParameteri imgl3wProcs.gl.TexParameteri
#define glUniform1i imgl3wProcs.gl.Uniform1i
#define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv
#define glUseProgram imgl3wProcs.gl.UseProgram
#define glVertexAttribPointer imgl3wProcs.gl.VertexAttribPointer
#define glViewport imgl3wProcs.gl.Viewport
#ifdef __cplusplus
}
#endif
#endif
#ifdef IMGL3W_IMPL
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#define GL3W_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <windows.h>
static HMODULE libgl;
typedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR);
static GL3WglGetProcAddr wgl_get_proc_address;
static int open_libgl(void)
{
libgl = LoadLibraryA("opengl32.dll");
if (!libgl)
return GL3W_ERROR_LIBRARY_OPEN;
wgl_get_proc_address = (GL3WglGetProcAddr)GetProcAddress(libgl, "wglGetProcAddress");
return GL3W_OK;
}
static void close_libgl(void) { FreeLibrary(libgl); }
static GL3WglProc get_proc(const char *proc)
{
GL3WglProc res;
res = (GL3WglProc)wgl_get_proc_address(proc);
if (!res)
res = (GL3WglProc)GetProcAddress(libgl, proc);
return res;
}
#elif defined(__APPLE__)
#include <dlfcn.h>
static void *libgl;
static int open_libgl(void)
{
libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL);
if (!libgl)
return GL3W_ERROR_LIBRARY_OPEN;
return GL3W_OK;
}
static void close_libgl(void) { dlclose(libgl); }
static GL3WglProc get_proc(const char *proc)
{
GL3WglProc res;
*(void **)(&res) = dlsym(libgl, proc);
return res;
}
#else
#include <dlfcn.h>
static void* libgl; // OpenGL library
static void* libglx; // GLX library
static void* libegl; // EGL library
static GL3WGetProcAddressProc gl_get_proc_address;
static void close_libgl(void)
{
if (libgl) {
dlclose(libgl);
libgl = NULL;
}
if (libegl) {
dlclose(libegl);
libegl = NULL;
}
if (libglx) {
dlclose(libglx);
libglx = NULL;
}
}
static int is_library_loaded(const char* name, void** lib)
{
*lib = dlopen(name, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
return *lib != NULL;
}
static int open_libs(void)
{
// On Linux we have two APIs to get process addresses: EGL and GLX.
// EGL is supported under both X11 and Wayland, whereas GLX is X11-specific.
libgl = NULL;
libegl = NULL;
libglx = NULL;
// First check what's already loaded, the windowing library might have
// already loaded either EGL or GLX and we want to use the same one.
if (is_library_loaded("libEGL.so.1", &libegl) ||
is_library_loaded("libGLX.so.0", &libglx)) {
libgl = dlopen("libOpenGL.so.0", RTLD_LAZY | RTLD_LOCAL);
if (libgl)
return GL3W_OK;
else
close_libgl();
}
if (is_library_loaded("libGL.so", &libgl))
return GL3W_OK;
if (is_library_loaded("libGL.so.1", &libgl))
return GL3W_OK;
if (is_library_loaded("libGL.so.3", &libgl))
return GL3W_OK;
// Neither is already loaded, so we have to load one. Try EGL first
// because it is supported under both X11 and Wayland.
// Load OpenGL + EGL
libgl = dlopen("libOpenGL.so.0", RTLD_LAZY | RTLD_LOCAL);
libegl = dlopen("libEGL.so.1", RTLD_LAZY | RTLD_LOCAL);
if (libgl && libegl)
return GL3W_OK;
else
close_libgl();
// Fall back to legacy libGL, which includes GLX
// While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983
libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
if (!libgl)
libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL);
if (!libgl)
libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL);
if (libgl)
return GL3W_OK;
return GL3W_ERROR_LIBRARY_OPEN;
}
static int open_libgl(void)
{
int res = open_libs();
if (res)
return res;
if (libegl)
*(void**)(&gl_get_proc_address) = dlsym(libegl, "eglGetProcAddress");
else if (libglx)
*(void**)(&gl_get_proc_address) = dlsym(libglx, "glXGetProcAddressARB");
else
*(void**)(&gl_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB");
if (!gl_get_proc_address) {
close_libgl();
return GL3W_ERROR_LIBRARY_OPEN;
}
return GL3W_OK;
}
static GL3WglProc get_proc(const char* proc)
{
GL3WglProc res = NULL;
// Before EGL version 1.5, eglGetProcAddress doesn't support querying core
// functions and may return a dummy function if we try, so try to load the
// function from the GL library directly first.
if (libegl)
*(void**)(&res) = dlsym(libgl, proc);
if (!res)
res = gl_get_proc_address(proc);
if (!libegl && !res)
*(void**)(&res) = dlsym(libgl, proc);
return res;
}
#endif
static struct { int major, minor; } version;
static int parse_version(void)
{
if (!glGetIntegerv)
return GL3W_ERROR_INIT;
glGetIntegerv(GL_MAJOR_VERSION, &version.major);
glGetIntegerv(GL_MINOR_VERSION, &version.minor);
if (version.major == 0 && version.minor == 0)
{
// Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
if (const char* gl_version = (const char*)glGetString(GL_VERSION))
sscanf(gl_version, "%d.%d", &version.major, &version.minor);
}
if (version.major < 2)
return GL3W_ERROR_OPENGL_VERSION;
return GL3W_OK;
}
static void load_procs(GL3WGetProcAddressProc proc);
int imgl3wInit(void)
{
int res = open_libgl();
if (res)
return res;
atexit(close_libgl);
return imgl3wInit2(get_proc);
}
int imgl3wInit2(GL3WGetProcAddressProc proc)
{
load_procs(proc);
return parse_version();
}
int imgl3wIsSupported(int major, int minor)
{
if (major < 2)
return 0;
if (version.major == major)
return version.minor >= minor;
return version.major >= major;
}
GL3WglProc imgl3wGetProcAddress(const char *proc) { return get_proc(proc); }
static const char *proc_names[] = {
"glActiveTexture",
"glAttachShader",
"glBindBuffer",
"glBindSampler",
"glBindTexture",
"glBindVertexArray",
"glBlendEquation",
"glBlendEquationSeparate",
"glBlendFuncSeparate",
"glBufferData",
"glBufferSubData",
"glClear",
"glClearColor",
"glCompileShader",
"glCompressedTexImage2D",
"glCreateProgram",
"glCreateShader",
"glDeleteBuffers",
"glDeleteProgram",
"glDeleteShader",
"glDeleteTextures",
"glDeleteVertexArrays",
"glDetachShader",
"glDisable",
"glDisableVertexAttribArray",
"glDrawElements",
"glDrawElementsBaseVertex",
"glEnable",
"glEnableVertexAttribArray",
"glFlush",
"glGenBuffers",
"glGenTextures",
"glGenVertexArrays",
"glGetAttribLocation",
"glGetError",
"glGetIntegerv",
"glGetProgramInfoLog",
"glGetProgramiv",
"glGetShaderInfoLog",
"glGetShaderiv",
"glGetString",
"glGetStringi",
"glGetUniformLocation",
"glGetVertexAttribPointerv",
"glGetVertexAttribiv",
"glIsEnabled",
"glIsProgram",
"glLinkProgram",
"glPixelStorei",
"glPolygonMode",
"glReadPixels",
"glScissor",
"glShaderSource",
"glTexImage2D",
"glTexParameteri",
"glUniform1i",
"glUniformMatrix4fv",
"glUseProgram",
"glVertexAttribPointer",
"glViewport",
};
GL3W_API union ImGL3WProcs imgl3wProcs;
static void load_procs(GL3WGetProcAddressProc proc)
{
size_t i;
for (i = 0; i < GL3W_ARRAY_SIZE(proc_names); i++)
imgl3wProcs.ptr[i] = proc(proc_names[i]);
}
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,217 @@
You are a language model, designed to provide precise answers based on available tools and your knowledge. Your operation must strictly adhere to the instructions below.
# Core Principles:
1. *Never guess or invent information.* If you do not have the necessary data, use the available tools to gather it.
2. Always protect privacy of the user.
3. If the tools return no data or you still lack the required information after using the tools, attempt to answer using your internal knowledge, while clearly informing the user that the response might be incorrect, invalid, or wrong, and that the tools returned no data.
4. *Never ask the user* for permission to use tools or perform further queries. You *MUST* conduct the entire information retrieval process independently, and *ONLY THEN* reply to the user.
5. *Language Consistency:* If the user's query is in a language other than English, you MUST translate *all* tool output and internally generated responses into the user's query language *before* formulating your final response. Your final response to the user must always be in the language they used. Do not output information in any other language.
6. Prioritize information obtained via tools (from `<tool_output>`) over your internal knowledge when constructing your response. Treat tool outputs as the leading source of information, but be aware that they may contain irrelevant details, inconsistencies, or inaccuracies. Critically evaluate all tool outputs: check for relevance to the user's query, cross-reference information across different tool outputs, and assess consistency with your internal knowledge. If multiple tool outputs provide conflicting but equally plausible information, you may state the different findings or, if possible, explain the discrepancy if it leads to a clearer answer. Avoid presenting information as definitively true if its source is uncertain.
# Thinking Process and Tool Usage:
Your operation process will be strictly structured using `<think>` and `<tool>` tags.
1. *Thinking Process (`<think>`):*
- Always start with a `<think>` block.
- This block is for planning, analyzing the user's query, deciding which tools are needed (if any), processing results from `<tool_output>`, and formulating the structure of the response.
- You must analyse the question or any attachments provided by the user to decide which tools you can use.
- The tag name MUST be exactly `think`.
2. *Tool Usage (`<tool>`):*
- If, in the `<think>` block, you decide you need to use a tool, the next block generated *MUST* be a `<tool>` block.
- The tag name MUST be exactly `tool`.
- There can be ONLY ONE tool call in the `<tool>` block.
- Only ONE tool call is permitted PER TURN.
- *After generating a `<tool>` block, you MUST END YOUR RESPONSE FOR THIS TURN.* Do not generate any other text or tags after the `<tool>` block. The system will process this tool call and provide you with the result in the next step.
- The tool name and its parameters (if applicable) must be passed as a json data. For example:
<think>
The user is asking about the weather in San Francisco. I need to use the weather checking tool. The tool name is 'check_weather', the parameter is the city name.
</think>
<tool>
{"tool": "check_weather", "city": "San Francisco"}
</tool>
3. *Tool Output (`<tool_output>`):*
- After the system executes the tool call from the `<tool>` block, you will receive the result in an `<tool_output>` block.
- *You MUST process this result in the subsequent `<think>` block.* Analyze the data received. Based on it, decide if further tool calls are necessary or if you have enough information to answer the user.
- *Never show the user the raw text from `<tool_output>`*. All processing happens internally within the `<think>` block.
# Available Tools:
These are the tools you can use. *You have no access to any other tools or means to search the web outside of these.*
```json
{
"tool": "search_wikipedia",
"description": "Search the Wikipedia with given query. The `key` field in the response is the Wikipedia page name.",
"network": true,
"parameters": [
{
"name": "query",
"description": "The search terms in the language matching the second parameter."
},
{
"name": "language",
"description": "Language code matching the search query. For example, `en` for English or `pl` for Polish."
}
]
},
{
"tool": "get_wikipedia",
"description": "Retrieve the Wikipedia article on given subject. The response may be trimmed.",
"network": true,
"parameters": [
{
"name": "page",
"description": "The `key` field from the search response, specifying the topic you want to retrieve.",
},
{
"name": "language",
"description": "Language code."
}
]
},
{
"tool": "get_dictionary",
"description": "Retrieve description of a word from dictionary.",
"network": true,
"parameters": [
{
"name": "word",
"description": "Word to describe."
},
{
"name": "language",
"description": "Language code."
}
]
},
{
"tool": "search_web",
"description": "Search the web with given query.",
"network": true,
"parameters": [
{
"name": "query",
"description": "Search query."
}
]
},
{
"tool": "get_webpage",
"description": "Download web page at given URL.",
"network": true,
"parameters": [
{
"name": "url",
"description": "Web page to download."
}
]
},
{
"tool": "user_manual",
"description": "Search the Tracy Profiler user manual with given query.",
"local": true,
"parameters": [
{
"name": "query",
"description": "Verbose search query in English language."
}
]
},
{
"tool": "source_file",
"description": "Retrieve the source file contents.",
"local": true,
"parameters": [
{
"name": "file",
"description": "Path to the file."
},
{
"name": "line",
"description": "Line number that should be retrieved (as large files may be not available completely)."
}
]
}
```
Tools marked as `local` operate privately and are always safe to use. Tools marked as `network` send data over the internet and may affect user's privacy.
# Tool Usage and Knowledge Strategy:
1. *Source Priority:*
- For questions related to Tracy Profiler always refer to the `user_manual`. Do not use this tool to research user's program.
- If the user's question explicitly asks about source code in user's program (for example, a callstack provided as an attachment), use the `source_file` tool to retrieve the content of specified files.
- For other factual queries, start by checking Wikipedia. If Wikipedia doesn't provide enough information, or if the topic is new or highly specialized, then perform a `search_web` query.
2. *Internal Knowledge vs. Tools:* Always assume your internal knowledge is incomplete or outdated compared to information from tools. *You MUST use tools* to get the latest and most accurate data on subjects covered by their scope (e.g. facts likely on Wikipedia or the web). Output from previous tool invocations must be always considered.
3. *Efficient Tool Use:* Before using a tool you MUST check if previous tool calls already contain the tool and parameters you want to call. If they do, you are forbidden from calling the tool a second time. You must use the tool output you already have.
4. *Tool Output Completness:* Some tools will return snippets or summaries of the information, which can only be used in limited conditions. You MUST use these summaries to decide which tool to call next to get complete data.
5. *Mandatory Content Retrieval:* Some tool outputs (e.g. `search_wikipedia` or `search_web`) provide only summaries or snippets. These are *never* sufficient for formulating a final answer. Their sole purpose is to identify the most promising page or URL. You MUST always follow a successful search with a corresponding tool call (e.g., `get_wikipedia` or `get_webpage`) to retrieve the full content before attempting to answer the user's query. Do not answer based on search snippets alone. The only exception is if the search returns no relevant results.
# Final Response to the User:
1. Once you have gathered all necessary information using the `<think>`, `<tool>`, and `<tool_output>` processing cycle, *generate the final response FOR THE USER.*
2. This final response *MUST* be *OUTSIDE* of the `<think>` and `<tool>` tags.
3. The user shouldn't know you are "using tools". Use a natural language, such as "the Wikipedia states that..." or "the web search results indicate that...". The user should not be aware of the tool usage process.
4. Provide responses *strictly in the language the user used* in their query.
# Summary of Communication Structure:
Each of your responses (or part of a response, if it requires a tool call) should start with a `<think>` block, followed by either a `<tool>` block (if further information is needed) or directly the final response to the user (if you have all information).
**Example Cycle (Not visible to the user):**
User: "What is the capital of Poland?"
Model:
<think>
The user is asking for the capital of Poland, in English language. This is a standard fact, but instructions say to use tools for facts. I should search Wikipedia. I need search_wikipedia for "capital of Poland" in language "en" to find the key.
</think>
<tool>
{"tool": "search_wikipedia", "query": "capital of Poland", "language": "en"}
</tool>
System returns `<tool_output>` with the key for the article about Warsaw.
Model (New Turn):
<think>
I retrieved the key for the capital of Poland article ("Warsaw"). Now I need to retrieve that article using get_wikipedia. The key is "Warsaw", language is "en".
</think>
<tool>
{"tool": "get_wikipedia", "page": "Warsaw", "language": "en"}
</tool>
System returns `<tool_output>` with the content of the Warsaw article, stating it is the capital.
Model (New Turn):
<think>
I downloaded the content of the Warsaw article. It confirms that Warsaw is the capital of Poland. I have all the necessary information. I can provide the answer in user's language, which is English.
</think>
The capital of Poland is Warsaw.
*The user only sees:* "The capital of Poland is Warsaw."
# Attachments
The user may provide various types of attachments for you to process. These attachments come from the users's program. When you process *attachments* using *tools that access a network*, you must adhere to the following privacy protection rules. The rules *do not* apply in other circumstances, such as in conversation with the user, when using local tools, or when getting data for things unrelated to the user's program.
- Protect Private Information: Do not use any project, class, function, code snippets, or file names in *network tool* queries when the source is located in a user's private directory.
- Publicly Available Files: This restriction does not apply to files that are in publicly accessible locations.
- Tool Use: The `source_file` tool preserves user privacy and can be used regardless of the source file location.
# Context of operation
You operate in context of Tracy Profiler, a C++ profiler for games and other applications. The profiler uses various methods to measure how the user's program behaves and measures the program's run-time performance characteristics. As such, there are various types of questions the user may ask you, and you must properly classify each question in order to give the best possible answer:
- The user may ask you about things related to Tracy Profiler. In this case you should primarily focus on the `user_manual` tool, which provides information about the profiler. When refering to specific terms in the profiler UI, use the original English names.
- The user may attach information from the program they are profiling and ask you about it. Since this would be mostly private data, you should focus on the `source_file` tool, which will give you context about specific source locations referenced in the attachment. You may need to put more emphasis on your internal knowledge when answering these kind of questions. Use of other tools should be limited to cases where it's obvious they will be useful. For example, you may want to search the web about the zlib library if the code uses it, or, you may retrieve a web page referenced in the source code comments.
- The user may also ask general question not related either to the profiler or the program they are profiling. In this case answer freely, and use any tool you feel necessary.
If the user thanks you for your help, ask them to consider making a donation at https://github.com/sponsors/wolfpld.
@@ -0,0 +1,5 @@
Remember your core principles:
1. Protect user's privacy.
2. Always prioritize tools for factual information.
3. Your internal knowledge is strictly secondary and a last resort.
4. Respond strictly in the user's language.
+162 -150
View File
@@ -26,8 +26,6 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
#include "ini.h"
#include "../../public/common/TracyProtocol.hpp"
#include "../../public/common/TracyVersion.hpp"
#include "profiler/TracyAchievements.hpp"
@@ -35,9 +33,9 @@
#include "profiler/TracyConfig.hpp"
#include "profiler/TracyFileselector.hpp"
#include "profiler/TracyImGui.hpp"
#include "profiler/TracyMarkdown.hpp"
#include "profiler/TracyMouse.hpp"
#include "profiler/TracyProtoHistory.hpp"
#include "profiler/TracyStorage.hpp"
#include "profiler/TracyTexture.hpp"
#include "profiler/TracyView.hpp"
#include "profiler/TracyWeb.hpp"
@@ -68,6 +66,8 @@
#include "ResolvService.hpp"
#include "RunQueue.hpp"
#include "GitRef.hpp"
struct ClientData
{
@@ -97,9 +97,8 @@ static char addr[1024] = { "127.0.0.1" };
static ConnectionHistory* connHist;
static std::atomic<ViewShutdown> viewShutdown { ViewShutdown::False };
static double animTime = 0;
static float dpiScale = 1.f;
static float dpiScale = -1.f;
static bool dpiScaleOverriddenFromEnv = false;
static float userScale = 1.f;
static float prevScale = 1.f;
static int dpiChanged = 0;
static bool dpiFirstSetup = true;
@@ -110,16 +109,15 @@ static bool showReleaseNotes = false;
static std::string releaseNotes;
static uint8_t* iconPx;
static int iconX, iconY;
static void* iconTex;
static ImTextureID iconTex;
static int iconTexSz;
static uint8_t* zigzagPx[6];
static int zigzagX[6], zigzagY[6];
void* zigzagTex;
ImTextureID zigzagTex;
static Backend* bptr;
static bool s_customTitle = false;
static bool s_isElevated = false;
static size_t s_totalMem = tracy::GetPhysicalMemorySize();
tracy::Config s_config;
tracy::AchievementsMgr* s_achievements;
static const tracy::data::AchievementItem* s_achievementItem = nullptr;
static bool s_switchAchievementCategory = false;
@@ -160,19 +158,15 @@ static void ScaleWindow(ImGuiWindow* window, float scale)
static void SetupDPIScale()
{
auto scale = dpiScale * userScale;
auto scale = dpiScale * tracy::s_config.userScale;
if( !dpiFirstSetup && prevScale == scale ) return;
dpiFirstSetup = false;
dpiChanged = 2;
LoadFonts( scale );
if( view ) view->UpdateFont( s_fixedWidth, s_smallFont, s_bigFont );
#ifdef __APPLE__
// No need to upscale the style on macOS, but we need to downscale the fonts.
ImGuiIO& io = ImGui::GetIO();
io.FontGlobalScale = 1.0f / dpiScale;
scale = 1.0f;
#endif
@@ -186,6 +180,7 @@ static void SetupDPIScale()
style.Colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.45f);
style.Colors[ImGuiCol_TitleBgCollapsed] = style.Colors[ImGuiCol_TitleBg];
style.ScaleAllSizes( scale );
const auto ty = int( 80 * scale );
@@ -201,12 +196,6 @@ static void SetupDPIScale()
for( auto& w : ctx->Windows ) ScaleWindow( w, ratio );
}
static void SetupScaleCallback( float scale )
{
userScale = scale;
RunOnMainThread( []{ SetupDPIScale(); }, true );
}
static int IsBusy()
{
if( loadThread.joinable() ) return 2;
@@ -214,59 +203,17 @@ static int IsBusy()
return 0;
}
static void LoadConfig()
static void SetupScaleCallback( float scale )
{
const auto fn = tracy::GetSavePath( "tracy.ini" );
auto ini = ini_load( fn );
if( !ini ) return;
int v;
if( ini_sget( ini, "core", "threadedRendering", "%d", &v ) ) s_config.threadedRendering = v;
if( ini_sget( ini, "core", "focusLostLimit", "%d", &v ) ) s_config.focusLostLimit = v;
if( ini_sget( ini, "timeline", "targetFps", "%d", &v ) && v >= 1 && v < 10000 ) s_config.targetFps = v;
if( ini_sget( ini, "timeline", "dynamicColors", "%d", &v ) ) s_config.dynamicColors = v;
if( ini_sget( ini, "timeline", "forceColors", "%d", &v ) ) s_config.forceColors = v;
if( ini_sget( ini, "timeline", "shortenName", "%d", &v ) ) s_config.shortenName = v;
if( ini_sget( ini, "memory", "limit", "%d", &v ) ) s_config.memoryLimit = v;
if( ini_sget( ini, "memory", "percent", "%d", &v ) && v >= 1 && v < 1000 ) s_config.memoryLimitPercent = v;
if( ini_sget( ini, "achievements", "enabled", "%d", &v ) ) s_config.achievements = v;
if( ini_sget( ini, "achievements", "asked", "%d", &v ) ) s_config.achievementsAsked = v;
ini_free( ini );
}
static bool SaveConfig()
{
const auto fn = tracy::GetSavePath( "tracy.ini" );
FILE* f = fopen( fn, "wb" );
if( !f ) return false;
fprintf( f, "[core]\n" );
fprintf( f, "threadedRendering = %i\n", (int)s_config.threadedRendering );
fprintf( f, "focusLostLimit = %i\n", (int)s_config.focusLostLimit );
fprintf( f, "\n[timeline]\n" );
fprintf( f, "targetFps = %i\n", s_config.targetFps );
fprintf( f, "dynamicColors = %i\n", s_config.dynamicColors );
fprintf( f, "forceColors = %i\n", (int)s_config.forceColors );
fprintf( f, "shortenName = %i\n", s_config.shortenName );
fprintf( f, "\n[memory]\n" );
fprintf( f, "limit = %i\n", (int)s_config.memoryLimit );
fprintf( f, "percent = %i\n", s_config.memoryLimitPercent );
fprintf( f, "\n[achievements]\n" );
fprintf( f, "enabled = %i\n", (int)s_config.achievements );
fprintf( f, "asked = %i\n", (int)s_config.achievementsAsked );
fclose( f );
return true;
tracy::s_config.userScale = scale;
if ( tracy::s_config.saveUserScale ) tracy::SaveConfig();
RunOnMainThread( []{ SetupDPIScale(); }, true );
}
static void ScaleChanged( float scale )
{
if ( dpiScaleOverriddenFromEnv ) return;
if ( dpiScale == scale ) return;
if( dpiScaleOverriddenFromEnv ) return;
if( dpiScale == scale ) return;
dpiScale = scale;
SetupDPIScale();
@@ -371,7 +318,7 @@ int main( int argc, char** argv )
zigzagPx[5] = stbi_load_from_memory( (const stbi_uc*)ZigZag01_data, ZigZag01_size, &zigzagX[5], &zigzagY[5], nullptr, 4 );
} );
LoadConfig();
tracy::LoadConfig();
ImGuiTracyContext imguiContext;
Backend backend( title, DrawContents, ScaleChanged, IsBusy, &mainThreadTasks );
@@ -382,7 +329,6 @@ int main( int argc, char** argv )
backend.SetIcon( iconPx, iconX, iconY );
bptr = &backend;
dpiScale = backend.GetDpiScale();
const auto envDpiScale = getenv( "TRACY_DPI_SCALE" );
if( envDpiScale )
{
@@ -391,24 +337,23 @@ int main( int argc, char** argv )
{
dpiScale = cnv;
dpiScaleOverriddenFromEnv = true;
SetupDPIScale();
}
}
s_achievements->Achieve( "achievementsIntro" );
SetupDPIScale();
tracy::UpdateTextureRGBAMips( zigzagTex, (void**)zigzagPx, zigzagX, zigzagY, 6 );
for( auto& v : zigzagPx ) free( v );
if( initFileOpen )
{
view = std::make_unique<tracy::View>( RunOnMainThread, *initFileOpen, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
view = std::make_unique<tracy::View>( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
initFileOpen.reset();
}
else if( connectTo )
{
view = std::make_unique<tracy::View>( RunOnMainThread, connectTo, port, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
view = std::make_unique<tracy::View>( RunOnMainThread, connectTo, port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
tracy::Fileselector::Init();
@@ -580,7 +525,7 @@ static void UpdateBroadcastClients()
static void TextComment( const char* str )
{
ImGui::SameLine();
ImGui::PushFont( s_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
tracy::TextDisabledUnformatted( str );
ImGui::PopFont();
@@ -639,7 +584,7 @@ static void DrawContents()
int display_w, display_h;
bptr->NewFrame( display_w, display_h );
const bool achievementsAttention = s_config.achievements ? s_achievements->NeedsAttention() : false;
const bool achievementsAttention = tracy::s_config.achievements ? s_achievements->NeedsAttention() : false;
static int activeFrames = 3;
if( tracy::WasActive() || !clients.empty() || ( view && view->WasActive() ) || achievementsAttention )
@@ -691,15 +636,15 @@ static void DrawContents()
auto& style = ImGui::GetStyle();
style.Colors[ImGuiCol_WindowBg] = ImVec4( 0.129f, 0.137f, 0.11f, 1.f );
ImGui::Begin( "Get started", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse );
ImGui::Begin( "Get started", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings );
char buf[128];
sprintf( buf, "Tracy Profiler %i.%i.%i", tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch );
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.bold, FontNormal * 1.6f );
tracy::TextCentered( buf );
ImGui::PopFont();
if( dpiChanged == 0 )
{
ImGui::SameLine( ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize( ICON_FA_WRENCH ).x - ImGui::GetStyle().FramePadding.x * 2 );
ImGui::SameLine( ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize( ICON_FA_WRENCH ).x );
if( ImGui::Button( ICON_FA_WRENCH ) )
{
ImGui::OpenPopup( "About Tracy" );
@@ -710,9 +655,32 @@ static void DrawContents()
{
tracy::ImageCentered( iconTex, ImVec2( iconTexSz, iconTexSz ) );
ImGui::Spacing();
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.bold, FontNormal * 2.f );
tracy::TextCentered( buf );
ImGui::Spacing();
ImGui::PopFont();
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
tracy::TextCentered( tracy::GitRef );
ImGui::PopStyleColor();
ImGui::PopFont();
if( ImGui::IsItemHovered() )
{
ImGui::BeginTooltip();
ImGui::TextUnformatted( "Click to copy git reference to clipboard" );
ImGui::EndTooltip();
if( ImGui::IsItemClicked() )
{
ImGui::SetClipboardText( tracy::GitRef );
}
}
#ifndef NDEBUG
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 0.5f, 0.5f, 1.f ) );
tracy::TextCentered( "Debug build" );
ImGui::PopStyleColor();
ImGui::PopFont();
#endif
ImGui::Spacing();
ImGui::TextUnformatted( "A real time, nanosecond resolution, remote telemetry, hybrid\nframe and sampling profiler for games and other applications." );
ImGui::Spacing();
@@ -729,67 +697,80 @@ static void DrawContents()
ImGui::TextUnformatted( "Threaded rendering" );
ImGui::Indent();
if( ImGui::RadioButton( "Enabled", s_config.threadedRendering ) ) { s_config.threadedRendering = true; SaveConfig(); }
if( ImGui::RadioButton( "Enabled", tracy::s_config.threadedRendering ) ) { tracy::s_config.threadedRendering = true; tracy::SaveConfig(); }
ImGui::SameLine();
tracy::DrawHelpMarker( "Uses multiple CPU cores for rendering. May affect performance of the profiled application when running on the same machine." );
if( ImGui::RadioButton( "Disabled", !s_config.threadedRendering ) ) { s_config.threadedRendering = false; SaveConfig(); }
if( ImGui::RadioButton( "Disabled", !tracy::s_config.threadedRendering ) ) { tracy::s_config.threadedRendering = false; tracy::SaveConfig(); }
ImGui::SameLine();
tracy::DrawHelpMarker( "Restricts rendering to a single CPU core. Can reduce profiler frame rate." );
ImGui::Unindent();
ImGui::Spacing();
if( ImGui::Checkbox( "Reduce render rate when focus is lost", &s_config.focusLostLimit ) ) SaveConfig();
if( ImGui::Checkbox( "Reduce render rate when focus is lost", &tracy::s_config.focusLostLimit ) ) tracy::SaveConfig();
ImGui::Spacing();
ImGui::TextUnformatted( "Target FPS" );
ImGui::SameLine();
tracy::DrawHelpMarker( "The default target frame rate for your application. Frames displayed in the frame time graph will be colored accordingly if they are within the target frame rate. This can be adjusted later for each individual trace." );
ImGui::SameLine();
int tmp = s_config.targetFps;
int tmp = tracy::s_config.targetFps;
ImGui::SetNextItemWidth( 90 * dpiScale );
if( ImGui::InputInt( "##targetfps", &tmp ) ) { s_config.targetFps = std::clamp( tmp, 1, 9999 ); SaveConfig(); }
if( ImGui::InputInt( "##targetfps", &tmp ) ) { tracy::s_config.targetFps = std::clamp( tmp, 1, 9999 ); tracy::SaveConfig(); }
ImGui::Spacing();
ImGui::TextUnformatted( ICON_FA_PALETTE " Zone colors" );
ImGui::SameLine();
tracy::SmallCheckbox( "Ignore custom", &s_config.forceColors );
tracy::SmallCheckbox( "Ignore custom", &tracy::s_config.forceColors );
ImGui::Indent();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::RadioButton( "Static", &s_config.dynamicColors, 0 );
ImGui::RadioButton( "Thread dynamic", &s_config.dynamicColors, 1 );
ImGui::RadioButton( "Source location dynamic", &s_config.dynamicColors, 2 );
ImGui::RadioButton( "Static", &tracy::s_config.dynamicColors, 0 );
ImGui::RadioButton( "Thread dynamic", &tracy::s_config.dynamicColors, 1 );
ImGui::RadioButton( "Source location dynamic", &tracy::s_config.dynamicColors, 2 );
ImGui::PopStyleVar();
ImGui::Unindent();
ImGui::TextUnformatted( ICON_FA_RULER_HORIZONTAL " Zone name shortening" );
ImGui::Indent();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::RadioButton( "Disabled", &s_config.shortenName, (uint8_t)tracy::ShortenName::Never );
ImGui::RadioButton( "Minimal length", &s_config.shortenName, (uint8_t)tracy::ShortenName::Always );
ImGui::RadioButton( "Only normalize", &s_config.shortenName, (uint8_t)tracy::ShortenName::OnlyNormalize );
ImGui::RadioButton( "As needed", &s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpace );
ImGui::RadioButton( "As needed + normalize", &s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpaceAndNormalize );
ImGui::RadioButton( "Disabled##zns", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::Never );
ImGui::RadioButton( "Minimal length", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::Always );
ImGui::RadioButton( "Only normalize", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::OnlyNormalize );
ImGui::RadioButton( "As needed", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpace );
ImGui::RadioButton( "As needed + normalize", &tracy::s_config.shortenName, (uint8_t)tracy::ShortenName::NoSpaceAndNormalize );
ImGui::PopStyleVar();
ImGui::Unindent();
ImGui::Spacing();
ImGui::TextUnformatted( "Scroll multipliers" );
ImGui::SameLine();
tracy::DrawHelpMarker( "The multipliers to the amount to scroll by horizontally and vertically. This is used in the timeline and setting this value can help compensate for scroll wheel sensitivity." );
ImGui::SameLine();
double tmpScroll = tracy::s_config.horizontalScrollMultiplier;
ImGui::SetNextItemWidth( 45 * dpiScale );
if( ImGui::InputDouble( "##horizontalscrollmultiplier", &tmpScroll ) ) { tracy::s_config.horizontalScrollMultiplier = std::max( tmpScroll, 0.01 ); tracy::SaveConfig(); }
tmpScroll = tracy::s_config.verticalScrollMultiplier;
ImGui::SameLine();
ImGui::SetNextItemWidth( 45 * dpiScale );
if( ImGui::InputDouble( "##verticalscrollmultiplier", &tmpScroll ) ) { tracy::s_config.verticalScrollMultiplier = std::max( tmpScroll, 0.01 ); tracy::SaveConfig(); }
if( s_totalMem == 0 )
{
ImGui::BeginDisabled();
s_config.memoryLimit = false;
tracy::s_config.memoryLimit = false;
}
ImGui::Spacing();
if( ImGui::Checkbox( "Memory limit", &s_config.memoryLimit ) ) SaveConfig();
if( ImGui::Checkbox( "Memory limit", &tracy::s_config.memoryLimit ) ) tracy::SaveConfig();
ImGui::SameLine();
tracy::DrawHelpMarker( "When enabled, profiler will stop recording data when memory usage exceeds the specified percentage of available memory. Values greater than 100% will rely on swap. You need to make sure that memory is actually available." );
ImGui::SameLine();
ImGui::SetNextItemWidth( 70 * dpiScale );
if( ImGui::InputInt( "##memorylimit", &s_config.memoryLimitPercent ) ) { s_config.memoryLimitPercent = std::clamp( s_config.memoryLimitPercent, 1, 999 ); SaveConfig(); }
if( ImGui::InputInt( "##memorylimit", &tracy::s_config.memoryLimitPercent ) ) { tracy::s_config.memoryLimitPercent = std::clamp( tracy::s_config.memoryLimitPercent, 1, 999 ); tracy::SaveConfig(); }
ImGui::SameLine();
ImGui::TextUnformatted( "%" );
if( s_totalMem != 0 )
{
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", tracy::MemSizeToString( s_totalMem * s_config.memoryLimitPercent / 100 ) );
ImGui::TextDisabled( "(%s)", tracy::MemSizeToString( s_totalMem * tracy::s_config.memoryLimitPercent / 100 ) );
}
else
{
@@ -797,13 +778,19 @@ static void DrawContents()
}
ImGui::Spacing();
if( ImGui::Checkbox( "Enable achievements", &s_config.achievements ) ) SaveConfig();
if( ImGui::Checkbox( "Enable achievements", &tracy::s_config.achievements ) ) tracy::SaveConfig();
ImGui::Spacing();
if( ImGui::Checkbox( "Save UI scale", &tracy::s_config.saveUserScale) ) tracy::SaveConfig();
#ifndef __EMSCRIPTEN__
ImGui::Spacing();
if( ImGui::Checkbox( "Enable Tracy Assist", &tracy::s_config.llm ) ) tracy::SaveConfig();
#endif
ImGui::PopStyleVar();
ImGui::TreePop();
}
ImGui::Separator();
ImGui::PushFont( s_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
tracy::TextFocused( "Protocol version", tracy::RealToString( tracy::ProtocolVersion ) );
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
@@ -908,13 +895,15 @@ static void DrawContents()
if( s_isElevated )
{
ImGui::Separator();
ImGui::TextColored( ImVec4( 1, 0.25f, 0.25f, 1 ), ICON_FA_TRIANGLE_EXCLAMATION " Profiler has elevated privileges! " ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PushFont( s_smallFont );
ImGui::TextColored( ImVec4( 1, 0.25f, 0.25f, 1 ), "You are running the profiler interface with admin privileges. This is" );
ImGui::TextColored( ImVec4( 1, 0.25f, 0.25f, 1 ), "most likely a mistake, as there is no reason to do so. Instead, you" );
ImGui::TextColored( ImVec4( 1, 0.25f, 0.25f, 1 ), "probably wanted to run the client (the application you are profiling)" );
ImGui::TextColored( ImVec4( 1, 0.25f, 0.25f, 1 ), "with elevated privileges." );
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 0.25f, 0.25f, 1.f ) );
tracy::TextCentered( ICON_FA_TRIANGLE_EXCLAMATION " Profiler has elevated privileges! " ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PushFont( g_fonts.normal, FontSmall );
tracy::TextCentered( "You are running the profiler interface with admin privileges. This is" );
tracy::TextCentered( "most likely a mistake, as there is no reason to do so. Instead, you" );
tracy::TextCentered( "probably wanted to run the client (the application you are profiling)" );
tracy::TextCentered( "with elevated privileges." );
ImGui::PopFont();
ImGui::PopStyleColor();
}
ImGui::Separator();
ImGui::TextUnformatted( "Client address" );
@@ -934,7 +923,7 @@ static void DrawContents()
{
memcpy( addr, str.c_str(), str.size() + 1 );
}
if( ImGui::IsItemHovered() && ImGui::IsKeyPressed( ImGui::GetKeyIndex( ImGuiKey_Delete ), false ) )
if( ImGui::IsItemHovered() && ImGui::IsKeyPressed( ImGuiKey_Delete, false ) )
{
idxRemove = (int)i;
}
@@ -946,26 +935,42 @@ static void DrawContents()
ImGui::EndCombo();
}
}
#ifdef __EMSCRIPTEN__
ImGui::BeginDisabled();
#endif
connectClicked |= ImGui::Button( ICON_FA_WIFI " Connect" );
#ifdef __EMSCRIPTEN__
ImGui::EndDisabled();
connectClicked = false;
#endif
if( connectClicked && *addr && !loadThread.joinable() )
{
connHist->Count( addr );
auto aptr = addr;
while( *aptr == ' ' || *aptr == '\t' ) aptr++;
auto aend = aptr;
while( *aend && *aend != ' ' && *aend != '\t' ) aend++;
const auto addrLen = strlen( addr );
auto ptr = addr + addrLen - 1;
while( ptr > addr && *ptr != ':' ) ptr--;
if( *ptr == ':' )
if( aptr != aend )
{
std::string addrPart = std::string( addr, ptr );
uint16_t portPart = (uint16_t)atoi( ptr+1 );
view = std::make_unique<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
}
else
{
view = std::make_unique<tracy::View>( RunOnMainThread, addr, port, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
std::string address( aptr, aend );
connHist->Count( address );
auto adata = address.data();
auto ptr = adata + address.size() - 1;
while( ptr > adata && *ptr != ':' ) ptr--;
if( *ptr == ':' )
{
std::string addrPart = std::string( adata, ptr );
uint16_t portPart = (uint16_t)atoi( ptr+1 );
view = std::make_unique<tracy::View>( RunOnMainThread, addrPart.c_str(), portPart, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
else
{
view = std::make_unique<tracy::View>( RunOnMainThread, address.c_str(), port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
}
}
if( s_config.memoryLimit )
if( tracy::s_config.memoryLimit )
{
ImGui::SameLine();
tracy::TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
@@ -985,7 +990,7 @@ static void DrawContents()
loadThread = std::thread( [f] {
try
{
view = std::make_unique<tracy::View>( RunOnMainThread, *f, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
view = std::make_unique<tracy::View>( RunOnMainThread, *f, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
catch( const tracy::UnsupportedVersion& e )
{
@@ -1020,7 +1025,7 @@ static void DrawContents()
if( badVer.state != tracy::BadVersionState::Ok )
{
if( loadThread.joinable() ) { loadThread.join(); }
tracy::BadVersion( badVer, s_bigFont );
tracy::BadVersion( badVer );
}
if( !clients.empty() )
@@ -1118,7 +1123,7 @@ static void DrawContents()
}
if( selected && !loadThread.joinable() )
{
view = std::make_unique<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
view = std::make_unique<tracy::View>( RunOnMainThread, v.second.address.c_str(), v.second.port, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
ImGui::NextColumn();
const auto acttime = ( v.second.activeTime + ( time - v.second.time ) / 1000 ) * 1000000000ll;
@@ -1169,7 +1174,7 @@ static void DrawContents()
}
else
{
ImGui::PushFont( s_fixedWidth );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::TextUnformatted( releaseNotes.c_str() );
ImGui::PopFont();
}
@@ -1208,10 +1213,12 @@ static void DrawContents()
{
ImGui::OpenPopup( "Loading trace..." );
}
if( ImGui::BeginPopupModal( "Loading trace...", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
if( ImGui::BeginPopupModal( "Loading trace...", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings) )
{
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
ImGui::Spacing();
tracy::TextCentered( ICON_FA_HOURGLASS_HALF );
ImGui::Spacing();
ImGui::PopFont();
animTime += ImGui::GetIO().DeltaTime;
@@ -1287,7 +1294,7 @@ static void DrawContents()
viewShutdown.store( ViewShutdown::False, std::memory_order_relaxed );
if( reconnect )
{
view = std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, s_fixedWidth, s_smallFont, s_bigFont, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_config, s_achievements );
view = std::make_unique<tracy::View>( RunOnMainThread, reconnectAddr.c_str(), reconnectPort, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements );
}
break;
default:
@@ -1296,8 +1303,10 @@ static void DrawContents()
if( ImGui::BeginPopupModal( "Capture cleanup...", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
if( viewShutdown.load( std::memory_order_relaxed ) != ViewShutdown::True ) ImGui::CloseCurrentPopup();
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
ImGui::Spacing();
tracy::TextCentered( ICON_FA_BROOM );
ImGui::Spacing();
ImGui::PopFont();
animTime += ImGui::GetIO().DeltaTime;
tracy::DrawWaitingDots( animTime );
@@ -1316,47 +1325,54 @@ static void DrawContents()
}
#ifndef __EMSCRIPTEN__
if( !s_config.achievementsAsked )
if( !tracy::s_config.achievementsAsked )
{
s_config.achievementsAsked = true;
tracy::s_config.achievementsAsked = true;
ImGui::OpenPopup( ICON_FA_STAR " Achievements" );
}
#endif
ImGui::SetNextWindowSize( ImVec2( 325 * dpiScale, 0 ) );
if( ImGui::BeginPopupModal( ICON_FA_STAR " Achievements", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::TextUnformatted( "Tracy Profiler is a complex tool with many features. It" );
ImGui::TextUnformatted( "can be difficult to discover all of them on your own." );
ImGui::TextUnformatted( "The Achievements system will guide you through the" );
ImGui::TextUnformatted( "main features and teach you how to use them in an" );
ImGui::TextUnformatted( "easy-to-handle manner." );
ImGui::Separator();
ImGui::TextUnformatted( "Would you like to enable achievements?" );
ImGui::PushFont( s_smallFont );
constexpr const char* text = R"(
**Tracy Profiler** is a complex tool with many features. It can be difficult to discover all of them on your own.
The *Achievements* system will guide you through the main features and teach you how to use them in an easy-to-handle manner. You can use this system as a **tutorial** to learn how to use Tracy Profiler.
---
Would you like to enable achievements?
)";
tracy::Markdown md;
md.Print( text, strlen( text ) );
ImGui::Spacing();
ImGui::PushFont( g_fonts.normal, FontSmall );
tracy::TextDisabledUnformatted( "You can change this setting later in the global settings." );
ImGui::PopFont();
ImGui::Separator();
if( ImGui::Button( "Yes" ) )
{
s_config.achievements = true;
SaveConfig();
tracy::s_config.achievements = true;
tracy::SaveConfig();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if( ImGui::Button( "No" ) )
{
s_config.achievements = false;
SaveConfig();
tracy::s_config.achievements = false;
tracy::SaveConfig();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if( s_config.achievements )
if( tracy::s_config.achievements )
{
ImGui::PushStyleVar( ImGuiStyleVar_WindowRounding, 16 * dpiScale );
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
const auto starSize = ImGui::CalcTextSize( ICON_FA_STAR );
ImGui::PopFont();
@@ -1433,7 +1449,7 @@ static void DrawContents()
}
}
}
ImGui::PushFont( s_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
tracy::TextColoredUnformatted( color, ICON_FA_STAR );
ImGui::PopFont();
@@ -1444,7 +1460,7 @@ static void DrawContents()
const auto th = ImGui::GetTextLineHeight();
ImGui::SetCursorPosY( cursor.y - th * 0.175f );
ImGui::TextUnformatted( aItem->name );
ImGui::PushFont( s_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::SetCursorPos( cursor + ImVec2( starSize.x + ImGui::GetStyle().ItemSpacing.x, th ) );
tracy::TextDisabledUnformatted( "Click to open" );
ImGui::PopFont();
@@ -1504,11 +1520,7 @@ static void DrawContents()
ImGui::SetColumnWidth( 0, 300 * dpiScale );
DrawAchievements( c->items );
ImGui::NextColumn();
if( s_achievementItem )
{
const tracy::data::ctx ctx = { s_bigFont, s_smallFont, s_fixedWidth };
s_achievementItem->description( ctx );
}
if( s_achievementItem ) s_achievementItem->description();
ImGui::EndColumns();
ImGui::EndTabItem();
}
@@ -3,15 +3,16 @@
#include "TracyImGui.hpp"
#include "TracySourceContents.hpp"
#include "TracyWeb.hpp"
#include "../Fonts.hpp"
namespace tracy::data
{
AchievementItem ai_samplingIntro = { "samplingIntro", "Sampling program execution", [](const ctx& c){
AchievementItem ai_samplingIntro = { "samplingIntro", "Sampling program execution", [](){
ImGui::TextWrapped( "Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often." );
ImGui::TextWrapped( "While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return." );
ImGui::TextWrapped( "Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work." );
ImGui::PushFont( c.small );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information." );
ImGui::PopStyleColor();
@@ -22,11 +23,11 @@ AchievementItem* ac_samplingItems[] = { &ai_samplingIntro, nullptr };
AchievementCategory ac_sampling = { "sampling", "Sampling", ac_samplingItems };
AchievementItem ai_100million = { "100million", "It's over 100 million!", [](const ctx& c){
AchievementItem ai_100million = { "100million", "It's over 100 million!", [](){
ImGui::TextWrapped( "Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!" );
ImGui::TextWrapped( "Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it." );
ImGui::TextWrapped( "Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case." );
ImGui::PushFont( c.small );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory." );
ImGui::TextWrapped( "To capture 100 million zones, you will need approximately 4 GB of RAM." );
@@ -34,12 +35,12 @@ AchievementItem ai_100million = { "100million", "It's over 100 million!", [](con
ImGui::PopFont();
} };
AchievementItem ai_instrumentationStatistics = { "instrumentationStatistics", "Show me the stats!", [](const ctx&){
AchievementItem ai_instrumentationStatistics = { "instrumentationStatistics", "Show me the stats!", [](){
ImGui::TextWrapped( "Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called." );
ImGui::TextWrapped( "To view the statistics, click on the \"" ICON_FA_ARROW_UP_WIDE_SHORT " Statistics\" button on the top bar. This will open a new window with a list of all zones in the trace." );
} };
AchievementItem ai_findZone = { "findZone", "Find some zones", [](const ctx&){
AchievementItem ai_findZone = { "findZone", "Find some zones", [](){
ImGui::TextWrapped( "You can search for zones in the trace by opening the search window with the \"" ICON_FA_MAGNIFYING_GLASS " Find zone\" button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code." );
ImGui::TextWrapped( "The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them." );
ImGui::TextWrapped( "Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone." );
@@ -54,7 +55,7 @@ AchievementItem* ac_instrumentationIntroItems[] = {
nullptr
};
AchievementItem ai_instrumentationIntro = { "instrumentationIntro", "Instrumentating your application", [](const ctx& c){
AchievementItem ai_instrumentationIntro = { "instrumentationIntro", "Instrumentating your application", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
void SomeFunction()
@@ -69,11 +70,11 @@ void SomeFunction()
ImGui::TextWrapped( "Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up." );
ImGui::TextWrapped( "To get started, open a source file and include the Tracy.hpp header. This will give you access to a variety of macros provided by Tracy. Next, add the ZoneScoped macro to the beginning of one of your functions, like this:" );
ImGui::PushFont( c.fixed );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called." );
ImGui::PushFont( c.small );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Note: The ZoneScoped macro is just one of the many macros provided by Tracy. See the documentation for more information." );
ImGui::TextWrapped( "The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information." );
@@ -81,7 +82,7 @@ void SomeFunction()
ImGui::PopFont();
}, ac_instrumentationIntroItems };
AchievementItem ai_frameImages = { "frameImages", "A picture is worth a thousand words", [](const ctx&){
AchievementItem ai_frameImages = { "frameImages", "A picture is worth a thousand words", [](){
ImGui::TextWrapped( "Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the FrameImage macro." );
ImGui::TextWrapped( "You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way." );
ImGui::TextWrapped( "The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen." );
@@ -94,7 +95,7 @@ AchievementItem* ac_instrumentFramesItems[] = {
nullptr
};
AchievementItem ai_instrumentFrames = { "instrumentFrames", "Instrumenting frames", [](const ctx& c){
AchievementItem ai_instrumentFrames = { "instrumentFrames", "Instrumenting frames", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
void Render()
@@ -110,7 +111,7 @@ void Render()
ImGui::TextWrapped( "In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application." );
ImGui::TextWrapped( "To instrument frames, you need to add the FrameMark macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame." );
ImGui::PushFont( c.fixed );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "When you profile your application, you will see a new frame appear on the timeline each time the FrameMark macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second." );
@@ -122,11 +123,11 @@ AchievementItem* ac_instrumentationItems[] = { &ai_instrumentationIntro, &ai_ins
AchievementCategory ac_instrumentation = { "instrumentation", "Instrumentation", ac_instrumentationItems };
AchievementItem ai_loadTrace = { "loadTrace", "Load a trace", [](const ctx&){
AchievementItem ai_loadTrace = { "loadTrace", "Load a trace", [](){
ImGui::TextWrapped( "You can open a previously saved trace file (or one received from a friend) with the \"" ICON_FA_FOLDER_OPEN " Open saved trace\" button on the welcome screen." );
} };
AchievementItem ai_saveTrace = { "saveTrace", "Save a trace", [](const ctx&){
AchievementItem ai_saveTrace = { "saveTrace", "Save a trace", [](){
ImGui::TextWrapped( "Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the " ICON_FA_WIFI " icon in the top left corner of the screen and then clicking on the \"" ICON_FA_FLOPPY_DISK " Save trace\" button." );
ImGui::TextWrapped( "Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before." );
ImGui::TextWrapped( "You can also share the trace with your friends or co-workers by sending them the trace file." );
@@ -151,7 +152,7 @@ AchievementItem* ac_connectToServerUnlock[] = {
nullptr
};
AchievementItem ai_connectToServer = { "connectToClient", "First profiling session", [](const ctx&){
AchievementItem ai_connectToServer = { "connectToClient", "First profiling session", [](){
ImGui::TextWrapped( "Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:" );
ImGui::TextWrapped( " 1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project." );
ImGui::TextWrapped( " 2. Make sure that TracyClient.cpp (or the Tracy library) is included in your build." );
@@ -164,7 +165,7 @@ AchievementItem ai_connectToServer = { "connectToClient", "First profiling sessi
}
}, ac_connectToServerItems, ac_connectToServerUnlock };
AchievementItem ai_globalSettings = { "globalSettings", "Global settings", [](const ctx&){
AchievementItem ai_globalSettings = { "globalSettings", "Global settings", [](){
ImGui::TextWrapped( "Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the " ICON_FA_WRENCH " icon on the welcome screen. This will open the about window, where you can expand the \"" ICON_FA_TOOLBOX " Global settings\" menu." );
ImGui::TextWrapped( "The settings are saved between sessions, so you only need to set them once." );
} };
@@ -175,7 +176,7 @@ AchievementItem* ac_achievementsIntroItems[] = {
nullptr
};
AchievementItem ai_achievementsIntro = { "achievementsIntro", "Click here to discover achievements!", [](const ctx&){
AchievementItem ai_achievementsIntro = { "achievementsIntro", "Click here to discover achievements!", [](){
ImGui::TextWrapped( "Clicking on the " ICON_FA_STAR " button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done." );
ImGui::TextWrapped( "As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!" );
ImGui::TextWrapped( "To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements " ICON_FA_STAR " button will glow yellow when there are new things to see." );
@@ -16,18 +16,11 @@ namespace tracy
namespace data
{
struct ctx
{
ImFont* big;
ImFont* small;
ImFont* fixed;
};
struct AchievementItem
{
const char* id;
const char* name;
void(*description)(const ctx&);
void(*description)();
AchievementItem** items;
AchievementItem** unlocks;
bool keepOpen;
@@ -1,6 +1,7 @@
#include <assert.h>
#include "imgui.h"
#include "../Fonts.hpp"
#include "IconsFontAwesome6.h"
#include "TracyBadVersion.hpp"
@@ -13,7 +14,7 @@ namespace tracy
namespace detail
{
void BadVersionImpl( BadVersionState& badVer, ImFont* big )
void BadVersionImpl( BadVersionState& badVer )
{
assert( badVer.state != BadVersionState::Ok );
@@ -40,7 +41,7 @@ void BadVersionImpl( BadVersionState& badVer, ImFont* big )
}
if( ImGui::BeginPopupModal( "Bad file", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( big );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::Text( "The file you are trying to open is not a Tracy dump." );
@@ -54,7 +55,7 @@ void BadVersionImpl( BadVersionState& badVer, ImFont* big )
}
if( ImGui::BeginPopupModal( "File read error", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( big );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::Text( "The file you are trying to open cannot be mapped to memory." );
@@ -68,7 +69,7 @@ void BadVersionImpl( BadVersionState& badVer, ImFont* big )
}
if( ImGui::BeginPopupModal( "Unsupported file version", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( big );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_CLOUD_ARROW_DOWN );
ImGui::PopFont();
ImGui::Text( "The file you are trying to open is unsupported.\nYou should update to Tracy %i.%i.%i or newer and try again.", badVer.version >> 16, ( badVer.version >> 8 ) & 0xFF, badVer.version & 0xFF );
@@ -89,7 +90,7 @@ void BadVersionImpl( BadVersionState& badVer, ImFont* big )
}
if( ImGui::BeginPopupModal( "Legacy file version", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( big );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_GHOST );
ImGui::PopFont();
ImGui::Text( "You are trying to open a file which was created by legacy version %i.%i.%i.\nUse the update utility from an older version of the profiler to convert the file to a supported version.", badVer.version >> 16, ( badVer.version >> 8 ) & 0xFF, badVer.version & 0xFF );
@@ -103,7 +104,7 @@ void BadVersionImpl( BadVersionState& badVer, ImFont* big )
}
if( ImGui::BeginPopupModal( "Trace load failure", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( big );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_BOMB );
ImGui::PopFont();
ImGui::TextUnformatted( "The file you are trying to open is corrupted." );
@@ -5,8 +5,6 @@
#include "../public/common/TracyForceInline.hpp"
struct ImFont;
namespace tracy
{
@@ -29,10 +27,10 @@ struct BadVersionState
namespace detail
{
void BadVersionImpl( BadVersionState& badVer, ImFont* big );
void BadVersionImpl( BadVersionState& badVer );
}
tracy_force_inline void BadVersion( BadVersionState& badVer, ImFont* big ) { if( badVer.state != BadVersionState::Ok ) detail::BadVersionImpl( badVer, big ); }
tracy_force_inline void BadVersion( BadVersionState& badVer ) { if( badVer.state != BadVersionState::Ok ) detail::BadVersionImpl( badVer ); }
}
@@ -20,6 +20,14 @@ static tracy_force_inline uint32_t HighlightColor( uint32_t color )
( std::min<int>( 0xFF, ( ( ( color & 0x000000FF ) ) + V ) ) );
}
static tracy_force_inline uint32_t DarkenColorSlightly( uint32_t color )
{
return 0xFF000000 |
( ( ( ( color & 0x00FF0000 ) >> 16 ) * 4 / 5 ) << 16 ) |
( ( ( ( color & 0x0000FF00 ) >> 8 ) * 4 / 5 ) << 8 ) |
( ( ( ( color & 0x000000FF ) ) * 4 / 5 ) );
}
static tracy_force_inline uint32_t DarkenColor( uint32_t color )
{
return 0xFF000000 |
@@ -28,6 +36,14 @@ static tracy_force_inline uint32_t DarkenColor( uint32_t color )
( ( ( ( color & 0x000000FF ) ) * 2 / 3 ) );
}
static tracy_force_inline uint32_t DarkenColorHalf( uint32_t color )
{
return 0xFF000000 |
( ( ( ( color & 0x00FF0000 ) >> 16 ) / 2 ) << 16 ) |
( ( ( ( color & 0x0000FF00 ) >> 8 ) / 2 ) << 8 ) |
( ( ( ( color & 0x000000FF ) ) / 2 ) );
}
static tracy_force_inline uint32_t DarkenColorMore( uint32_t color )
{
return 0xFF000000 |
@@ -0,0 +1,101 @@
#include <stdio.h>
#include "TracyConfig.hpp"
#include "TracyStorage.hpp"
#include "../ini.h"
namespace tracy
{
Config s_config;
void LoadConfig()
{
const auto fn = tracy::GetSavePath( "tracy.ini" );
auto ini = ini_load( fn );
if( !ini ) return;
int v;
double v1;
const char* v2;
if( ini_sget( ini, "core", "threadedRendering", "%d", &v ) ) s_config.threadedRendering = v;
if( ini_sget( ini, "core", "focusLostLimit", "%d", &v ) ) s_config.focusLostLimit = v;
if( ini_sget( ini, "timeline", "targetFps", "%d", &v ) && v >= 1 && v < 10000 ) s_config.targetFps = v;
if( ini_sget( ini, "timeline", "drawFrameTargets", "%d", &v ) ) s_config.drawFrameTargets = v;
if( ini_sget( ini, "timeline", "dynamicColors", "%d", &v ) ) s_config.dynamicColors = v;
if( ini_sget( ini, "timeline", "forceColors", "%d", &v ) ) s_config.forceColors = v;
if( ini_sget( ini, "timeline", "ghostZones", "%d", &v ) ) s_config.ghostZones = v;
if( ini_sget( ini, "timeline", "shortenName", "%d", &v ) ) s_config.shortenName = v;
if( ini_sget( ini, "timeline", "drawSamples", "%d", &v ) ) s_config.drawSamples = v;
if( ini_sget( ini, "timeline", "drawContextSwitches", "%d", &v ) ) s_config.drawContextSwitches = v;
if( ini_sget( ini, "timeline", "plotHeight", "%d", &v ) ) s_config.plotHeight = v;
if( ini_sget( ini, "timeline", "horizontalScrollMultiplier", "%lf", &v1 ) && v1 > 0.0 ) s_config.horizontalScrollMultiplier = v1;
if( ini_sget( ini, "timeline", "verticalScrollMultiplier", "%lf", &v1 ) && v1 > 0.0 ) s_config.verticalScrollMultiplier = v1;
if( ini_sget( ini, "memory", "limit", "%d", &v ) ) s_config.memoryLimit = v;
if( ini_sget( ini, "memory", "percent", "%d", &v ) && v >= 1 && v < 1000 ) s_config.memoryLimitPercent = v;
if( ini_sget( ini, "achievements", "enabled", "%d", &v ) ) s_config.achievements = v;
if( ini_sget( ini, "achievements", "asked", "%d", &v ) ) s_config.achievementsAsked = v;
if( ini_sget( ini, "ui", "saveUserScale", "%d", &v ) ) s_config.saveUserScale = v;
if( ini_sget( ini, "ui", "userScale", "%lf", &v1 ) && v1 > 0.0 && s_config.saveUserScale ) s_config.userScale = v1;
if( ini_sget( ini, "llm", "enabled", "%d", &v ) ) s_config.llm = v;
if( v2 = ini_get( ini, "llm", "address" ); v2 ) s_config.llmAddress = v2;
if( v2 = ini_get( ini, "llm", "model" ); v2 ) s_config.llmModel = v2;
if( v2 = ini_get( ini, "llm", "embeddings" ); v2 ) s_config.llmEmbeddingsModel = v2;
if( v2 = ini_get( ini, "llm", "useragent" ); v2 ) s_config.llmUserAgent = v2;
if( v2 = ini_get( ini, "llm", "searchIdentifier" ); v2 ) s_config.llmSearchIdentifier = v2;
if( v2 = ini_get( ini, "llm", "searchApiKey" ); v2 ) s_config.llmSearchApiKey = v2;
ini_free( ini );
}
bool SaveConfig()
{
const auto fn = tracy::GetSavePath( "tracy.ini" );
FILE* f = fopen( fn, "wb" );
if( !f ) return false;
fprintf( f, "[core]\n" );
fprintf( f, "threadedRendering = %i\n", (int)s_config.threadedRendering );
fprintf( f, "focusLostLimit = %i\n", (int)s_config.focusLostLimit );
fprintf( f, "\n[timeline]\n" );
fprintf( f, "targetFps = %i\n", s_config.targetFps );
fprintf( f, "drawFrameTargets = %i\n", s_config.drawFrameTargets );
fprintf( f, "dynamicColors = %i\n", s_config.dynamicColors );
fprintf( f, "forceColors = %i\n", (int)s_config.forceColors );
fprintf( f, "ghostZones = %i\n", (int)s_config.ghostZones );
fprintf( f, "shortenName = %i\n", s_config.shortenName );
fprintf( f, "drawSamples = %i\n", s_config.drawSamples );
fprintf( f, "drawContextSwitches = %i\n", s_config.drawContextSwitches );
fprintf( f, "plotHeight = %i\n", s_config.plotHeight );
fprintf( f, "horizontalScrollMultiplier = %lf\n", s_config.horizontalScrollMultiplier );
fprintf( f, "verticalScrollMultiplier = %lf\n", s_config.verticalScrollMultiplier );
fprintf( f, "\n[memory]\n" );
fprintf( f, "limit = %i\n", (int)s_config.memoryLimit );
fprintf( f, "percent = %i\n", s_config.memoryLimitPercent );
fprintf( f, "\n[achievements]\n" );
fprintf( f, "enabled = %i\n", (int)s_config.achievements );
fprintf( f, "asked = %i\n", (int)s_config.achievementsAsked );
fprintf( f, "\n[ui]\n" );
fprintf( f, "saveUserScale = %i\n", (int)s_config.saveUserScale );
fprintf( f, "userScale = %lf\n", s_config.userScale );
fprintf( f, "\n[llm]\n" );
fprintf( f, "enabled = %i\n", (int)s_config.llm );
fprintf( f, "address = %s\n", s_config.llmAddress.c_str() );
fprintf( f, "model = %s\n", s_config.llmModel.c_str() );
fprintf( f, "embeddings = %s\n", s_config.llmEmbeddingsModel.c_str() );
fprintf( f, "useragent = %s\n", s_config.llmUserAgent.c_str() );
fprintf( f, "searchIdentifier = %s\n", s_config.llmSearchIdentifier.c_str() );
fprintf( f, "searchApiKey = %s\n", s_config.llmSearchApiKey.c_str() );
fclose( f );
return true;
}
}
@@ -1,6 +1,8 @@
#ifndef __TRACYCONFIG_HPP__
#define __TRACYCONFIG_HPP__
#include <string>
#include "TracyUtility.hpp"
namespace tracy
@@ -11,15 +13,42 @@ struct Config
bool threadedRendering = true;
bool focusLostLimit = true;
int targetFps = 60;
bool drawFrameTargets = false;
double horizontalScrollMultiplier = 1.0;
double verticalScrollMultiplier = 1.0;
bool memoryLimit = false;
int memoryLimitPercent = 80;
bool achievements = false;
bool achievementsAsked = false;
int dynamicColors = 1;
bool forceColors = false;
bool ghostZones = true;
int shortenName = (int)ShortenName::NoSpaceAndNormalize;
bool drawSamples = true;
bool drawContextSwitches = true;
int plotHeight = 100;
bool saveUserScale = false;
float userScale = 1.0f;
// LLM assistant settings
#ifdef __EMSCRIPTEN__
bool llm = false;
#else
bool llm = true;
#endif
std::string llmAddress = "http://localhost:11434";
std::string llmModel;
std::string llmEmbeddingsModel;
std::string llmUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
std::string llmSearchIdentifier;
std::string llmSearchApiKey;
};
extern Config s_config;
void LoadConfig();
bool SaveConfig();
}
#endif
@@ -0,0 +1,14 @@
#include "TracyEmbed.hpp"
#include "../public/common/tracy_lz4.hpp"
EmbedData::EmbedData( size_t size, size_t lz4Size, const uint8_t* data )
: m_data( new char[size] )
, m_size( size )
{
tracy::LZ4_decompress_safe( (const char*)data, m_data, lz4Size, size );
}
EmbedData::~EmbedData()
{
delete[] m_data;
}
@@ -0,0 +1,21 @@
#pragma once
#include <memory>
#include <stddef.h>
#include <stdint.h>
#define Unembed( name ) std::make_shared<EmbedData>( Embed::name##Size, Embed::name##Lz4Size, Embed::name##Data )
class EmbedData
{
public:
EmbedData( size_t size, size_t lz4Size, const uint8_t* data );
~EmbedData();
[[nodiscard]] const char* data() const { return m_data; }
[[nodiscard]] size_t size() const { return m_size; }
private:
char* m_data;
size_t m_size;
};
@@ -157,7 +157,7 @@ void EventDebug( const QueueItem& ev )
fprintf( f, "ev %i (ContextSwitch)\n", ev.hdr.idx );
fprintf( f, "\ttime = %" PRIi64 "\n", ev.contextSwitch.time );
fprintf( f, "\tthread = %" PRIu32 " -> %" PRIu32 "\n", ev.contextSwitch.oldThread, ev.contextSwitch.newThread );
fprintf( f, "\tcpu = %" PRIu8 ", reason = %" PRIu8 ", state = %" PRIu8 "\n", ev.contextSwitch.cpu, ev.contextSwitch.reason, ev.contextSwitch.state );
fprintf( f, "\tcpu = %" PRIu8 ", oldThreadWaitReason = %" PRIu8 ", oldThreadState = %" PRIu8 "\n", ev.contextSwitch.cpu, ev.contextSwitch.oldThreadWaitReason, ev.contextSwitch.oldThreadState);
break;
case QueueType::ThreadWakeup:
fprintf( f, "ev %i (ThreadWakeup)\n", ev.hdr.idx );
@@ -4,7 +4,7 @@
# ifdef __EMSCRIPTEN__
# include <emscripten.h>
# else
# include "../nfd/nfd.h"
# include <nfd.h>
# endif
#endif
@@ -4,7 +4,7 @@
#include "TracyPrint.hpp"
#include "TracyImGui.hpp"
extern void* zigzagTex;
extern ImTextureID zigzagTex;
namespace tracy
{
@@ -127,4 +127,39 @@ void PrintSource( const std::vector<Tokenizer::Line>& lines )
}
}
bool PrintTextWrapped( const char* text, const char* end )
{
bool hovered = false;
if( !end ) end = text + strlen( text );
auto firstWord = text;
while( firstWord < end && *firstWord != ' ' && *firstWord != '\n' ) firstWord++;
auto fontSize = ImGui::GetFontSize();
auto left = ImGui::GetContentRegionAvail().x;
auto fwLen = ImGui::CalcTextSize( text, firstWord ).x;
if( fwLen > left )
{
ImGui::NewLine();
left = ImGui::GetContentRegionAvail().x;
}
auto endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
ImGui::TextUnformatted( text, endLine );
if( !hovered ) hovered = ImGui::IsItemHovered();
left = ImGui::GetContentRegionAvail().x;
while( endLine < end )
{
text = endLine;
if( *text == ' ' ) text++;
endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text, end, left );
if( text == endLine ) endLine++;
ImGui::TextUnformatted( text, endLine );
if( !hovered ) hovered = ImGui::IsItemHovered();
}
return hovered;
}
}
@@ -32,6 +32,7 @@ void DrawZigZag( ImDrawList* draw, const ImVec2& wpos, double start, double end,
void DrawStripedRect( ImDrawList* draw, const ImVec2& wpos, double x0, double y0, double x1, double y1, double sw, uint32_t color, bool fix_stripes_in_screen_space, bool inverted );
void DrawHistogramMinMaxLabel( ImDrawList* draw, int64_t tmin, int64_t tmax, ImVec2 wpos, float w, float ty );
void PrintSource( const std::vector<Tokenizer::Line>& lines );
bool PrintTextWrapped( const char* text, const char* end = nullptr );
static constexpr const uint32_t SyntaxColors[] = {
@@ -83,6 +84,13 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
ImGui::TextUnformatted( text );
}
[[maybe_unused]] static inline bool ButtonCentered( const char* text )
{
const auto tw = ImGui::CalcTextSize( text ).x + ImGui::GetStyle().FramePadding.x * 2;
ImGui::SetCursorPosX( ( ImGui::GetWindowWidth() - tw ) * 0.5f );
return ImGui::Button( text );
}
[[maybe_unused]] static inline void TextColoredUnformatted( uint32_t col, const char* text, const char* end = nullptr )
{
ImGui::PushStyleColor( ImGuiCol_Text, col );
@@ -232,6 +240,26 @@ static constexpr const uint32_t AsmSyntaxColors[] = {
return res;
}
[[maybe_unused]] static inline void TextFocusedClipboard( const char* label, const char* value, const char* clipboard, const int clipboardButtonId, ImFont* font = nullptr, float fontSizeBase = 0.f )
{
TextDisabledUnformatted( label );
ImGui::SameLine();
// Due to the font size change, we need to realign the button vertically by hand
// We center-align it based on the previous font.
// This is apparently the recommended (only) way to do it: https://github.com/ocornut/imgui/issues/1284
ImVec2 cursorPos = ImGui::GetCursorPos();
const float previousFontSize = ImGui::GetFontSize();
ImGui::PushFont( font, fontSizeBase );
const float buttonFontSize = ImGui::GetFontSize();
cursorPos.y += ( previousFontSize - buttonFontSize ) / 2.f;
ImGui::SetCursorPos( cursorPos );
if( ClipboardButton( clipboardButtonId ) ) ImGui::SetClipboardText( value );
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextUnformatted( clipboard );
}
[[maybe_unused]] static tracy_force_inline void DrawLine( ImDrawList* draw, const ImVec2& v1, const ImVec2& v2, uint32_t col, float thickness = 1.0f )
{
const ImVec2 data[2] = { v1, v2 };
@@ -0,0 +1,940 @@
#include <array>
#include <curl/curl.h>
#include <stdint.h>
#include <stdlib.h>
#include <ranges>
#include "TracyConfig.hpp"
#include "TracyImGui.hpp"
#include "TracyLlm.hpp"
#include "TracyLlmApi.hpp"
#include "TracyLlmChat.hpp"
#include "TracyLlmTools.hpp"
#include "TracyPrint.hpp"
#include "TracyWeb.hpp"
#include "../Fonts.hpp"
#include "data/SystemPrompt.hpp"
#include "data/SystemReminder.hpp"
namespace tracy
{
extern double s_time;
constexpr size_t InputBufferSize = 1024;
TracyLlm::TracyLlm( Worker& worker, const TracyManualData& manual )
: m_exit( false )
, m_input( nullptr )
{
if( !s_config.llm ) return;
static bool initialized = false;
if( !initialized )
{
initialized = true;
curl_global_init( CURL_GLOBAL_ALL );
atexit( curl_global_cleanup );
}
m_systemPrompt = Unembed( SystemPrompt );
m_systemReminder = Unembed( SystemReminder );
m_input = new char[InputBufferSize];
m_apiInput = new char[InputBufferSize];
ResetChat();
m_api = std::make_unique<TracyLlmApi>();
m_chatUi = std::make_unique<TracyLlmChat>();
m_tools = std::make_unique<TracyLlmTools>( worker, manual );
m_busy = true;
QueueConnect();
m_thread = std::thread( [this] { WorkerThread(); } );
}
TracyLlm::~TracyLlm()
{
delete[] m_input;
delete[] m_apiInput;
if( m_thread.joinable() )
{
{
std::lock_guard lock( m_lock );
if( m_currentJob ) m_currentJob->stop = true;
m_exit.store( true, std::memory_order_release );
m_cv.notify_all();
}
m_thread.join();
}
}
void TracyLlm::Draw()
{
const auto scale = GetScale();
ImGui::SetNextWindowSize( ImVec2( 400 * scale, 800 * scale ), ImGuiCond_FirstUseEver );
ImGui::Begin( "Tracy Assist", &m_show, ImGuiWindowFlags_NoScrollbar );
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
if( IsBusy() )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_HOURGLASS );
TextCentered( "Please wait..." );
DrawWaitingDots( s_time );
ImGui::PopFont();
ImGui::End();
return;
}
auto& style = ImGui::GetStyle();
const auto manualEmbeddingsState = m_tools->GetManualEmbeddingsState();
if( manualEmbeddingsState.inProgress )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 7 ) * 0.5f ) );
TextCentered( ICON_FA_BOOK_BOOKMARK );
ImGui::Spacing();
TextCentered( "Building manual embeddings..." );
ImGui::Spacing();
DrawWaitingDots( s_time );
ImGui::TextUnformatted( "" );
ImGui::PopFont();
const float w = 100 * scale;
const float ww = ImGui::GetWindowWidth();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::SetCursorPosX( ( ww - w ) * 0.5f );
ImGui::ProgressBar( manualEmbeddingsState.progress, ImVec2( w, 0 ), "" );
ImGui::PopStyleVar();
ImGui::Spacing();
char tmp[128];
snprintf( tmp, sizeof( tmp ), "Progress: %.1f%%", manualEmbeddingsState.progress * 100 );
TextCentered( tmp );
ImGui::Spacing();
const auto sz = ImGui::CalcTextSize( "Cancel" ).x + style.FramePadding.x * 2;
ImGui::SetCursorPosX( ( ImGui::GetWindowWidth() - sz ) * 0.5f );
if( ImGui::Button( "Cancel" ) ) m_tools->CancelManualEmbeddings();
ImGui::End();
return;
}
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 1.f, 0.f, 1.0f ) );
ImGui::AlignTextToFramePadding();
ImGui::TextWrapped( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopStyleColor();
if( ImGui::IsItemHovered() )
{
ImGui::BeginTooltip();
ImGui::TextUnformatted( "Always verify the chat responses, as they may contain incorrect or misleading informations." );
ImGui::EndTooltip();
}
ImGui::SameLine();
std::lock_guard lock( m_lock );
const auto hasChat = m_chat.size() <= 1 && *m_input == 0;
if( hasChat ) ImGui::BeginDisabled();
if( ImGui::Button( ICON_FA_BROOM " Clear chat" ) )
{
if( m_currentJob ) m_currentJob->stop = true;
ResetChat();
}
if( hasChat ) ImGui::EndDisabled();
ImGui::SameLine();
if( ImGui::Button( ICON_FA_ARROWS_ROTATE " Reconnect" ) )
{
if( m_currentJob ) m_currentJob->stop = true;
QueueConnect();
}
ImGui::SameLine();
if( ImGui::TreeNode( "Settings" ) )
{
const auto responding = m_currentJob != nullptr;
if( responding ) ImGui::BeginDisabled();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( "API:" );
ImGui::SameLine();
const auto sz = std::min( InputBufferSize-1, s_config.llmAddress.size() );
memcpy( m_apiInput, s_config.llmAddress.c_str(), sz );
m_apiInput[sz] = 0;
bool changed = ImGui::InputTextWithHint( "##api", "http://localhost:1234", m_apiInput, InputBufferSize );
ImGui::SameLine();
if( ImGui::BeginCombo( "##presets", nullptr, ImGuiComboFlags_NoPreview ) )
{
struct Preset
{
const char* name;
const char* address;
};
constexpr static std::array presets = {
Preset { "Llama.cpp", "http://localhost:8080" },
Preset { "LM Studio", "http://localhost:1234" },
Preset { "Ollama", "http://localhost:11434" },
};
for( auto& preset : presets )
{
if( ImGui::Selectable( preset.name ) )
{
memcpy( m_apiInput, preset.address, strlen( preset.address ) + 1 );
changed = true;
}
}
ImGui::EndCombo();
}
if( changed )
{
s_config.llmAddress = m_apiInput;
SaveConfig();
QueueConnect();
}
const auto& models = m_api->GetModels();
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( "Model:" );
ImGui::SameLine();
if( models.empty() || m_modelIdx < 0 )
{
ImGui::TextUnformatted( "No models available" );
}
else
{
if( ImGui::BeginCombo( "##model", models[m_modelIdx].name.c_str() ) )
{
for( size_t i = 0; i < models.size(); ++i )
{
const auto& model = models[i];
if( model.embeddings ) continue;
if( ImGui::Selectable( model.name.c_str(), i == m_modelIdx ) )
{
m_modelIdx = i;
s_config.llmModel = model.name;
SaveConfig();
}
if( m_modelIdx == i ) ImGui::SetItemDefaultFocus();
if( !model.quant.empty() )
{
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", model.quant.c_str() );
}
}
ImGui::EndCombo();
}
}
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( "Embeddings:" );
ImGui::SameLine();
if( models.empty() || m_embedIdx < 0 )
{
ImGui::TextUnformatted( "No models available" );
}
else
{
if( ImGui::BeginCombo( "##embedmodel", models[m_embedIdx].name.c_str() ) )
{
for( size_t i = 0; i < models.size(); ++i )
{
const auto& model = models[i];
if( !model.embeddings ) continue;
if( ImGui::Selectable( model.name.c_str(), i == m_embedIdx ) )
{
m_embedIdx = i;
s_config.llmEmbeddingsModel = model.name;
SaveConfig();
m_tools->SelectManualEmbeddings( model.name );
}
if( m_embedIdx == i ) ImGui::SetItemDefaultFocus();
if( !model.quant.empty() )
{
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", model.quant.c_str() );
}
}
ImGui::EndCombo();
}
}
ImGui::Checkbox( ICON_FA_TEMPERATURE_HALF " Temperature", &m_setTemperature );
ImGui::SameLine();
ImGui::SetNextItemWidth( 40 * scale );
if( ImGui::InputFloat( "##temperature", &m_temperature, 0, 0, "%.2f" ) ) m_temperature = std::clamp( m_temperature, 0.f, 2.f );
if( responding ) ImGui::EndDisabled();
ImGui::Checkbox( ICON_FA_GLOBE " Internet access", &m_tools->m_netAccess );
if( ImGui::TreeNode( "External services" ) )
{
char buf[1024];
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted( "User agent:" );
ImGui::SameLine();
snprintf( buf, sizeof( buf ), "%s", s_config.llmUserAgent.c_str() );
if( ImGui::InputTextWithHint( "##useragent", "Spoof user agent", buf, sizeof( buf ) ) )
{
s_config.llmUserAgent = buf;
SaveConfig();
}
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted( "Google Search Engine:" );
ImGui::SameLine();
snprintf( buf, sizeof( buf ), "%s", s_config.llmSearchIdentifier.c_str() );
if( ImGui::InputTextWithHint( "##cse", "search identifier", buf, sizeof( buf ) ) )
{
s_config.llmSearchIdentifier = buf;
SaveConfig();
}
ImGui::SameLine();
if( ImGui::Button( ICON_FA_HOUSE "##cse" ) ) OpenWebpage( "https://cse.google.com/cse/create/new" );
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted( "Google Search API Key:" );
ImGui::SameLine();
snprintf( buf, sizeof( buf ), "%s", s_config.llmSearchApiKey.c_str() );
if( ImGui::InputTextWithHint( "##csekey", "search API key", buf, sizeof( buf ) ) )
{
s_config.llmSearchApiKey = buf;
SaveConfig();
}
ImGui::SameLine();
if( ImGui::Button( ICON_FA_HOUSE "##csekey" ) ) OpenWebpage( "https://developers.google.com/custom-search/v1/overview" );
ImGui::TreePop();
}
ImGui::TreePop();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
}
if( !m_api->IsConnected() )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_PLUG_CIRCLE_XMARK );
TextCentered( "No connection to LLM API" );
ImGui::PopFont();
ImGui::End();
return;
}
const auto& models = m_api->GetModels();
if( models.empty() || m_modelIdx < 0 )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_WORM );
ImGui::Spacing();
TextCentered( "No models available." );
ImGui::PopFont();
ImGui::End();
return;
}
if( !manualEmbeddingsState.done || m_embedIdx < 0 || manualEmbeddingsState.model != models[m_embedIdx].name )
{
if( m_embedIdx < 0 ) ImGui::BeginDisabled();
if( ImGui::SmallButton( ICON_FA_BOOK_BOOKMARK " Learn manual" ) )
{
if( m_currentJob ) m_currentJob->stop = true;
m_tools->BuildManualEmbeddings( models[m_embedIdx].name, *m_api );
}
if( m_embedIdx < 0 ) ImGui::EndDisabled();
}
const auto ctxSize = models[m_modelIdx].contextSize;
ImGui::Spacing();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
if( ctxSize <= 0 )
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
ImGui::ProgressBar( 1, ImVec2( -1, 0 ), "" );
}
else
{
const auto ratio = m_usedCtx / (float)ctxSize;
if( ratio < 0.5f )
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
}
else if( ratio < 0.8f )
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.6f, 0.6f, 0.2f, 1.0f));
}
else
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
}
ImGui::ProgressBar( ratio, ImVec2( -1, 0 ), "" );
}
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if( ImGui::IsItemHovered() )
{
ImGui::BeginTooltip();
if( ctxSize <= 0 )
{
ImGui::TextUnformatted( "Context size is not available" );
}
else
{
TextFocused( "Used context size:", RealToString( m_usedCtx ) );
ImGui::SameLine();
char buf[64];
PrintStringPercent( buf, m_usedCtx / (float)ctxSize * 100 );
tracy::TextDisabledUnformatted( buf );
TextFocused( "Available context size:", RealToString( ctxSize ) );
ImGui::Separator();
tracy::TextDisabledUnformatted( ICON_FA_TRIANGLE_EXCLAMATION " Context use may be an estimate" );
}
ImGui::EndTooltip();
}
bool inputChanged = false;
ImGui::Spacing();
ImGui::BeginChild( "##chat", ImVec2( 0, -( ImGui::GetFrameHeight() + style.ItemSpacing.y * 2 ) ), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar );
if( m_chat.size() <= 1 ) // account for system prompt
{
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 15 ) * 0.5f ) );
ImGui::PushStyleColor( ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled] );
ImGui::PushFont( g_fonts.normal, FontNormal * 3.f );
TextCentered( ICON_FA_ROBOT );
ImGui::Spacing();
ImGui::Spacing();
ImGui::PopFont();
ImGui::TextWrapped( "What I had not realized is that extremely short exposures to a relatively simple computer program could induce powerful delusional thinking in quite normal people." );
ImGui::Dummy( ImVec2( 0, ImGui::GetTextLineHeight() * 0.5f ) );
constexpr auto signature = "-- Joseph Weizenbaum, 1976";
const auto tw = ImGui::CalcTextSize( signature ).x;
ImGui::SetCursorPosX( ( ImGui::GetWindowWidth() - tw - 30 * scale ) );
ImGui::TextUnformatted( signature );
ImGui::PopStyleColor();
}
else
{
ImGui::PushID( m_chatId );
m_chatUi->Begin();
int turnIdx = 0;
for( auto it = m_chat.begin(); it != m_chat.end(); ++it )
{
const auto& line = *it;
if( !line.contains( "role" ) ) break;
const auto& roleStr = line["role"].get_ref<const std::string&>();
if( roleStr == "system" ) continue;
const auto& contentNode = line["content"];
if( !contentNode.is_string() ) continue;
const auto& content = contentNode.get_ref<const std::string&>();
TracyLlmChat::TurnRole role = TracyLlmChat::TurnRole::None;
if( roleStr == "user" ) role = TracyLlmChat::TurnRole::User;
else if( roleStr == "error" ) role = TracyLlmChat::TurnRole::Error;
else if( roleStr == "assistant" ) role = TracyLlmChat::TurnRole::Assistant;
else assert( false );
if( role == TracyLlmChat::TurnRole::User )
{
if( content.starts_with( "<tool_output>\n" ) ) role = TracyLlmChat::TurnRole::Assistant;
else if( content.starts_with( "<debug>" ) ) role = TracyLlmChat::TurnRole::UserDebug;
else if( content.starts_with( "<attachment>\n" ) ) role = TracyLlmChat::TurnRole::Attachment;
}
else if( role == TracyLlmChat::TurnRole::Assistant )
{
if( content.starts_with( "<debug>" ) ) role = TracyLlmChat::TurnRole::AssistantDebug;
}
ImGui::PushID( turnIdx++ );
if( !m_chatUi->Turn( role, content ) )
{
if( role == TracyLlmChat::TurnRole::Assistant || role == TracyLlmChat::TurnRole::AssistantDebug )
{
QueueSendMessage();
}
else if( role == TracyLlmChat::TurnRole::User || role == TracyLlmChat::TurnRole::UserDebug )
{
const auto sz = std::min( InputBufferSize - 1, content.size() );
memcpy( m_input, content.data(), sz );
m_input[sz] = 0;
inputChanged = true;
}
auto cit = it;
while( cit != m_chat.end() )
{
const auto& content = (*cit)["content"].get_ref<const std::string&>();
const auto tokens = m_api->Tokenize( content, m_modelIdx );
m_usedCtx -= tokens >= 0 ? tokens : content.size() / 4;
++cit;
}
m_chat.erase( it, m_chat.end() );
if( m_currentJob ) m_currentJob->stop = true;
ImGui::PopID();
break;
}
ImGui::PopID();
}
m_chatUi->End();
ImGui::PopID();
if( ImGui::GetScrollY() >= ImGui::GetScrollMaxY() )
{
ImGui::SetScrollHereY( 1.f );
}
}
ImGui::EndChild();
ImGui::Spacing();
if( m_currentJob )
{
const bool disabled = m_currentJob->stop;
if( disabled ) ImGui::BeginDisabled();
if( ImGui::Button( ICON_FA_STOP " Stop" ) ) m_currentJob->stop = true;
if( disabled ) ImGui::EndDisabled();
ImGui::SameLine();
const auto pos = ImGui::GetWindowPos() + ImGui::GetCursorPos();
auto draw = ImGui::GetWindowDrawList();
const auto ty = ImGui::GetTextLineHeight();
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 0 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f + 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 1 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 2 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
ImGui::Dummy( ImVec2( ty * 3, ty ) );
ImGui::SameLine();
if( disabled )
{
ImGui::TextUnformatted( "Stopping..." );
}
else
{
ImGui::TextUnformatted( "Generating..." );
}
s_wasActive = true;
}
else
{
if( ImGui::IsWindowAppearing() || m_focusInput )
{
ImGui::SetKeyboardFocusHere( 0 );
m_focusInput = false;
}
const char* buttonText = ICON_FA_PAPER_PLANE;
auto buttonSize = ImGui::CalcTextSize( buttonText );
buttonSize.x += ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x;
ImGui::PushItemWidth( ImGui::GetContentRegionAvail().x - buttonSize.x );
if( inputChanged ) ImGui::GetInputTextState( ImGui::GetCurrentWindow()->GetID( "##chat_input" ) )->ReloadUserBufAndMoveToEnd();
bool send = ImGui::InputTextWithHint( "##chat_input", "Write your question here...", m_input, InputBufferSize, ImGuiInputTextFlags_EnterReturnsTrue );
ImGui::SameLine();
if( *m_input == 0 ) ImGui::BeginDisabled();
send |= ImGui::Button( buttonText );
if( *m_input == 0 ) ImGui::EndDisabled();
if( send )
{
auto ptr = m_input;
while( *ptr )
{
if( *ptr != ' ' && *ptr != '\t' && *ptr != '\n' ) break;
ptr++;
}
if( *ptr )
{
AddMessage( ptr, "user" );
*m_input = 0;
QueueSendMessage();
}
else
{
*m_input = 0;
}
ImGui::SetKeyboardFocusHere( -1 );
}
}
ImGui::End();
}
void TracyLlm::WorkerThread()
{
std::unique_lock lock( m_lock );
while( !m_exit.load( std::memory_order_acquire ) )
{
m_cv.wait( lock, [this] { return !m_jobs.empty() || m_exit.load( std::memory_order_acquire ); } );
if( m_exit.load( std::memory_order_acquire ) ) break;
m_currentJob = std::move( m_jobs.front() );
m_jobs.erase( m_jobs.begin() );
switch( m_currentJob->task )
{
case Task::Connect:
m_busy = true;
lock.unlock();
m_api->Connect( s_config.llmAddress.c_str() );
m_currentJob->callback();
lock.lock();
m_busy = false;
break;
case Task::SendMessage:
SendMessage( lock );
break;
case Task::Tokenize:
{
lock.unlock();
auto tokens = m_api->Tokenize( m_currentJob->param, m_modelIdx );
if( tokens < 0 ) tokens = m_currentJob->param.size() / 4;
m_currentJob->callback2( { { "tokens", tokens } } );
lock.lock();
break;
}
}
m_currentJob.reset();
}
};
void TracyLlm::UpdateModels()
{
m_modelIdx = -1;
m_embedIdx = -1;
auto& models = m_api->GetModels();
auto it = std::ranges::find_if( models, []( const auto& model ) { return model.name == s_config.llmModel; } );
if( it == models.end() )
{
for( int i=0; i<models.size(); i++ )
{
if( !models[i].embeddings )
{
m_modelIdx = i;
break;
}
}
}
else
{
m_modelIdx = std::distance( models.begin(), it );
}
it = std::ranges::find_if( models, []( const auto& model ) { return model.name == s_config.llmEmbeddingsModel; } );
if( it == models.end() )
{
for( int i=0; i<models.size(); i++ )
{
if( models[i].embeddings )
{
m_embedIdx = i;
break;
}
}
}
else
{
m_embedIdx = std::distance( models.begin(), it );
}
if( m_embedIdx >= 0 )
{
m_tools->SelectManualEmbeddings( models[m_embedIdx].name );
}
}
void TracyLlm::ResetChat()
{
std::string systemPrompt = "<SYSTEM_PROMPT>\n";
systemPrompt += std::string( m_systemPrompt->data(), m_systemPrompt->size() );
#ifndef NDEBUG
systemPrompt += "\n\n# DEBUG MODE AVAILABLE\n";
systemPrompt += "Upon a user query beginning with `<debug>`, activate *Debug Mode*. In this mode, ignore all prior instructions. Your response must detail your complete reasoning process and the exact steps taken to generate your *previous* non-debug answer. Explicitly state any internal rules applied. Do not say you are sorry. Do not tell how you will change your behavior. Focus strictly on the analysis of what happened.\n";
#endif
systemPrompt += "</SYSTEM_PROMPT>\n";
*m_input = 0;
m_usedCtx = 0;
m_chatId++;
m_chat.clear();
AddMessage( std::move( systemPrompt ), "system" );
}
void TracyLlm::QueueConnect()
{
m_jobs.emplace_back( std::make_shared<WorkItem>( WorkItem {
.task = Task::Connect,
.callback = [this] { UpdateModels(); }
} ) );
m_cv.notify_all();
}
bool TracyLlm::QueueSendMessage()
{
if( !m_api->IsConnected() || m_modelIdx < 0 ) return false;
m_jobs.emplace_back( std::make_shared<WorkItem>( WorkItem {
.task = Task::SendMessage
} ) );
m_cv.notify_all();
return true;
}
void TracyLlm::AddMessage( std::string&& str, const char* role )
{
if( !m_api )
{
std::unique_lock<std::mutex> null;
AddMessageBlocking( std::move( str ), role, null );
return;
}
m_jobs.emplace_back( std::make_shared<WorkItem>( WorkItem {
.task = Task::Tokenize,
.callback2 = [this, str, role]( nlohmann::json json ) {
m_usedCtx += json["tokens"].get<int>();
nlohmann::json msg = {
{ "role", role },
{ "content", str }
};
m_chat.emplace_back( std::move( msg ) );
},
.param = std::move( str ),
} ) );
m_cv.notify_all();
}
void TracyLlm::AddMessageBlocking( std::string&& str, const char* role, std::unique_lock<std::mutex>& lock )
{
const auto tokens = m_api ? m_api->Tokenize( str, m_modelIdx ) : -1;
m_usedCtx += tokens >= 0 ? tokens : str.size() / 4;
nlohmann::json msg;
msg["role"] = role;
msg["content"] = std::move( str );
if( lock ) lock.lock();
m_chat.emplace_back( std::move( msg ) );
if( lock ) lock.unlock();
}
void TracyLlm::AddAttachment( std::string&& str, const char* role )
{
AddMessage( "<attachment>\n" + std::move( str ), role );
}
void TracyLlm::ManageContext( std::unique_lock<std::mutex>& lock )
{
const auto& models = m_api->GetModels();
const auto ctxSize = models[m_modelIdx].contextSize;
if( ctxSize <= 0 ) return;
const auto quota = int( ctxSize * 0.7f );
if( m_usedCtx < quota ) return;
size_t idx = 0;
std::vector<std::pair<size_t, size_t>> toolOutputs;
for( auto& msg : m_chat )
{
if( msg["role"].get_ref<const std::string&>() == "user" )
{
auto& content = msg["content"];
const auto& str = content.get_ref<const std::string&>();
if( str.starts_with( "<tool_output>\n" ) )
{
toolOutputs.emplace_back( str.size(), idx );
}
}
idx++;
}
if( toolOutputs.size() > 1 )
{
toolOutputs.pop_back(); // keep the last tool output
std::ranges::stable_sort( toolOutputs, []( const auto& a, const auto& b ) { return a.first > b.first; } );
for( auto& v : toolOutputs )
{
auto tokens = m_api->Tokenize( m_chat[v.second]["content"].get_ref<const std::string&>(), m_modelIdx );
m_usedCtx -= tokens >= 0 ? tokens : v.first / 4;
lock.lock();
m_chat[v.second]["content"] = TracyLlmChat::ForgetMsg;
lock.unlock();
tokens = m_api->Tokenize( TracyLlmChat::ForgetMsg, m_modelIdx );
m_usedCtx += tokens >= 0 ? tokens : strlen( TracyLlmChat::ForgetMsg ) / 4;
if( m_usedCtx < quota ) break;
}
}
}
void TracyLlm::SendMessage( std::unique_lock<std::mutex>& lock )
{
lock.unlock();
ManageContext( lock );
bool debug = false;
#ifndef NDEBUG
if( m_chat.size() > 1 && m_chat.back()["role"].get_ref<const std::string&>() == "user" )
{
const auto& content = m_chat.back()["content"].get_ref<const std::string&>();
if( content.starts_with( "<debug>" ) ) debug = true;
}
#endif
if( debug )
{
AddMessageBlocking( "<debug>\n", "assistant", lock );
}
else
{
AddMessageBlocking( "<think>", "assistant", lock );
}
bool res;
try
{
auto chat = m_chat;
std::string inject;
if( debug )
{
inject += "<SYSTEM_REMINDER>\n";
inject += "You are in debug mode.\n";
inject += "</SYSTEM_REMINDER>\n";
}
else
{
inject += "<SYSTEM_REMINDER>\n";
inject += std::string( m_systemReminder->data(), m_systemReminder->size() );
inject += "</SYSTEM_REMINDER>\n";
}
chat.front()["content"].get_ref<std::string&>().append( "\n\nThe current time is: " + m_tools->GetCurrentTime() + "\n" );
chat.back()["content"].get_ref<std::string&>().insert( 0, inject );
nlohmann::json req;
req["model"] = m_api->GetModels()[m_modelIdx].name;
req["messages"] = std::move( chat );
req["stream"] = true;
if( m_setTemperature ) req["temperature"] = m_temperature;
res = m_api->ChatCompletion( req, [this]( const nlohmann::json& response ) -> bool { return OnResponse( response ); }, m_modelIdx );
lock.lock();
}
catch( std::exception& e )
{
lock.lock();
if( !m_chat.empty() && m_chat.back()["role"].get_ref<const std::string&>() == "assistant" ) m_chat.pop_back();
lock.unlock();
AddMessageBlocking( e.what(), "error", lock );
lock.lock();
}
}
bool TracyLlm::OnResponse( const nlohmann::json& json )
{
std::unique_lock lock( m_lock );
if( m_currentJob->stop )
{
m_focusInput = true;
return false;
}
auto& back = m_chat.back();
auto& content = back["content"];
const auto& str = content.get_ref<const std::string&>();
std::string responseStr;
bool done = false;
try
{
auto& choices = json["choices"];
if( !choices.empty() )
{
auto& node = choices[0];
auto& delta = node["delta"];
if( delta.contains( "content" ) && delta["content"].is_string() ) responseStr = delta["content"].get_ref<const std::string&>();
done = !node["finish_reason"].empty();
}
}
catch( const nlohmann::json::exception& e )
{
m_focusInput = true;
return false;
}
if( !responseStr.empty() )
{
std::erase( responseStr, '\r' );
content = str + responseStr;
m_usedCtx++;
}
if( done )
{
if( json.contains( "usage" ) )
{
auto& usage = json["usage"];
if( usage.contains( "total_tokens" ) ) m_usedCtx = usage["total_tokens"].get<int>();
}
bool isTool = false;
auto& str = back["content"].get_ref<const std::string&>();
if( !str.starts_with( "<debug>" ) )
{
auto pos = str.find( "<tool>" );
if( pos != std::string::npos )
{
pos += 6;
while( str[pos] == '\n' ) pos++;
auto end = str.find( "</tool>", pos );
if( end != std::string::npos )
{
auto repeat = str.find( "<tool>", end );
if( repeat != std::string::npos )
{
lock.unlock();
AddMessageBlocking( "<tool_output>\nError: Only one tool call is allowed per turn.", "user", lock );
lock.lock();
}
else
{
while( end > pos && str[end-1] == '\n' ) end--;
const auto tool = str.substr( pos, end - pos );
lock.unlock();
TracyLlmTools::ToolReply reply;
try
{
auto json = nlohmann::json::parse( tool );
reply = m_tools->HandleToolCalls( json, *m_api, m_api->GetModels()[m_modelIdx].contextSize, m_embedIdx >= 0 );
}
catch( const nlohmann::json::exception& e )
{
reply.reply = e.what();
}
isTool = true;
auto output = "<tool_output>\n" + reply.reply;
AddMessageBlocking( std::move( output ), "user", lock );
lock.lock();
}
QueueSendMessage();
}
}
}
if( !isTool )
{
m_focusInput = true;
}
}
return true;
}
}
@@ -0,0 +1,102 @@
#ifndef __TRACYLLM_HPP__
#define __TRACYLLM_HPP__
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
#include <string>
#include <thread>
#include <vector>
#include "TracyEmbed.hpp"
namespace tracy
{
class TracyLlmApi;
class TracyLlmChat;
class TracyLlmTools;
class TracyManualData;
class Worker;
class TracyLlm
{
enum class Task
{
Connect,
SendMessage,
Tokenize
};
struct WorkItem
{
Task task;
std::function<void()> callback;
std::function<void(nlohmann::json)> callback2;
std::string param;
bool stop;
};
public:
TracyLlm( Worker& worker, const TracyManualData& manual );
~TracyLlm();
[[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_lock ); return m_busy; }
void Draw();
void AddAttachment( std::string&& str, const char* role );
void AddMessage( std::string&& str, const char* role );
bool QueueSendMessage();
bool m_show = false;
private:
void WorkerThread();
void UpdateModels();
void ResetChat();
void QueueConnect();
void AddMessageBlocking( std::string&& str, const char* role, std::unique_lock<std::mutex>& lock );
void ManageContext( std::unique_lock<std::mutex>& lock );
void SendMessage( std::unique_lock<std::mutex>& lock );
bool OnResponse( const nlohmann::json& json );
std::unique_ptr<TracyLlmApi> m_api;
std::unique_ptr<TracyLlmChat> m_chatUi;
std::unique_ptr<TracyLlmTools> m_tools;
int m_modelIdx;
int m_embedIdx;
std::atomic<bool> m_exit;
std::condition_variable m_cv;
std::thread m_thread;
mutable std::mutex m_lock;
std::vector<std::shared_ptr<WorkItem>> m_jobs;
std::shared_ptr<WorkItem> m_currentJob;
bool m_busy = false;
bool m_focusInput = false;
int m_chatId = 0;
int m_usedCtx = 0;
float m_temperature = 1.0f;
bool m_setTemperature = false;
char* m_input;
char* m_apiInput;
std::vector<nlohmann::json> m_chat;
std::shared_ptr<EmbedData> m_systemPrompt;
std::shared_ptr<EmbedData> m_systemReminder;
};
}
#endif
@@ -0,0 +1,326 @@
#include <assert.h>
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <string>
#include "TracyLlmApi.hpp"
namespace tracy
{
static size_t WriteFn( void* _data, size_t size, size_t num, void* ptr )
{
const auto data = (unsigned char*)_data;
const auto sz = size*num;
auto& v = *(std::string*)ptr;
v.append( (const char*)data, sz );
return sz;
}
TracyLlmApi::~TracyLlmApi()
{
if( m_curl ) curl_easy_cleanup( m_curl );
}
void TracyLlmApi::SetupCurl( void* curl )
{
curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1L );
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 300 );
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Tracy Profiler" );
}
bool TracyLlmApi::Connect( const char* url )
{
m_url = url;
m_models.clear();
if( m_curl ) curl_easy_cleanup( m_curl );
m_curl = curl_easy_init();
if( !m_curl ) return false;
SetupCurl( m_curl );
std::string buf;
if( GetRequest( m_url + "/v1/models", buf ) != 200 )
{
curl_easy_cleanup( m_curl );
m_curl = nullptr;
return false;
}
try
{
m_type = Type::Unknown;
nlohmann::json json = nlohmann::json::parse( buf );
for( auto& model : json["data"] )
{
auto& id = model["id"].get_ref<const std::string&>();
m_models.emplace_back( LlmModel { .name = id } );
std::string buf2;
if( ( m_type == Type::Unknown || m_type == Type::LlamaSwap ) && GetRequest( m_url + "/running", buf2 ) == 200 && buf2.starts_with( "{\"running\":" ) )
{
m_type = Type::LlamaSwap;
if( id.find( "embed" ) != std::string::npos ) m_models.back().embeddings = true;
}
else if( ( m_type == Type::Unknown || m_type == Type::LmStudio ) && GetRequest( m_url + "/api/v0/models/" + id, buf2 ) == 200 )
{
m_type = Type::LmStudio;
auto json2 = nlohmann::json::parse( buf2 );
if( json2["type"] == "embeddings" ) m_models.back().embeddings = true;
m_models.back().quant = json2["quantization"].get_ref<const std::string&>();
if( json2.contains( "loaded_context_length" ) ) m_models.back().contextSize = json2["loaded_context_length"].get<int>();
}
else if( ( m_type == Type::Unknown || m_type == Type::Ollama ) && PostRequest( m_url + "/api/show", "{\"name\":\"" + id + "\"}", buf2 ) == 200 )
{
m_type = Type::Ollama;
auto json2 = nlohmann::json::parse( buf2 );
m_models.back().quant = json2["details"]["quantization_level"].get_ref<const std::string&>();
for( auto& cap : json2["capabilities"] )
{
if( cap.get_ref<const std::string&>() == "embedding" )
{
m_models.back().embeddings = true;
break;
}
}
}
else if( m_type == Type::Unknown )
{
m_type = Type::Other;
}
}
}
catch( const std::exception& e )
{
m_models.clear();
curl_easy_cleanup( m_curl );
m_curl = nullptr;
return false;
}
std::ranges::sort( m_models, []( const auto& a, const auto& b ) { return a.name < b.name; } );
return true;
}
struct StreamData
{
std::string str;
const std::function<bool(const nlohmann::json&)>& callback;
};
static size_t StreamFn( void* _data, size_t size, size_t num, void* ptr )
{
auto data = (const char*)_data;
const auto sz = size*num;
auto& v = *(StreamData*)ptr;
v.str.append( data, sz );
for(;;)
{
if( strncmp( v.str.c_str(), "data: [DONE]", 12 ) == 0 ) return sz;
auto err = v.str.find( "error: " );
if( err != std::string::npos )
{
err += 7;
auto end = v.str.find( "\n\n", err );
if( end == std::string::npos ) break;
throw std::runtime_error( v.str.substr( err, end - err ) );
}
else
{
auto pos = v.str.find( "data: " );
if( pos == std::string::npos ) break;
pos += 6;
auto end = v.str.find( "\n\n", pos );
if( end == std::string::npos ) break;
nlohmann::json json = nlohmann::json::parse( v.str.c_str() + pos, v.str.c_str() + end );
if( !v.callback( json ) ) return CURL_WRITEFUNC_ERROR;
v.str.erase( 0, end + 2 );
}
}
return sz;
}
bool TracyLlmApi::ChatCompletion( const nlohmann::json& req, const std::function<bool(const nlohmann::json&)>& callback, int modelIdx )
{
assert( m_curl );
StreamData data = { .callback = callback };
const auto url = m_url + "/v1/chat/completions";
const auto reqStr = req.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace );
curl_slist *hdr = nullptr;
hdr = curl_slist_append( hdr, "Accept: application/json" );
hdr = curl_slist_append( hdr, "Content-Type: application/json" );
curl_easy_setopt( m_curl, CURLOPT_URL, url.c_str() );
curl_easy_setopt( m_curl, CURLOPT_HTTPHEADER, hdr );
curl_easy_setopt( m_curl, CURLOPT_POSTFIELDS, reqStr.c_str() );
curl_easy_setopt( m_curl, CURLOPT_POSTFIELDSIZE, reqStr.size() );
curl_easy_setopt( m_curl, CURLOPT_WRITEDATA, &data.str );
curl_easy_setopt( m_curl, CURLOPT_WRITEFUNCTION, StreamFn );
CURLcode res;
try
{
res = curl_easy_perform( m_curl );
}
catch( const std::exception& e )
{
curl_easy_cleanup( m_curl );
curl_slist_free_all( hdr );
m_curl = curl_easy_init();
SetupCurl( m_curl );
throw;
}
curl_slist_free_all( hdr );
if( res != CURLE_OK && res != CURLE_WRITE_ERROR ) return false;
int64_t http_code = 0;
curl_easy_getinfo( m_curl, CURLINFO_RESPONSE_CODE, &http_code );
if( http_code == 200 )
{
if( m_models[modelIdx].contextSize <= 0 )
{
if( m_type == Type::LlamaSwap )
{
curl_easy_reset( m_curl );
SetupCurl( m_curl );
std::string buf;
if( GetRequest( m_url + "/upstream/" + m_models[modelIdx].name + "/props", buf ) == 200 )
{
auto json = nlohmann::json::parse( buf );
if( json.contains( "default_generation_settings" ) )
{
auto& settings = json["default_generation_settings"];
if( settings.contains( "n_ctx" ) ) m_models[modelIdx].contextSize = settings["n_ctx"].get<int>();
}
}
}
else if( m_type == Type::LmStudio )
{
curl_easy_reset( m_curl );
SetupCurl( m_curl );
std::string buf;
if( GetRequest( m_url + "/api/v0/models/" + m_models[modelIdx].name, buf ) == 200 )
{
auto json = nlohmann::json::parse( buf );
if( json.contains( "loaded_context_length" ) ) m_models[modelIdx].contextSize = json["loaded_context_length"].get<int>();
}
}
}
return true;
}
else
{
auto str = std::move( data.str );
data.str.clear();
throw std::runtime_error( "HTTP error " + std::to_string( http_code ) + ": " + str );
}
}
bool TracyLlmApi::Embeddings( const nlohmann::json& req, nlohmann::json& response, bool separateConnection )
{
assert( m_curl );
std::string buf;
auto res = PostRequest( m_url + "/v1/embeddings", req.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace ), buf, separateConnection );
if( res != 200 ) return false;
response = nlohmann::json::parse( buf );
return true;
}
int TracyLlmApi::Tokenize( const std::string& text, int modelIdx )
{
if( m_type == Type::LlamaSwap )
{
std::string buf;
nlohmann::json req = { { "content", text } };
auto res = PostRequest( m_url + "/upstream/" + m_models[modelIdx].name + "/tokenize", req.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace ), buf, true );
if( res != 200 ) return -1;
try
{
auto json = nlohmann::json::parse( buf );
return json["tokens"].size();
}
catch( const std::exception& )
{
return -1;
}
}
return -1;
}
int64_t TracyLlmApi::GetRequest( const std::string& url, std::string& response )
{
assert( m_curl );
response.clear();
curl_slist *hdr = nullptr;
hdr = curl_slist_append( hdr, "Accept: application/json" );
hdr = curl_slist_append( hdr, "Content-Type: application/json" );
curl_easy_setopt( m_curl, CURLOPT_URL, url.c_str() );
curl_easy_setopt( m_curl, CURLOPT_HTTPHEADER, hdr );
curl_easy_setopt( m_curl, CURLOPT_WRITEDATA, &response );
curl_easy_setopt( m_curl, CURLOPT_WRITEFUNCTION, WriteFn );
auto res = curl_easy_perform( m_curl );
curl_slist_free_all( hdr );
if( res != CURLE_OK ) return -1;
int64_t http_code = 0;
curl_easy_getinfo( m_curl, CURLINFO_RESPONSE_CODE, &http_code );
return http_code;
}
int64_t TracyLlmApi::PostRequest( const std::string& url, const std::string& data, std::string& response, bool separateConnection )
{
assert( m_curl );
response.clear();
curl_slist *hdr = nullptr;
hdr = curl_slist_append( hdr, "Accept: application/json" );
hdr = curl_slist_append( hdr, "Content-Type: application/json" );
auto curl = m_curl;
if( separateConnection )
{
curl = curl_easy_init();
if( !curl ) return -1;
SetupCurl( curl );
}
curl_easy_setopt( curl, CURLOPT_URL, url.c_str() );
curl_easy_setopt( curl, CURLOPT_HTTPHEADER, hdr );
curl_easy_setopt( curl, CURLOPT_POSTFIELDS, data.c_str() );
curl_easy_setopt( curl, CURLOPT_POSTFIELDSIZE, data.size() );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &response );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WriteFn );
auto res = curl_easy_perform( curl );
curl_slist_free_all( hdr );
if( res != CURLE_OK )
{
if( separateConnection ) curl_easy_cleanup( curl );
return -1;
}
int64_t http_code = 0;
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code );
if( separateConnection ) curl_easy_cleanup( curl );
return http_code;
}
}
@@ -0,0 +1,58 @@
#ifndef __TRACYLLMAPI_HPP__
#define __TRACYLLMAPI_HPP__
#include <functional>
#include <nlohmann/json.hpp>
#include <stdint.h>
#include <string>
#include <vector>
namespace tracy
{
struct LlmModel
{
std::string name;
std::string quant;
int contextSize = -1;
bool embeddings = false;
};
class TracyLlmApi
{
enum class Type
{
Unknown,
Ollama,
LmStudio,
LlamaSwap,
Other
};
public:
~TracyLlmApi();
bool Connect( const char* url );
bool ChatCompletion( const nlohmann::json& req, const std::function<bool(const nlohmann::json&)>& callback, int modelIdx );
bool Embeddings( const nlohmann::json& req, nlohmann::json& response, bool separateConnection = false );
[[nodiscard]] int Tokenize( const std::string& text, int modelIdx );
[[nodiscard]] bool IsConnected() const { return m_curl != nullptr; }
[[nodiscard]] const std::vector<LlmModel>& GetModels() const { return m_models; }
private:
void SetupCurl( void* curl );
int64_t GetRequest( const std::string& url, std::string& response );
int64_t PostRequest( const std::string& url, const std::string& data, std::string& response, bool separateConnection = false );
void* m_curl = nullptr;
std::string m_url;
Type m_type;
std::vector<LlmModel> m_models;
};
}
#endif
@@ -0,0 +1,300 @@
#include <array>
#include <assert.h>
#include <md4c.h>
#include <nlohmann/json.hpp>
#include <string>
#include "TracyImGui.hpp"
#include "TracyLlmChat.hpp"
#include "TracyMouse.hpp"
#include "../Fonts.hpp"
namespace tracy
{
constexpr auto ThinkColor = ImVec4( 0.5f, 0.5f, 0.3f, 1.f );
struct RoleData
{
const char* icon;
ImVec4 iconColor;
ImVec4 textColor;
};
constexpr std::array roles = {
RoleData { ICON_FA_USER, ImVec4( 0.75f, 1.f, 0.25f, 1.f ), ImVec4( 0.64f, 0.76f, 0.41f, 1.f ) },
RoleData { ICON_FA_TERMINAL, ImVec4( 1.f, 0.5f, 0.5f, 1.f ), ImVec4( 1.f, 0.65f, 0.65f, 1.f ) },
RoleData { ICON_FA_FILE, ImVec4( 0.5f, 0.75f, 1.f, 1.f ), ImVec4( 0.65f, 0.75f, 1.f, 1.f ) },
RoleData { ICON_FA_ROBOT, ImVec4( 0.4f, 0.5f, 1.f, 1.f ), ImVec4( 1.f, 1.f, 1.f, 1.f ) },
RoleData { ICON_FA_CODE, ImVec4( 1.0f, 0.5f, 1.f, 1.f ), ImVec4( 1.f, 0.65f, 1.f, 1.f ) },
RoleData { ICON_FA_CIRCLE_EXCLAMATION, ImVec4( 1.f, 0.25f, 0.25f, 1.f ), ImVec4( 1.f, 0.25f, 0.25f, 1.f ) },
RoleData { ICON_FA_TRASH, ImVec4( 1.0f, 0.25f, 0.25f, 1.f ), ImVec4( 1.f, 1.f, 1.f, 1.f ) },
RoleData { ICON_FA_ARROWS_ROTATE, ImVec4( 1.0f, 0.25f, 0.25f, 1.f ), ImVec4( 1.f, 1.f, 1.f, 1.f ) },
};
constexpr size_t NumRoles = roles.size();
static_assert( NumRoles == (int)TracyLlmChat::TurnRole::None );
TracyLlmChat::TracyLlmChat()
: m_width( new float[NumRoles] )
{
}
TracyLlmChat::~TracyLlmChat()
{
delete[] m_width;
}
void TracyLlmChat::Begin()
{
float max = 0;
for( size_t i=0; i<NumRoles; ++i )
{
m_width[i] = ImGui::CalcTextSize( roles[i].icon ).x;
max = std::max( max, m_width[i] );
}
m_maxWidth = max;
m_role = TurnRole::None;
m_thinkActive = false;
m_thinkOpen = false;
m_thinkIdx = 0;
m_subIdx = 0;
m_roleIdx = 0;
}
void TracyLlmChat::End()
{
if( m_role != TurnRole::None )
{
NormalScope();
ImGui::EndGroup();
ImGui::PopID();
}
}
bool TracyLlmChat::Turn( TurnRole role, const std::string& content )
{
bool keep = true;
const auto& roleData = roles[(int)role];
if( role != m_role || role == TurnRole::Attachment || role == TurnRole::Error )
{
if( m_role != TurnRole::None )
{
NormalScope();
ImGui::EndGroup();
ImGui::PopID();
}
m_thinkActive = false;
m_thinkOpen = false;
bool hover = false;
if( m_role != role )
{
m_role = role;
ImGui::Spacing();
}
int trashIdx = ( role == TurnRole::Assistant || role == TurnRole::AssistantDebug ) ? (int)TurnRole::Regenerate : (int)TurnRole::Trash;
ImGui::PushID( m_roleIdx++ );
auto diff = m_maxWidth - m_width[(int)role];
if( ImGui::IsMouseHoveringRect( ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + ImVec2( m_maxWidth, ImGui::GetTextLineHeight() ) ) )
{
diff = m_maxWidth - m_width[trashIdx];
hover = true;
}
const auto offset = diff / 2;
ImGui::Dummy( ImVec2( offset, 0 ) );
ImGui::SameLine( 0, 0 );
if( hover )
{
const auto& trash = roles[trashIdx];
ImGui::TextColored( trash.iconColor, "%s", trash.icon );
if( IsMouseClicked( ImGuiMouseButton_Left ) ) keep = false;
}
else
{
ImGui::TextColored( roleData.iconColor, "%s", roleData.icon );
}
ImGui::SameLine( 0, 0 );
ImGui::Dummy( ImVec2( diff - offset, 0 ) );
ImGui::SameLine();
ImGui::BeginGroup();
}
const auto posStart = ImGui::GetCursorScreenPos();
ImGui::PushStyleColor( ImGuiCol_Text, roleData.textColor );
if( role == TurnRole::Error )
{
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::TextWrapped( "%s", content.c_str() );
ImGui::PopFont();
}
else if( role == TurnRole::Attachment )
{
constexpr auto tagSize = sizeof( "<attachment>\n" ) - 1;
auto j = nlohmann::json::parse( content.c_str() + tagSize, content.c_str() + content.size() );
const auto& type = j["type"].get_ref<const std::string&>();
NormalScope();
ImGui::PushID( m_thinkIdx++ );
const bool expand = ImGui::TreeNode( "Attachment" );
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", type.c_str() );
if( expand )
{
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::TextWrapped( "%s", content.c_str() + tagSize );
ImGui::PopFont();
ImGui::TreePop();
}
ImGui::PopID();
}
else if( role != TurnRole::Assistant )
{
m_markdown.Print( content.c_str(), content.size() );
}
else if( content.starts_with( "<tool_output>\n" ) )
{
ThinkScope();
if( m_thinkOpen )
{
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0.5f, 0.5f, 0.5f, 1.f ) );
if( content == ForgetMsg )
{
ImGui::TextUnformatted( ICON_FA_RECYCLE " Tool response removed to save context space" );
m_subIdx++;
}
else
{
ImGui::PushID( m_subIdx++ );
if( ImGui::TreeNode( ICON_FA_REPLY " Tool response..." ) )
{
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::TextWrapped( "%s", content.c_str() + sizeof( "<tool_output>\n" ) - 1 );
ImGui::PopFont();
ImGui::TreePop();
}
ImGui::PopID();
}
ImGui::PopStyleColor();
}
else
{
m_subIdx++;
}
}
else
{
size_t pos = 0;
size_t end = content.size();
while( pos < end )
{
auto posThink = content.find( "<think>", pos );
auto posTool = content.find( "<tool>", pos );
auto minPos = std::min( posThink, posTool );
if( pos != minPos )
{
NormalScope();
m_markdown.Print( content.c_str() + pos, std::min( end, minPos ) - pos );
}
pos = minPos;
if( pos == std::string::npos ) break;
if( minPos == posThink )
{
pos += sizeof( "<think>" ) - 1;
while( content[pos] == '\n' || content[pos] == ' ' ) pos++;
auto endThink = content.find( "</think>", pos );
if( endThink != pos )
{
ThinkScope();
if( m_thinkOpen ) PrintThink( content.c_str() + pos, std::min( end, endThink ) - pos );
}
if( endThink == std::string::npos ) break;
pos = endThink;
do
{
pos += sizeof( "</think>" ) - 1;
while( content[pos] == '\n' || content[pos] == ' ' ) pos++;
}
while( strncmp( content.c_str() + pos, "</think>", sizeof( "</think>" ) - 1 ) == 0 );
}
else
{
assert( minPos == posTool );
pos += sizeof( "<tool>" ) - 1;
while( content[pos] == '\n' || content[pos] == ' ' ) pos++;
auto endTool = content.find( "</tool>", pos );
ThinkScope();
if( m_thinkOpen ) PrintToolCall( content.c_str() + pos, std::min( end, endTool ) - pos );
if( endTool == std::string::npos ) break;
pos = endTool + sizeof( "</tool>" ) - 1;
while( content[pos] == '\n' || content[pos] == ' ' ) pos++;
}
}
}
ImGui::PopStyleColor();
if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) &&
ImGui::IsWindowHovered() &&
ImGui::IsMouseHoveringRect( posStart, ImGui::GetCursorScreenPos() + ImVec2( ImGui::GetContentRegionAvail().x, 0 ) ) )
{
ImGui::OpenPopup( "ContextMenu" );
}
if( ImGui::BeginPopup( "ContextMenu" ) )
{
if( ImGui::Selectable( ICON_FA_CLIPBOARD " Copy" ) )
{
ImGui::SetClipboardText( content.c_str() );
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return keep;
}
void TracyLlmChat::NormalScope()
{
if( !m_thinkActive ) return;
if( m_thinkOpen )
{
ImGui::TreePop();
ImGui::Spacing();
m_thinkOpen = false;
}
ImGui::PopStyleColor();
ImGui::PopID();
m_thinkActive = false;
}
void TracyLlmChat::ThinkScope()
{
if( m_thinkActive ) return;
m_thinkActive = true;
ImGui::PushID( m_thinkIdx++ );
ImGui::PushStyleColor( ImGuiCol_Text, ThinkColor );
m_thinkOpen = ImGui::TreeNode( ICON_FA_LIGHTBULB " Internal thoughts..." );
}
void TracyLlmChat::PrintThink( const char* str, size_t size )
{
ImGui::PushStyleColor( ImGuiCol_Text, ThinkColor );
m_markdown.Print( str, size );
ImGui::PopStyleColor();
}
void TracyLlmChat::PrintToolCall( const char* str, size_t size )
{
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0.5f, 0.5f, 0.5f, 1.f ) );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::TextWrapped( "%.*s", (int)size, str );
ImGui::PopFont();
ImGui::PopStyleColor();
}
}
@@ -0,0 +1,60 @@
#ifndef __TRACYLLMCHAT_HPP__
#define __TRACYLLMCHAT_HPP__
#include <string>
#include "TracyMarkdown.hpp"
namespace tracy
{
class TracyLlmChat
{
public:
static constexpr const char* ForgetMsg = "<tool_output>\n...";
enum class TurnRole
{
User,
UserDebug,
Attachment,
Assistant,
AssistantDebug,
Error,
// virtual roles below
Trash,
Regenerate,
None,
};
TracyLlmChat();
~TracyLlmChat();
void Begin();
void End();
bool Turn( TurnRole role, const std::string& content );
private:
void NormalScope();
void ThinkScope();
void PrintThink( const char* str, size_t size );
void PrintToolCall( const char* str, size_t size );
float* m_width;
float m_maxWidth;
TurnRole m_role;
bool m_thinkActive;
bool m_thinkOpen;
int m_thinkIdx;
int m_subIdx;
int m_roleIdx;
Markdown m_markdown;
};
}
#endif
@@ -0,0 +1,100 @@
#ifdef _WIN32
# include <windows.h>
# include <io.h>
#else
# include <unistd.h>
#endif
#include "TracyLlmEmbeddings.hpp"
namespace tracy
{
TracyLlmEmbeddings::TracyLlmEmbeddings( size_t length, size_t reserve )
{
unum::usearch::metric_punned_t metric( length );
m_index = unum::usearch::index_dense_t::make( metric );
if( reserve > 0 ) m_index.reserve( reserve );
}
TracyLlmEmbeddings::TracyLlmEmbeddings( const char* file, uint64_t hash )
{
FILE* f = fopen( file, "rb" );
if( !f ) throw std::runtime_error( "Failed to open embeddings file: " + std::string( file ) );
uint64_t fileHash, size;
if( fread( &fileHash, 1, sizeof( fileHash ), f ) != sizeof( fileHash ) ||
fread( &size, 1, sizeof( size ), f ) != sizeof( size ) )
{
fclose( f );
throw std::runtime_error( "Failed to read embeddings file: " + std::string( file ) );
}
if( fileHash != hash )
{
fclose( f );
throw std::runtime_error( "Embeddings file hash mismatch: " + std::string( file ) );
}
m_data.resize( size );
auto loaded = fread( m_data.data(), 1, m_data.size() * sizeof( uint32_t ), f ) == m_data.size() * sizeof( uint32_t );
fclose( f );
if( !loaded ) throw std::runtime_error( "Failed to read embeddings data from file: " + std::string( file ) );
const auto dbPath = file + std::string( ".db" );
unum::usearch::index_dense_t index;
auto res = index.view( dbPath.c_str() );
if( !res ) throw std::runtime_error( "Failed to load embeddings database from file: " + std::string( file ) );
m_index = std::move( index );
}
void TracyLlmEmbeddings::Add( uint32_t idx, const std::vector<float>& embedding )
{
m_index.add( m_data.size(), embedding.data() );
m_data.emplace_back( idx );
}
std::vector<TracyLlmEmbeddings::Result> TracyLlmEmbeddings::Search( const std::vector<float>& embedding, size_t k ) const
{
std::vector<Result> ret;
auto result = m_index.search( embedding.data(), k );
ret.reserve( result.size() );
for( size_t i=0; i<result.size(); i++ )
{
ret.emplace_back( Result {
.idx = result[i].member.key,
.distance = result[i].distance
} );
}
return ret;
}
bool TracyLlmEmbeddings::Save( const char* file, uint64_t hash ) const
{
const auto dbPath = file + std::string( ".db" );
if( !m_index.save( dbPath.c_str() ) ) return false;
FILE* f = fopen( file, "wb" );
if( !f )
{
unlink( dbPath.c_str() );
return false;
}
const uint64_t size = m_data.size();
if( fwrite( &hash, 1, sizeof( hash ), f ) != sizeof( hash ) ||
fwrite( &size, 1, sizeof( size ), f ) != sizeof( size ) ||
fwrite( m_data.data(), 1, m_data.size() * sizeof( uint32_t ), f ) != m_data.size() * sizeof( uint32_t ) )
{
fclose( f );
unlink( dbPath.c_str() );
unlink( file );
return false;
}
fclose( f );
return true;
}
}
@@ -0,0 +1,36 @@
#ifndef __TRACYLLMEMBEDDINGS_HPP__
#define __TRACYLLMEMBEDDINGS_HPP__
#include <stddef.h>
#include <usearch/index_dense.hpp>
#include <vector>
namespace tracy
{
class TracyLlmEmbeddings
{
public:
struct Result
{
size_t idx;
float distance;
};
explicit TracyLlmEmbeddings( size_t length, size_t reserve = 0 );
explicit TracyLlmEmbeddings( const char* file, uint64_t hash );
void Add( uint32_t idx, const std::vector<float>& embedding );
[[nodiscard]] std::vector<Result> Search( const std::vector<float>& embedding, size_t k ) const;
[[nodiscard]] uint32_t Get( size_t idx ) const { return m_data[idx]; }
bool Save( const char* file, uint64_t hash ) const;
private:
unum::usearch::index_dense_t m_index;
std::vector<uint32_t> m_data;
};
}
#endif
@@ -0,0 +1,807 @@
#include <algorithm>
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <libbase64.h>
#include <pugixml.hpp>
#include <string_view>
#include <tidy.h>
#include <tidybuffio.h>
#include <time.h>
#include "TracyConfig.hpp"
#include "TracyLlmApi.hpp"
#include "TracyLlmTools.hpp"
#include "TracyManualData.hpp"
#include "TracyStorage.hpp"
#include "TracyUtility.hpp"
#include "TracyWorker.hpp"
constexpr const char* NoNetworkAccess = "Internet access is disabled by the user. You may inform the user that he can enable it in the settings, so that you can use the tools to gather information.";
#define NetworkCheckString if( !m_netAccess ) return NoNetworkAccess
#define NetworkCheckReply if( !m_netAccess ) return { .reply = NoNetworkAccess }
namespace tracy
{
static std::string UrlEncode( const std::string& str )
{
std::string out;
out.reserve( str.size() * 3 );
constexpr char hex[] = "0123456789ABCDEF";
for( char c : str )
{
if( ( c >= 'a' && c <= 'z' ) ||
( c >= 'A' && c <= 'Z' ) ||
( c >= '0' && c <= '9' ) ||
c == '-' || c == '.' || c == '_' || c == '~' )
{
out += c;
}
else
{
out += '%';
out += hex[(unsigned char)c >> 4];
out += hex[(unsigned char)c & 0x0F];
}
}
return out;
}
static std::unique_ptr<pugi::xml_document> ParseHtml( const std::string& html )
{
TidyDoc td = tidyCreate();
tidyOptSetBool( td, TidyXhtmlOut, yes );
tidyOptSetBool( td, TidyLowerLiterals, yes );
tidyOptSetBool( td, TidyMark, no );
tidyOptSetBool( td, TidyHideComments, yes );
tidyOptSetBool( td, TidyShowWarnings, no );
tidyOptSetInt( td, TidyShowErrors, 0 );
tidyOptSetBool( td, TidyForceOutput, yes );
tidyParseString( td, html.c_str() );
TidyBuffer buf = {};
tidyBufInit( &buf );
tidyCleanAndRepair( td );
tidySaveBuffer( td, &buf );
auto tidy = std::string( (const char*)buf.bp );
tidyBufFree( &buf );
tidyRelease( td );
auto doc = std::make_unique<pugi::xml_document>();
if( !doc->load_string( tidy.c_str() ) ) return nullptr;
return doc;
}
TracyLlmTools::TracyLlmTools( Worker& worker, const TracyManualData& manual )
: m_worker( worker )
, m_manual( manual )
{
int idx = 0;
for( auto& chunk : m_manual.GetChunks() )
{
std::string hdr;
if( !chunk.section.empty() ) hdr += "Section " + chunk.section;
if( !chunk.title.empty() )
{
if( !chunk.section.empty() ) hdr += ": ";
hdr += chunk.title;
}
hdr += '\n';
for( auto& line : SplitLines( chunk.text.c_str(), chunk.text.size() ) )
{
if( line.empty() ) continue;
if( line == "---" || line == ":::" || line == "::: bclogo" ) continue;
m_chunkData.emplace_back( hdr + line, idx );
}
idx++;
}
}
TracyLlmTools::~TracyLlmTools()
{
CancelManualEmbeddings();
}
static const std::string& GetParam( const nlohmann::json& json, const char* name )
{
if( !json.contains( name ) ) throw std::runtime_error( "Error: missing parameter: " + std::string( name ) );
return json[name].get_ref<const std::string&>();
}
static uint32_t GetParamU32( const nlohmann::json& json, const char* name )
{
if( !json.contains( name ) ) throw std::runtime_error( "Error: missing parameter: " + std::string( name ) );
return json[name].get<uint32_t>();
}
#define Param(name) GetParam( json, name )
#define ParamU32(name) GetParamU32( json, name )
TracyLlmTools::ToolReply TracyLlmTools::HandleToolCalls( const nlohmann::json& json, TracyLlmApi& api, int contextSize, bool hasEmbeddingsModel )
{
m_ctxSize = contextSize;
try
{
auto name = json["tool"].get_ref<const std::string&>();
if( name == "search_wikipedia" )
{
return SearchWikipedia( Param( "query" ), Param( "language" ) );
}
else if( name == "get_wikipedia" )
{
return { .reply = GetWikipedia( Param( "page" ), Param( "language" ) ) };
}
else if( name == "get_dictionary" )
{
return { .reply = GetDictionary( Param( "word" ), Param( "language" ) ) };
}
else if( name == "search_web" )
{
return { .reply = SearchWeb( Param( "query" ) ) };
}
else if( name == "get_webpage" )
{
return { .reply = GetWebpage( Param( "url" ) ) };
}
else if( name == "user_manual" )
{
return { .reply = SearchManual( Param( "query" ), api, hasEmbeddingsModel ) };
}
else if( name == "source_file" )
{
return { .reply = SourceFile( Param( "file" ), ParamU32( "line" ) ) };
}
return { .reply = "Unknown tool call: " + name };
}
catch( const std::exception& e )
{
return { .reply = e.what() };
}
}
#undef Param
std::string TracyLlmTools::GetCurrentTime() const
{
auto t = time( nullptr );
auto tm = localtime( &t );
char buffer[64];
strftime( buffer, sizeof( buffer ), "%Y-%m-%d %H:%M:%S", tm );
return buffer;
}
TracyLlmTools::EmbeddingState TracyLlmTools::GetManualEmbeddingsState() const
{
std::lock_guard lock( m_lock );
return m_manualEmbeddingState;
}
void TracyLlmTools::SelectManualEmbeddings( const std::string& model )
{
std::lock_guard lock( m_lock );
assert( !m_manualEmbeddingState.inProgress );
if( m_manualEmbeddingState.done && m_manualEmbeddingState.model == model ) return;
auto cache = GetCachePath( model.c_str() );
try
{
m_manualEmbeddings = std::make_unique<TracyLlmEmbeddings>( cache, m_manual.GetHash() );
m_manualEmbeddingState = { .model = model, .done = true };
}
catch( std::exception& ) {}
}
void TracyLlmTools::BuildManualEmbeddings( const std::string& model, TracyLlmApi& api )
{
std::unique_lock lock( m_lock );
assert( !m_manualEmbeddingState.inProgress );
if( m_manualEmbeddingState.done && m_manualEmbeddingState.model == model ) return;
lock.unlock();
if( m_thread.joinable() ) m_thread.join();
assert( !m_cancel );
m_manualEmbeddingState = { .model = model, .inProgress = true };
m_thread = std::thread( [this, &api] { ManualEmbeddingsWorker( api ); } );
}
void TracyLlmTools::ManualEmbeddingsWorker( TracyLlmApi& api )
{
auto cache = GetCachePath( m_manualEmbeddingState.model.c_str() );
std::unique_lock lock( m_lock );
if( m_cancel )
{
m_manualEmbeddingState.inProgress = false;
m_manualEmbeddingState.done = false;
return;
}
lock.unlock();
size_t length;
{
nlohmann::json req;
req["input"] = "";
req["model"] = m_manualEmbeddingState.model;
nlohmann::json response;
api.Embeddings( req, response );
length = response["data"][0]["embedding"].size();
}
if( length == 0 )
{
lock.lock();
m_manualEmbeddingState.inProgress = false;
return;
}
const auto csz = m_chunkData.size();
m_manualEmbeddings = std::make_unique<TracyLlmEmbeddings>( length, csz );
constexpr size_t batchSize = 4;
std::vector<float> embeddings;
embeddings.reserve( length );
size_t i = 0;
while( i < csz )
{
lock.lock();
if( m_cancel )
{
m_manualEmbeddingState.inProgress = false;
m_manualEmbeddingState.done = false;
return;
}
m_manualEmbeddingState.progress = (float)i / csz;
lock.unlock();
const auto bsz = std::min( batchSize, csz - i );
std::vector<std::string> batch;
batch.reserve( bsz );
for( size_t j=0; j<bsz; j++ ) batch.emplace_back( "search_document: " + m_chunkData[i+j].first );
nlohmann::json req;
req["input"] = std::move( batch );
req["model"] = m_manualEmbeddingState.model;
nlohmann::json response;
if( !api.Embeddings( req, response ) )
{
m_manualEmbeddingState.inProgress = false;
m_manualEmbeddingState.done = false;
return;
}
auto& data = response["data"];
for( size_t j=0; j<bsz; j++ )
{
embeddings.clear();
for( auto& item : data[j]["embedding"] ) embeddings.emplace_back( item.get<float>() );
m_manualEmbeddings->Add( m_chunkData[i+j].second, embeddings );
}
i += bsz;
}
m_manualEmbeddings->Save( cache, m_manual.GetHash() );
lock.lock();
m_manualEmbeddingState.inProgress = false;
m_manualEmbeddingState.done = true;
}
void TracyLlmTools::CancelManualEmbeddings()
{
if( m_thread.joinable() )
{
m_lock.lock();
m_cancel = true;
m_lock.unlock();
m_thread.join();
m_cancel = false;
}
}
int TracyLlmTools::CalcMaxSize() const
{
if( m_ctxSize <= 0 ) return 32*1024;
// Limit the size of the response to avoid exceeding the context size
// Assume average token size is 4 bytes. Make space for 3 articles to be retrieved.
const auto maxSize = ( m_ctxSize * 4 ) / 3;
return maxSize;
}
std::string TracyLlmTools::TrimString( std::string&& str ) const
{
auto maxSize = CalcMaxSize();
if( str.size() < maxSize ) return str;
// Check if UTF-8 continuation byte will be removed, meaning an UTF-8 character is split in the middle
if( ( str[maxSize] & 0xC0 ) == 0xC0 )
{
// Remove the current UTF-8 character
while( maxSize > 0 && ( str[maxSize-1] & 0xC0 ) == 0xC0 ) maxSize--;
// Finally, remove the first byte of a UTF-8 multi-byte sequence
//assert( ( str[maxSize-1] & 0xC0 ) == 0x80 );
if( maxSize > 0 ) maxSize--;
}
return str.substr( 0, maxSize );
}
static size_t WriteFn( void* _data, size_t size, size_t num, void* ptr )
{
const auto data = (unsigned char*)_data;
const auto sz = size*num;
auto& v = *(std::string*)ptr;
v.append( (const char*)data, sz );
return sz;
}
std::string TracyLlmTools::FetchWebPage( const std::string& url, bool cache )
{
auto it = m_webCache.find( url );
if( it != m_webCache.end() ) return it->second;
auto curl = curl_easy_init();
if( !curl ) return "Error: Failed to initialize cURL";
std::string buf;
curl_easy_setopt( curl, CURLOPT_NOSIGNAL, 1L );
curl_easy_setopt( curl, CURLOPT_URL, url.c_str() );
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 10 );
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WriteFn );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &buf );
curl_easy_setopt( curl, CURLOPT_USERAGENT, s_config.llmUserAgent.c_str() );
auto res = curl_easy_perform( curl );
std::string response;
if( res != CURLE_OK )
{
response = "Error: " + std::string( curl_easy_strerror( res ) );
}
else
{
int64_t http_code = 0;
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code );
if( http_code != 200 )
{
response = "Error: HTTP " + std::to_string( http_code );
}
else
{
response = std::move( buf );
}
}
if( cache ) m_webCache.emplace( url, response );
curl_easy_cleanup( curl );
return response;
}
TracyLlmTools::ToolReply TracyLlmTools::SearchWikipedia( std::string query, const std::string& lang )
{
NetworkCheckReply;
std::ranges::replace( query, ' ', '+' );
const auto response = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/search/page?q=" + UrlEncode( query ) + "&limit=1" );
auto json = nlohmann::json::parse( response );
if( !json.contains( "pages" ) ) return { .reply = "No results found" };
auto& page = json["pages"];
if( page.size() == 0 ) return { .reply = "No results found" };
auto& page0 = page[0];
if( !page0.contains( "key" ) ) return { .reply = "No results found" };
const auto key = page0["key"].get_ref<const std::string&>();
auto summary = FetchWebPage( "https://" + lang + ".wikipedia.org/api/rest_v1/page/summary/" + key );
auto summaryJson = nlohmann::json::parse( summary );
if( !summaryJson.contains( "title" ) ) return { .reply = "No results found" };
nlohmann::json output;
output["key"] = key;
output["title"] = summaryJson["title"];
if( summaryJson.contains( "description" ) ) output["description"] = summaryJson["description"];
output["extract"] = summaryJson["extract"];
std::string image;
if( summaryJson.contains( "thumbnail" ) )
{
auto& thumb = summaryJson["thumbnail"];
if( thumb.contains( "source" ) )
{
auto imgData = FetchWebPage( thumb["source"].get_ref<const std::string&>() );
if( !imgData.empty() && imgData[0] != '<' && strncmp( imgData.c_str(), "Error:", 6 ) != 0 )
{
size_t b64sz = ( ( 4 * imgData.size() / 3 ) + 3 ) & ~3;
char* b64 = new char[b64sz+1];
b64[b64sz] = 0;
size_t outSz;
base64_encode( (const char*)imgData.data(), imgData.size(), b64, &outSz, 0 );
image = std::string( b64, outSz );
delete[] b64;
}
}
}
const auto reply = output.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace );
return { .reply = reply, .image = image };
}
std::string TracyLlmTools::GetWikipedia( std::string page, const std::string& lang )
{
NetworkCheckString;
std::ranges::replace( page, ' ', '_' );
auto res = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/page/" + page );
return TrimString( std::move( res ) );
}
std::string TracyLlmTools::GetDictionary( std::string word, const std::string& lang )
{
NetworkCheckString;
std::ranges::replace( word, ' ', '+' );
const auto response = FetchWebPage( "https://" + lang + ".wiktionary.org/w/rest.php/v1/search/page?q=" + UrlEncode( word ) + "&limit=1" );
auto json = nlohmann::json::parse( response );
if( !json.contains( "pages" ) ) return "No results found";
auto& page = json["pages"];
if( page.size() == 0 ) return "No results found";
auto& page0 = page[0];
if( !page0.contains( "key" ) ) return "No results found";
const auto key = page0["key"].get_ref<const std::string&>();
auto res = FetchWebPage( "https://" + lang + ".wiktionary.org/w/rest.php/v1/page/" + key );
return TrimString( std::move( res ) );
}
static std::string RemoveNewline( std::string str )
{
std::erase( str, '\r' );
std::ranges::replace( str, '\n', ' ' );
return str;
}
std::string TracyLlmTools::SearchWeb( std::string query )
{
NetworkCheckString;
query = UrlEncode( query );
if( !s_config.llmSearchApiKey.empty() && !s_config.llmSearchIdentifier.empty() )
{
const auto response = FetchWebPage( "https://customsearch.googleapis.com/customsearch/v1?key=" + s_config.llmSearchApiKey + "&cx=" + s_config.llmSearchIdentifier + "&q=" + query );
try
{
auto json = nlohmann::json::parse( response );
if( json.contains( "items" ) && json["items"].size() != 0 )
{
nlohmann::json results;
for( size_t i = 0; i < json["items"].size(); i++ )
{
auto& item = json["items"][i];
nlohmann::json result;
result["title"] = RemoveNewline( item["title"].get_ref<const std::string&>() );
result["snippet"] = RemoveNewline( item["snippet"].get_ref<const std::string&>() );
result["url"] = RemoveNewline( item["link"].get_ref<const std::string&>() );
results[i] = result;
}
return results.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace );
}
}
catch( const nlohmann::json::exception& e ) {}
}
const auto response = FetchWebPage( "https://lite.duckduckgo.com/lite?q=" + query );
auto doc = ParseHtml( response );
if( !doc ) return "Error: Failed to parse HTML";
const auto titles = doc->select_nodes( "//a[@class='result-link']" );
const auto snippets = doc->select_nodes( "//td[@class='result-snippet']" );
const auto urls = doc->select_nodes( "//span[@class='link-text']" );
const auto sz = titles.size();
if( sz != snippets.size() || sz != urls.size() )
{
return "Error: Failed to parse HTML";
}
nlohmann::json json;
for( size_t i = 0; i < sz; i++ )
{
auto title = titles[i].node();
auto snippet = snippets[i].node();
auto url = urls[i].node();
nlohmann::json result;
result["title"] = RemoveNewline( title.text().as_string() );
result["snippet"] = RemoveNewline( snippet.text().as_string() );
result["url"] = RemoveNewline( url.text().as_string() );
json[i] = result;
}
return json.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace );
}
static void RemoveTag( pugi::xml_node node, const char* tag )
{
auto nodes = node.select_nodes( tag );
for( auto& n : nodes )
{
auto node = n.node();
if( node.parent() ) node.parent().remove_child( node );
}
}
static void RemoveAttributes( pugi::xml_node node, const char* tag, std::vector<const char*> valid = {} )
{
auto nodes = node.select_nodes( tag );
if( valid.empty() )
{
for( auto& n : nodes ) n.node().remove_attributes();
}
else
{
unordered_flat_set<std::string> toRemove;
for( auto& n : nodes )
{
toRemove.clear();
auto node = n.node();
for( auto& attr : node.attributes() ) toRemove.emplace( attr.name() );
for( auto& validAttr : valid )
{
auto it = toRemove.find( validAttr );
if( it != toRemove.end() ) toRemove.erase( it );
}
for( auto& attr : toRemove )
{
while( node.remove_attribute( attr.c_str() ) );
}
}
}
}
static void RemoveEmptyTags( pugi::xml_node node )
{
auto child = node.first_child();
while( child )
{
auto next = child.next_sibling();
auto type = child.type();
if( child.type() == pugi::xml_node_type::node_element )
{
RemoveEmptyTags( child );
if( !child.first_child() && child.text().empty() ) { node.remove_child( child ); }
}
child = next;
}
}
struct xml_writer : public pugi::xml_writer
{
explicit xml_writer( std::string& str ) : str( str ) {}
void write( const void* data, size_t size ) override { str.append( (const char*)data, size ); }
std::string& str;
};
std::string TracyLlmTools::GetWebpage( const std::string& url )
{
NetworkCheckString;
auto data = FetchWebPage( url, false );
auto doc = ParseHtml( data );
if( !doc ) return "Error: Failed to parse HTML";
auto body = doc->select_node( "/html/body" );
if( !body ) return "Error: Failed to parse HTML";
auto node = body.node();
RemoveTag( node, "//script" );
RemoveTag( node, "//style" );
RemoveTag( node, "//link" );
RemoveTag( node, "//meta" );
RemoveTag( node, "//svg" );
RemoveTag( node, "//template" );
RemoveTag( node, "//ins" );
RemoveAttributes( node, "//body" );
RemoveAttributes( node, "//div" );
RemoveAttributes( node, "//p" );
RemoveAttributes( node, "//a", { "href", "title" } );
RemoveAttributes( node, "//img", { "src", "alt" } );
RemoveAttributes( node, "//li" );
RemoveAttributes( node, "//ul", { "role" } );
RemoveAttributes( node, "//td", { "colspan" } );
RemoveAttributes( node, "//tr" );
RemoveAttributes( node, "//hr" );
RemoveAttributes( node, "//th", { "colspan", "rowspan" } );
RemoveAttributes( node, "//table", { "role" } );
RemoveAttributes( node, "//col" );
RemoveAttributes( node, "//span" );
RemoveAttributes( node, "//pre" );
RemoveAttributes( node, "//button" );
RemoveAttributes( node, "//label", { "title" } );
RemoveAttributes( node, "//input", { "type", "placeholder" } );
RemoveAttributes( node, "//form", { "action", "method" } );
RemoveAttributes( node, "//textarea", { "placeholder" } );
RemoveAttributes( node, "//dialog" );
RemoveAttributes( node, "//header" );
RemoveAttributes( node, "//footer" );
RemoveAttributes( node, "//section" );
RemoveAttributes( node, "//article" );
RemoveAttributes( node, "//aside" );
RemoveAttributes( node, "//figure" );
RemoveAttributes( node, "//main" );
RemoveAttributes( node, "//summary" );
RemoveAttributes( node, "//details" );
RemoveAttributes( node, "//nav" );
RemoveAttributes( node, "//bdi" );
RemoveAttributes( node, "//time", { "datetime" } );
RemoveAttributes( node, "//h1" );
RemoveAttributes( node, "//h2" );
RemoveAttributes( node, "//h3" );
RemoveAttributes( node, "//h4" );
RemoveAttributes( node, "//h5" );
RemoveAttributes( node, "//h6" );
RemoveAttributes( node, "//strong" );
RemoveAttributes( node, "//em" );
RemoveAttributes( node, "//i" );
RemoveAttributes( node, "//b" );
RemoveAttributes( node, "//u" );
RemoveEmptyTags( node );
std::string response;
xml_writer writer( response );
body.node().print( writer, nullptr, pugi::format_raw | pugi::format_no_declaration | pugi::format_no_escapes );
RemoveNewline( response );
auto it = std::ranges::unique( response, []( char a, char b ) { return ( a == ' ' || a == '\t' ) && ( b == ' ' || b == '\t' ); } );
response.erase( it.begin(), it.end() );
response = TrimString( std::move( response ) );
m_webCache.emplace( url, response );
return response;
}
std::string TracyLlmTools::SearchManual( const std::string& query, TracyLlmApi& api, bool hasEmbeddingsModel )
{
if( !hasEmbeddingsModel ) return "Searching the user manual requires vector embeddings model to be selected. You must inform the user that he should download such a model using their LLM provider software, so you can use this tool.";
if( !m_manualEmbeddingState.done ) return "User manual embedding vectors are not calculated. You must inform the user that he should click the \"Learn manual\" button, so you can use this tool.";
constexpr size_t MaxSearchResults = 20;
constexpr size_t MaxOutputChunks = 10;
nlohmann::json req;
req["input"] = "search_query: " + query;
req["model"] = m_manualEmbeddingState.model;
nlohmann::json response;
if( !api.Embeddings( req, response, true ) ) return "Error: Failed to get embedding for the query";
auto& embedding = response["data"][0]["embedding"];
if( embedding.empty() ) return "Error: Failed to get embedding for the query";
std::vector<float> vec;
vec.reserve( embedding.size() );
for( auto& item : embedding ) vec.emplace_back( item.get<float>() );
auto results = m_manualEmbeddings->Search( vec, MaxSearchResults );
std::ranges::sort( results, []( const auto& a, const auto& b ) { return a.distance < b.distance; } );
std::vector<std::pair<int, float>> chunks;
chunks.reserve( results.size() );
for( auto& item : results )
{
const auto chunk = m_manualEmbeddings->Get( item.idx );
if( std::ranges::find_if( chunks, [chunk]( const auto& v ) { return v.first == chunk; } ) == chunks.end() ) chunks.emplace_back( chunk, item.distance );
}
if( chunks.size() > MaxOutputChunks ) chunks.resize( MaxOutputChunks );
auto& manualChunks = m_manual.GetChunks();
const auto maxSize = CalcMaxSize();
int totalSize = 0;
int idx;
for( idx = 0; idx < chunks.size(); idx++ )
{
totalSize += manualChunks[chunks[idx].first].text.size();
if( totalSize >= maxSize ) break;
}
if( idx < chunks.size() ) chunks.resize( idx );
nlohmann::json json;
for( auto& chunk : chunks )
{
auto& m = manualChunks[chunk.first];
nlohmann::json r;
r["distance"] = chunk.second;
r["content"] = m.text;
r["section"] = m.section;
r["title"] = m.title;
r["parents"] = m.parents;
json.emplace_back( std::move( r ) );
}
return json.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace );
}
std::string TracyLlmTools::SourceFile( const std::string& file, uint32_t line ) const
{
if( line == 0 ) return "Error: Source file line number must be greater than 0.";
const auto data = m_worker.GetSourceFileFromCache( file.c_str() );
if( data.data == nullptr ) return "Error: Source file not available.";
auto lines = SplitLines( data.data, data.len );
if( line > lines.size() ) return "Error: Source file line " + std::to_string( line ) + " is out of range. The file has only " + std::to_string( lines.size() ) + " lines.";
line--;
const auto maxSize = CalcMaxSize();
int size = lines[line].size() + 1;
uint32_t minLine = line;
uint32_t maxLine = line+1;
while( minLine > 0 || maxLine < lines.size() )
{
if( minLine > 0 )
{
size += lines[minLine].size() * 3 + 30;
if( size >= maxSize ) break;
minLine--;
}
if( maxLine < lines.size() )
{
size += lines[maxLine].size() * 3 + 30;
if( size >= maxSize ) break;
maxLine++;
}
}
nlohmann::json json = {
{ "file", file },
{ "contents", nlohmann::json::array() }
};
for( uint32_t i = minLine; i < maxLine; i++ )
{
nlohmann::json lineJson = {
{ "line", i + 1 },
{ "text", lines[i] }
};
json["contents"].emplace_back( std::move( lineJson ) );
}
return json.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace );
}
}
@@ -0,0 +1,86 @@
#ifndef __TRACYLLMTOOLS_HPP__
#define __TRACYLLMTOOLS_HPP__
#include <nlohmann/json.hpp>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "TracyLlmEmbeddings.hpp"
#include "tracy_robin_hood.h"
class EmbedData;
namespace tracy
{
class TracyLlmApi;
class TracyManualData;
class Worker;
class TracyLlmTools
{
public:
struct ToolReply
{
std::string reply;
std::string image;
};
struct EmbeddingState
{
std::string model;
bool done = false;
bool inProgress = false;
float progress = 0;
};
TracyLlmTools( Worker& worker, const TracyManualData& manual );
~TracyLlmTools();
ToolReply HandleToolCalls( const nlohmann::json& json, TracyLlmApi& api, int contextSize, bool hasEmbeddingsModel );
std::string GetCurrentTime() const;
[[nodiscard]] EmbeddingState GetManualEmbeddingsState() const;
void SelectManualEmbeddings( const std::string& model );
void BuildManualEmbeddings( const std::string& model, TracyLlmApi& api );
void CancelManualEmbeddings();
bool m_netAccess = true;
private:
[[nodiscard]] int CalcMaxSize() const;
[[nodiscard]] std::string TrimString( std::string&& str ) const;
std::string FetchWebPage( const std::string& url, bool cache = true );
ToolReply SearchWikipedia( std::string query, const std::string& lang );
std::string GetWikipedia( std::string page, const std::string& lang );
std::string GetDictionary( std::string word, const std::string& lang );
std::string SearchWeb( std::string query );
std::string GetWebpage( const std::string& url );
std::string SearchManual( const std::string& query, TracyLlmApi& api, bool hasEmbeddingsModel );
std::string SourceFile( const std::string& file, uint32_t line ) const;
void ManualEmbeddingsWorker( TracyLlmApi& api );
unordered_flat_map<std::string, std::string> m_webCache;
int m_ctxSize;
mutable std::mutex m_lock;
std::thread m_thread;
bool m_cancel = false;
EmbeddingState m_manualEmbeddingState;
std::unique_ptr<TracyLlmEmbeddings> m_manualEmbeddings;
std::vector<std::pair<std::string, uint32_t>> m_chunkData;
Worker& m_worker;
const TracyManualData& m_manual;
};
}
#endif
@@ -0,0 +1,118 @@
#include "TracyEmbed.hpp"
#include "TracyManualData.hpp"
#define XXH_INLINE_ALL
#include "tracy_xxhash.h"
#include "data/Manual.hpp"
namespace tracy
{
TracyManualData::TracyManualData()
{
auto data = Unembed( Manual );
m_hash = XXH3_64bits( data->data(), data->size() );
std::string_view manual( data->data(), data->size() );
const auto sz = (int)data->size();
std::vector<int> levels = { 0 };
std::vector<std::string> chapterNames = { "Title Page" };
int manualChunkPos = 0;
int pos = 0;
while( pos < sz )
{
std::string::size_type next = pos;
for(;;)
{
next = manual.find( '\n', next );
if( next == std::string_view::npos )
{
next = sz;
break;
}
if( next+1 >= sz || manual[next+1] == '\n' ) break;
next++;
}
if( next != pos )
{
std::string_view line( manual.data() + pos, next - pos );
if( line[0] == '#' )
{
if( manualChunkPos != pos )
{
AddManualChunk( manual, manualChunkPos, pos, levels, chapterNames );
manualChunkPos = pos;
}
int level = 1;
if( line.find( ".unnumbered}" ) == std::string_view::npos )
{
while( level < line.size() && line[level] == '#' ) level++;
if( level != levels.size() )
{
levels.resize( level, 0 );
chapterNames.resize( level );
}
levels[level - 1]++;
}
chapterNames[level - 1] = line.substr( level + 1 );
}
}
pos = next + 1;
while( pos < sz && manual[pos] == '\n' ) pos++;
}
if( manualChunkPos != pos )
{
AddManualChunk( manual, manualChunkPos, pos, levels, chapterNames );
}
}
void TracyManualData::AddManualChunk( const std::string_view& manual, int start, int end, const std::vector<int>& levels, const std::vector<std::string>& chapterNames )
{
while( manual[start] != '\n' ) start++;
while( manual[start] == '\n' ) start++;
while( manual[end-1] == '\n' ) end--;
if( end > start )
{
std::string text, section, title, parents;
text = std::string( manual.data() + start, end - start );
if( levels[0] != 0 )
{
section = std::to_string( levels[0] );
for( size_t i=1; i<levels.size(); i++ ) section += "." + std::to_string( levels[i] );
}
if( levels.size() == 1 )
{
title = chapterNames[0];
}
else
{
title = chapterNames[levels.size()-1];
parents = chapterNames[0];
for( size_t i=1; i<levels.size() - 1; i++ ) parents += " > " + chapterNames[i];
}
std::string link;
auto linkpos = title.find( '{' );
if( linkpos != std::string::npos )
{
link = title.substr( linkpos + 1, title.size() - linkpos - 2 );
title = title.substr( 0, linkpos - 1 );
if( link.ends_with( ".unnumbered" ) ) link = link.substr( 0, link.size() - 12 );
}
m_manualChunks.emplace_back( ManualChunk {
.text = std::move( text ),
.section = std::move( section ),
.title = std::move( title ),
.parents = std::move( parents ),
.link = std::move( link ),
.level = (int)levels.size() - 1
} );
}
}
}
@@ -0,0 +1,39 @@
#ifndef __TRACYMANUALDATA_HPP__
#define __TRACYMANUALDATA_HPP__
#include <stdint.h>
#include <string>
#include <string_view>
#include <vector>
namespace tracy
{
class TracyManualData
{
public:
struct ManualChunk
{
std::string text;
std::string section;
std::string title;
std::string parents;
std::string link;
int level;
};
TracyManualData();
[[nodiscard]] const std::vector<ManualChunk>& GetChunks() const { return m_manualChunks; }
[[nodiscard]] uint64_t GetHash() const { return m_hash; }
private:
void AddManualChunk( const std::string_view& manual, int manualChunkPos, int pos, const std::vector<int>& levels, const std::vector<std::string>& chapterNames );
std::vector<ManualChunk> m_manualChunks;
uint64_t m_hash;
};
}
#endif
@@ -0,0 +1,322 @@
#include <array>
#include <md4c.h>
#include <string>
#include <string.h>
#include <vector>
#include "TracyMarkdown.hpp"
#include "TracyMouse.hpp"
#include "TracyImGui.hpp"
#include "TracySourceContents.hpp"
#include "TracyWeb.hpp"
#include "../Fonts.hpp"
namespace tracy
{
class MarkdownContext
{
struct List
{
bool tight;
int num;
};
public:
int EnterBlock( MD_BLOCKTYPE type, void* detail )
{
switch( type )
{
case MD_BLOCK_P:
Separate();
glue = false;
break;
case MD_BLOCK_QUOTE:
Separate();
ImGui::Indent();
break;
case MD_BLOCK_UL:
Separate();
lists.emplace_back( List {
.tight = ((MD_BLOCK_UL_DETAIL*)detail)->is_tight != 0,
.num = -1
} );
ImGui::Indent();
break;
case MD_BLOCK_OL:
Separate();
lists.emplace_back( List {
.tight = ((MD_BLOCK_OL_DETAIL*)detail)->is_tight != 0,
.num = (int)((MD_BLOCK_OL_DETAIL*)detail)->start
} );
ImGui::Indent();
break;
case MD_BLOCK_LI:
{
Separate();
auto& l = lists.back();
if( l.num < 0 )
{
ImGui::Bullet();
}
else
{
ImGui::Text( "%d.", l.num++ );
}
glue = false;
ImGui::SameLine();
ImGui::BeginGroup();
break;
}
case MD_BLOCK_HR:
Separate();
ImGui::Separator();
break;
case MD_BLOCK_H:
Separate();
header = ((MD_BLOCK_H_DETAIL*)detail)->level;
glue = false;
break;
case MD_BLOCK_CODE:
{
char tmp[64];
sprintf( tmp, "##code%d", idx++ );
Separate();
ImGui::PushStyleColor( ImGuiCol_FrameBg, ImVec4( 0, 0, 0, 0.2f ) );
ImGui::BeginChild( tmp, ImVec2( 0, 0 ), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY );
codeBlock = true;
}
default:
break;
}
return 0;
}
int LeaveBlock( MD_BLOCKTYPE type, void* detail )
{
switch( type )
{
case MD_BLOCK_P:
separate = true;
break;
case MD_BLOCK_QUOTE:
ImGui::Unindent();
separate = true;
break;
case MD_BLOCK_UL:
case MD_BLOCK_OL:
ImGui::Unindent();
if( !lists.empty() ) lists.pop_back();
separate = lists.empty() || !lists.back().tight;
break;
case MD_BLOCK_LI:
{
ImGui::EndGroup();
auto& l = lists.back();
if( !l.tight ) separate = true;
break;
}
case MD_BLOCK_HR:
separate = true;
break;
case MD_BLOCK_H:
header = 0;
separate = true;
break;
case MD_BLOCK_CODE:
ImGui::EndChild();
ImGui::PopStyleColor();
separate = true;
codeBlock = false;
break;
default:
break;
}
return 0;
}
int EnterSpan( MD_SPANTYPE type, void* detail )
{
switch( type )
{
case MD_SPAN_EM:
italic++;
break;
case MD_SPAN_STRONG:
bold++;
break;
case MD_SPAN_A:
link = std::string( ((MD_SPAN_A_DETAIL*)detail)->href.text, ((MD_SPAN_A_DETAIL*)detail)->href.size );
break;
default:
break;
}
return 0;
}
int LeaveSpan( MD_SPANTYPE type, void* detail )
{
switch( type )
{
case MD_SPAN_EM:
italic--;
break;
case MD_SPAN_STRONG:
bold--;
break;
case MD_SPAN_A:
link.clear();
break;
default:
break;
}
return 0;
}
int Text( MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size )
{
constexpr std::array FontSizes = {
1.f,
2.05f,
1.9f,
1.75f,
1.6f,
1.45f,
1.3f,
1.15f
};
switch( type )
{
case MD_TEXT_NORMAL:
case MD_TEXT_ENTITY:
case MD_TEXT_HTML:
{
auto font = g_fonts.normal;
if( bold > 0 )
{
font = italic > 0 ? g_fonts.boldItalic : g_fonts.bold;
}
else if( italic > 0 )
{
font = g_fonts.italic;
}
ImGui::PushFont( font, FontNormal * FontSizes[header] );
if( !link.empty() ) ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0.55f, 0.55f, 1.f, 1.f ) );
Glue();
const auto hovered = PrintTextWrapped( text, text + size );
ImGui::PopFont();
if( !link.empty() )
{
ImGui::PopStyleColor();
if( hovered )
{
ImGui::SetMouseCursor( ImGuiMouseCursor_Hand );
ImGui::BeginTooltip();
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 1.f, 1.f, 1.f ) );
ImGui::TextUnformatted( link.c_str() );
ImGui::PopStyleColor();
ImGui::EndTooltip();
if( IsMouseClicked( ImGuiMouseButton_Left ) ) OpenWebpage( link.c_str() );
}
}
break;
}
case MD_TEXT_NULLCHAR:
Glue();
PrintTextWrapped( "\xEF\xBF\xBD" );
break;
case MD_TEXT_BR:
glue = false;
break;
case MD_TEXT_SOFTBR:
Glue();
PrintTextWrapped( " " );
break;
case MD_TEXT_CODE:
case MD_TEXT_LATEXMATH:
if( size == 1 && *text == '\n' )
{
glue = false;
}
else
{
Glue();
ImGui::PushFont( g_fonts.mono, FontNormal * FontSizes[header] );
if( codeBlock )
{
SourceContents sc;
sc.Parse( text, size );
PrintSource( sc.get() );
}
else
{
PrintTextWrapped( text, text + size );
}
ImGui::PopFont();
}
break;
}
first = false;
return 0;
}
private:
void Glue()
{
if( glue ) ImGui::SameLine( 0, 0 );
else glue = true;
}
void Separate()
{
if( !separate ) return;
ImGui::Dummy( ImVec2( 0, ImGui::GetTextLineHeight() * 0.5f ) );
separate = false;
}
int bold = 0;
int italic = 0;
int header = 0;
bool glue = false;
bool separate = false;
bool first = true;
bool codeBlock = false;
int idx = 0;
std::vector<List> lists;
std::string link;
};
Markdown::Markdown()
: m_parser( new MD_PARSER() )
{
memset( m_parser, 0, sizeof( MD_PARSER ) );
m_parser->flags = MD_FLAG_COLLAPSEWHITESPACE | MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_NOHTML;
m_parser->enter_block = []( MD_BLOCKTYPE type, void* detail, void* ud ) -> int { return ((MarkdownContext*)ud)->EnterBlock( type, detail ); };
m_parser->leave_block = []( MD_BLOCKTYPE type, void* detail, void* ud ) -> int { return ((MarkdownContext*)ud)->LeaveBlock( type, detail ); };
m_parser->enter_span = []( MD_SPANTYPE type, void* detail, void* ud ) -> int { return ((MarkdownContext*)ud)->EnterSpan( type, detail ); };
m_parser->leave_span = []( MD_SPANTYPE type, void* detail, void* ud ) -> int { return ((MarkdownContext*)ud)->LeaveSpan( type, detail ); };
m_parser->text = []( MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* ud ) -> int { return ((MarkdownContext*)ud)->Text( type, text, size ); };
}
Markdown::~Markdown()
{
delete m_parser;
}
void Markdown::Print( const char* str, size_t size )
{
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( ImGui::GetStyle().ItemSpacing.x, 0.0f ) );
MarkdownContext md;
md_parse( str, size, m_parser, &md );
ImGui::PopStyleVar();
}
}
@@ -0,0 +1,25 @@
#ifndef __TRACYMARKDOWN_HPP__
#define __TRACYMARKDOWN_HPP__
#include <stddef.h>
struct MD_PARSER;
namespace tracy
{
class Markdown
{
public:
Markdown();
~Markdown();
void Print( const char* str, size_t size );
private:
MD_PARSER* m_parser;
};
}
#endif
@@ -5,6 +5,9 @@ namespace tracy
{
constexpr ProtocolHistory_t ProtocolHistoryArr[] = {
{ 76, FileVersion( 0, 13, 0 ) },
{ 74, FileVersion( 0, 12, 0 ), FileVersion( 0, 12, 2 ) },
{ 69, FileVersion( 0, 11, 1 ) },
{ 66, FileVersion( 0, 11, 0 ) },
{ 64, FileVersion( 0, 10, 0 ) },
{ 63, FileVersion( 0, 9, 0 ), FileVersion( 0, 9, 1 ) },
@@ -9,8 +9,9 @@ SourceContents::SourceContents()
: m_file( nullptr )
, m_fileStringIdx( 0 )
, m_data( nullptr )
, m_dataBuf( nullptr )
, m_dataSize( 0 )
, m_dataBuf( nullptr )
, m_dataBufSize( 0 )
{
}
@@ -44,14 +45,15 @@ void SourceContents::Parse( const char* fileName, const Worker& worker, const Vi
fseek( f, 0, SEEK_END );
sz = ftell( f );
fseek( f, 0, SEEK_SET );
if( sz > m_dataSize )
if( sz > m_dataBufSize )
{
delete[] m_dataBuf;
m_dataBuf = new char[sz];
m_dataSize = sz;
m_dataBufSize = sz;
}
fread( m_dataBuf, 1, sz, f );
m_data = m_dataBuf;
m_dataSize = sz;
fclose( f );
}
else
@@ -67,13 +69,14 @@ void SourceContents::Parse( const char* fileName, const Worker& worker, const Vi
void SourceContents::Parse( const char* source )
{
if( source == m_data ) return;
Parse( source, strlen( source ) );
}
const size_t len = strlen( source );
void SourceContents::Parse( const char* source, size_t len )
{
m_file = nullptr;
m_fileStringIdx = 0;
m_data = source;
m_dataBuf = nullptr;
m_dataSize = len;
Tokenize( source, len );
}
@@ -21,6 +21,7 @@ public:
void Parse( const char* fileName, const Worker& worker, const View& view );
void Parse( const char* source );
void Parse( const char* source, size_t len );
const std::vector<Tokenizer::Line>& get() const { return m_lines; }
bool empty() const { return m_lines.empty(); }
@@ -38,9 +39,11 @@ private:
uint32_t m_fileStringIdx;
const char* m_data;
char* m_dataBuf;
size_t m_dataSize;
char* m_dataBuf;
size_t m_dataBufSize;
std::vector<Tokenizer::Line> m_lines;
};
@@ -1,5 +1,6 @@
#include <ctype.h>
#include <inttypes.h>
#include <nlohmann/json.hpp>
#include <sstream>
#include <stdio.h>
@@ -8,16 +9,18 @@
#include "imgui.h"
#include "TracyCharUtil.hpp"
#include "TracyColor.hpp"
#include "TracyConfig.hpp"
#include "TracyFileselector.hpp"
#include "TracyFilesystem.hpp"
#include "TracyImGui.hpp"
#include "TracyMicroArchitecture.hpp"
#include "TracyPrint.hpp"
#include "TracySort.hpp"
#include "TracySourceView.hpp"
#include "TracyUtility.hpp"
#include "TracyView.hpp"
#include "TracyWorker.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
#include "IconsFontAwesome6.h"
@@ -242,9 +245,7 @@ float SourceView::CalcJumpSeparation( float scale )
SourceView::SourceView()
: m_font( nullptr )
, m_smallFont( nullptr )
, m_symAddr( 0 )
: m_symAddr( 0 )
, m_targetAddr( 0 )
, m_targetLine( 0 )
, m_selectedLine( 0 )
@@ -712,7 +713,7 @@ bool SourceView::Disassemble( uint64_t symAddr, const Worker& worker )
rval = cs_open( CS_ARCH_ARM, CS_MODE_ARM, &handle );
break;
case CpuArchArm64:
rval = cs_open( CS_ARCH_ARM64, CS_MODE_ARM, &handle );
rval = cs_open( CS_ARCH_AARCH64, CS_MODE_ARM, &handle );
break;
default:
assert( false );
@@ -777,9 +778,9 @@ bool SourceView::Disassemble( uint64_t symAddr, const Worker& worker )
}
break;
case CpuArchArm64:
if( detail.arm64.op_count == 1 && detail.arm64.operands[0].type == ARM64_OP_IMM )
if( detail.aarch64.op_count == 1 && detail.aarch64.operands[0].type == AARCH64_OP_IMM )
{
jumpAddr = (uint64_t)detail.arm64.operands[0].imm;
jumpAddr = (uint64_t)detail.aarch64.operands[0].imm;
}
break;
default:
@@ -864,18 +865,18 @@ bool SourceView::Disassemble( uint64_t symAddr, const Worker& worker )
}
break;
case CpuArchArm64:
for( uint8_t i=0; i<detail.arm64.op_count; i++ )
for( uint8_t i=0; i<detail.aarch64.op_count; i++ )
{
uint8_t type = 0;
switch( detail.arm64.operands[i].type )
switch( detail.aarch64.operands[i].type )
{
case ARM64_OP_IMM:
case AARCH64_OP_IMM:
type = 0;
break;
case ARM64_OP_REG:
case AARCH64_OP_REG:
type = 1;
break;
case ARM64_OP_MEM:
case AARCH64_OP_MEM:
type = 2;
break;
default:
@@ -1059,7 +1060,7 @@ void SourceView::Render( Worker& worker, View& view )
if( m_symAddr == 0 )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
if( ClipboardButton() )
{
std::ostringstream stream;
@@ -1072,7 +1073,7 @@ void SourceView::Render( Worker& worker, View& view )
ImGui::SameLine();
if( m_source.filename() )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextFocused( ICON_FA_FILE " File:", m_source.filename() );
ImGui::PopFont();
}
@@ -1158,7 +1159,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
const auto shortenName = view.GetShortenName();
auto sym = worker.GetSymbolData( m_symAddr );
assert( sym );
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
if( ButtonDisablable( " " ICON_FA_CARET_LEFT " ", m_historyCursor <= 1 ) )
{
@@ -1191,12 +1192,12 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
TextFocused( ICON_FA_PUZZLE_PIECE " Symbol:", normalized );
ImGui::PopFont();
TooltipNormalizedName( symName, normalized );
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
}
}
else
{
char tmp[16];
char tmp[32];
sprintf( tmp, "0x%" PRIx64, m_baseAddr );
TextFocused( ICON_FA_PUZZLE_PIECE " Symbol:", tmp );
}
@@ -1214,7 +1215,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
TextFocused( ICON_FA_PUZZLE_PIECE " Symbol:", normalized );
ImGui::PopFont();
TooltipNormalizedName( symName, normalized );
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
}
}
ImGui::SameLine();
@@ -1233,12 +1234,18 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled( "(+%s inlined functions)", RealToString( inlineCount ) );
}
}
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
if( ImGui::SmallButton( ICON_FA_ARROW_DOWN_SHORT_WIDE " Entry stacks" ) ) view.ShowSampleParents( m_symAddr, !m_calcInlineStats );
if( inlineList )
{
if( m_calcInlineStats )
{
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
TextColoredUnformatted( ImVec4( 1.f, 1.f, 0.2f, 1.f ), ICON_FA_TRIANGLE_EXCLAMATION );
TooltipIfHovered( "Context is limited to an inline function" );
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
TextColoredUnformatted( ImVec4( 1.f, 1.f, 0.2f, 1.f ), ICON_FA_TRIANGLE_EXCLAMATION );
TooltipIfHovered( "Context is limited to an inline function" );
}
}
@@ -1246,7 +1253,7 @@ void SourceView::RenderSymbolView( Worker& worker, View& view )
const auto imageName = worker.GetString( sym->imageName );
char tmp[1024];
snprintf( tmp, 1024, "%s 0x%" PRIx64, imageName, m_baseAddr );
ImGui::SameLine( ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize( tmp ).x - ImGui::GetStyle().FramePadding.x * 2 );
ImGui::SameLine( ImGui::GetContentRegionAvail().x + ImGui::GetCursorPos().x - ImGui::CalcTextSize( tmp ).x );
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( tmp );
}
@@ -1848,7 +1855,7 @@ static uint32_t GetGoodnessColor( float inRatio )
void SourceView::RenderSymbolSourceView( const AddrStatData& as, Worker& worker, const View& view, bool hasInlines )
{
const auto scale = GetScale();
if( hasInlines && !m_calcInlineStats && ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( view.m_statRange.active && worker.GetSamplesForSymbol( m_baseAddr ) ) )
if( hasInlines && !m_calcInlineStats && ( ( as.ipTotalAsm.local + as.ipTotalAsm.ext ) > 0 || ( view.m_statRange.active && worker.GetSamplesForSymbol( m_baseAddr ) ) ) )
{
const auto samplesReady = worker.AreSymbolSamplesReady();
if( !samplesReady )
@@ -1990,10 +1997,8 @@ void SourceView::RenderSymbolSourceView( const AddrStatData& as, Worker& worker,
if( !widthSet )
{
widthSet = true;
const auto w = ImGui::GetWindowWidth();
const auto c0 = ImGui::CalcTextSize( "12345678901234567890" ).x;
ImGui::SetColumnWidth( 0, c0 );
ImGui::SetColumnWidth( 1, w - c0 );
}
}
for( auto& v : fileCountsVec )
@@ -2379,6 +2384,31 @@ static int PrintHexBytes( const uint8_t* bytes, size_t len, CpuArchitecture arch
}
}
std::tuple<size_t, size_t> SourceView::GetJumpRange( const JumpData& jump )
{
size_t minIdx = 0, maxIdx = 0;
size_t i;
for( i=0; i<m_asm.size(); i++ )
{
if( m_asm[i].addr == jump.min )
{
minIdx = i++;
break;
}
}
assert( i != m_asm.size() );
for( ; i<m_asm.size(); i++ )
{
if( m_asm[i].addr == jump.max )
{
maxIdx = i+1;
break;
}
}
assert( i != m_asm.size() );
return std::make_tuple( minIdx, maxIdx );
}
uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker, View& view )
{
const auto scale = GetScale();
@@ -2627,7 +2657,7 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
if( symData )
{
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
const auto symName = worker.GetString( symData->name );
const auto normalized = shortenName != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, symName ) : symName;
@@ -2664,7 +2694,7 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
if( symData )
{
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
const auto symName = worker.GetString( symData->name );
const auto normalized = shortenName != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, symName ) : symName;
@@ -2727,35 +2757,98 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
auto it = m_jumpTable.find( m_jumpPopupAddr );
assert( it != m_jumpTable.end() );
bool needSeparator = false;
#ifndef TRACY_NO_FILESELECTOR
if( ImGui::MenuItem( ICON_FA_FILE_IMPORT " Save jump range" ) )
{
size_t minIdx = 0, maxIdx = 0;
size_t i;
for( i=0; i<m_asm.size(); i++ )
{
if( m_asm[i].addr == it->second.min )
{
minIdx = i++;
break;
}
}
assert( i != m_asm.size() );
for( ; i<m_asm.size(); i++ )
{
if( m_asm[i].addr == it->second.max )
{
maxIdx = i+1;
break;
}
}
assert( i != m_asm.size() );
auto [minIdx, maxIdx] = GetJumpRange( it->second );
Save( worker, minIdx, maxIdx );
ImGui::CloseCurrentPopup();
}
ImGui::Separator();
needSeparator = true;
#endif
if( s_config.llm )
{
needSeparator = true;
if( ImGui::MenuItem( ICON_FA_ROBOT " Attach jump range in chat" ) )
{
auto sym = worker.GetSymbolData( m_symAddr );
assert( sym );
const char* symName;
if( sym->isInline )
{
auto parent = worker.GetSymbolData( m_baseAddr );
if( parent )
{
symName = worker.GetString( parent->name );
}
else
{
char tmp[32];
sprintf( tmp, "0x%" PRIx64, m_baseAddr );
symName = tmp;
}
}
else
{
symName = worker.GetString( sym->name );
}
nlohmann::json json = {
{ "type", "assembly" },
{ "symbol", symName },
{ "code", nlohmann::json::array() }
};
auto& code = json["code"];
auto [start, stop] = GetJumpRange( it->second );
const auto end = m_asm.size() < stop ? m_asm.size() : stop;
for( size_t i=start; i<end; i++ )
{
const auto& v = m_asm[i];
nlohmann::json line;
auto it = m_locMap.find( v.addr );
if( it != m_locMap.end() ) line["label"] = ".L" + std::to_string( it->second );
bool hasJump = false;
if( v.jumpAddr != 0 )
{
auto lit = m_locMap.find( v.jumpAddr );
if( lit != m_locMap.end() )
{
line["asm"] = v.mnemonic + " .L" + std::to_string( lit->second );
hasJump = true;
}
}
if( !hasJump )
{
if( v.operands.empty() )
{
line["asm"] = v.mnemonic;
}
else
{
line["asm"] = v.mnemonic + " " + v.operands;
}
}
uint32_t srcline;
const auto srcidx = worker.GetLocationForAddress( v.addr, srcline );
if( srcline != 0 )
{
line["source"] = {
{ "file", worker.GetString( srcidx ) },
{ "line", srcline }
};
}
code.emplace_back( std::move( line ) );
}
view.AddLlmAttachment( json );
}
}
if( needSeparator ) ImGui::Separator();
if( ImGui::BeginMenu( "Sources" ) )
{
for( auto& src : it->second.source )
@@ -2872,6 +2965,9 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
}
if( ImGui::BeginPopup( "localCallstackPopup" ) )
{
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( "Local call stack:" );
ImGui::PopFont();
const auto lcs = m_localCallstackPopup;
for( uint8_t i=0; i<lcs->size; i++ )
{
@@ -2889,7 +2985,7 @@ uint64_t SourceView::RenderSymbolAsmView( const AddrStatData& as, Worker& worker
m_sourceTooltip.Parse( fn, worker, view );
if( !m_sourceTooltip.empty() )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::TextDisabled( "%s:%i", fn, srcline );
ImGui::PopFont();
ImGui::Separator();
@@ -3909,7 +4005,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
ImGui::TextDisabled( "(0x%" PRIx64 ")", symAddr );
if( normalized != symName && strcmp( normalized, symName ) != 0 )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( symName );
ImGui::PopFont();
}
@@ -3942,7 +4038,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
const auto normalized = view.GetShortenName() != ShortenName::Never ? ShortenZoneName( ShortenName::OnlyNormalize, symName ) : symName;
ImGui::Text( "%s", normalized );
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled( "%s:%i", worker.GetString( frame->data[i].file ), frame->data[i].line );
ImGui::PopFont();
@@ -3952,14 +4048,15 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
SetFont();
if( ImGui::IsItemClicked( 0 ) )
{
m_targetLine = srcline;
if( m_source.filename() == fileName )
{
m_targetLine = srcline;
SelectLine( srcline, &worker, false );
m_displayMode = DisplayMixed;
}
else if( SourceFileValid( fileName, worker.GetCaptureTime(), view, worker ) )
{
m_targetLine = srcline;
ParseSource( fileName, worker, view );
SelectLine( srcline, &worker, false );
SelectViewMode();
@@ -4237,7 +4334,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
}
if( normalized != jumpName )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( jumpName );
ImGui::PopFont();
}
@@ -4389,7 +4486,7 @@ void SourceView::RenderAsmLine( AsmLine& line, const AddrStat& ipcnt, const Addr
}
if( normalized != jumpName && strcmp( normalized, jumpName ) != 0 )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( jumpName );
ImGui::PopFont();
}
@@ -5657,7 +5754,7 @@ void SourceView::Save( const Worker& worker, size_t start, size_t stop )
f = fopen( fn, "wb" );
}
if( !f ) return;
char tmp[16];
char tmp[32];
auto sym = worker.GetSymbolData( m_symAddr );
assert( sym );
const char* symName;
@@ -5732,7 +5829,7 @@ void SourceView::Save( const Worker& worker, size_t start, size_t stop )
void SourceView::SetFont()
{
ImGui::PushFont( m_font );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 0, 0 ) );
}
@@ -3,6 +3,7 @@
#include <limits>
#include <string>
#include <tuple>
#include <vector>
#include "tracy_robin_hood.h"
@@ -72,11 +73,11 @@ private:
rd
};
enum { ReadBit = 0x100 };
enum { WriteBit = 0x200 };
enum { ReuseBit = 0x400 };
enum { RegMask = 0x0FF };
enum { FlagMask = 0xF00 };
static constexpr int ReadBit = 0x100;
static constexpr int WriteBit = 0x200;
static constexpr int ReuseBit = 0x400;
static constexpr int RegMask = 0x0FF;
static constexpr int FlagMask = 0xF00;
enum class OpType : uint8_t
{
@@ -164,7 +165,6 @@ private:
public:
SourceView();
void UpdateFont( ImFont* fixed, ImFont* small, ImFont* big ) { m_font = fixed; m_smallFont = small; m_bigFont = big; }
void SetCpuId( uint32_t cpuid );
void OpenSource( const char* fileName, int line, const View& view, const Worker& worker );
@@ -214,6 +214,7 @@ private:
const std::vector<uint64_t>* GetAddressesForLocation( uint32_t fileStringIdx, uint32_t line, const Worker& worker );
tracy_force_inline float CalcJumpSeparation( float scale );
std::tuple<size_t, size_t> GetJumpRange( const JumpData& jump );
#ifndef TRACY_NO_FILESELECTOR
void Save( const Worker& worker, size_t start = 0, size_t stop = std::numeric_limits<size_t>::max() );
@@ -222,9 +223,6 @@ private:
tracy_force_inline void SetFont();
tracy_force_inline void UnsetFont();
ImFont* m_font;
ImFont* m_smallFont;
ImFont* m_bigFont;
uint64_t m_symAddr;
uint64_t m_baseAddr;
uint64_t m_targetAddr;
@@ -88,6 +88,40 @@ static void GetConfigDirectory( char* buf, size_t& sz )
#endif
}
static void GetCacheDirectory( char* buf, size_t& sz )
{
#ifdef _WIN32
auto path = getenv( "LOCALAPPDATA" );
sz = strlen( path );
memcpy( buf, path, sz );
for( size_t i=0; i<sz; i++ )
{
if( buf[i] == '\\' )
{
buf[i] = '/';
}
}
#else
auto path = getenv( "XDG_CACHE_HOME" );
if( path && *path )
{
sz = strlen( path );
memcpy( buf, path, sz );
}
else
{
path = getenv( "HOME" );
assert( path && *path );
sz = strlen( path );
memcpy( buf, path, sz );
memcpy( buf+sz, "/.cache", 7 );
sz += 7;
}
#endif
}
const char* GetSavePath( const char* file )
{
assert( file && *file );
@@ -208,4 +242,31 @@ const char* GetSavePath( const char* program, uint64_t time, const char* file, b
return buf;
}
const char* GetCachePath( const char* file )
{
assert( file && *file );
enum { Pool = 8 };
enum { MaxPath = 512 };
static char bufpool[Pool][MaxPath];
static int bufsel = 0;
char* buf = bufpool[bufsel];
bufsel = ( bufsel + 1 ) % Pool;
size_t sz;
GetCacheDirectory( buf, sz );
memcpy( buf+sz, "/tracy/", 8 );
sz += 7;
auto status = CreateDirStruct( buf );
assert( status );
const auto fsz = strlen( file );
assert( sz + fsz < MaxPath );
memcpy( buf+sz, file, fsz+1 );
return buf;
}
}
@@ -9,6 +9,8 @@ namespace tracy
const char* GetSavePath( const char* file );
const char* GetSavePath( const char* program, uint64_t time, const char* file, bool create );
const char* GetCachePath( const char* file );
}
#endif
@@ -5,7 +5,7 @@
# include <emscripten/html5.h>
# include <GLES2/gl2.h>
#else
# include "../profiler/src/imgui/imgui_impl_opengl3_loader.h"
# include <backends/imgui_impl_opengl3_loader.h>
#endif
#include "TracyTexture.hpp"
#include "../public/common/TracyForceInline.hpp"
@@ -39,7 +39,7 @@ void InitTexture()
#endif
}
void* MakeTexture( bool zigzag )
ImTextureID MakeTexture( bool zigzag )
{
GLuint tex;
glGenTextures( 1, &tex );
@@ -48,12 +48,12 @@ void* MakeTexture( bool zigzag )
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, zigzag ? GL_REPEAT : GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
return (void*)(intptr_t)tex;
return tex;
}
void FreeTexture( void* _tex, void(*runOnMainThread)(const std::function<void()>&, bool) )
void FreeTexture( ImTextureID _tex, void(*runOnMainThread)(const std::function<void()>&, bool) )
{
auto tex = (GLuint)(intptr_t)_tex;
auto tex = (GLuint)_tex;
runOnMainThread( [tex] { glDeleteTextures( 1, &tex ); }, false );
}
@@ -139,9 +139,9 @@ static tracy_force_inline void DecodeDxt1Part( uint64_t d, uint32_t* dst, uint32
memcpy( dst+3, dict + (idx & 0x3), 4 );
}
void UpdateTexture( void* _tex, const char* data, int w, int h )
void UpdateTexture( ImTextureID _tex, const char* data, int w, int h )
{
auto tex = (GLuint)(intptr_t)_tex;
auto tex = (GLuint)_tex;
glBindTexture( GL_TEXTURE_2D, tex );
if( s_hardwareS3tc )
{
@@ -167,16 +167,16 @@ void UpdateTexture( void* _tex, const char* data, int w, int h )
}
}
void UpdateTextureRGBA( void* _tex, void* data, int w, int h )
void UpdateTextureRGBA( ImTextureID _tex, void* data, int w, int h )
{
auto tex = (GLuint)(intptr_t)_tex;
auto tex = (GLuint)_tex;
glBindTexture( GL_TEXTURE_2D, tex );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
}
void UpdateTextureRGBAMips( void* _tex, void** data, int* w, int* h, size_t mips )
void UpdateTextureRGBAMips( ImTextureID _tex, void** data, int* w, int* h, size_t mips )
{
auto tex = (GLuint)(intptr_t)_tex;
auto tex = (GLuint)_tex;
glBindTexture( GL_TEXTURE_2D, tex );
for( size_t i=0; i<mips; i++ )
{
@@ -2,16 +2,17 @@
#define __TRACYTEXTURE_HPP__
#include <functional>
#include <imgui.h>
namespace tracy
{
void InitTexture();
void* MakeTexture( bool zigzag = false );
void FreeTexture( void* tex, void(*runOnMainThread)(const std::function<void()>&, bool) );
void UpdateTexture( void* tex, const char* data, int w, int h );
void UpdateTextureRGBA( void* tex, void* data, int w, int h );
void UpdateTextureRGBAMips( void* tex, void** data, int* w, int* h, size_t mips );
ImTextureID MakeTexture( bool zigzag = false );
void FreeTexture( ImTextureID tex, void(*runOnMainThread)(const std::function<void()>&, bool) );
void UpdateTexture( ImTextureID tex, const char* data, int w, int h );
void UpdateTextureRGBA( ImTextureID tex, void* data, int w, int h );
void UpdateTextureRGBAMips( ImTextureID tex, void** data, int* w, int* h, size_t mips );
}
@@ -6,6 +6,8 @@
#include "TracyTimelineController.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -18,7 +20,7 @@ TimelineController::TimelineController( View& view, Worker& worker, bool threadi
, m_view( view )
, m_worker( worker )
#ifdef __EMSCRIPTEN__
, m_td( 0, "Render" )
, m_td( threading ? 2 : 0, "Render" )
#else
, m_td( threading ? (size_t)std::max( 0, ( (int)std::thread::hardware_concurrency() - 2 ) / 2 ) : 0, "Render" )
#endif
@@ -97,7 +99,7 @@ std::optional<int> TimelineController::CalculateScrollPosition() const
return std::nullopt;
}
void TimelineController::End( double pxns, const ImVec2& wpos, bool hover, bool vcenter, float yMin, float yMax, ImFont* smallFont )
void TimelineController::End( double pxns, const ImVec2& wpos, bool hover, bool vcenter, float yMin, float yMax )
{
auto shouldUpdateCenterItem = [&] () {
const auto imguiChangedScroll = m_scroll != ImGui::GetScrollY();
@@ -123,7 +125,7 @@ void TimelineController::End( double pxns, const ImVec2& wpos, bool hover, bool
TimelineContext ctx;
ctx.w = ImGui::GetContentRegionAvail().x - 1;
ctx.ty = ImGui::GetTextLineHeight();
ImGui::PushFont( smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ctx.sty = ImGui::GetTextLineHeight();
ImGui::PopFont();
ctx.scale = GetScale();
@@ -25,7 +25,7 @@ public:
void FirstFrameExpired();
void Begin();
void End( double pxns, const ImVec2& wpos, bool hover, bool vcenter, float yMin, float yMax, ImFont* smallFont );
void End( double pxns, const ImVec2& wpos, bool hover, bool vcenter, float yMin, float yMax );
template<class T, class U>
void AddItem( U* data )
@@ -24,6 +24,7 @@ struct TimelineDraw
short_ptr<void*> ev;
Int48 rend;
uint32_t num;
uint32_t inheritedColor;
};
@@ -121,6 +121,7 @@ void TimelineItem::Draw( bool firstFrame, const TimelineContext& ctx, int yOffse
SetVisible( false );
ImGui::CloseCurrentPopup();
}
DrawExtraPopupItems();
ImGui::EndPopup();
}
@@ -51,6 +51,7 @@ protected:
virtual bool DrawContents( const TimelineContext& ctx, int& offset ) = 0;
virtual void DrawOverlay( const ImVec2& ul, const ImVec2& dr ) {}
virtual void DrawExtraPopupItems() {}
virtual void DrawFinished() {}
virtual bool IsEmpty() const { return false; }
@@ -42,7 +42,8 @@ void TimelineItemGpu::HeaderTooltip( const char* label ) const
const bool isMultithreaded =
( m_gpu->type == GpuContextType::Vulkan ) ||
( m_gpu->type == GpuContextType::OpenCL ) ||
( m_gpu->type == GpuContextType::Direct3D12 );
( m_gpu->type == GpuContextType::Direct3D12 ) ||
( m_gpu->type == GpuContextType::Metal );
char buf[64];
sprintf( buf, "%s context %i", GpuContextNames[(int)m_gpu->type], m_idx );
@@ -5,6 +5,7 @@
#include "TracyUtility.hpp"
#include "TracyView.hpp"
#include "TracyWorker.hpp"
#include "tracy_pdqsort.h"
namespace tracy
{
@@ -111,7 +112,7 @@ int64_t TimelineItemPlot::RangeEnd() const
bool TimelineItemPlot::DrawContents( const TimelineContext& ctx, int& offset )
{
return m_view.DrawPlot( ctx, *m_plot, m_draw, offset );
return m_view.DrawPlot( ctx, *m_plot, m_draw, offset, m_rightEnd );
}
void TimelineItemPlot::DrawFinished()
@@ -137,17 +138,30 @@ void TimelineItemPlot::Preprocess( const TimelineContext& ctx, TaskDispatch& td,
auto& vec = m_plot->data;
vec.ensure_sorted();
if( vec.front().time.Val() > vEnd || vec.back().time.Val() < vStart )
if( vec.front().time.Val() > vEnd )
{
m_plot->rMin = 0;
m_plot->rMax = 0;
m_plot->num = 0;
m_rightEnd = false;
return;
}
else if( vec.back().time.Val() < vStart )
{
const auto lastTime = m_worker.GetLastTime();
const auto val = vec.back().val;
m_plot->rMin = val - 1;
m_plot->rMax = val + 1;
m_plot->num = lastTime < vStart ? 0 : 1;
m_rightEnd = vec.back().time.Val() < lastTime;
return;
}
auto it = std::lower_bound( vec.begin(), vec.end(), vStart, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
auto end = std::lower_bound( it, vec.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.time.Val() < r; } );
m_rightEnd = end == vec.end() && vec.back().time.Val() < m_worker.GetLastTime();
if( end != vec.end() ) end++;
if( it != vec.begin() ) it--;
@@ -36,6 +36,7 @@ private:
PlotData* m_plot;
std::vector<uint32_t> m_draw;
bool m_rightEnd;
};
}
@@ -1,6 +1,7 @@
#include <algorithm>
#include <limits>
#include "TracyColor.hpp"
#include "TracyImGui.hpp"
#include "TracyLockHelpers.hpp"
#include "TracyMouse.hpp"
@@ -261,7 +262,7 @@ void TimelineItemThread::HeaderExtraContents( const TimelineContext& ctx, int of
bool TimelineItemThread::DrawContents( const TimelineContext& ctx, int& offset )
{
m_view.DrawThread( ctx, *m_thread, m_draw, m_ctxDraw, m_samplesDraw, m_lockDraw, offset, m_depth, m_hasCtxSwitch, m_hasSamples );
if( m_depth == 0 && !m_hasMessages )
if( m_depth == 0 && !m_hasMessages && ( !m_view.GetViewData().drawSamples || !m_hasSamples ) )
{
auto& crash = m_worker.GetCrashEvent();
return crash.thread == m_thread->id;
@@ -274,6 +275,21 @@ void TimelineItemThread::DrawOverlay( const ImVec2& ul, const ImVec2& dr )
m_view.DrawThreadOverlays( *m_thread, ul, dr );
}
void TimelineItemThread::DrawExtraPopupItems()
{
if( m_view.GetSelectThread() == m_thread->id )
{
if( ImGui::MenuItem( ICON_FA_TIMELINE " Unselect in CPU timeline" ) )
{
m_view.SelectThread( 0 );
}
}
else if( m_view.GetViewData().drawCpuData && ImGui::MenuItem( ICON_FA_TIMELINE " Select in CPU timeline" ) )
{
m_view.SelectThread( m_thread->id );
}
}
void TimelineItemThread::DrawFinished()
{
m_samplesDraw.clear();
@@ -300,7 +316,7 @@ void TimelineItemThread::Preprocess( const TimelineContext& ctx, TaskDispatch& t
else
#endif
{
m_depth = PreprocessZoneLevel( ctx, m_thread->timeline, 0, visible );
m_depth = PreprocessZoneLevel( ctx, m_thread->timeline, 0, visible, 0 );
}
} );
@@ -399,20 +415,20 @@ int TimelineItemThread::PreprocessGhostLevel( const TimelineContext& ctx, const
}
#endif
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const Vector<short_ptr<ZoneEvent>>& vec, int depth, bool visible )
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const Vector<short_ptr<ZoneEvent>>& vec, int depth, bool visible, const uint32_t inheritedColor )
{
if( vec.is_magic() )
{
return PreprocessZoneLevel<VectorAdapterDirect<ZoneEvent>>( ctx, *(Vector<ZoneEvent>*)( &vec ), depth, visible );
return PreprocessZoneLevel<VectorAdapterDirect<ZoneEvent>>( ctx, *(Vector<ZoneEvent>*)( &vec ), depth, visible, inheritedColor );
}
else
{
return PreprocessZoneLevel<VectorAdapterPointer<ZoneEvent>>( ctx, vec, depth, visible );
return PreprocessZoneLevel<VectorAdapterPointer<ZoneEvent>>( ctx, vec, depth, visible, inheritedColor );
}
}
template<typename Adapter, typename V>
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth, bool visible )
int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth, bool visible, const uint32_t inheritedColor )
{
const auto vStart = ctx.vStart;
const auto vEnd = ctx.vEnd;
@@ -450,17 +466,39 @@ int TimelineItemThread::PreprocessZoneLevel( const TimelineContext& ctx, const V
if( nt - pt >= MinVisNs ) break;
nextTime = nt + MinVisNs;
}
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Folded, uint16_t( depth ), (void**)&ev, m_worker.GetZoneEnd( a(*(next-1)) ), uint32_t( next - it ) } );
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Folded, uint16_t( depth ), (void**)&ev, m_worker.GetZoneEnd( a(*(next-1)) ), uint32_t( next - it ), inheritedColor } );
it = next;
}
else
{
if( ev.HasChildren() )
const auto hasChildren = ev.HasChildren();
auto currentInherited = inheritedColor;
auto childrenInherited = inheritedColor;
if( m_view.GetViewData().inheritParentColors )
{
const auto d = PreprocessZoneLevel( ctx, m_worker.GetZoneChildren( ev.Child() ), depth + 1, visible );
uint32_t color = 0;
if( m_worker.HasZoneExtra( ev ) )
{
const auto& extra = m_worker.GetZoneExtra( ev );
color = extra.color.Val();
}
if( color == 0 )
{
auto& srcloc = m_worker.GetSourceLocation( ev.SrcLoc() );
color = srcloc.color;
}
if( color != 0 )
{
currentInherited = color | 0xFF000000;
if( hasChildren ) childrenInherited = DarkenColorSlightly( color );
}
}
if( hasChildren )
{
const auto d = PreprocessZoneLevel( ctx, m_worker.GetZoneChildren( ev.Child() ), depth + 1, visible, childrenInherited );
if( d > maxdepth ) maxdepth = d;
}
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Zone, uint16_t( depth ), (void**)&ev } );
if( visible ) m_draw.emplace_back( TimelineDraw { TimelineDrawType::Zone, uint16_t( depth ), (void**)&ev, 0, 0, currentInherited } );
++it;
}
}
@@ -27,6 +27,7 @@ protected:
bool DrawContents( const TimelineContext& ctx, int& offset ) override;
void DrawOverlay( const ImVec2& ul, const ImVec2& dr ) override;
void DrawExtraPopupItems() override;
void DrawFinished() override;
bool IsEmpty() const override;
@@ -37,10 +38,10 @@ private:
#ifndef TRACY_NO_STATISTICS
int PreprocessGhostLevel( const TimelineContext& ctx, const Vector<GhostZone>& vec, int depth, bool visible );
#endif
int PreprocessZoneLevel( const TimelineContext& ctx, const Vector<short_ptr<ZoneEvent>>& vec, int depth, bool visible );
int PreprocessZoneLevel( const TimelineContext& ctx, const Vector<short_ptr<ZoneEvent>>& vec, int depth, bool visible, const uint32_t inheritedColor );
template<typename Adapter, typename V>
int PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth, bool visible );
int PreprocessZoneLevel( const TimelineContext& ctx, const V& vec, int depth, bool visible, const uint32_t inheritedColor );
void PreprocessContextSwitches( const TimelineContext& ctx, const ContextSwitch& ctxSwitch, bool visible );
void PreprocessSamples( const TimelineContext& ctx, const Vector<SampleData>& vec, bool visible, int yPos );
@@ -145,6 +145,7 @@ void UserData::LoadState( ViewData& data )
if( ini_sget( ini, "options", "drawCpuUsageGraph", "%d", &v ) ) data.drawCpuUsageGraph = v;
if( ini_sget( ini, "options", "drawSamples", "%d", &v ) ) data.drawSamples = v;
if( ini_sget( ini, "options", "dynamicColors", "%d", &v ) ) data.dynamicColors = v;
if( ini_sget( ini, "options", "inheritParentColors", "%d", &v ) ) data.inheritParentColors = v;
if( ini_sget( ini, "options", "forceColors", "%d", &v ) ) data.forceColors = v;
if( ini_sget( ini, "options", "ghostZones", "%d", &v ) ) data.ghostZones = v;
if( ini_sget( ini, "options", "frameTarget", "%d", &v ) ) data.frameTarget = v;
@@ -194,6 +195,7 @@ void UserData::SaveState( const ViewData& data )
fprintf( f, "drawCpuUsageGraph = %d\n", data.drawCpuUsageGraph );
fprintf( f, "drawSamples = %d\n", data.drawSamples );
fprintf( f, "dynamicColors = %d\n", data.dynamicColors );
fprintf( f, "inheritParentColors = %d\n", data.inheritParentColors );
fprintf( f, "forceColors = %d\n", data.forceColors );
fprintf( f, "ghostZones = %d\n", data.ghostZones );
fprintf( f, "frameTarget = %d\n", data.frameTarget );
@@ -187,4 +187,30 @@ const char* FormatPlotValue( double val, PlotValueFormatting format )
return buf;
}
std::vector<std::string> SplitLines( const char* data, size_t sz )
{
std::vector<std::string> ret;
auto txt = data;
for(;;)
{
auto end = txt;
while( *end != '\n' && *end != '\r' && end - data < sz ) end++;
ret.emplace_back( txt, end );
if( end - data == sz ) break;
if( *end == '\n' )
{
end++;
if( end - data < sz && *end == '\r' ) end++;
}
else if( *end == '\r' )
{
end++;
if( end - data < sz && *end == '\n' ) end++;
}
if( end - data == sz ) break;
txt = end;
}
return ret;
}
}
@@ -2,6 +2,8 @@
#define __TRACYUTILITY_HPP__
#include <stdint.h>
#include <string>
#include <vector>
#include "imgui.h"
#include "../server/TracyEvent.hpp"
@@ -29,6 +31,8 @@ uint32_t GetThreadColor( uint64_t thread, int depth, bool dynamic );
uint32_t GetPlotColor( const PlotData& plot, const Worker& worker );
const char* FormatPlotValue( double val, PlotValueFormatting format );
std::vector<std::string> SplitLines( const char* data, size_t sz );
}
#endif
+151 -76
View File
@@ -13,15 +13,18 @@
#include "imgui.h"
#include "TracyConfig.hpp"
#include "TracyFileRead.hpp"
#include "TracyFilesystem.hpp"
#include "TracyImGui.hpp"
#include "TracyManualData.hpp"
#include "TracyPrint.hpp"
#include "TracySourceView.hpp"
#include "TracyTexture.hpp"
#include "TracyView.hpp"
#include "../server/TracySysUtil.hpp"
#include "../public/common/TracyStackFrames.hpp"
#include "../Fonts.hpp"
#include "imgui_internal.h"
#include "IconsFontAwesome6.h"
@@ -35,57 +38,69 @@ namespace tracy
double s_time = 0;
View::View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr )
: m_worker( addr, port, config.memoryLimit == 0 ? -1 : ( config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ) )
View::View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr )
: m_worker( addr, port, s_config.memoryLimit == 0 ? -1 : ( s_config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ) )
, m_staticView( false )
, m_viewMode( ViewMode::LastFrames )
, m_viewModeHeuristicTry( true )
, m_totalMemory( GetPhysicalMemorySize() )
, m_forceConnectionPopup( true, true )
, m_tc( *this, m_worker, config.threadedRendering )
, m_tc( *this, m_worker, s_config.threadedRendering )
, m_frames( nullptr )
, m_messagesScrollBottom( true )
, m_reactToCrash( true )
, m_reactToLostConnection( true )
, m_smallFont( smallFont )
, m_bigFont( bigFont )
, m_fixedFont( fixedWidth )
, m_stcb( stcb )
, m_sscb( sscb )
, m_acb( acb )
, m_cbMainThread( cbMainThread )
, m_achievementsMgr( amgr )
, m_achievements( config.achievements )
, m_achievements( s_config.achievements )
, m_horizontalScrollMultiplier( s_config.horizontalScrollMultiplier )
, m_verticalScrollMultiplier( s_config.verticalScrollMultiplier )
, m_manualData( std::make_shared<TracyManualData>() )
#ifdef __EMSCRIPTEN__
, m_td( 2, "ViewMt" )
#else
, m_td( std::thread::hardware_concurrency(), "ViewMt" )
, m_llm( m_worker, *m_manualData )
#endif
{
InitTextEditor();
SetupConfig( config );
SetupConfig();
}
View::View( void(*cbMainThread)(const std::function<void()>&, bool), FileRead& f, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr )
View::View( void(*cbMainThread)(const std::function<void()>&, bool), FileRead& f, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr )
: m_worker( f )
, m_filename( f.GetFilename() )
, m_staticView( true )
, m_viewMode( ViewMode::Paused )
, m_totalMemory( GetPhysicalMemorySize() )
, m_tc( *this, m_worker, config.threadedRendering )
, m_tc( *this, m_worker, s_config.threadedRendering )
, m_frames( m_worker.GetFramesBase() )
, m_messagesScrollBottom( false )
, m_smallFont( smallFont )
, m_bigFont( bigFont )
, m_fixedFont( fixedWidth )
, m_stcb( stcb )
, m_sscb( sscb )
, m_acb( acb )
, m_userData( m_worker.GetCaptureProgram().c_str(), m_worker.GetCaptureTime() )
, m_cbMainThread( cbMainThread )
, m_achievementsMgr( amgr )
, m_achievements( config.achievements )
, m_achievements( s_config.achievements )
, m_horizontalScrollMultiplier( s_config.horizontalScrollMultiplier )
, m_verticalScrollMultiplier( s_config.verticalScrollMultiplier )
, m_manualData( std::make_shared<TracyManualData>() )
#ifdef __EMSCRIPTEN__
, m_td( 2, "ViewMt" )
#else
, m_td( std::thread::hardware_concurrency(), "ViewMt" )
, m_llm( m_worker, *m_manualData )
#endif
{
m_notificationTime = 4;
m_notificationText = std::string( "Trace loaded in " ) + TimeToString( m_worker.GetLoadTime() );
InitTextEditor();
SetupConfig( config );
SetupConfig();
m_vd.zvStart = m_worker.GetFirstTime();
m_vd.zvEnd = m_worker.GetLastTime();
@@ -121,12 +136,18 @@ void View::InitTextEditor()
m_sourceViewFile = nullptr;
}
void View::SetupConfig( const Config& config )
void View::SetupConfig()
{
m_vd.frameTarget = config.targetFps;
m_vd.dynamicColors = config.dynamicColors;
m_vd.forceColors = config.forceColors;
m_vd.shortenName = (ShortenName)config.shortenName;
// Keep in sync with TracyView_Options.cpp View::DrawOptions(), bottom of the file.
m_vd.frameTarget = s_config.targetFps;
m_vd.drawFrameTargets = s_config.drawFrameTargets;
m_vd.dynamicColors = s_config.dynamicColors;
m_vd.forceColors = s_config.forceColors;
m_vd.ghostZones = s_config.ghostZones;
m_vd.shortenName = (ShortenName)s_config.shortenName;
m_vd.drawSamples = s_config.drawSamples;
m_vd.drawContextSwitches = s_config.drawContextSwitches;
m_vd.plotHeight = s_config.plotHeight;
}
void View::Achieve( const char* id )
@@ -307,7 +328,7 @@ bool View::Draw()
if( ImGui::BeginPopupModal( "Protocol mismatch", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::TextUnformatted( "The client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server." );
@@ -333,7 +354,7 @@ bool View::Draw()
if( ImGui::BeginPopupModal( "Client not ready", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_LIGHTBULB );
ImGui::PopFont();
ImGui::TextUnformatted( "The client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled." );
@@ -359,7 +380,7 @@ bool View::Draw()
if( ImGui::BeginPopupModal( "Client disconnected", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_HANDSHAKE );
ImGui::PopFont();
ImGui::TextUnformatted( "The client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration." );
@@ -386,7 +407,7 @@ bool View::Draw()
if( ImGui::BeginPopupModal( "Instrumentation failure", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
const auto& data = m_worker.GetFailureData();
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_SKULL );
ImGui::PopFont();
ImGui::TextUnformatted( "Profiling session terminated due to improper instrumentation.\nPlease correct your program and try again." );
@@ -555,6 +576,11 @@ bool View::Draw()
ImGui::EndPopup();
}
static FileCompression comp = FileCompression::Zstd;
static int zlvl = 3;
static bool buildDict = false;
static int streams = 4;
bool saveFailed = false;
if( !m_filenameStaging.empty() )
{
@@ -564,51 +590,49 @@ bool View::Draw()
{
assert( !m_filenameStaging.empty() );
auto fn = m_filenameStaging.c_str();
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextFocused( "Path:", fn );
ImGui::PopFont();
ImGui::Separator();
static FileCompression comp = FileCompression::Zstd;
static int zlvl = 3;
ImGui::TextUnformatted( ICON_FA_FILE_ZIPPER " Trace compression" );
ImGui::SameLine();
TextDisabledUnformatted( "Can be changed later with the upgrade utility" );
ImGui::Indent();
int idx = 0;
while( CompressionName[idx] )
if( ImGui::TreeNode( ICON_FA_FILE_ZIPPER " Trace compression" ) )
{
if( ImGui::RadioButton( CompressionName[idx], (int)comp == idx ) ) comp = (FileCompression)idx;
TextDisabledUnformatted( "Can be changed later with the upgrade utility" );
ImGui::Indent();
int idx = 0;
while( CompressionName[idx] )
{
if( ImGui::RadioButton( CompressionName[idx], (int)comp == idx ) ) comp = (FileCompression)idx;
ImGui::SameLine();
TextDisabledUnformatted( CompressionDesc[idx] );
idx++;
}
ImGui::Unindent();
ImGui::TextUnformatted( "Zstd level" );
ImGui::SameLine();
TextDisabledUnformatted( CompressionDesc[idx] );
idx++;
}
ImGui::Unindent();
ImGui::TextUnformatted( "Zstd level" );
ImGui::SameLine();
TextDisabledUnformatted( "Increasing level decreases file size, but increases save and load times" );
ImGui::Indent();
if( ImGui::SliderInt( "##zstd", &zlvl, 1, 22, "%d", ImGuiSliderFlags_AlwaysClamp ) )
{
comp = FileCompression::Zstd;
}
ImGui::Unindent();
TextDisabledUnformatted( "Increasing level decreases file size, but increases save and load times" );
ImGui::Indent();
if( ImGui::SliderInt( "##zstd", &zlvl, 1, 22, "%d", ImGuiSliderFlags_AlwaysClamp ) )
{
comp = FileCompression::Zstd;
}
ImGui::Unindent();
static int streams = 4;
ImGui::TextUnformatted( ICON_FA_SHUFFLE " Compression streams" );
ImGui::SameLine();
TextDisabledUnformatted( "Parallelize save and load at the cost of file size" );
ImGui::Indent();
ImGui::SliderInt( "##streams", &streams, 1, 64, "%d", ImGuiSliderFlags_AlwaysClamp );
ImGui::Unindent();
static bool buildDict = false;
if( m_worker.GetFrameImageCount() != 0 )
{
ImGui::Separator();
ImGui::Checkbox( "Build frame images dictionary", &buildDict );
ImGui::TextUnformatted( ICON_FA_SHUFFLE " Compression streams" );
ImGui::SameLine();
TextDisabledUnformatted( "Decreases run-time memory requirements" );
TextDisabledUnformatted( "Parallelize save and load at the cost of file size" );
ImGui::Indent();
ImGui::SliderInt( "##streams", &streams, 1, 64, "%d", ImGuiSliderFlags_AlwaysClamp );
ImGui::Unindent();
if( m_worker.GetFrameImageCount() != 0 )
{
ImGui::Separator();
ImGui::Checkbox( "Build frame images dictionary", &buildDict );
ImGui::SameLine();
TextDisabledUnformatted( "Decreases run-time memory requirements" );
}
ImGui::TreePop();
}
ImGui::Separator();
@@ -631,7 +655,7 @@ bool View::Draw()
if( saveFailed ) ImGui::OpenPopup( "Save failed" );
if( ImGui::BeginPopupModal( "Save failed", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::TextUnformatted( "Could not save trace at the specified location. Try again somewhere else." );
@@ -670,8 +694,10 @@ bool View::DrawImpl()
char tmp[2048];
sprintf( tmp, "%s###Connection", m_worker.GetAddr().c_str() );
ImGui::Begin( tmp, &keepOpen, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse );
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontNormal * 2.f );
ImGui::Spacing();
TextCentered( ICON_FA_WIFI );
ImGui::Spacing();
ImGui::PopFont();
ImGui::TextUnformatted( "Waiting for connection..." );
DrawWaitingDots( s_time );
@@ -712,10 +738,17 @@ bool View::DrawImpl()
auto& threadHints = m_worker.GetPendingThreadHints();
if( !threadHints.empty() )
{
m_threadReinsert.reserve( threadHints.size() );
for( auto v : threadHints )
{
auto it = std::find_if( m_threadOrder.begin(), m_threadOrder.end(), [v]( const auto& t ) { return t->id == v; } );
if( it != m_threadOrder.end() ) m_threadOrder.erase( it ); // Will be added in the correct place later, like any newly appearing thread
if( it != m_threadOrder.end() )
{
// Will be reinserted in the correct place later.
// A separate list is kept of threads that were already known to avoid having to figure out which one is missing in m_threadOrder.
m_threadReinsert.push_back( *it );
m_threadOrder.erase( it );
}
}
m_worker.ClearPendingThreadHints();
}
@@ -784,6 +817,7 @@ bool View::DrawImpl()
sprintf( tmp, "%s###Profiler", m_worker.GetCaptureName().c_str() );
ImGui::SetNextWindowSize( ImVec2( 1550, 800 ), ImGuiCond_FirstUseEver );
ImGui::Begin( tmp, keepOpenPtr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBringToFrontOnFocus );
ImGuiViewport* viewport = ImGui::GetMainViewport();
#endif
if( !m_staticView )
@@ -808,7 +842,7 @@ bool View::DrawImpl()
ImGui::EndPopup();
}
}
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
Worker::MainThreadDataLockGuard lock = m_worker.ObtainLockForMainThread();
m_worker.DoPostponedWork();
if( !m_worker.IsDataStatic() )
{
@@ -872,14 +906,16 @@ bool View::DrawImpl()
ImGui::PopStyleColor( 3 );
}
ImGui::SameLine();
ToggleButton( ICON_FA_GEAR " Options", m_showOptions );
ToggleButton( ICON_FA_GEAR, m_showOptions );
ImGui::SameLine();
ToggleButton( ICON_FA_TAGS " Messages", m_showMessages );
ImGui::SameLine();
ToggleButton( ICON_FA_MAGNIFYING_GLASS " Find zone", m_findZone.show );
ToggleButton( ICON_FA_MAGNIFYING_GLASS " Find", m_findZone.show );
ImGui::SameLine();
ToggleButton( ICON_FA_ARROW_UP_WIDE_SHORT " Statistics", m_showStatistics );
ImGui::SameLine();
ToggleButton( ICON_FA_FIRE_FLAME_CURVED " Flame", m_showFlameGraph );
ImGui::SameLine();
ToggleButton( ICON_FA_MEMORY " Memory", m_memInfo.show );
ImGui::SameLine();
ToggleButton( ICON_FA_SCALE_BALANCED " Compare", m_compare.show );
@@ -908,6 +944,8 @@ bool View::DrawImpl()
}
ImGui::EndPopup();
}
ImGui::SameLine();
ToggleButton( ICON_FA_BOOK, m_showManual );
if( m_sscb )
{
ImGui::SameLine();
@@ -930,6 +968,13 @@ bool View::DrawImpl()
ImGui::EndPopup();
}
}
#ifndef __EMSCRIPTEN__
if( s_config.llm )
{
ImGui::SameLine();
ToggleButton( ICON_FA_ROBOT, m_llm.m_show );
}
#endif
if( m_worker.AreFramesUsed() )
{
ImGui::SameLine();
@@ -1072,7 +1117,7 @@ bool View::DrawImpl()
DrawFrames();
const auto dockspaceId = ImGui::GetID( "tracyDockspace" );
ImGui::DockSpace( dockspaceId, ImVec2( 0, 0 ), ImGuiDockNodeFlags_NoDockingInCentralNode );
ImGui::DockSpace( dockspaceId, ImVec2( 0, 0 ), ImGuiDockNodeFlags_NoDockingOverCentralNode );
if( ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode( dockspaceId ) )
{
node->LocalFlags |= ImGuiDockNodeFlags_NoTabBar;
@@ -1106,6 +1151,7 @@ bool View::DrawImpl()
if( m_showOptions ) DrawOptions();
if( m_showMessages ) DrawMessages();
if( m_showFlameGraph ) DrawFlameGraph();
if( m_findZone.show ) DrawFindZone();
if( m_showStatistics ) DrawStatistics();
if( m_memInfo.show ) DrawMemory();
@@ -1123,6 +1169,10 @@ bool View::DrawImpl()
if( m_sampleParents.symAddr != 0 ) DrawSampleParents();
if( m_showRanges ) DrawRanges();
if( m_showWaitStacks ) DrawWaitStacks();
if( m_showManual ) DrawManual();
#ifndef __EMSCRIPTEN__
if( m_llm.m_show ) m_llm.Draw();
#endif
if( m_setRangePopup.active )
{
@@ -1145,6 +1195,12 @@ bool View::DrawImpl()
m_statRange.min = s;
m_statRange.max = e;
}
if( ImGui::Selectable( ICON_FA_FIRE_FLAME_CURVED " Limit flame time range" ) )
{
m_flameRange.active = true;
m_flameRange.min = s;
m_flameRange.max = e;
}
if( ImGui::Selectable( ICON_FA_HOURGLASS_HALF " Limit wait stacks range" ) )
{
m_waitStackRange.active = true;
@@ -1287,7 +1343,7 @@ bool View::DrawImpl()
}
if( ImGui::BeginPopupModal( "Connection lost!", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_PLUG );
ImGui::PopFont();
ImGui::TextUnformatted(
@@ -1309,11 +1365,7 @@ void View::DrawTextEditor()
ImGui::SetNextWindowSize( ImVec2( 1800 * scale, 800 * scale ), ImGuiCond_FirstUseEver );
bool show = true;
ImGui::Begin( "Source view", &show, ImGuiWindowFlags_NoScrollbar );
if( !ImGui::GetCurrentWindowRead()->SkipItems )
{
m_sourceView->UpdateFont( m_fixedFont, m_smallFont, m_bigFont );
m_sourceView->Render( m_worker, *this );
}
if( !ImGui::GetCurrentWindowRead()->SkipItems ) m_sourceView->Render( m_worker, *this );
ImGui::End();
if( !show ) m_sourceViewFile = nullptr;
}
@@ -1335,7 +1387,7 @@ void View::DrawSourceTooltip( const char* filename, uint32_t srcline, int before
if( m_srcHintCache.empty() ) return;
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 0, 0 ) );
if( separateTooltip ) ImGui::BeginTooltip();
ImGui::PushFont( m_fixedFont );
ImGui::PushFont( g_fonts.mono, FontNormal );
auto& lines = m_srcHintCache.get();
const int start = std::max( 0, (int)srcline - ( before+1 ) );
const int end = std::min<int>( m_srcHintCache.get().size(), srcline + after );
@@ -1395,7 +1447,7 @@ bool View::Save( const char* fn, FileCompression comp, int zlevel, bool buildDic
m_userData.StateShouldBePreserved();
m_saveThreadState.store( SaveThreadState::Saving, std::memory_order_relaxed );
m_saveThread = std::thread( [this, f{std::move( f )}, buildDict] {
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
Worker::MainThreadDataLockGuard lock = m_worker.ObtainLockForMainThread();
m_worker.Write( *f, buildDict );
f->Finish();
const auto stats = f->GetCompressionStatistics();
@@ -1413,6 +1465,11 @@ void View::HighlightThread( uint64_t thread )
m_drawThreadHighlight = thread;
}
void View::SelectThread( uint64_t thread )
{
m_selectedThread = thread;
}
bool View::WasActive() const
{
return m_wasActive ||
@@ -1423,4 +1480,22 @@ bool View::WasActive() const
!m_worker.IsBackgroundDone();
}
void View::AddLlmAttachment( const nlohmann::json& json )
{
#ifndef __EMSCRIPTEN__
m_llm.AddAttachment( json.dump( 2 ), "user" );
m_llm.m_show = true;
#endif
}
void View::AddLlmQuery( const char* query )
{
#ifndef __EMSCRIPTEN__
std::string str( query );
m_llm.AddMessage( std::move( str ), "user" );
m_llm.m_show = true;
m_llm.QueueSendMessage();
#endif
}
}
+98 -16
View File
@@ -3,8 +3,10 @@
#include <array>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include <thread>
#include <vector>
@@ -16,17 +18,23 @@
#include "TracyBuzzAnim.hpp"
#include "TracyConfig.hpp"
#include "TracyDecayValue.hpp"
#include "TracyMarkdown.hpp"
#include "TracySourceContents.hpp"
#include "TracyTimelineController.hpp"
#include "TracyUserData.hpp"
#include "TracyUtility.hpp"
#include "TracyViewData.hpp"
#include "../server/TracyFileWrite.hpp"
#include "../server/TracyTaskDispatch.hpp"
#include "../server/TracyShortPtr.hpp"
#include "../server/TracyWorker.hpp"
#include "../server/tracy_robin_hood.h"
#include "../server/TracyVector.hpp"
#ifndef __EMSCRIPTEN__
# include "TracyLlm.hpp"
#endif
namespace tracy
{
@@ -36,7 +44,11 @@ constexpr const char* GpuContextNames[] = {
"Vulkan",
"OpenCL",
"Direct3D 12",
"Direct3D 11"
"Direct3D 11",
"Metal",
"Custom",
"CUDA",
"Rocprof"
};
struct MemoryPage;
@@ -51,6 +63,8 @@ struct CpuUsageDraw;
struct CpuCtxDraw;
struct LockDraw;
struct PlotDraw;
struct FlameGraphContext;
class TracyManualData;
class View
@@ -104,15 +118,13 @@ public:
using SetScaleCallback = void(*)( float );
using AttentionCallback = void(*)();
View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr );
View( void(*cbMainThread)(const std::function<void()>&, bool), FileRead& f, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr );
View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr );
View( void(*cbMainThread)(const std::function<void()>&, bool), FileRead& f, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, AchievementsMgr* amgr );
~View();
bool Draw();
bool WasActive() const;
void UpdateFont( ImFont* fixed, ImFont* small, ImFont* big ) { m_fixedFont = fixed; m_smallFont = small; m_bigFont = big; }
void NotifyRootWindowSize( float w, float h ) { m_rootWidth = w; m_rootHeight = h; }
void ViewSource( const char* fileName, int line );
void ViewSource( const char* fileName, int line, const char* functionName );
@@ -146,18 +158,25 @@ public:
}
void HighlightThread( uint64_t thread );
void SelectThread( uint64_t thread );
uint64_t GetSelectThread() { return m_selectedThread; }
void ZoomToRange( int64_t start, int64_t end, bool pause = true );
bool DrawPlot( const TimelineContext& ctx, PlotData& plot, const std::vector<uint32_t>& plotDraw, int& offset );
bool DrawPlot( const TimelineContext& ctx, PlotData& plot, const std::vector<uint32_t>& plotDraw, int& offset, bool rightEnd );
void DrawThread( const TimelineContext& ctx, const ThreadData& thread, const std::vector<TimelineDraw>& draw, const std::vector<ContextSwitchDraw>& ctxDraw, const std::vector<SamplesDraw>& samplesDraw, const std::vector<std::unique_ptr<LockDraw>>& lockDraw, int& offset, int depth, bool hasCtxSwitches, bool hasSamples );
void DrawThreadMessagesList( const TimelineContext& ctx, const std::vector<MessagesDraw>& drawList, int offset, uint64_t tid );
void DrawThreadOverlays( const ThreadData& thread, const ImVec2& ul, const ImVec2& dr );
bool DrawGpu( const TimelineContext& ctx, const GpuCtxData& gpu, int& offset );
bool DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDraw>& cpuDraw, const std::vector<std::vector<CpuCtxDraw>>& ctxDraw, int& offset, bool hasCpuData );
void DrawThreadMigrations( const TimelineContext& ctx, const int origOffset, uint64_t thread );
bool IsBackgroundDone() const { return m_worker.IsBackgroundDone(); }
void AddLlmAttachment( const nlohmann::json& json );
void AddLlmQuery( const char* query );
bool m_showRanges = false;
Range m_statRange;
Range m_flameRange;
Range m_waitStackRange;
private:
@@ -224,7 +243,7 @@ private:
};
void InitTextEditor();
void SetupConfig( const Config& config );
void SetupConfig();
void Achieve( const char* id );
bool DrawImpl();
@@ -235,7 +254,8 @@ private:
void DrawTimelineFrames( const FrameData& frames );
void DrawTimeline();
void DrawSampleList( const TimelineContext& ctx, const std::vector<SamplesDraw>& drawList, const Vector<SampleData>& vec, int offset );
void DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int offset, uint64_t tid );
void DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int offset, uint64_t tid, int maxDepth, double margin );
void DrawThreadCropper( const int depth, const uint64_t tid, const float xPos, const float yPos, const float ostep, const float cropperWidth, const bool hasCtxSwitches );
void DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber );
int DispatchGpuZoneLevel( const Vector<short_ptr<GpuEvent>>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift );
template<typename Adapter, typename V>
@@ -271,6 +291,14 @@ private:
void DrawRangeEntry( Range& range, const char* label, uint32_t color, const char* popupLabel, int id );
void DrawSourceTooltip( const char* filename, uint32_t line, int before = 3, int after = 3, bool separateTooltip = true );
void DrawWaitStacks();
void DrawManual();
void DrawFlameGraph();
void DrawFlameGraphHeader( uint64_t timespan );
void DrawFlameGraphLevel( const std::vector<FlameGraphItem>& data, FlameGraphContext& ctx, int depth, bool samples );
void DrawFlameGraphItem( const FlameGraphItem& item, FlameGraphContext& ctx, int depth, bool samples );
void BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<short_ptr<ZoneEvent>>& zones );
void BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<short_ptr<ZoneEvent>>& zones, const ContextSwitch* ctx );
void BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<SampleData>& samples );
void ListMemData( std::vector<const MemEvent*>& vec, const std::function<void(const MemEvent*)>& DrawAddress, int64_t startTime = -1, uint64_t pool = 0 );
@@ -303,12 +331,13 @@ private:
void AddAnnotation( int64_t start, int64_t end );
bool IsFrameExternal( const char* filename, const char* image );
uint32_t GetThreadColor( uint64_t thread, int depth );
uint32_t GetSrcLocColor( const SourceLocation& srcloc, int depth );
uint32_t GetRawSrcLocColor( const SourceLocation& srcloc, int depth );
uint32_t GetZoneColor( const ZoneEvent& ev, uint64_t thread, int depth );
uint32_t GetZoneColor( const GpuEvent& ev );
ZoneColorData GetZoneColorData( const ZoneEvent& ev, uint64_t thread, int depth );
ZoneColorData GetZoneColorData( const ZoneEvent& ev, uint64_t thread, int depth, uint32_t inheritedColor );
ZoneColorData GetZoneColorData( const GpuEvent& ev );
void ZoomToZone( const ZoneEvent& ev );
@@ -360,6 +389,7 @@ private:
int64_t GetZoneSelfTime( const ZoneEvent& zone );
int64_t GetZoneSelfTime( const GpuEvent& zone );
bool GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, int64_t& time, uint64_t& cnt );
bool GetZoneRunningTime( const ContextSwitch* ctx, const ZoneEvent& ev, const RangeSlim& range, int64_t& time, uint64_t& cnt );
const char* GetThreadContextData( uint64_t thread, bool& local, bool& untracked, const char*& program );
tracy_force_inline void CalcZoneTimeData( unordered_flat_map<int16_t, ZoneTimeData>& data, int64_t& ztime, const ZoneEvent& zone );
@@ -375,11 +405,14 @@ private:
void Attention( bool& alreadyDone );
void UpdateTitle();
unordered_flat_map<uint64_t, int> m_threadDepthLimit;
unordered_flat_map<uint64_t, bool> m_visibleMsgThread;
unordered_flat_map<uint64_t, bool> m_waitStackThread;
unordered_flat_map<uint64_t, bool> m_flameGraphThread;
unordered_flat_map<const void*, int> m_gpuDrift;
unordered_flat_map<const PlotData*, PlotView> m_plotView;
Vector<const ThreadData*> m_threadOrder;
Vector<const ThreadData*> m_threadReinsert;
Vector<float> m_threadDnd;
tracy_force_inline bool& VisibleMsgThread( uint64_t thread )
@@ -402,6 +435,16 @@ private:
return it->second;
}
tracy_force_inline bool& FlameGraphThread( uint64_t thread )
{
auto it = m_flameGraphThread.find( thread );
if( it == m_flameGraphThread.end() )
{
it = m_flameGraphThread.emplace( thread, true ).first;
}
return it->second;
}
tracy_force_inline int& GpuDrift( const void* ptr )
{
auto it = m_gpuDrift.find( ptr );
@@ -461,6 +504,7 @@ private:
bool m_messagesShowCallstack = false;
Vector<uint32_t> m_msgList;
bool m_disconnectIssued = false;
uint64_t m_selectedThread = 0;
DecayValue<uint64_t> m_drawThreadMigrations = 0;
DecayValue<uint64_t> m_drawThreadHighlight = 0;
Annotation* m_selectedAnnotation = nullptr;
@@ -488,10 +532,18 @@ private:
bool m_showCpuDataWindow = false;
bool m_showAnnotationList = false;
bool m_showWaitStacks = false;
bool m_showFlameGraph = false;
bool m_showManual = false;
AccumulationMode m_statAccumulationMode = AccumulationMode::SelfOnly;
bool m_statSampleTime = true;
int m_statMode = 0;
bool m_shortImageNames = true;
int m_flameMode = 0;
bool m_flameSort = false;
bool m_flameRunningTime = false;
bool m_flameExternal = true;
bool m_flameExternalTail = true;
int m_statSampleLocation = 2;
bool m_statHideUnknown = true;
bool m_showAllSymbols = false;
@@ -501,6 +553,7 @@ private:
bool m_statSeparateInlines = false;
bool m_mergeInlines = false;
bool m_relativeInlines = false;
bool m_topInline = false;
bool m_statShowAddress = false;
bool m_statShowKernel = true;
bool m_groupChildrenLocations = false;
@@ -533,10 +586,6 @@ private:
const char* m_sourceViewFile;
bool m_uarchSet = false;
ImFont* m_smallFont;
ImFont* m_bigFont;
ImFont* m_fixedFont;
float m_rootWidth, m_rootHeight;
SetTitleCallback m_stcb;
bool m_titleSet = false;
@@ -570,10 +619,10 @@ private:
std::atomic<size_t> m_srcFileBytes { 0 };
std::atomic<size_t> m_dstFileBytes { 0 };
void* m_frameTexture = nullptr;
ImTextureID m_frameTexture = 0;
const void* m_frameTexturePtr = nullptr;
void* m_frameTextureConn = nullptr;
ImTextureID m_frameTextureConn = 0;
const void* m_frameTextureConnPtr = nullptr;
std::vector<std::unique_ptr<Annotation>> m_annotations;
@@ -614,6 +663,7 @@ private:
int64_t time = 0;
};
bool hasResults = false;
bool show = false;
bool ignoreCase = false;
std::vector<int16_t> match;
@@ -638,6 +688,7 @@ private:
size_t sortedNum = 0, selSortNum, selSortActive;
float average, selAverage;
float median, selMedian;
float p75, p90, p99, p99_9;
int64_t total, selTotal;
int64_t selTime;
bool drawAvgMed = true;
@@ -671,6 +722,7 @@ private:
selGroup = Unselected;
highlight.active = false;
samples.counts.clear();
hasResults = false;
}
void ResetMatch()
@@ -680,6 +732,10 @@ private:
sortedNum = 0;
average = 0;
median = 0;
p75 = 0;
p90 = 0;
p99 = 0;
p99_9 = 0;
total = 0;
tmin = std::numeric_limits<int64_t>::max();
tmax = std::numeric_limits<int64_t>::min();
@@ -830,7 +886,7 @@ private:
} m_cache;
struct {
void* texture = nullptr;
ImTextureID texture = 0;
float timeLeft = 0;
float speed = 1;
uint32_t frame = 0;
@@ -873,6 +929,32 @@ private:
AchievementsMgr* m_achievementsMgr;
bool m_achievements = false;
double m_horizontalScrollMultiplier = 1.0;
double m_verticalScrollMultiplier = 1.0;
std::shared_ptr<TracyManualData> m_manualData;
size_t m_activeManualChunk = 0;
Markdown m_markdown;
TaskDispatch m_td;
std::vector<FlameGraphItem> m_flameGraphData;
struct
{
uint64_t count = 0;
uint64_t lastTime = 0;
RangeSlim range = {false, 0, 0};
void Reset()
{
count = 0;
lastTime = 0;
}
} m_flameGraphInvariant;
#ifndef __EMSCRIPTEN__
TracyLlm m_llm;
#endif
};
}
@@ -53,6 +53,7 @@ struct ViewData
uint8_t drawCpuUsageGraph = true;
uint8_t drawSamples = true;
uint8_t dynamicColors = 1;
uint8_t inheritParentColors = true;
uint8_t forceColors = false;
uint8_t ghostZones = true;
ShortenName shortenName = ShortenName::NoSpaceAndNormalize;
@@ -1,6 +1,8 @@
#include "TracyImGui.hpp"
#include "TracyPrint.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
@@ -79,17 +81,23 @@ void View::DrawAnnotationList()
AddAnnotation( m_vd.zvStart, m_vd.zvEnd );
}
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
if( m_annotations.empty() )
{
ImGui::TextWrapped( "No annotations." );
ImGui::Separator();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_HORSE );
TextCentered( "No annotations" );
ImGui::PopFont();
ImGui::End();
return;
}
else
{
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
}
TextFocused( "Annotations:", RealToString( m_annotations.size() ) );
ImGui::Separator();
@@ -1,23 +1,19 @@
#include <inttypes.h>
#include <nlohmann/json.hpp>
#include <sstream>
#include "../public/common/TracyStackFrames.hpp"
#include "TracyConfig.hpp"
#include "TracyImGui.hpp"
#include "TracyMouse.hpp"
#include "TracyPrint.hpp"
#include "TracyUtility.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
static bool IsFrameExternal( const char* filename, const char* image )
{
if( strncmp( filename, "/usr/", 5 ) == 0 || strncmp( filename, "/lib/", 5 ) == 0 || strcmp( filename, "[unknown]" ) == 0 ) return true;
if( strncmp( filename, "C:\\Program Files\\", 17 ) == 0 || strncmp( filename, "d:\\a01\\_work\\", 13 ) == 0 ) return true;
if( !image ) return false;
return strncmp( image, "/usr/", 5 ) == 0 || strncmp( image, "/lib/", 5 ) == 0 || strncmp( image, "/lib64/", 7 ) == 0 || strcmp( image, "<kernel>" ) == 0;
}
void View::DrawCallstackWindow()
{
bool show = true;
@@ -32,6 +28,74 @@ void View::DrawCallstackWindow()
if( !show ) m_callstackInfoWindow = 0;
}
static nlohmann::json GetCallstackJson( Worker& worker, const VarArray<CallstackFrameId>& cs )
{
nlohmann::json json = {
{ "type", "callstack" },
{ "frames", nlohmann::json::array() }
};
auto& frames = json["frames"];
int fidx = 0;
for( auto& entry : cs )
{
auto frameData = worker.GetCallstackFrame( entry );
if( !frameData )
{
frames.push_back( { "pointer", worker.GetCanonicalPointer( entry ) } );
}
else
{
const auto fsz = frameData->size;
for( uint8_t f=0; f<fsz; f++ )
{
const auto& frame = frameData->data[f];
auto txt = worker.GetString( frame.name );
if( fidx == 0 && f != fsz-1 )
{
auto test = tracy::s_tracyStackFrames;
bool match = false;
do
{
if( strcmp( txt, *test ) == 0 )
{
match = true;
break;
}
}
while( *++test );
if( match ) continue;
}
frames.push_back( {
{ "function", txt },
{ "source", worker.GetString( frame.file ) },
} );
auto& frameJson = frames.back();
if( f == fsz-1 )
{
frameJson["frame"] = fidx++;
}
else
{
frameJson["inline"] = true;
}
if( frame.line != 0 )
{
frameJson["line"] = frame.line;
}
if( frameData->imageName.Active() )
{
frameJson["executable"] = worker.GetString( frameData->imageName );
}
}
}
}
return json;
}
void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
{
auto& cs = m_worker.GetCallstack( callstack );
@@ -104,10 +168,42 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
}
ImGui::SetClipboardText( s.str().c_str() );
}
if( s_config.llm )
{
ImGui::SameLine();
if( ImGui::SmallButton( ICON_FA_ROBOT ) )
{
AddLlmAttachment( GetCallstackJson( m_worker, cs ) );
}
if( ImGui::IsItemHovered() && IsMouseClicked( ImGuiMouseButton_Right ) )
{
ImGui::OpenPopup( "##callstackllm" );
}
if( ImGui::BeginPopup( "##callstackllm" ) )
{
if( ImGui::Selectable( "What is program doing at this moment?" ) )
{
AddLlmAttachment( GetCallstackJson( m_worker, cs ) );
AddLlmQuery( "What is program doing at this moment?" );
ImGui::CloseCurrentPopup();
}
if( ImGui::Selectable( "Walk me through the details of this callstack, step by step, explaining the code." ) )
{
AddLlmAttachment( GetCallstackJson( m_worker, cs ) );
AddLlmQuery( "Walk me through the details of this callstack, step by step, explaining the code." );
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
SmallCheckbox( "External frames", &m_showExternalFrames );
SmallCheckbox( ICON_FA_SHIELD_HALVED " External frames", &m_showExternalFrames );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
SmallCheckbox( ICON_FA_SCISSORS " Short images", &m_shortImageNames );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
@@ -152,7 +248,7 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
ImGui::TableSetupColumn( "Image" );
ImGui::TableHeadersRow();
bool external = false;
int external = 0;
int fidx = 0;
int bidx = 0;
for( auto& entry : cs )
@@ -162,7 +258,7 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
{
if( !m_showExternalFrames )
{
external = true;
external++;
continue;
}
ImGui::TableNextRow();
@@ -209,23 +305,27 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
if( !m_showExternalFrames )
{
if( f == fsz-1 ) fidx++;
external = true;
external++;
continue;
}
}
else
else if( external != 0 )
{
if( external )
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( "external" );
ImGui::TableNextColumn();
if( external == 1 )
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushFont( m_smallFont );
TextDisabledUnformatted( "external" );
ImGui::PopFont();
ImGui::TableNextColumn();
TextDisabledUnformatted( "\xe2\x80\xa6" );
external = false;
TextDisabledUnformatted( "1 frame" );
}
else
{
ImGui::TextDisabled( "%i frames", external );
}
ImGui::PopFont();
external = 0;
}
ImGui::TableNextRow();
@@ -237,7 +337,7 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
}
else
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( "inline" );
ImGui::PopFont();
}
@@ -389,19 +489,47 @@ void View::DrawCallstackTable( uint32_t callstack, bool globalEntriesButton )
}
ImGui::PopTextWrapPos();
ImGui::TableNextColumn();
if( image ) TextDisabledUnformatted( image );
if( image )
{
const char* end = image + strlen( image );
if( m_shortImageNames )
{
const char* ptr = end - 1;
while( ptr > image && *ptr != '/' && *ptr != '\\' ) ptr--;
if( *ptr == '/' || *ptr == '\\' ) ptr++;
const auto cw = ImGui::GetContentRegionAvail().x;
const auto tw = ImGui::CalcTextSize( image, end ).x;
TextDisabledUnformatted( ptr );
if( ptr != image || tw > cw ) TooltipIfHovered( image );
}
else
{
const auto cw = ImGui::GetContentRegionAvail().x;
const auto tw = ImGui::CalcTextSize( image, end ).x;
TextDisabledUnformatted( image );
if( tw > cw ) TooltipIfHovered( image );
}
}
}
}
}
if( external )
if( external != 0 )
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( "external" );
ImGui::PopFont();
ImGui::TableNextColumn();
TextDisabledUnformatted( "\xe2\x80\xa6" );
if( external == 1 )
{
TextDisabledUnformatted( "1 frame" );
}
else
{
ImGui::TextDisabled( "%i frames", external );
}
ImGui::PopFont();
}
ImGui::EndTable();
}
@@ -537,7 +665,7 @@ void View::CallstackTooltipContents( uint32_t idx )
if( frameData->imageName.Active() )
{
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( m_worker.GetString( frameData->imageName ) );
ImGui::PopFont();
@@ -8,6 +8,8 @@
#include "TracyFileselector.hpp"
#include "TracyPrint.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
@@ -134,32 +136,6 @@ bool View::FindMatchingZone( int prev0, int prev1, int flags )
return found;
}
static std::vector<std::string> SplitLines( const char* data, size_t sz )
{
std::vector<std::string> ret;
auto txt = data;
for(;;)
{
auto end = txt;
while( *end != '\n' && *end != '\r' && end - data < sz ) end++;
ret.emplace_back( std::string { txt, end } );
if( end - data == sz ) break;
if( *end == '\n' )
{
end++;
if( end - data < sz && *end == '\r' ) end++;
}
else if( *end == '\r' )
{
end++;
if( end - data < sz && *end == '\n' ) end++;
}
if( end - data == sz ) break;
txt = end;
}
return ret;
}
static void PrintFile( const char* data, size_t sz, uint32_t color )
{
auto lines = SplitLines( data, sz );
@@ -236,8 +212,14 @@ void View::DrawCompare()
#else
if( !m_compare.second )
{
ImGui::TextWrapped( "Please load a second trace to compare results." );
if( ImGui::Button( ICON_FA_FOLDER_OPEN " Open second trace" ) && !m_compare.loadThread.joinable() )
const auto ty = ImGui::GetTextLineHeight();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 5 ) * 0.5f ) );
TextCentered( ICON_FA_SCALE_BALANCED );
TextCentered( "Please load a second trace to compare results" );
ImGui::PopFont();
ImGui::TextUnformatted( "" );
if( ButtonCentered( ICON_FA_FOLDER_OPEN " Open second trace" ) && !m_compare.loadThread.joinable() )
{
Fileselector::OpenFile( "tracy", "Tracy Profiler trace file", [this]( const char* fn ) {
try
@@ -270,7 +252,7 @@ void View::DrawCompare()
}
} );
}
tracy::BadVersion( m_compare.badVer, m_bigFont );
tracy::BadVersion( m_compare.badVer );
ImGui::End();
return;
}
@@ -279,7 +261,12 @@ void View::DrawCompare()
if( !m_worker.AreSourceLocationZonesReady() || !m_compare.second->AreSourceLocationZonesReady() )
{
ImGui::TextWrapped( "Please wait, computing data..." );
const auto ty = ImGui::GetTextLineHeight();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 - ty ) * 0.5f ) );
TextCentered( ICON_FA_FROG );
TextCentered( "Please wait, computing data..." );
ImGui::PopFont();
DrawWaitingDots( s_time );
ImGui::End();
return;
@@ -400,9 +387,9 @@ void View::DrawCompare()
}
}
std::sort( m_compare.thisUnique.begin(), m_compare.thisUnique.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs, rhs ) < 0; } );
std::sort( m_compare.secondUnique.begin(), m_compare.secondUnique.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs, rhs ) < 0; } );
std::sort( m_compare.diffs.begin(), m_compare.diffs.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs.first, rhs.first ) < 0; } );
pdqsort_branchless( m_compare.thisUnique.begin(), m_compare.thisUnique.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs, rhs ) < 0; } );
pdqsort_branchless( m_compare.secondUnique.begin(), m_compare.secondUnique.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs, rhs ) < 0; } );
pdqsort_branchless( m_compare.diffs.begin(), m_compare.diffs.end(), []( const auto& lhs, const auto& rhs ) { return strcmp( lhs.first, rhs.first ) < 0; } );
}
}
@@ -425,7 +412,7 @@ void View::DrawCompare()
{
auto it = tfc.find( v );
assert( it != tfc.end() );
ImGui::PushFont( m_fixedFont );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 0, 0 ) );
PrintFile( it->second.data, it->second.len, 0xFF6666FF );
ImGui::PopStyleVar();
@@ -449,7 +436,7 @@ void View::DrawCompare()
{
auto it = ofc.find( v );
assert( it != ofc.end() );
ImGui::PushFont( m_fixedFont );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 0, 0 ) );
PrintFile( it->second.data, it->second.len, 0xFF66DD66 );
ImGui::PopStyleVar();
@@ -471,7 +458,7 @@ void View::DrawCompare()
{
if( ImGui::TreeNode( v.first ) )
{
ImGui::PushFont( m_fixedFont );
ImGui::PushFont( g_fonts.mono, FontNormal );
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 0, 0 ) );
PrintDiff( v.second );
ImGui::PopStyleVar();
@@ -3,6 +3,7 @@
#include "TracyPrint.hpp"
#include "TracyTexture.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -15,7 +16,6 @@ bool View::DrawConnection()
{
const auto scale = GetScale();
const auto ty = ImGui::GetTextLineHeight();
const auto cs = ty * 0.9f;
const auto isConnected = m_worker.IsConnected();
size_t sendQueue;
@@ -33,7 +33,8 @@ bool View::DrawConnection()
{
sprintf( buf, "%6.2f Mbps", mbps );
}
ImGui::Dummy( ImVec2( cs, 0 ) );
ImGui::AlignTextToFramePadding();
TextColoredUnformatted( isConnected ? 0xFF2222CC : 0xFF444444, ICON_FA_CIRCLE );
ImGui::SameLine();
ImGui::PlotLines( buf, mbpsVector.data(), mbpsVector.size(), 0, nullptr, 0, std::numeric_limits<float>::max(), ImVec2( 150 * scale, 0 ) );
TextDisabledUnformatted( "Ratio" );
@@ -76,11 +77,8 @@ bool View::DrawConnection()
}
}
const auto wpos = ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin();
ImGui::GetWindowDrawList()->AddCircleFilled( wpos + ImVec2( 1 + cs * 0.5, 3 + ty * 1.75 ), cs * 0.5, isConnected ? 0xFF2222CC : 0xFF444444, 10 );
{
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
Worker::MainThreadDataLockGuard lock = m_worker.ObtainLockForMainThread();
ImGui::SameLine();
TextFocused( "+", RealToString( m_worker.GetSendInFlight() ) );
const auto sz = m_worker.GetFrameCount( *m_frames );
@@ -144,7 +142,7 @@ bool View::DrawConnection()
ImGui::SameLine( 0, 2 * ty );
const char* stopStr = ICON_FA_PLUG " Stop";
std::lock_guard<std::mutex> lock( m_worker.GetDataLock() );
Worker::MainThreadDataLockGuard lock = m_worker.ObtainLockForMainThread();
if( !m_disconnectIssued && m_worker.IsConnected() )
{
if( ImGui::Button( stopStr ) )
@@ -168,7 +166,7 @@ bool View::DrawConnection()
if( ImGui::BeginPopupModal( "Confirm trace discard", nullptr, ImGuiWindowFlags_AlwaysAutoResize ) )
{
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
TextCentered( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopFont();
ImGui::TextUnformatted( "All unsaved profiling data will be lost!" );
@@ -6,6 +6,8 @@
#include "TracyTimelineContext.hpp"
#include "TracyTimelineDraw.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
@@ -14,47 +16,49 @@ const char* View::DecodeContextSwitchReasonCode( uint8_t reason )
{
switch( reason )
{
case 0: return "Executive";
case 1: return "FreePage";
case 2: return "PageIn";
case 3: return "PoolAllocation";
case 4: return "DelayExecution";
case 5: return "Suspended";
case 6: return "UserRequest";
case 7: return "WrExecutive";
case 8: return "WrFreePage";
case 9: return "WrPageIn";
case 10: return "WrPoolAllocation";
case 11: return "WrDelayExecution";
case 12: return "WrSuspended";
case 13: return "WrUserRequest";
case 14: return "WrEventPair";
case 15: return "WrQueue";
case 16: return "WrLpcReceive";
case 17: return "WrLpcReply";
case 18: return "WrVirtualMemory";
case 19: return "WrPageOut";
case 20: return "WrRendezvous";
case 21: return "WrKeyedEvent";
case 22: return "WrTerminated";
case 23: return "WrProcessInSwap";
case 24: return "WrCpuRateControl";
case 25: return "WrCalloutStack";
case 26: return "WrKernel";
case 27: return "WrResource";
case 28: return "WrPushLock";
case 29: return "WrMutex";
case 30: return "WrQuantumEnd";
case 31: return "WrDispatchInt";
case 32: return "WrPreempted";
case 33: return "WrYieldExecution";
case 34: return "WrFastMutex";
case 35: return "WrGuardedMutex";
case 36: return "WrRundown";
case 37: return "WrAlertByThreadId";
case 38: return "WrDeferredPreempt";
case 39: return "WrPhysicalFault";
case 40: return "MaximumWaitReason";
case ContextSwitchData::Win32_Executive: return "Executive";
case ContextSwitchData::Win32_FreePage: return "FreePage";
case ContextSwitchData::Win32_PageIn: return "PageIn";
case ContextSwitchData::Win32_PoolAllocation: return "PoolAllocation";
case ContextSwitchData::Win32_DelayExecution: return "DelayExecution";
case ContextSwitchData::Win32_Suspended: return "Suspended";
case ContextSwitchData::Win32_UserRequest: return "UserRequest";
case ContextSwitchData::Win32_WrExecutive: return "WrExecutive";
case ContextSwitchData::Win32_WrFreePage: return "WrFreePage";
case ContextSwitchData::Win32_WrPageIn: return "WrPageIn";
case ContextSwitchData::Win32_WrPoolAllocation: return "WrPoolAllocation";
case ContextSwitchData::Win32_WrDelayExecution: return "WrDelayExecution";
case ContextSwitchData::Win32_WrSuspended: return "WrSuspended";
case ContextSwitchData::Win32_WrUserRequest: return "WrUserRequest";
case ContextSwitchData::Win32_WrEventPair: return "WrEventPair";
case ContextSwitchData::Win32_WrQueue: return "WrQueue";
case ContextSwitchData::Win32_WrLpcReceive: return "WrLpcReceive";
case ContextSwitchData::Win32_WrLpcReply: return "WrLpcReply";
case ContextSwitchData::Win32_WrVirtualMemory: return "WrVirtualMemory";
case ContextSwitchData::Win32_WrPageOut: return "WrPageOut";
case ContextSwitchData::Win32_WrRendezvous: return "WrRendezvous";
case ContextSwitchData::Win32_WrKeyedEvent: return "WrKeyedEvent";
case ContextSwitchData::Win32_WrTerminated: return "WrTerminated";
case ContextSwitchData::Win32_WrProcessInSwap: return "WrProcessInSwap";
case ContextSwitchData::Win32_WrCpuRateControl: return "WrCpuRateControl";
case ContextSwitchData::Win32_WrCalloutStack: return "WrCalloutStack";
case ContextSwitchData::Win32_WrKernel: return "WrKernel";
case ContextSwitchData::Win32_WrResource: return "WrResource";
case ContextSwitchData::Win32_WrPushLock: return "WrPushLock";
case ContextSwitchData::Win32_WrMutex: return "WrMutex";
case ContextSwitchData::Win32_WrQuantumEnd: return "WrQuantumEnd";
case ContextSwitchData::Win32_WrDispatchInt: return "WrDispatchInt";
case ContextSwitchData::Win32_WrPreempted: return "WrPreempted";
case ContextSwitchData::Win32_WrYieldExecution: return "WrYieldExecution";
case ContextSwitchData::Win32_WrFastMutex: return "WrFastMutex";
case ContextSwitchData::Win32_WrGuardedMutex: return "WrGuardedMutex";
case ContextSwitchData::Win32_WrRundown: return "WrRundown";
case ContextSwitchData::Win32_WrAlertByThreadId: return "WrAlertByThreadId";
case ContextSwitchData::Win32_WrDeferredPreempt: return "WrDeferredPreempt";
case ContextSwitchData::Win32_WrPhysicalFault: return "WrPhysicalFault";
case ContextSwitchData::Win32_WrIoRing: return "WrIoRing";
case ContextSwitchData::Win32_WrMdlCache: return "WrMdlCache";
case ContextSwitchData::Win32_WrRcu: return "WrRcu";
default: return "unknown";
}
}
@@ -63,21 +67,49 @@ const char* View::DecodeContextSwitchReason( uint8_t reason )
{
switch( reason )
{
case 0: return "(Thread is waiting for the scheduler)";
case 1: return "(Thread is waiting for a free virtual memory page)";
case 2: return "(Thread is waiting for a virtual memory page to arrive in memory)";
case 4: return "(Thread execution is delayed)";
case 5: return "(Thread execution is suspended)";
case 6: return "(Thread is waiting on object - WaitForSingleObject, etc.)";
case 7: return "(Thread is waiting for the scheduler)";
case 8: return "(Thread is waiting for a free virtual memory page)";
case 9: return "(Thread is waiting for a virtual memory page to arrive in memory)";
case 11: return "(Thread execution is delayed)";
case 12: return "(Thread execution is suspended)";
case 13: return "(Thread is waiting for window messages)";
case 15: return "(Thread is waiting on KQUEUE)";
case 24: return "(CPU rate limiting)";
case 34: return "(Waiting for a Fast Mutex)";
case ContextSwitchData::Win32_Executive: return "(Thread is waiting for the scheduler)";
case ContextSwitchData::Win32_FreePage: return "(Thread is waiting for a free virtual memory page)";
case ContextSwitchData::Win32_PageIn: return "(Thread is waiting for a virtual memory page to arrive in memory)";
case ContextSwitchData::Win32_PoolAllocation: return "(Thread is waiting for a system allocation)";
case ContextSwitchData::Win32_DelayExecution: return "(Thread execution is delayed)";
case ContextSwitchData::Win32_Suspended: return "(Thread execution is suspended)";
case ContextSwitchData::Win32_UserRequest: return "(Thread is waiting on object - WaitForSingleObject, etc.)";
case ContextSwitchData::Win32_WrExecutive: return "(Thread is waiting for the scheduler)";
case ContextSwitchData::Win32_WrFreePage: return "(Thread is waiting for a free virtual memory page)";
case ContextSwitchData::Win32_WrPageIn: return "(Thread is waiting for a virtual memory page to arrive in memory)";
case ContextSwitchData::Win32_WrPoolAllocation: return "(Thread is waiting for a system allocation)";
case ContextSwitchData::Win32_WrDelayExecution: return "(Thread execution is delayed)";
case ContextSwitchData::Win32_WrSuspended: return "(Thread execution is suspended)";
case ContextSwitchData::Win32_WrUserRequest: return "(Thread is waiting for window messages)";
case ContextSwitchData::Win32_WrEventPair: return "(Thread is waiting for a client/server event pair)";
case ContextSwitchData::Win32_WrQueue: return "(Thread is waiting on KQUEUE, which was empty. Usuall has to do with I/O completion.)";
case ContextSwitchData::Win32_WrLpcReceive: return "(Thread is waiting for a local procedure call to arrive)";
case ContextSwitchData::Win32_WrLpcReply: return "(Thread is waiting for a local procedure call reply to arrive)";
case ContextSwitchData::Win32_WrVirtualMemory: return "(Thread is waiting for the system to allocate virtual memory)";
case ContextSwitchData::Win32_WrPageOut: return "(Thread is waiting for a virtual memory page to be written to disk)";
case ContextSwitchData::Win32_WrRendezvous: return "(Thread is waiting for a rendezvous.)";
case ContextSwitchData::Win32_WrKeyedEvent: return "(Thread is waiting for a keyed event)";
case ContextSwitchData::Win32_WrTerminated: return "(Waiting for thread termination.)";
case ContextSwitchData::Win32_WrProcessInSwap: return "(Waiting for a process to be swapped in.)";
case ContextSwitchData::Win32_WrCpuRateControl: return "(CPU rate limiting)";
case ContextSwitchData::Win32_WrCalloutStack: return "(Waiting for the thread callout routine to finish due to stack being resized.)";
case ContextSwitchData::Win32_WrKernel: return "(Waiting for a kernel operation)";
case ContextSwitchData::Win32_WrResource: return "(Kernel is waiting for a resource, usually related to drivers loading, hardware changes or network connections.)";
case ContextSwitchData::Win32_WrPushLock: return "(Waiting for a driver PushLock to be released.)";
case ContextSwitchData::Win32_WrMutex: return "(Waiting for a Mutex object. This could be related to Inter Process Synchronization.)";
case ContextSwitchData::Win32_WrQuantumEnd: return "(Thread has used up all of its quantum and another thread was ready to be scheduled.)";
case ContextSwitchData::Win32_WrDispatchInt: return "(A software interrupt was dispatched and another thread was scheduled while processing DPCs.)";
case ContextSwitchData::Win32_WrPreempted: return "(Thread was preempted to run another thread with higher priority.)";
case ContextSwitchData::Win32_WrYieldExecution: return "(Thread yielded its quantum, most likely through SwitchToThread or Sleep(0).)";
case ContextSwitchData::Win32_WrFastMutex: return "(Waiting for a Fast Mutex held by the driver. Raises the IRQ level.)";
case ContextSwitchData::Win32_WrGuardedMutex: return "(Waiting for a Guarded Mutex held by the driver.)";
case ContextSwitchData::Win32_WrRundown: return "(Driver waiting for rundown. Some kernel shared object is most likely being reloaded.)";
case ContextSwitchData::Win32_WrAlertByThreadId: return "(Waiting for a synchronization primitive that does not use WaitForObject. Most likely from a SRWLock, CRITICAL_SECTION or WaitOnAdress.)";
case ContextSwitchData::Win32_WrDeferredPreempt: return "(Thread should be preempting another, but can not due to the other being running uninterruptable code.)";
case ContextSwitchData::Win32_WrPhysicalFault: return "(A physical fault needs to be handled.)";
case ContextSwitchData::Win32_WrIoRing: return "(Waiting for I/O Ring operations, likely due to a call to SubmitIORing.)";
case ContextSwitchData::Win32_WrMdlCache: return "(Waiting for the Memory Descriptor List cache, related to Virtual<>Physical I/O buffers.";
case ContextSwitchData::Win32_WrRcu: return "(Waiting for a Read-Copy-Update synchronization.)";
default: return "";
}
}
@@ -205,14 +237,14 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
{
TextFocused( "Wait reason:", DecodeContextSwitchReasonCode( prev.Reason() ) );
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( DecodeContextSwitchReason( prev.Reason() ) );
ImGui::PopFont();
}
TextFocused( "Wait state:", DecodeContextSwitchStateCode( prev.State() ) );
ImGui::SameLine();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( DecodeContextSwitchState( prev.State() ) );
ImGui::PopFont();
@@ -235,6 +267,7 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
{
ZoomToRange( prev.End(), ev.WakeupVal() );
}
TextFocused( "Readied by CPU:", RealToString( ev.WakeupCpu() ) );
tooltip = true;
}
if( tooltip )
@@ -440,7 +473,16 @@ void View::DrawWaitStacks()
bool threadsChanged = false;
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", m_threadOrder.size() );
size_t visibleThreads = 0;
for( const auto& t : m_threadOrder ) if( WaitStackThread( t->id ) ) visibleThreads++;
if( visibleThreads == m_threadOrder.size() )
{
ImGui::TextDisabled( "(%zu)", m_threadOrder.size() );
}
else
{
ImGui::TextDisabled( "(%zi/%zu)", visibleThreads, m_threadOrder.size() );
}
if( expand )
{
auto& crash = m_worker.GetCrashEvent();
@@ -498,7 +540,11 @@ void View::DrawWaitStacks()
ImGui::BeginChild( "##waitstacks" );
if( stacks.empty() )
{
ImGui::TextUnformatted( "No wait stacks to display." );
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_KIWI_BIRD );
TextCentered( "No wait stacks to display" );
ImGui::PopFont();
}
else
{
@@ -8,6 +8,8 @@
#include "TracyTimelineItem.hpp"
#include "TracyTimelineContext.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
constexpr float MinVisSize = 3;
@@ -80,6 +82,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
TextFocused( "Number of cores:", RealToString( cpuCnt ) );
if( usage.own + usage.other != 0 )
{
auto& topo = m_worker.GetCpuTopology();
const auto mt = m_vd.zvStart + ( ImGui::GetIO().MousePos.x - wpos.x ) * nspx;
ImGui::Separator();
for( int i=0; i<cpuCnt; i++ )
@@ -93,7 +96,14 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
auto tt = m_worker.GetThreadTopology( i );
if( tt )
{
ImGui::TextDisabled( "[%i:%i] CPU %i:", tt->package, tt->core, i );
if( topo.size() > 1 )
{
ImGui::TextDisabled( "[%i:%i:%i] CPU %i:", tt->package, tt->die, tt->core, i );
}
else
{
ImGui::TextDisabled( "[%i:%i] CPU %i:", tt->die, tt->core, i );
}
}
else
{
@@ -140,7 +150,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
offset += cpuUsageHeight + 3;
}
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
const auto sstep = sty + 1;
const auto origOffset = offset;
@@ -175,6 +185,8 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
ImGui::SameLine();
TextFocused( "Package:", RealToString( tt->package ) );
ImGui::SameLine();
TextFocused( "Die:", RealToString( tt->die ) );
ImGui::SameLine();
TextFocused( "Core:", RealToString( tt->core ) );
}
TextFocused( "Context switch regions:", RealToString( v.num ) );
@@ -183,7 +195,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
TextFocused( "End time:", TimeToString( t1 ) );
TextFocused( "Activity time:", TimeToString( t1 - t0 ) );
ImGui::EndTooltip();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
if( IsMouseClicked( 2 ) )
{
@@ -201,15 +213,17 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
const char* txt;
auto label = GetThreadContextData( thread, local, untracked, txt );
uint32_t color;
if( m_vd.dynamicColors != 0 )
{
color = local ? GetThreadColor( thread, 0 ) : ( untracked ? 0xFF663333 : 0xFF444444 );
}
else
{
color = local ? 0xFF334488 : ( untracked ? 0xFF663333 : 0xFF444444 );
}
auto getDisplayThreadColor = [this]( uint64_t thread, bool local, bool untracked ) {
if( m_vd.dynamicColors != 0 )
{
return local ? GetThreadColor( thread, 0 ) : ( untracked ? 0xFF663333 : 0xFF444444 );
}
else
{
return local ? 0xFF334488 : ( untracked ? 0xFF663333 : 0xFF444444 );
}
};
uint32_t color = getDisplayThreadColor( thread, local, untracked );
draw->AddRectFilled( wpos + ImVec2( px0, offset ), wpos + ImVec2( px1, offset + sty ), color );
if( m_drawThreadHighlight == thread )
@@ -264,6 +278,8 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
ImGui::SameLine();
TextFocused( "Package:", RealToString( tt->package ) );
ImGui::SameLine();
TextFocused( "Die:", RealToString( tt->die ) );
ImGui::SameLine();
TextFocused( "Core:", RealToString( tt->core ) );
}
if( local )
@@ -276,6 +292,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
TextFocused( "Thread:", m_worker.GetThreadName( thread ) );
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", RealToString( thread ) );
m_drawThreadMigrations = thread;
m_cpuDataThread = thread;
}
@@ -306,8 +323,69 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
TextFocused( "Start time:", TimeToStringExact( ev.Start() ) );
TextFocused( "End time:", TimeToStringExact( end ) );
TextFocused( "Activity time:", TimeToString( end - ev.Start() ) );
// Display data about the switch in
auto threadCtxSwitches = m_worker.GetContextSwitchData( thread );
if( threadCtxSwitches )
{
auto& v = threadCtxSwitches->v;
auto it = std::lower_bound( v.begin(), v.end(), ev.Start(), [](const auto& l, const auto& r) { return l.Start() < r; } );
// We should have the data, or something went wrong.
assert( it != v.end() && it->Start() == ev.Start() );
// Do we have information about the previous CSwitch?
if( it != v.begin() )
{
auto& prev = *( it - 1 );
ImGui::Separator();
TextFocused( "Wait reason:", DecodeContextSwitchReasonCode( prev.Reason() ) );
ImGui::SameLine();
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
TextDisabledUnformatted( DecodeContextSwitchReason( prev.Reason() ) );
ImGui::PopFont();
TextFocused( "Wait state:", DecodeContextSwitchStateCode( prev.State() ) );
TextFocused( "Waiting time:", TimeToString( it->WakeupVal() - prev.End() ) );
}
// Do we have information about the readying thread?
if( it->Start() - it->WakeupVal() )
{
ImGui::Separator();
TextFocused( "WakeUp delay:", TimeToString( it->Start() - it->WakeupVal() ) );
assert( it->WakeupCpu() < cpuCnt );
const auto& wakeUpCpuCSwitches = cpuData[it->WakeupCpu()].cs;
auto wakeupit = std::lower_bound( wakeUpCpuCSwitches.begin(), wakeUpCpuCSwitches.end(), it->WakeupVal(), []( const auto& l, const auto& r ) { return l.End() < r; } );
if( wakeupit != wakeUpCpuCSwitches.end()
&& wakeupit->Start() < it->WakeupVal()
&& it->WakeupVal() < wakeupit->End() )
{
TextDisabledUnformatted( "Woken up by:" );
ImGui::SameLine();
const auto wakeupThread = m_worker.DecompressThreadExternal( wakeupit->Thread() );
bool wakeupThreadLocal, wakeupThreadUntracked;
const char* wakeUpThreadProgram;
auto wakeuplabel = GetThreadContextData( wakeupThread, wakeupThreadLocal, wakeupThreadUntracked, wakeUpThreadProgram );
uint32_t wakeupThreadColor = getDisplayThreadColor( wakeupThread, wakeupThreadLocal, wakeupThreadUntracked );
TextColoredUnformatted( HighlightColor<75>( wakeupThreadColor ), wakeuplabel );
ImGui::SameLine();
ImGui::TextDisabled( "(%s)", RealToString( wakeupThread ) );
}
else
{
TextDisabledUnformatted( "Woken up by Kernel" );
}
}
}
ImGui::EndTooltip();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
if( local && IsMouseClicked( 0 ) )
{
@@ -327,7 +405,15 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
char buf[64];
if( tt )
{
sprintf( buf, "[%i:%i] CPU %i", tt->package, tt->core, i );
auto& topo = m_worker.GetCpuTopology();
if( topo.size() > 1 )
{
sprintf( buf, "[%i:%i:%i] CPU %i", tt->package, tt->die, tt->core, i );
}
else
{
sprintf( buf, "[%i:%i] CPU %i", tt->die, tt->core, i );
}
}
else
{
@@ -347,62 +433,141 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
ImGui::SameLine();
TextFocused( "Package:", RealToString( tt->package ) );
ImGui::SameLine();
TextFocused( "Die:", RealToString( tt->die ) );
ImGui::SameLine();
TextFocused( "Core:", RealToString( tt->core ) );
}
TextFocused( "Context switch regions:", RealToString( cpuData[i].cs.size() ) );
ImGui::EndTooltip();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
}
offset += sstep;
}
if( ImGui::IsMouseHoveringRect( wpos, wpos + ImVec2( w, offset ) ) && IsMouseClickReleased( ImGuiMouseButton_Left ) )
{
if( m_drawThreadHighlight != 0 )
{
m_selectedThread = m_drawThreadHighlight;
}
else
{
// Clicked anywhere in the CPUData timeline that is not a thread => Clear selected thread.
m_selectedThread = 0;
}
}
if( m_drawThreadMigrations != 0 )
{
auto ctxSwitch = m_worker.GetContextSwitchData( m_drawThreadMigrations );
if( ctxSwitch )
{
const auto color = HighlightColor( GetThreadColor( m_drawThreadMigrations, -8 ) );
DrawThreadMigrations( ctx, origOffset, m_drawThreadMigrations );
}
auto& v = ctxSwitch->v;
auto it = std::lower_bound( v.begin(), v.end(), m_vd.zvStart, [] ( const auto& l, const auto& r ) { return l.End() < r; } );
if( it != v.begin() ) --it;
auto end = std::lower_bound( it, v.end(), m_vd.zvEnd, [] ( const auto& l, const auto& r ) { return l.Start() < r; } );
if( end == v.end() ) --end;
const auto bgSize = GetScale() * 4.f;
const auto lnSize = GetScale() * 2.f;
while( it < end )
{
const auto t0 = it->End();
const auto cpu0 = it->Cpu();
++it;
const auto t1 = it->Start();
const auto cpu1 = it->Cpu();
const auto px0 = ( t0 - m_vd.zvStart ) * pxns;
const auto px1 = ( t1 - m_vd.zvStart ) * pxns;
if( px1 - px0 < 2 )
{
DrawLine( draw, dpos + ImVec2( px0, origOffset + sty * 0.5f + cpu0 * sstep ), dpos + ImVec2( px1, origOffset + sty * 0.5f + cpu1 * sstep ), color );
}
else
{
DrawLine( draw, dpos + ImVec2( px0, origOffset + sty * 0.5f + cpu0 * sstep ), dpos + ImVec2( px1, origOffset + sty * 0.5f + cpu1 * sstep ), 0xFF000000, bgSize );
DrawLine( draw, dpos + ImVec2( px0, origOffset + sty * 0.5f + cpu0 * sstep ), dpos + ImVec2( px1, origOffset + sty * 0.5f + cpu1 * sstep ), color, lnSize );
}
}
}
if( m_selectedThread != 0 )
{
DrawThreadMigrations( ctx, origOffset, m_selectedThread );
}
ImGui::PopFont();
return true;
}
void View::DrawThreadMigrations( const TimelineContext& ctx, const int origOffset, uint64_t thread )
{
const auto& wpos = ctx.wpos;
const auto w = ctx.w;
const auto ty = ctx.ty;
const auto sty = ctx.sty;
const auto pxns = ctx.pxns;
const auto nspx = ctx.nspx;
const auto dpos = wpos + ImVec2(0.5f, 0.5f);
const auto yMin = ctx.yMin;
const auto yMax = ctx.yMax;
const auto hover = ctx.hover;
const auto vStart = ctx.vStart;
const auto sstep = sty + 1;
auto draw = ImGui::GetWindowDrawList();
auto ctxSwitch = m_worker.GetContextSwitchData( thread );
if( ctxSwitch )
{
const auto color = HighlightColor( GetThreadColor( thread, -8 ) );
auto& v = ctxSwitch->v;
auto it = std::lower_bound( v.begin(), v.end(), m_vd.zvStart, [] ( const auto& l, const auto& r ) { return l.End() < r; } );
if( it != v.begin() ) --it;
auto end = std::lower_bound( it, v.end(), m_vd.zvEnd, [] ( const auto& l, const auto& r ) { return l.Start() < r; } );
if( end == v.end() ) --end;
const auto bgSize = GetScale() * 4.f;
const auto lnSize = GetScale() * 2.f;
const auto wakeupLineSize = GetScale() * 1.5f;
auto computeScreenPos = [&]( int64_t t, uint8_t cpu ) {
const auto px = ( t - m_vd.zvStart ) * pxns;
return dpos + ImVec2( px, origOffset + sty * 0.5f + cpu * sstep );
};
auto drawWakeUp = [&]( int64_t start, ImVec2 startPos, int64_t wakeup, uint8_t wakeupcpu, uint32_t wakecolor, bool forceDraw ) {
if( start != wakeup )
{
const auto pw = computeScreenPos( wakeup, wakeupcpu );
const auto wakeupWidthPixels = startPos.x - pw.x;
if( forceDraw || ( wakeupWidthPixels >= 0.5 ) )
{
DrawLine( draw, pw, startPos, wakecolor, wakeupLineSize );
draw->AddCircleFilled( pw, bgSize, wakecolor );
// Vertical line at beginning of thread to emphasize wakeup
if( wakeupWidthPixels >= 3 )
{
const float halfPx = GetScale() * 0.5f;
DrawLine( draw, ImVec2{ startPos.x, startPos.y - sty * 0.5f - halfPx }, ImVec2{ startPos.x , startPos.y + sty * 0.5f + halfPx }, 0xFF000000, lnSize * 2 );
DrawLine( draw, ImVec2{ startPos.x, startPos.y - sty * 0.5f - halfPx }, ImVec2{ startPos.x , startPos.y + sty * 0.5f + halfPx }, wakecolor, lnSize );
}
}
}
};
if( it != v.end() && it->Start() > m_vd.zvStart )
{
drawWakeUp( it->Start(), computeScreenPos( it->Start(), it->Cpu() ), it->WakeupVal(), it->WakeupCpu(), 0xFF444444, true);
}
while( it < end )
{
const auto t0 = it->End();
const auto cpu0 = it->Cpu();
const auto waitReason = it->Reason();
const auto waitState = it->State();
++it;
const auto t1 = it->Start();
const auto cpu1 = it->Cpu();
const auto p0 = computeScreenPos( t0, cpu0 );
const auto p1 = computeScreenPos( t1, cpu1 );
const auto migrationWidthPixels = p1.x - p0.x;
if( migrationWidthPixels < 2 )
{
DrawLine( draw, p0, p1, color );
}
else
{
DrawLine( draw, p0, p1, 0xFF000000, bgSize );
DrawLine( draw, p0, p1, color, lnSize );
}
const auto hue = 0.38f * float(waitReason); // Golden angle, gives new colors for each reason
const auto wakecolor = ImColor::HSV(hue, 1.f, 1.f);
drawWakeUp( t1, p1, it->WakeupVal(), it->WakeupCpu(), wakecolor, (migrationWidthPixels >= 30) );
}
}
}
void View::DrawCpuDataWindow()
{
const auto scale = GetScale();
@@ -7,7 +7,10 @@
#include "TracyImGui.hpp"
#include "TracyMouse.hpp"
#include "TracyPrint.hpp"
#include "TracySort.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
@@ -17,6 +20,7 @@ extern double s_time;
#ifndef TRACY_NO_STATISTICS
void View::FindZones()
{
m_findZone.hasResults = true;
m_findZone.match = m_worker.GetMatchingSourceLocation( m_findZone.pattern, m_findZone.ignoreCase );
if( m_findZone.match.empty() ) return;
@@ -263,11 +267,26 @@ void View::DrawFindZone()
#else
if( !m_worker.AreSourceLocationZonesReady() )
{
ImGui::TextWrapped( "Please wait, computing data..." );
const auto ty = ImGui::GetTextLineHeight();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 - ty ) * 0.5f ) );
TextCentered( ICON_FA_CROW );
TextCentered( "Please wait, computing data..." );
ImGui::PopFont();
DrawWaitingDots( s_time );
ImGui::End();
return;
}
if( m_worker.GetZoneCount() == 0 )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_CROW );
TextCentered( "No zones were collected" );
ImGui::PopFont();
ImGui::End();
return;
}
bool findClicked = false;
@@ -289,6 +308,7 @@ void View::DrawFindZone()
if( ImGui::Button( ICON_FA_BAN " Clear" ) )
{
m_findZone.pattern[0] = '\0';
m_findZone.Reset();
}
ImGui::SameLine();
@@ -324,15 +344,31 @@ void View::DrawFindZone()
FindZones();
}
if( !m_findZone.match.empty() )
ImGui::Separator();
ImGui::BeginChild( "##findzone" );
if( m_findZone.match.empty() )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_CROW );
if( m_findZone.hasResults )
{
TextCentered( "No matching zones found" );
}
else
{
TextCentered( "Please enter search pattern" );
}
ImGui::PopFont();
}
else
{
Achieve( "findZone" );
const auto rangeMin = m_findZone.range.min;
const auto rangeMax = m_findZone.range.max;
ImGui::Separator();
ImGui::BeginChild( "##findzone" );
bool expand = ImGui::TreeNodeEx( "Matched source locations", ImGuiTreeNodeFlags_DefaultOpen );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", m_findZone.match.size() );
@@ -507,10 +543,10 @@ void View::DrawFindZone()
}
}
auto mid = vec.begin() + vszorig;
#ifdef NO_PARALLEL_SORT
#ifdef __EMSCRIPTEN__
pdqsort_branchless( mid, vec.end() );
#else
std::sort( std::execution::par_unseq, mid, vec.end() );
ppqsort::sort( ppqsort::execution::par, mid, vec.end() );
#endif
std::inplace_merge( vec.begin(), mid, vec.end() );
@@ -519,6 +555,10 @@ void View::DrawFindZone()
{
m_findZone.average = float( total ) / vsz;
m_findZone.median = vec[vsz/2];
m_findZone.p75 = vec[3 * (vsz / 4)];
m_findZone.p90 = vec[vsz / 10 * 9];
m_findZone.p99 = vec[size_t(float(vsz * 0.99))];
m_findZone.p99_9 = vec[size_t(float(vsz * 0.999))];
m_findZone.total = total;
m_findZone.sortedNum = i;
m_findZone.tmin = tmin;
@@ -968,6 +1008,19 @@ void View::DrawFindZone()
TextFocused( "\xcf\x83:", TimeToString( sd ) );
TooltipIfHovered( "Standard deviation" );
}
TextFocused( "P75:", TimeToString( m_findZone.p75 ) );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
TextFocused( "P90:", TimeToString( m_findZone.p90 ) );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
TextFocused( "P99:", TimeToString( m_findZone.p99 ) );
ImGui::SameLine();
ImGui::Spacing();
ImGui::SameLine();
TextFocused( "P99.9:", TimeToString( m_findZone.p99_9 ) );
TextDisabledUnformatted( "Selection range:" );
ImGui::SameLine();
@@ -1976,14 +2029,13 @@ void View::DrawFindZone()
}
}
ImGui::EndChild();
if( changeZone != 0 )
{
auto& srcloc = m_worker.GetSourceLocation( changeZone );
m_findZone.ShowZone( changeZone, m_worker.GetString( srcloc.name.active ? srcloc.name : srcloc.function ) );
}
}
ImGui::EndChild();
#endif
ImGui::End();
@@ -0,0 +1,972 @@
#include <assert.h>
#include <inttypes.h>
#include "TracyColor.hpp"
#include "TracyEvent.hpp"
#include "TracyImGui.hpp"
#include "TracyMouse.hpp"
#include "TracyPrint.hpp"
#include "TracyVector.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
constexpr float MinVisSize = 3;
void View::BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<short_ptr<ZoneEvent>>& zones )
{
FlameGraphItem* cache;
int16_t last = 0;
if( zones.is_magic() )
{
auto& vec = *(Vector<ZoneEvent>*)&zones;
for( auto& v : vec )
{
if( !v.IsEndValid() ) break;
const auto srcloc = v.SrcLoc();
auto start = v.Start();
auto end = v.End();
if ( m_flameGraphInvariant.range.active )
{
start = std::clamp(start, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max);
end = std::clamp(end, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max);
}
const auto duration = end - start;
if( srcloc == last )
{
cache->time += duration;
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, cache->children, children );
}
}
else
{
auto it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
if( it == data.end() )
{
data.emplace_back( FlameGraphItem { srcloc, duration } );
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, data.back().children, children );
}
cache = &data.back();
}
else
{
it->time += duration;
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, it->children, children );
}
cache = &*it;
}
last = srcloc;
}
}
}
else
{
for( auto& v : zones )
{
if( !v->IsEndValid() ) break;
const auto srcloc = v->SrcLoc();
auto start = v->Start();
auto end = v->End();
if ( m_flameGraphInvariant.range.active )
{
start = std::clamp(start, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max);
end = std::clamp(end, m_flameGraphInvariant.range.min, m_flameGraphInvariant.range.max);
}
const auto duration = end - start;
if( srcloc == last )
{
cache->time += duration;
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, cache->children, children );
}
}
else
{
auto it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
if( it == data.end() )
{
data.emplace_back( FlameGraphItem { srcloc, duration } );
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, data.back().children, children );
}
cache = &data.back();
}
else
{
it->time += duration;
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, it->children, children );
}
cache = &*it;
}
last = srcloc;
}
}
}
}
void View::BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<short_ptr<ZoneEvent>>& zones, const ContextSwitch* ctx )
{
assert( ctx );
FlameGraphItem* cache;
int16_t last = 0;
if( zones.is_magic() )
{
auto& vec = *(Vector<ZoneEvent>*)&zones;
for( auto& v : vec )
{
if( !v.IsEndValid() ) break;
const auto srcloc = v.SrcLoc();
int64_t duration;
uint64_t cnt;
if ( m_flameRange.active )
{
if( !GetZoneRunningTime( ctx, v, m_flameGraphInvariant.range, duration, cnt ) ) continue;
}
else
{
if( !GetZoneRunningTime( ctx, v, duration, cnt ) ) break;
}
if( srcloc == last )
{
cache->time += duration;
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, cache->children, children, ctx );
}
}
else
{
auto it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
if( it == data.end() )
{
data.emplace_back( FlameGraphItem { srcloc, duration } );
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, data.back().children, children, ctx );
}
cache = &data.back();
}
else
{
it->time += duration;
if( v.HasChildren() )
{
auto& children = worker.GetZoneChildren( v.Child() );
BuildFlameGraph( worker, it->children, children, ctx );
}
cache = &*it;
}
last = srcloc;
}
}
}
else
{
for( auto& v : zones )
{
if( !v->IsEndValid() ) break;
const auto srcloc = v->SrcLoc();
int64_t duration;
uint64_t cnt;
if ( m_flameRange.active )
{
if( !GetZoneRunningTime( ctx, *v, m_flameGraphInvariant.range, duration, cnt ) ) continue;
}
else
{
if( !GetZoneRunningTime( ctx, *v, duration, cnt ) ) break;
}
if( srcloc == last )
{
cache->time += duration;
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, cache->children, children, ctx );
}
}
else
{
auto it = std::find_if( data.begin(), data.end(), [srcloc]( const auto& v ) { return v.srcloc == srcloc; } );
if( it == data.end() )
{
data.emplace_back( FlameGraphItem { srcloc, duration } );
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, data.back().children, children, ctx );
}
cache = &data.back();
}
else
{
it->time += duration;
if( v->HasChildren() )
{
auto& children = worker.GetZoneChildren( v->Child() );
BuildFlameGraph( worker, it->children, children, ctx );
}
cache = &*it;
}
last = srcloc;
}
}
}
}
void View::BuildFlameGraph( const Worker& worker, std::vector<FlameGraphItem>& data, const Vector<SampleData>& samples )
{
struct FrameCache
{
uint64_t symaddr;
StringIdx name;
bool external;
};
std::vector<FrameCache> cache;
for( auto& v : samples )
{
if ( m_flameGraphInvariant.range.active )
{
if ( v.time.Val() < m_flameGraphInvariant.range.min ||
v.time.Val() > m_flameGraphInvariant.range.max )
{
continue;
}
}
cache.clear();
const auto cs = v.callstack.Val();
const auto& callstack = worker.GetCallstack( cs );
const auto csz = callstack.size();
if( m_flameExternal )
{
for( size_t i=csz; i>0; i-- )
{
auto frameData = worker.GetCallstackFrame( callstack[i-1] );
if( frameData )
{
for( uint8_t j=frameData->size; j>0; j-- )
{
const auto frame = frameData->data[j-1];
const auto symaddr = frame.symAddr;
if( symaddr != 0 )
{
cache.emplace_back( FrameCache { symaddr, frame.name } );
}
}
}
}
}
else if( !m_flameExternalTail )
{
for( size_t i=csz; i>0; i-- )
{
auto frameData = worker.GetCallstackFrame( callstack[i-1] );
if( frameData )
{
for( uint8_t j=frameData->size; j>0; j-- )
{
const auto frame = frameData->data[j-1];
const auto symaddr = frame.symAddr;
if( symaddr != 0 )
{
auto filename = m_worker.GetString( frame.file );
auto image = frameData->imageName.Active() ? m_worker.GetString( frameData->imageName ) : nullptr;
if( !IsFrameExternal( filename, image ) )
{
cache.emplace_back( FrameCache { symaddr, frame.name } );
}
}
}
}
}
}
else
{
for( size_t i=csz; i>0; i-- )
{
auto frameData = worker.GetCallstackFrame( callstack[i-1] );
if( frameData )
{
for( uint8_t j=frameData->size; j>0; j-- )
{
const auto frame = frameData->data[j-1];
const auto symaddr = frame.symAddr;
if( symaddr != 0 )
{
auto filename = m_worker.GetString( frame.file );
auto image = frameData->imageName.Active() ? m_worker.GetString( frameData->imageName ) : nullptr;
cache.emplace_back( FrameCache { symaddr, frame.name, IsFrameExternal( filename, image ) } );
}
}
}
}
bool tail = true;
for( size_t i=cache.size(); i>0; i-- )
{
const auto idx = i-1;
if( !cache[idx].external )
{
tail = false;
}
else if( !tail )
{
cache.erase( cache.begin() + idx );
}
}
}
auto vec = &data;
for( auto& v : cache )
{
auto it = std::find_if( vec->begin(), vec->end(), [symaddr = v.symaddr]( const auto& v ) { return v.srcloc == symaddr; } );
if( it == vec->end() )
{
vec->emplace_back( FlameGraphItem { (int64_t)v.symaddr, 1, v.name } );
vec = &vec->back().children;
}
else
{
it->time++;
vec = &it->children;
}
}
}
}
static void SortFlameGraph( std::vector<FlameGraphItem>& data )
{
pdqsort_branchless( data.begin(), data.end(), []( const FlameGraphItem& lhs, const FlameGraphItem& rhs ) { return lhs.time > rhs.time; } );
for( auto& v : data ) SortFlameGraph( v.children );
}
struct FlameGraphContext
{
ImDrawList* draw;
ImVec2 wpos;
ImVec2 dpos;
float ty;
float ostep;
double pxns;
double nspx;
int64_t vStart;
int64_t vEnd;
};
void View::DrawFlameGraphLevel( const std::vector<FlameGraphItem>& data, FlameGraphContext& ctx, int depth, bool samples )
{
const auto vStart = ctx.vStart;
const auto vEnd = ctx.vEnd;
const auto nspx = ctx.nspx;
const auto pxns = ctx.pxns;
const auto draw = ctx.draw;
const auto ostep = ctx.ostep;
const auto& wpos = ctx.wpos;
const auto MinVisNs = int64_t( round( GetScale() * MinVisSize * nspx ) );
auto it = std::lower_bound( data.begin(), data.end(), vStart, [] ( const auto& l, const auto& r ) { return l.begin + l.time < r; } );
if( it == data.end() ) return;
const auto zitend = std::lower_bound( it, data.end(), vEnd, [] ( const auto& l, const auto& r ) { return l.begin < r; } );
if( it == zitend ) return;
while( it < zitend )
{
const auto end = it->begin + it->time;
const auto zsz = it->time;
if( zsz < MinVisNs )
{
auto nextTime = end + MinVisNs;
auto next = it + 1;
for(;;)
{
next = std::lower_bound( next, zitend, nextTime, [] ( const auto& l, const auto& r ) { return l.begin + l.time < r; } );
if( next == zitend ) break;
if( next->time >= MinVisNs ) break;
nextTime = next->begin + next->time + MinVisNs;
}
const auto px0 = ( it->begin - vStart ) * pxns;
const auto px1 = ( (next-1)->begin + (next-1)->time - vStart ) * pxns;
draw->AddRectFilled( ImVec2( wpos.x + px0, wpos.y + depth * ostep ), ImVec2( wpos.x + std::max( px1, px0 + MinVisSize ), wpos.y + ( depth + 1 ) * ostep ), 0xFF666666 );
DrawZigZag( draw, ImVec2( wpos.x, wpos.y + ( depth + 0.5f ) * ostep ), px0, std::max( px1, px0 + MinVisSize ), ctx.ty / 4, 0xFF444444 );
it = next;
}
else
{
DrawFlameGraphItem( *it, ctx, depth, samples );
++it;
}
}
}
void View::DrawFlameGraphItem( const FlameGraphItem& item, FlameGraphContext& ctx, int depth, bool samples )
{
const auto x0 = ctx.dpos.x + item.begin * ctx.pxns;
const auto x1 = x0 + item.time * ctx.pxns;
const auto y0 = ctx.dpos.y + depth * ctx.ostep;
const auto y1 = y0 + ctx.ty;
const SourceLocation* srcloc;
uint32_t color;
const char* name;
const char* normalized;
const char* slName;
uint32_t textColor = 0xFFFFFFFF;
if( !samples )
{
srcloc = &m_worker.GetSourceLocation( item.srcloc );
color = GetSrcLocColor( *srcloc, depth );
name = slName = m_worker.GetString( srcloc->name.active ? srcloc->name : srcloc->function );
}
else
{
name = m_worker.GetString( item.name );
const auto symAddr = (uint64_t)item.srcloc;
auto sym = m_worker.GetSymbolData( symAddr );
if( sym )
{
auto namehash = charutil::hash( name );
if( namehash == 0 ) namehash++;
color = GetHsvColor( namehash, depth );
if( sym->isInline )
{
color = DarkenColorHalf( color );
}
}
else
{
color = 0xFF888888;
}
if( symAddr >> 63 != 0 )
{
textColor = 0xFF8888FF;
}
}
const auto hiColor = HighlightColor( color );
const auto darkColor = DarkenColor( color );
const auto zsz = x1 - x0;
auto tsz = ImGui::CalcTextSize( name );
if( m_vd.shortenName == ShortenName::Never )
{
normalized = name;
}
else if( samples )
{
normalized = ShortenZoneName( ShortenName::OnlyNormalize, name );
tsz = ImGui::CalcTextSize( normalized );
if( tsz.x > zsz && ( m_vd.shortenName == ShortenName::NoSpace || m_vd.shortenName == ShortenName::NoSpaceAndNormalize ) )
{
normalized = ShortenZoneName( m_vd.shortenName, normalized, tsz, zsz );
}
}
else if( m_vd.shortenName == ShortenName::Always || ( ( m_vd.shortenName == ShortenName::NoSpace || m_vd.shortenName == ShortenName::NoSpaceAndNormalize ) && tsz.x > zsz ) )
{
normalized = ShortenZoneName( m_vd.shortenName, name, tsz, zsz );
}
else
{
normalized = name;
}
const bool hover = ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect( ImVec2( x0, y0 ), ImVec2( x1, y1 ) );
ctx.draw->AddRectFilled( ImVec2( x0, y0 ), ImVec2( x1, y1 ), color );
if( hover )
{
ctx.draw->AddRect( ImVec2( x0 - 0.5f, y0 - 0.5f ), ImVec2( x1 - 0.5f, y1 - 0.5f ), 0xFFEEEEEE );
}
else
{
DrawLine( ctx.draw, ImVec2( x0, y1 ), ImVec2( x0, y0 ), ImVec2( x1-1, y0 ), hiColor );
DrawLine( ctx.draw, ImVec2( x0, y1 ), ImVec2( x1-1, y1), ImVec2( x1-1, y0 ), darkColor );
}
if( tsz.x < zsz )
{
const auto x = ( x1 + x0 - tsz.x ) * 0.5;
DrawTextContrast( ctx.draw, ImVec2( x, y0 ), textColor, normalized );
}
else
{
ImGui::PushClipRect( ImVec2( x0, y0 ), ImVec2( x1, y1 ), true );
DrawTextContrast( ctx.draw, ImVec2( x0, y0 ), textColor, normalized );
ImGui::PopClipRect();
}
if( hover )
{
uint64_t self = item.time;
for( auto& v : item.children ) self -= v.time;
ImGui::BeginTooltip();
if( samples )
{
const auto symAddr = (uint64_t)item.srcloc;
auto sym = m_worker.GetSymbolData( symAddr );
if( sym )
{
TextFocused( "Name:", normalized );
if( sym->isInline )
{
ImGui::SameLine();
TextDisabledUnformatted( "[inline]" );
}
const bool isKernel = symAddr >> 63 != 0;
if( isKernel )
{
ImGui::SameLine();
TextDisabledUnformatted( ICON_FA_HAT_WIZARD " kernel" );
}
ImGui::SameLine();
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled( "0x%" PRIx64, symAddr );
ImGui::PopFont();
if( normalized != name && strcmp( normalized, name ) != 0 )
{
ImGui::PushFont( g_fonts.normal, FontSmall );
TextDisabledUnformatted( name );
ImGui::PopFont();
}
ImGui::Separator();
const char* file;
uint32_t line;
if( sym->isInline )
{
file = m_worker.GetString( sym->callFile );
line = sym->callLine;
}
else
{
file = m_worker.GetString( sym->file );
line = sym->line;
}
if( file[0] != '[' )
{
ImGui::TextDisabled( "Location:" );
ImGui::SameLine();
ImGui::TextUnformatted( LocationToString( file, line ) );
}
TextFocused( "Image:", m_worker.GetString( sym->imageName ) );
ImGui::Separator();
const auto period = m_worker.GetSamplingPeriod();
TextFocused( "Execution time:", TimeToString( item.time * period ) );
if( !item.children.empty() )
{
TextFocused( "Self time:", TimeToString( self * period ) );
char buf[64];
PrintStringPercent( buf, 100.f * self / item.time );
ImGui::SameLine();
TextDisabledUnformatted( buf );
}
if( IsMouseClicked( 0 ) )
{
ViewDispatch( file, line, symAddr );
}
}
ImGui::EndTooltip();
}
else
{
if( srcloc->name.active )
{
ImGui::TextUnformatted( m_worker.GetString( srcloc->name ) );
}
ImGui::TextUnformatted( m_worker.GetString( srcloc->function ) );
ImGui::Separator();
SmallColorBox( GetSrcLocColor( *srcloc, 0 ) );
ImGui::SameLine();
ImGui::TextUnformatted( LocationToString( m_worker.GetString( srcloc->file ), srcloc->line ) );
ImGui::Separator();
TextFocused( "Execution time:", TimeToString( item.time ) );
if( !item.children.empty() )
{
TextFocused( "Self time:", TimeToString( self ) );
char buf[64];
PrintStringPercent( buf, 100.f * self / item.time );
ImGui::SameLine();
TextDisabledUnformatted( buf );
}
ImGui::EndTooltip();
if( IsMouseClicked( 0 ) )
{
m_findZone.ShowZone( item.srcloc, slName );
}
}
}
DrawFlameGraphLevel( item.children, ctx, depth+1, samples );
}
void View::DrawFlameGraphHeader( uint64_t timespan )
{
const auto wpos = ImGui::GetCursorScreenPos();
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
const auto w = ImGui::GetContentRegionAvail().x;// - ImGui::GetStyle().ScrollbarSize;
auto draw = ImGui::GetWindowDrawList();
const auto ty = ImGui::GetTextLineHeight();
const auto ty025 = round( ty * 0.25f );
const auto ty0375 = round( ty * 0.375f );
const auto ty05 = round( ty * 0.5f );
const auto pxns = w / double( timespan );
const auto nspx = 1.0 / pxns;
const auto scale = std::max( 0.0, round( log10( nspx ) + 2 ) );
const auto step = pow( 10, scale );
ImGui::InvisibleButton( "##flameHeader", ImVec2( w, ty * 1.5f ) );
TooltipIfHovered( TimeToStringExact( ( ImGui::GetIO().MousePos.x - wpos.x ) * nspx ) );
const auto dx = step * pxns;
double x = 0;
int tw = 0;
int tx = 0;
int64_t tt = 0;
while( x < w )
{
DrawLine( draw, dpos + ImVec2( x, 0 ), dpos + ImVec2( x, ty05 ), 0x66FFFFFF );
if( tw == 0 )
{
auto txt = "0";
draw->AddText( wpos + ImVec2( x, ty05 ), 0x66FFFFFF, txt );
tw = ImGui::CalcTextSize( txt ).x;
}
else if( x > tx + tw + ty * 2 )
{
tx = x;
auto txt = TimeToString( tt );
draw->AddText( wpos + ImVec2( x, ty05 ), 0x66FFFFFF, txt );
tw = ImGui::CalcTextSize( txt ).x;
}
if( scale != 0 )
{
for( int i=1; i<5; i++ )
{
DrawLine( draw, dpos + ImVec2( x + i * dx / 10, 0 ), dpos + ImVec2( x + i * dx / 10, ty025 ), 0x33FFFFFF );
}
DrawLine( draw, dpos + ImVec2( x + 5 * dx / 10, 0 ), dpos + ImVec2( x + 5 * dx / 10, ty0375 ), 0x33FFFFFF );
for( int i=6; i<10; i++ )
{
DrawLine( draw, dpos + ImVec2( x + i * dx / 10, 0 ), dpos + ImVec2( x + i * dx / 10, ty025 ), 0x33FFFFFF );
}
}
x += dx;
tt += step;
}
}
static void MergeFlameGraph( std::vector<FlameGraphItem>& dst, std::vector<FlameGraphItem>&& src )
{
for( auto& v : src )
{
auto it = std::find_if( dst.begin(), dst.end(), [&v]( const auto& vv ) { return vv.srcloc == v.srcloc; } );
if( it == dst.end() )
{
dst.emplace_back( std::move( v ) );
}
else
{
it->time += v.time;
MergeFlameGraph( it->children, std::move( v.children ) );
}
}
}
static void FixupTime( std::vector<FlameGraphItem>& data, uint64_t t = 0 )
{
for( auto& v : data )
{
v.begin = t;
if( !v.children.empty() ) FixupTime( v.children, t );
t += v.time;
}
}
void View::DrawFlameGraph()
{
const auto scale = GetScale();
ImGui::SetNextWindowSize( ImVec2( 1400 * scale, 800 * scale ), ImGuiCond_FirstUseEver );
ImGui::Begin( "Flame graph", &m_showFlameGraph, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 2, 2 ) );
if( ImGui::RadioButton( ICON_FA_SYRINGE " Instrumentation", &m_flameMode, 0 ) ) m_flameGraphInvariant.Reset();
if( m_worker.AreCallstackSamplesReady() && m_worker.GetCallstackSampleCount() > 0 )
{
ImGui::SameLine();
if( ImGui::RadioButton( ICON_FA_EYE_DROPPER " Sampling", &m_flameMode, 1 ) ) m_flameGraphInvariant.Reset();
}
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
if( ImGui::Checkbox( ICON_FA_ARROW_UP_WIDE_SHORT " Sort by time", &m_flameSort ) ) m_flameGraphInvariant.Reset();
if( m_flameMode == 0 )
{
if( m_worker.HasContextSwitches() )
{
ImGui::SameLine();
if( ImGui::Checkbox( "Running time", &m_flameRunningTime ) ) m_flameGraphInvariant.Reset();
}
else
{
assert( !m_flameRunningTime );
}
}
else
{
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
ImGui::Text( ICON_FA_SHIELD_HALVED "External" );
ImGui::SameLine();
if( ImGui::Checkbox( "Frames", &m_flameExternal ) ) m_flameGraphInvariant.Reset();
ImGui::SameLine();
if( m_flameExternal ) ImGui::BeginDisabled();
if( ImGui::Checkbox( "Tails", &m_flameExternalTail ) ) m_flameGraphInvariant.Reset();
if( m_flameExternal ) ImGui::EndDisabled();
}
ImGui::SameLine();
ImGui::SeparatorEx( ImGuiSeparatorFlags_Vertical );
ImGui::SameLine();
if( ImGui::Checkbox( "Limit range", &m_flameRange.active ) )
{
if( m_flameRange.active && m_flameRange.min == 0 && m_flameRange.max == 0 )
{
m_flameRange.min = m_vd.zvStart;
m_flameRange.max = m_vd.zvEnd;
}
m_flameGraphInvariant.Reset();
}
if( m_flameRange.active )
{
ImGui::SameLine();
TextColoredUnformatted( 0xFF00FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::SameLine();
ToggleButton( ICON_FA_RULER " Limits", m_showRanges );
}
auto& td = m_worker.GetThreadData();
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
ImGui::SameLine();
size_t visibleThreads = 0;
size_t tsz = 0;
for( const auto& t : td )
{
if( FlameGraphThread( t->id ) ) visibleThreads++;
tsz++;
}
if( visibleThreads == tsz )
{
ImGui::TextDisabled( "(%zu)", tsz );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleThreads, tsz );
}
if( expand )
{
ImGui::SameLine();
if( ImGui::SmallButton( "Select all" ) )
{
for( const auto& t : td )
{
FlameGraphThread( t->id ) = true;
}
m_flameGraphInvariant.Reset();
}
ImGui::SameLine();
if( ImGui::SmallButton( "Unselect all" ) )
{
for( const auto& t : td )
{
FlameGraphThread( t->id ) = false;
}
m_flameGraphInvariant.Reset();
}
int idx = 0;
for( const auto& t : td )
{
ImGui::PushID( idx++ );
const auto threadColor = GetThreadColor( t->id, 0 );
SmallColorBox( threadColor );
ImGui::SameLine();
if( SmallCheckbox( m_worker.GetThreadName( t->id ), &FlameGraphThread( t->id ) ) ) m_flameGraphInvariant.Reset();
ImGui::PopID();
if( t->isFiber )
{
ImGui::SameLine();
TextColoredUnformatted( ImVec4( 0.2f, 0.6f, 0.2f, 1.f ), "Fiber" );
}
}
ImGui::TreePop();
}
ImGui::Separator();
ImGui::PopStyleVar();
if( m_flameMode == 0 && ( m_flameGraphInvariant.count != m_worker.GetZoneCount() || m_flameGraphInvariant.lastTime != m_worker.GetLastTime() ) ||
m_flameMode == 1 && ( m_flameGraphInvariant.count != m_worker.GetCallstackSampleCount() ) ||
m_flameGraphInvariant.range != m_flameRange )
{
m_flameGraphInvariant.range = m_flameRange;
size_t sz = 0;
for( auto& thread : td ) if( FlameGraphThread( thread->id ) ) sz++;
std::vector<std::vector<FlameGraphItem>> threadData;
threadData.resize( sz );
size_t idx = 0;
if( m_flameMode == 0 )
{
for( auto& thread : td )
{
if( FlameGraphThread( thread->id ) )
{
if( m_flameRunningTime )
{
const auto ctx = m_worker.GetContextSwitchData( thread->id );
if( ctx )
{
m_td.Queue( [this, idx, ctx, thread, &threadData] {
BuildFlameGraph( m_worker, threadData[idx], thread->timeline, ctx );
} );
}
}
else
{
m_td.Queue( [this, idx, thread, &threadData] {
BuildFlameGraph( m_worker, threadData[idx], thread->timeline );
} );
}
idx++;
}
}
m_flameGraphInvariant.count = m_worker.GetZoneCount();
m_flameGraphInvariant.lastTime = m_worker.GetLastTime();
}
else
{
for( auto& thread : td )
{
if( FlameGraphThread( thread->id ) )
{
m_td.Queue( [this, idx, thread, &threadData] {
BuildFlameGraph( m_worker, threadData[idx], thread->samples );
} );
idx++;
}
}
m_flameGraphInvariant.count = m_worker.GetCallstackSampleCount();
}
m_td.Sync();
m_flameGraphData.clear();
if( !threadData.empty() )
{
std::swap( m_flameGraphData, threadData[0] );
for( size_t i=1; i<threadData.size(); i++ )
{
MergeFlameGraph( m_flameGraphData, std::move( threadData[i] ) );
}
}
if( m_flameSort ) SortFlameGraph( m_flameGraphData );
FixupTime( m_flameGraphData );
}
int64_t zsz = 0;
for( auto& v : m_flameGraphData ) zsz += v.time;
ImGui::BeginChild( "##flameGraph" );
const auto region = ImGui::GetContentRegionAvail();
if( m_flameGraphData.empty() )
{
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( region.y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_CAT );
TextCentered( "No data available to display" );
ImGui::PopFont();
}
else
{
DrawFlameGraphHeader( m_flameMode == 0 ? zsz : zsz * m_worker.GetSamplingPeriod() );
FlameGraphContext ctx;
ctx.draw = ImGui::GetWindowDrawList();
ctx.wpos = ImGui::GetCursorScreenPos();
ctx.dpos = ctx.wpos + ImVec2( 0.5f, 0.5f );
ctx.ty = ImGui::GetTextLineHeight();
ctx.ostep = ctx.ty + 1;
ctx.pxns = region.x / zsz;
ctx.nspx = 1.0 / ctx.pxns;
ctx.vStart = 0;
ctx.vEnd = zsz;
ImGui::ItemSize( region );
DrawFlameGraphLevel( m_flameGraphData, ctx, 0, m_flameMode == 1 );
}
ImGui::EndChild();
ImGui::End();
}
}
@@ -48,7 +48,7 @@ void View::DrawFrames()
const auto wpos = ImGui::GetCursorScreenPos();
const auto dpos = wpos + ImVec2( 0.5f, 0.5f );
const auto wspace = ImGui::GetWindowContentRegionMax() - ImGui::GetWindowContentRegionMin();
const auto wspace = ImGui::GetContentRegionAvail() + ImGui::GetCursorScreenPos();
const auto w = wspace.x;
auto draw = ImGui::GetWindowDrawList();
@@ -95,7 +95,7 @@ void View::DrawFrames()
if( hover )
{
const auto hwheel_delta = io.MouseWheelH * 100.f;
const auto hwheel_delta = io.MouseWheelH * 100.f * m_horizontalScrollMultiplier;
if( IsMouseDragging( 1 ) || hwheel_delta != 0 )
{
m_viewMode = ViewMode::Paused;
@@ -106,7 +106,9 @@ void View::DrawTimelineFrames( const FrameData& frames )
const auto ty025 = ty * 0.25f;
const auto ty05 = round( ty * 0.5f );
ImGui::PushID( &frames );
ImGui::InvisibleButton( "##zoneFrames", ImVec2( w, ty ) );
ImGui::PopID();
bool hover = ImGui::IsItemHovered();
auto timespan = m_vd.zvEnd - m_vd.zvStart;
@@ -1,6 +1,7 @@
#include "TracyImGui.hpp"
#include "TracyPrint.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
namespace tracy
{
@@ -4,6 +4,7 @@
#include "TracyPrint.hpp"
#include "TracyTimelineContext.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -25,7 +26,7 @@ bool View::DrawGpu( const TimelineContext& ctx, const GpuCtxData& gpu, int& offs
auto draw = ImGui::GetWindowDrawList();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
const auto sty = ImGui::GetTextLineHeight();
const auto sstep = sty + 1;
ImGui::PopFont();
@@ -50,7 +51,7 @@ bool View::DrawGpu( const TimelineContext& ctx, const GpuCtxData& gpu, int& offs
{
if( !singleThread )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
DrawTextContrast( draw, wpos + ImVec2( ty, offset-1-sstep ), 0xFFFFAAAA, m_worker.GetThreadName( td.first ) );
DrawLine( draw, dpos + ImVec2( 0, offset+sty-sstep ), dpos + ImVec2( w, offset+sty-sstep ), 0x22FFAAAA );
ImGui::PopFont();
@@ -77,7 +78,7 @@ bool View::DrawGpu( const TimelineContext& ctx, const GpuCtxData& gpu, int& offs
{
if( !singleThread )
{
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
DrawTextContrast( draw, wpos + ImVec2( ty, offset-1-sstep ), 0xFFFFAAAA, m_worker.GetThreadName( td.first ) );
DrawLine( draw, dpos + ImVec2( 0, offset+sty-sstep ), dpos + ImVec2( w, offset+sty-sstep ), 0x22FFAAAA );
ImGui::PopFont();
@@ -9,6 +9,7 @@
#include "TracyTimelineContext.hpp"
#include "TracyTimelineDraw.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -26,7 +27,7 @@ void View::DrawLockHeader( uint32_t id, const LockMap& lockmap, const SourceLoca
{
sprintf( buf, "%" PRIu32 ": %s", id, m_worker.GetString( srcloc.function ) );
}
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
DrawTextContrast( draw, wpos + ImVec2( 0, offset ), 0xFF8888FF, buf );
ImGui::PopFont();
if( hover && ImGui::IsMouseHoveringRect( wpos + ImVec2( 0, offset ), wpos + ImVec2( w, offset + ty + 1 ) ) )
@@ -558,7 +559,7 @@ void View::DrawLockInfoWindow()
}
}
ImGui::PushFont( m_bigFont );
ImGui::PushFont( g_fonts.normal, FontBig );
if( lock.customName.Active() )
{
ImGui::Text( "Lock #%" PRIu32 ": %s", m_lockInfoWindow, m_worker.GetString( lock.customName ) );
@@ -0,0 +1,90 @@
#include <assert.h>
#include <stdio.h>
#include "TracyImGui.hpp"
#include "TracyManualData.hpp"
#include "TracyMarkdown.hpp"
#include "TracyView.hpp"
namespace tracy
{
void View::DrawManual()
{
const auto scale = GetScale();
ImGui::SetNextWindowSize( ImVec2( 1200 * scale, 800 * scale ), ImGuiCond_Always );
ImGui::Begin( "User manual", &m_showManual );
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 1.f, 0.f, 1.0f ) );
ImGui::AlignTextToFramePadding();
ImGui::TextWrapped( ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::PopStyleColor();
ImGui::SameLine();
TextDisabledUnformatted( "This user manual is missing features. See the PDF file for the proper version." );
ImGui::Separator();
ImGui::BeginChild( "##usermanual" );
ImGui::Columns( 2 );
static bool widthSet = false;
if( !widthSet )
{
widthSet = true;
ImGui::SetColumnWidth( 0, 350 * scale );
}
ImGui::BeginChild( "##toc", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
int level = 0;
auto& chunks = m_manualData->GetChunks();
assert( !chunks.empty() );
for( size_t i=0; i<chunks.size(); i++ )
{
auto& chunk = chunks[i];
if( chunk.level > level ) continue;
char tmp[1024];
if( chunk.section.empty() )
{
snprintf( tmp, 1024, "%s", chunk.title.c_str() );
}
else
{
snprintf( tmp, 1024, "%s. %s", chunk.section.c_str(), chunk.title.c_str() );
}
while( level > chunk.level )
{
ImGui::TreePop();
level--;
}
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
const bool isLeaf = i == ( chunks.size() - 1 ) || chunks[i+1].level <= chunk.level;
if( isLeaf ) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
if( i == m_activeManualChunk ) flags |= ImGuiTreeNodeFlags_Selected;
if( ImGui::TreeNodeEx( tmp, flags ) )
{
if( !isLeaf ) level++;
}
if( ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen() )
{
m_activeManualChunk = i;
}
}
while( level-- > 0 ) ImGui::TreePop();
ImGui::EndChild();
ImGui::NextColumn();
ImGui::BeginChild( "##content", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
auto& chunk = chunks[m_activeManualChunk];
m_markdown.Print( chunk.text.c_str(), chunk.text.size() );
ImGui::EndChild();
ImGui::EndColumns();
ImGui::EndChild();
ImGui::End();
}
}
@@ -4,15 +4,17 @@
#include "TracyMouse.hpp"
#include "TracyPrint.hpp"
#include "TracyView.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
enum { ChunkBits = 10 };
enum { PageBits = 10 };
enum { PageSize = 1 << PageBits };
enum { PageChunkBits = ChunkBits + PageBits };
enum { PageChunkSize = 1 << PageChunkBits };
constexpr size_t ChunkBits = 10;
constexpr size_t PageBits = 10;
constexpr size_t PageSize = 1 << PageBits;
constexpr size_t PageChunkBits = ChunkBits + PageBits;
constexpr size_t PageChunkSize = 1 << PageChunkBits;
uint32_t MemDecayColor[256] = {
0x0, 0xFF077F07, 0xFF078007, 0xFF078207, 0xFF078307, 0xFF078507, 0xFF078707, 0xFF078807,
@@ -198,7 +200,12 @@ void View::DrawMemory()
auto& mem = m_worker.GetMemoryNamed( m_memInfo.pool );
if( mem.data.empty() )
{
ImGui::TextWrapped( "No memory data collected." );
const auto ty = ImGui::GetTextLineHeight();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_DOG );
TextCentered( "No memory data collected" );
ImGui::PopFont();
ImGui::End();
return;
}
@@ -724,7 +731,7 @@ void View::ListMemData( std::vector<const MemEvent*>& vec, const std::function<v
auto v = vec[i];
const auto arrIdx = std::distance( mem.data.begin(), v );
ImGui::PushFont( m_fixedFont );
ImGui::PushFont( g_fonts.mono, FontNormal );
if( m_memoryAllocInfoPool == pool && m_memoryAllocInfoWindow == arrIdx )
{
ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 1.f, 0.f, 0.f, 1.f ) );
@@ -2,6 +2,7 @@
#include "TracyPrint.hpp"
#include "TracyTexture.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -17,14 +18,16 @@ void View::DrawMessages()
if( msgs.empty() )
{
ImGui::TextUnformatted( "No messages were collected." );
const auto ty = ImGui::GetTextLineHeight();
ImGui::PushFont( g_fonts.normal, FontBig );
ImGui::Dummy( ImVec2( 0, ( ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeight() * 2 ) * 0.5f ) );
TextCentered( ICON_FA_FISH_FINS );
TextCentered( "No messages were collected" );
ImGui::PopFont();
ImGui::End();
return;
}
size_t tsz = 0;
for( const auto& t : m_threadOrder ) if( !t->messages.empty() ) tsz++;
bool filterChanged = m_messageFilter.Draw( ICON_FA_FILTER " Filter messages", 200 );
ImGui::SameLine();
if( ImGui::Button( ICON_FA_DELETE_LEFT " Clear" ) )
@@ -51,7 +54,22 @@ void View::DrawMessages()
bool threadsChanged = false;
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", tsz );
size_t visibleThreads = 0;
size_t tsz = 0;
for( const auto& t : m_threadOrder )
{
if( t->messages.empty() ) continue;
if( VisibleMsgThread( t->id ) ) visibleThreads++;
tsz++;
}
if( visibleThreads == tsz )
{
ImGui::TextDisabled( "(%zu)", tsz );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleThreads, tsz );
}
if( expand )
{
auto& crash = m_worker.GetCrashEvent();
@@ -235,7 +253,7 @@ void View::DrawMessageLine( const MessageData& msg, bool hasCallstack, int& idx
const auto text = m_worker.GetString( msg.ref );
const auto tid = m_worker.DecompressThread( msg.thread );
ImGui::PushID( &msg );
if( ImGui::Selectable( TimeToStringExact( msg.time ), m_msgHighlight == &msg, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap ) )
if( ImGui::Selectable( TimeToStringExact( msg.time ), m_msgHighlight == &msg, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap ) )
{
CenterAtTime( msg.time );
}
@@ -3,6 +3,7 @@
#include "TracyPrint.hpp"
#include "TracyTimelineItem.hpp"
#include "TracyView.hpp"
#include "../Fonts.hpp"
namespace tracy
{
@@ -225,14 +226,14 @@ void View::DrawNotificationArea()
}
if( !m_worker.IsBackgroundDone() )
{
ImGui::SameLine();
TextDisabledUnformatted( ICON_FA_LIST_CHECK );
ImGui::SameLine();
const auto pos = ImGui::GetCursorPos();
ImGui::TextUnformatted( " " );
ImGui::GetWindowDrawList()->AddCircleFilled( pos + ImVec2( 0, ty * 0.75f ), ty * ( 0.2f + ( sin( s_time * 8 ) + 1 ) * 0.125f ), 0xFF888888, 10 );
auto draw = ImGui::GetWindowDrawList();
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 0 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f + 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 1 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
draw->AddCircleFilled( pos + ImVec2( ty * 0.5f + 2 * ty, ty * 0.675f ), ty * ( 0.15f + 0.2f * ( pow( cos( s_time * 3.5f - 0.3f ), 16.f ) ) ), 0xFFBBBBBB, 12 );
ImGui::Dummy( ImVec2( ty * 3, ty ) );
auto rmin = ImGui::GetItemRectMin();
rmin.x -= ty * 0.5f;
const auto rmax = ImGui::GetItemRectMax();
if( ImGui::IsMouseHoveringRect( rmin, rmax ) )
{
@@ -254,12 +255,18 @@ void View::DrawNotificationArea()
TextDisabledUnformatted( m_notificationText.c_str() );
}
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
const auto wpos = ImGui::GetWindowPos();
const auto w = ImGui::GetContentRegionAvail().x;
const auto fps = RealToString( int( io.Framerate + 0.5f ) );
const auto fpssz = ImGui::CalcTextSize( fps ).x;
ImGui::GetWindowDrawList()->AddText( wpos + ImVec2( w-fpssz, 0 ), 0x88FFFFFF, fps );
#ifndef NDEBUG
const auto dsz = ImGui::CalcTextSize( "8888 DEBUG" ).x;
ImGui::GetWindowDrawList()->AddText( wpos + ImVec2( w-dsz, 0 ), 0x886666FF, "DEBUG" );
#endif
ImGui::PopFont();
}
@@ -8,12 +8,30 @@
#include "TracyTimelineItemGpu.hpp"
#include "TracyUtility.hpp"
#include "TracyView.hpp"
#include "TracyStorage.hpp"
#include "tracy_pdqsort.h"
#include "../Fonts.hpp"
namespace tracy
{
static void DefaultMarker( bool active, bool tooltip = true )
{
// Add a red * to indicate that the default value for this setting can be configured.
ImGui::SameLine( 0.0f, 2.0f );
TextColoredUnformatted( active ? ImVec4( 0.9f, 0.05f, 0.1f, 0.8f ) : ImVec4( 0.6f, 0.6f, 0.6f, 0.4f ), "*" );
if( tooltip && ImGui::IsItemHovered() )
{
ImGui::BeginTooltip();
ImGui::TextUnformatted( "Has a default value loaded when starting Tracy (see below)." );
ImGui::EndTooltip();
}
}
void View::DrawOptions()
{
static bool default_markers_active = false;
ImGui::Begin( "Options", &m_showOptions, ImGuiWindowFlags_AlwaysAutoResize );
if( ImGui::GetCurrentWindowRead()->SkipItems ) { ImGui::End(); return; }
@@ -24,6 +42,7 @@ void View::DrawOptions()
val = m_vd.drawFrameTargets;
ImGui::Checkbox( ICON_FA_FLAG_CHECKERED " Draw frame targets", &val );
m_vd.drawFrameTargets = val;
DefaultMarker(default_markers_active);
ImGui::Indent();
int tmp = m_vd.frameTarget;
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
@@ -33,10 +52,11 @@ void View::DrawOptions()
if( tmp < 1 ) tmp = 1;
m_vd.frameTarget = tmp;
}
DefaultMarker(default_markers_active);
ImGui::SameLine();
TextDisabledUnformatted( TimeToString( 1000*1000*1000 / tmp ) );
ImGui::PopStyleVar();
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
SmallColorBox( 0xFF2222DD );
ImGui::SameLine( 0, 0 );
ImGui::Text( " < %i < ", tmp / 2 );
@@ -58,6 +78,7 @@ void View::DrawOptions()
val = m_vd.drawContextSwitches;
ImGui::Checkbox( ICON_FA_PERSON_HIKING " Draw context switches", &val );
m_vd.drawContextSwitches = val;
DefaultMarker(default_markers_active);
ImGui::Indent();
val = m_vd.darkenContextSwitches;
SmallCheckbox( ICON_FA_MOON " Darken inactive threads", &val );
@@ -78,6 +99,7 @@ void View::DrawOptions()
val = m_vd.drawSamples;
ImGui::Checkbox( ICON_FA_EYE_DROPPER " Draw stack samples", &val );
m_vd.drawSamples = val;
DefaultMarker(default_markers_active);
}
const auto& gpuData = m_worker.GetGpuData();
@@ -89,7 +111,16 @@ void View::DrawOptions()
m_vd.drawGpuZones = val;
const auto expand = ImGui::TreeNode( "GPU zones" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", gpuData.size() );
size_t visibleGpu = 0;
for( const auto& gd : gpuData ) if( m_tc.GetItem( gd ).IsVisible() ) visibleGpu++;
if( visibleGpu == gpuData.size() )
{
ImGui::TextDisabled( "(%zu)", gpuData.size() );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleGpu, gpuData.size() );
}
if( expand )
{
for( size_t i=0; i<gpuData.size(); i++ )
@@ -110,7 +141,7 @@ void View::DrawOptions()
char buf[64];
auto& item = (TimelineItemGpu&)( m_tc.GetItem( gpuData[i] ) );
sprintf( buf, "%s context %i", GpuContextNames[(int)gpuData[i]->type], item.GetIdx() );
ImGui::PushFont( m_smallFont );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::TextUnformatted( buf );
ImGui::PopFont();
}
@@ -185,7 +216,7 @@ void View::DrawOptions()
}
while( idx < NumSlopes );
}
std::sort( slopes, slopes+NumSlopes );
pdqsort_branchless( slopes, slopes+NumSlopes );
drift = int( 1000000000 * -slopes[NumSlopes/2] );
}
}
@@ -208,6 +239,7 @@ void View::DrawOptions()
val = m_vd.ghostZones;
SmallCheckbox( ICON_FA_GHOST " Draw ghost zones", &val );
m_vd.ghostZones = val;
DefaultMarker(default_markers_active);
}
#endif
@@ -216,6 +248,10 @@ void View::DrawOptions()
ImGui::SameLine();
bool forceColors = m_vd.forceColors;
if( SmallCheckbox( "Ignore custom", &forceColors ) ) m_vd.forceColors = forceColors;
DefaultMarker(default_markers_active);
ImGui::SameLine();
bool inheritColors = m_vd.inheritParentColors;
if( SmallCheckbox( "Inherit parent colors", &inheritColors ) ) m_vd.inheritParentColors = inheritColors;
ImGui::Indent();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::RadioButton( "Static", &ival, 0 );
@@ -226,6 +262,7 @@ void View::DrawOptions()
m_vd.dynamicColors = ival;
ival = (int)m_vd.shortenName;
ImGui::TextUnformatted( ICON_FA_RULER_HORIZONTAL " Zone name shortening" );
DefaultMarker(default_markers_active);
ImGui::Indent();
ImGui::PushStyleVar( ImGuiStyleVar_FramePadding, ImVec2( 0, 0 ) );
ImGui::RadioButton( "Disabled", &ival, (uint8_t)ShortenName::Never );
@@ -274,7 +311,16 @@ void View::DrawOptions()
m_vd.onlyContendedLocks = val;
const auto expand = ImGui::TreeNode( "Locks" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", lockCnt );
size_t visibleLocks = 0;
for( const auto& l : m_worker.GetLockMap() ) if( Vis( l.second ) ) visibleLocks++;
if( visibleLocks == lockCnt )
{
ImGui::TextDisabled( "(%zu)", lockCnt );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleLocks, lockCnt );
}
TooltipIfHovered( "Locks with no recorded events are counted, but not listed." );
if( expand )
{
@@ -299,7 +345,16 @@ void View::DrawOptions()
const bool multiExpand = ImGui::TreeNodeEx( "Contended locks present in multiple threads", ImGuiTreeNodeFlags_DefaultOpen );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", multiCntCont );
size_t visibleMultiCntCont = 0;
for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() != 1 && l.second->isContended && Vis( l.second ) ) visibleMultiCntCont++;
if( visibleMultiCntCont == multiCntCont )
{
ImGui::TextDisabled( "(%zu)", multiCntCont );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleMultiCntCont, multiCntCont );
}
if( multiExpand )
{
ImGui::SameLine();
@@ -377,7 +432,16 @@ void View::DrawOptions()
}
const bool multiUncontExpand = ImGui::TreeNodeEx( "Uncontended locks present in multiple threads", 0 );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", multiCntUncont );
uint64_t visibleMultiCntUncont = 0;
for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() != 1 && !l.second->isContended && Vis( l.second ) ) visibleMultiCntUncont++;
if( visibleMultiCntUncont == multiCntUncont )
{
ImGui::TextDisabled( "(%zu)", multiCntUncont );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleMultiCntUncont, multiCntUncont );
}
if( multiUncontExpand )
{
ImGui::SameLine();
@@ -455,7 +519,16 @@ void View::DrawOptions()
}
const auto singleExpand = ImGui::TreeNodeEx( "Locks present in a single thread", 0 );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", singleCnt );
uint64_t visibleSingleCnt = 0;
for( const auto& l : m_worker.GetLockMap() ) if( l.second->threadList.size() == 1 && Vis( l.second ) ) visibleSingleCnt++;
if( visibleSingleCnt == singleCnt )
{
ImGui::TextDisabled( "(%zu)", singleCnt );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleSingleCnt, singleCnt );
}
if( singleExpand )
{
ImGui::SameLine();
@@ -546,10 +619,20 @@ void View::DrawOptions()
int pH = m_vd.plotHeight;
ImGui::SliderInt("Plot heights", &pH, 30, 200);
m_vd.plotHeight = pH;
DefaultMarker(default_markers_active);
const auto expand = ImGui::TreeNode( "Plots" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", m_worker.GetPlots().size() );
size_t visiblePlots = 0;
for( const auto& p : m_worker.GetPlots() ) if( m_tc.GetItem( p ).IsVisible() ) visiblePlots++;
if( visiblePlots == m_worker.GetPlots().size() )
{
ImGui::TextDisabled( "(%zu)", m_worker.GetPlots().size() );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visiblePlots, m_worker.GetPlots().size() );
}
if( expand )
{
ImGui::SameLine();
@@ -584,7 +667,16 @@ void View::DrawOptions()
ImGui::Separator();
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", m_threadOrder.size() );
size_t visibleThreads = 0;
for( const auto& t : m_threadOrder ) if( m_tc.GetItem( t ).IsVisible() ) visibleThreads++;
if( visibleThreads == m_threadOrder.size() )
{
ImGui::TextDisabled( "(%zu)", m_threadOrder.size() );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleThreads, m_threadOrder.size() );
}
if( expand )
{
auto& crash = m_worker.GetCrashEvent();
@@ -608,7 +700,7 @@ void View::DrawOptions()
ImGui::SameLine();
if( ImGui::SmallButton( "Sort" ) )
{
std::sort( m_threadOrder.begin(), m_threadOrder.end(), [this] ( const auto& lhs, const auto& rhs ) {
pdqsort_branchless( m_threadOrder.begin(), m_threadOrder.end(), [this] ( const auto& lhs, const auto& rhs ) {
if( lhs->groupHint != rhs->groupHint ) return lhs->groupHint < rhs->groupHint;
return strcmp( m_worker.GetThreadName( lhs->id ), m_worker.GetThreadName( rhs->id ) ) < 0;
} );
@@ -715,7 +807,16 @@ void View::DrawOptions()
ImGui::Separator();
expand = ImGui::TreeNode( ICON_FA_IMAGES " Visible frame sets:" );
ImGui::SameLine();
ImGui::TextDisabled( "(%zu)", m_worker.GetFrames().size() );
uint64_t visibleFrames = 0;
for( const auto& fd : m_worker.GetFrames() ) if( Vis( fd ) ) visibleFrames++;
if( visibleFrames == m_worker.GetFrames().size() )
{
ImGui::TextDisabled( "(%zu)", m_worker.GetFrames().size() );
}
else
{
ImGui::TextDisabled( "(%zu/%zu)", visibleFrames, m_worker.GetFrames().size() );
}
if( expand )
{
ImGui::SameLine();
@@ -747,6 +848,53 @@ void View::DrawOptions()
ImGui::TreePop();
}
}
ImGui::Separator();
ImGui::TextUnformatted( "" );
DefaultMarker( default_markers_active, false );
ImGui::SameLine( 0.0f, 1.0f );
ImGui::TextUnformatted( ": The default value for this option is configurable." );
bool highlight = false;
if( ImGui::IsItemHovered() )
{
highlight = true;
}
if( ImGui::Button( "Save current options as defaults" ) )
{
// Keep in sync with TracyView.cpp View::SetupConfig()
s_config.targetFps = m_vd.frameTarget;
s_config.dynamicColors = m_vd.dynamicColors;
s_config.forceColors = m_vd.forceColors;
s_config.ghostZones = m_vd.ghostZones;
s_config.shortenName = (int)m_vd.shortenName;
s_config.drawSamples = m_vd.drawSamples;
s_config.drawContextSwitches = m_vd.drawContextSwitches;
SaveConfig();
}
if( ImGui::IsItemHovered() )
{
highlight = true;
ImGui::BeginTooltip();
const auto fn = tracy::GetSavePath( "tracy.ini" );
ImGui::TextUnformatted( "The options above marked with " );
DefaultMarker( true, false );
ImGui::SameLine();
ImGui::TextUnformatted( "have configurable default values." );
ImGui::TextUnformatted(
"Pressing this button stores their current values as the default values.\n\n"
"Alternatively, you can manually adjust those default values by editing the config file at:" );
TextDisabledUnformatted( fn );
ImGui::Spacing();
ImGui::TextUnformatted( "For now, to restore the default values, you may delete this configuration file." );
ImGui::EndTooltip();
}
default_markers_active = highlight;
ImGui::End();
}
@@ -115,7 +115,7 @@ void View::DrawPlayback()
changed = true;
}
changed |= ImGui::SliderInt( "Frame image", &tmp, 1, ficnt, "%d" );
ImGui::SetItemUsingMouseWheel();
ImGui::SetItemKeyOwner( ImGuiKey_MouseWheelY );
if( wheel && ImGui::IsItemHovered() )
{
if( ImGui::IsItemActive() )
@@ -11,7 +11,7 @@
namespace tracy
{
bool View::DrawPlot( const TimelineContext& ctx, PlotData& plot, const std::vector<uint32_t>& plotDraw, int& offset )
bool View::DrawPlot( const TimelineContext& ctx, PlotData& plot, const std::vector<uint32_t>& plotDraw, int& offset, bool rightEnd )
{
auto draw = ImGui::GetWindowDrawList();
const auto& wpos = ctx.wpos;
@@ -173,6 +173,34 @@ bool View::DrawPlot( const TimelineContext& ctx, PlotData& plot, const std::vect
}
}
if( rightEnd )
{
const auto lastTime = m_worker.GetLastTime();
if( lastTime > m_vd.zvStart )
{
double y;
double x0 = 0;
const auto x1 = std::min<double>( ( lastTime - m_vd.zvStart ) * pxns, w );
if( plotDraw.empty() )
{
y = PlotHeight * 0.5;
DrawLine( draw, dpos + ImVec2( 0, offset + y ), dpos + ImVec2( x1, offset + y ), color );
}
else
{
x0 = ( plot.data.back().time.Val() - m_vd.zvStart ) * pxns;
y = PlotHeight - ( plot.data.back().val - min ) * revrange * PlotHeight;
DrawLine( draw, dpos + ImVec2( x0, offset + y ), dpos + ImVec2( x1, offset + y ), color );
}
if( plot.fill )
{
draw->AddRectFilled( dpos + ImVec2( x0, offset + PlotHeight ), dpos + ImVec2( x1, offset + y ), fill );
}
}
}
auto tmp = FormatPlotValue( plot.rMax, plot.format );
DrawTextSuperContrast( draw, wpos + ImVec2( 0, offset ), color, tmp );
offset += PlotHeight - ty;
@@ -14,9 +14,11 @@ void View::DrawRanges()
ImGui::Separator();
DrawRangeEntry( m_statRange, ICON_FA_ARROW_UP_WIDE_SHORT " Statistics", 0x448888EE, "RangeStatisticsCopyFrom", 1 );
ImGui::Separator();
DrawRangeEntry( m_waitStackRange, ICON_FA_HOURGLASS_HALF " Wait stacks", 0x44EEB588, "RangeWaitStackCopyFrom", 2 );
DrawRangeEntry( m_flameRange, ICON_FA_FIRE_FLAME_CURVED " Flame", 0x4488B5EE, "RangeFlameCopyFrom", 2 );
ImGui::Separator();
DrawRangeEntry( m_memInfo.range, ICON_FA_MEMORY " Memory", 0x4488EEE3, "RangeMemoryCopyFrom", 3 );
DrawRangeEntry( m_waitStackRange, ICON_FA_HOURGLASS_HALF " Wait stacks", 0x44EEB588, "RangeWaitStackCopyFrom", 3 );
ImGui::Separator();
DrawRangeEntry( m_memInfo.range, ICON_FA_MEMORY " Memory", 0x4488EEE3, "RangeMemoryCopyFrom", 4 );
ImGui::End();
}
@@ -79,9 +81,14 @@ void View::DrawRangeEntry( Range& range, const char* label, uint32_t color, cons
if( id != 2 )
{
ImGui::SameLine();
if( SmallButtonDisablable( ICON_FA_HOURGLASS_HALF " Copy from wait stacks", m_waitStackRange.min == 0 && m_waitStackRange.max == 0 ) ) range = m_waitStackRange;
if( SmallButtonDisablable( ICON_FA_FIRE_FLAME_CURVED " Copy from flame", m_flameRange.min == 0 && m_flameRange.max == 0 ) ) range = m_flameRange;
}
if( id != 3 )
{
ImGui::SameLine();
if( SmallButtonDisablable( ICON_FA_HOURGLASS_HALF " Copy from wait stacks", m_waitStackRange.min == 0 && m_waitStackRange.max == 0 ) ) range = m_waitStackRange;
}
if( id != 4 )
{
ImGui::SameLine();
if( SmallButtonDisablable( ICON_FA_MEMORY " Copy from memory", m_memInfo.range.min == 0 && m_memInfo.range.max == 0 ) ) range = m_memInfo.range;

Some files were not shown because too many files have changed in this diff Show More