diff --git a/examples/opengl/triangle/CMakeLists.txt b/examples/opengl/triangle/CMakeLists.txt new file mode 100644 index 00000000..0d4a93d5 --- /dev/null +++ b/examples/opengl/triangle/CMakeLists.txt @@ -0,0 +1,83 @@ +# CMakeLists.txt — OpenGL spinning triangle demo +# +# macOS: +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja . +# cmake --build build/ninja +# +# Linux (requires libsdl3-dev libgl1-mesa-dev): +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja . +# cmake --build build/ninja +# +# Windows: +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja . +# cmake --build build/ninja + +cmake_minimum_required(VERSION 3.16) +project(gl_spinning_triangle LANGUAGES C CXX) + +# --------------------------------------------------------------------------- +# Tracy root — defaults to three directories above this CMakeLists.txt. +# --------------------------------------------------------------------------- +set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..") +option(TRACY_ENABLE "Enable Tracy profiling" ON) + +# --------------------------------------------------------------------------- +# Platform — SDL3 (cross-platform windowing, must be installed on the system) +# --------------------------------------------------------------------------- +find_package(SDL3 REQUIRED) + +# --------------------------------------------------------------------------- +# GL extension loader — GLEW (Windows + Linux, fetched automatically) +# --------------------------------------------------------------------------- +if(NOT APPLE) + include(FetchContent) + set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "" FORCE) + set(ONLY_LIBS ON CACHE BOOL "" FORCE) + FetchContent_Declare(glew + GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git + GIT_TAG master # pin to a specific commit for reproducible builds + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(glew) +endif() + +set(PLATFORM_SOURCES platform/platform_sdl3.cpp) + +if(APPLE) + set(PLATFORM_LIBS SDL3::SDL3 "-framework OpenGL") +elseif(WIN32) + set(PLATFORM_LIBS SDL3::SDL3 opengl32 libglew_static) +else() + set(PLATFORM_LIBS SDL3::SDL3 GL libglew_static) +endif() + +# --------------------------------------------------------------------------- +# Target +# --------------------------------------------------------------------------- +add_executable(gl_spinning_triangle + spinning_triangle.cpp + "${TRACY_DIR}/public/TracyClient.cpp" + ${PLATFORM_SOURCES} +) + +# Suppress upstream warnings from TracyClient.cpp +if(MSVC) + set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp" + PROPERTIES COMPILE_FLAGS "/w" + ) +else() + set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp" + PROPERTIES COMPILE_FLAGS "-w" + ) +endif() + +target_compile_features(gl_spinning_triangle PRIVATE cxx_std_17) + +if(TRACY_ENABLE) + target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_ENABLE) +endif() + +target_include_directories(gl_spinning_triangle PRIVATE + "${TRACY_DIR}/public" +) +target_link_libraries(gl_spinning_triangle PRIVATE ${PLATFORM_LIBS}) diff --git a/examples/opengl/triangle/platform/platform.h b/examples/opengl/triangle/platform/platform.h new file mode 100644 index 00000000..4513e9f9 --- /dev/null +++ b/examples/opengl/triangle/platform/platform.h @@ -0,0 +1,37 @@ +// platform.h — interface between platform-agnostic code and platform backends +// +// Each platform_*.mm / platform_*.cpp file implements these four functions. +// Exactly one backend must be linked into the final binary. + +#pragma once + +#ifdef __APPLE__ +# include +#else +# include +#endif + +// Initialize the windowing system, create a window, and make an OpenGL 3.3 +// Core Profile context current on the calling thread. +// Returns true on success. +bool platformInit(int width, int height, const char* title); + +// Load OpenGL function pointers (no-op on macOS where the framework exports them directly). +// Must be called after platformInit() while the GL context is current. +// Returns true on success. +bool platformInitGL(); + +// Elapsed wall-clock time in seconds since platformInit(). +double platformGetTime(); + +// Swap front and back buffers (present the rendered frame). +void platformSwapBuffers(); + +// Pixel scaling factor relative to the logical window size (1.0 on non-HiDPI displays). +// Must be called after platformInit(). +void platformGetPixelDensityScale(float* x, float* y); + +// Enter the platform event/render loop. +// Calls render() each frame at ~60 fps. +// Calls shutdown() exactly once before returning. +void platformRunLoop(void (*render)(), void (*shutdown)()); diff --git a/examples/opengl/triangle/platform/platform_sdl3.cpp b/examples/opengl/triangle/platform/platform_sdl3.cpp new file mode 100644 index 00000000..99fe418a --- /dev/null +++ b/examples/opengl/triangle/platform/platform_sdl3.cpp @@ -0,0 +1,85 @@ +// platform_sdl3.cpp — SDL3 windowing backend (cross-platform) +#include "platform.h" // GL headers first (gl3.h / glew.h) so SDL sees guards set + +#define SDL_MAIN_HANDLED // we don't want SDL_main +#include + +#include +#include + +static SDL_Window* sWin = nullptr; +static SDL_GLContext sCtx = nullptr; +static std::chrono::steady_clock::time_point sStartTime; + +bool platformInit(int width, int height, const char* title) { + if (!SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError()); + return false; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + sWin = SDL_CreateWindow(title, width, height, SDL_WINDOW_OPENGL); + if (!sWin) { + fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return false; + } + SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + sCtx = SDL_GL_CreateContext(sWin); + if (!sCtx) { + fprintf(stderr, "ERROR: SDL_GL_CreateContext failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(sWin); + SDL_Quit(); + return false; + } + + SDL_GL_SetSwapInterval(1); + sStartTime = std::chrono::steady_clock::now(); + return true; +} + +bool platformInitGL() { +#ifndef __APPLE__ + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + fprintf(stderr, "Failed to initialize GLEW\n"); + return false; + } +#endif + return true; +} + +double platformGetTime() { + return std::chrono::duration( + std::chrono::steady_clock::now() - sStartTime).count(); +} + +void platformSwapBuffers() { SDL_GL_SwapWindow(sWin); } + +void platformGetPixelDensityScale(float* x, float* y) { + int pw, ph, ww, wh; + SDL_GetWindowSizeInPixels(sWin, &pw, &ph); + SDL_GetWindowSize(sWin, &ww, &wh); + *x = (ww > 0) ? (float)pw / (float)ww : 1.0f; + *y = (wh > 0) ? (float)ph / (float)wh : 1.0f; +} + +void platformRunLoop(void (*render)(), void (*shutdown)()) { + bool running = true; + while (running) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) running = false; + if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false; + } + if (running) render(); + } + shutdown(); + SDL_GL_DestroyContext(sCtx); + SDL_DestroyWindow(sWin); + SDL_Quit(); +} diff --git a/examples/opengl/triangle/spinning_triangle.cpp b/examples/opengl/triangle/spinning_triangle.cpp new file mode 100644 index 00000000..2c4f728c --- /dev/null +++ b/examples/opengl/triangle/spinning_triangle.cpp @@ -0,0 +1,145 @@ +// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling. + +#ifdef __APPLE__ +// NOTE: OpenGL is only available on MacOS (no iOS support) +// Including and using anything related to OpenGL on Apple (like ) +// will emit deprecation warnings, unless GL_SILENCE_DEPRECATION is defined +#define GL_SILENCE_DEPRECATION +// NOTE: TracyOpenGL.hpp will not work as expected even on Apple devices that +// support OpenGL, because the OpenGL drivers do not implement ARB_timer_query +// properly (querying GL_TIMESTAMP always resolves to 0). TracyOpenGL.hpp will +// emit a compiler warning, and a Tracy message to the trace/profiler, but the +// program will still run. +#endif + +#include "platform/platform.h" // also includes OpenGL headers + +#include + +// NOTE: opt-in toggle for periodic recalibrations during Collect() +#define TRACY_OPENGL_AUTO_CALIBRATION +#include + +static const int kWidth = 800; +static const int kHeight = 600; + +static GLuint gProgram = 0; +static GLuint gVao = 0; +static GLint gAngleLoc = -1; + +// Vertex colors and positions are baked in; rotation is driven by a uniform. +static const char* kVertSrc = R"( +#version 150 core +uniform float uAngle; +const vec2 kPos[3] = vec2[3]( + vec2( 0.0, 0.5 ), + vec2(-0.433, -0.25 ), + vec2( 0.433, -0.25 ) +); +const vec3 kCol[3] = vec3[3]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); +out vec3 vColor; +void main() { + float c = cos(uAngle); + float s = sin(uAngle); + vec2 p = kPos[gl_VertexID]; + gl_Position = vec4(p.x*c - p.y*s, p.x*s + p.y*c, 0.0, 1.0); + vColor = kCol[gl_VertexID]; +} +)"; + +static const char* kFragSrc = R"( +#version 150 core +in vec3 vColor; +out vec4 fragColor; +void main() { fragColor = vec4(vColor, 1.0); } +)"; + +static GLuint compileShader(GLenum type, const char* src) { + GLuint s = glCreateShader(type); + glShaderSource(s, 1, &src, nullptr); + glCompileShader(s); + GLint ok = 0; + glGetShaderiv(s, GL_COMPILE_STATUS, &ok); + if (!ok) { + char log[512]; + glGetShaderInfoLog(s, sizeof(log), nullptr, log); + fprintf(stderr, "Shader compile error: %s\n", log); + glDeleteShader(s); + return 0; + } + return s; +} + +static int initGL() { + if (!platformInitGL()) return 1; + + TracyGpuContext; + TracyGpuContextName("OpenGL", 6); + + GLuint vert = compileShader(GL_VERTEX_SHADER, kVertSrc); + GLuint frag = compileShader(GL_FRAGMENT_SHADER, kFragSrc); + if (!vert || !frag) return 1; + + gProgram = glCreateProgram(); + glAttachShader(gProgram, vert); + glAttachShader(gProgram, frag); + glLinkProgram(gProgram); + glDeleteShader(vert); + glDeleteShader(frag); + + GLint ok = 0; + glGetProgramiv(gProgram, GL_LINK_STATUS, &ok); + if (!ok) { + char log[512]; + glGetProgramInfoLog(gProgram, sizeof(log), nullptr, log); + fprintf(stderr, "Program link error: %s\n", log); + return 1; + } + + gAngleLoc = glGetUniformLocation(gProgram, "uAngle"); + + // Core profile requires a bound VAO even with no vertex attributes. + glGenVertexArrays(1, &gVao); + glBindVertexArray(gVao); + + glClearColor(0.05f, 0.05f, 0.08f, 1.0f); + float scaleX, scaleY; + platformGetPixelDensityScale(&scaleX, &scaleY); + glViewport(0, 0, (int)(kWidth * scaleX), (int)(kHeight * scaleY)); + return 0; +} + +static void renderFrame() { + ZoneScoped; + + glClear(GL_COLOR_BUFFER_BIT); + glUseProgram(gProgram); + + { + TracyGpuZone("triangle draw"); + glUniform1f(gAngleLoc, (float)platformGetTime()); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + platformSwapBuffers(); + TracyGpuCollect; +} + +static void shutdown() { + fprintf(stderr, "application is shutting down...\n"); + glDeleteVertexArrays(1, &gVao); + glDeleteProgram(gProgram); +} + +int main() { + if (!platformInit(kWidth, kHeight, "OpenGL Spinning Triangle")) + return 1; + if (initGL() != 0) + return 2; + platformRunLoop(renderFrame, shutdown); + return 0; +} diff --git a/public/tracy/TracyOpenGL.hpp b/public/tracy/TracyOpenGL.hpp index f9e35b15..36b2a75f 100644 --- a/public/tracy/TracyOpenGL.hpp +++ b/public/tracy/TracyOpenGL.hpp @@ -1,7 +1,12 @@ #ifndef __TRACYOPENGL_HPP__ #define __TRACYOPENGL_HPP__ -#if !defined TRACY_ENABLE || defined __APPLE__ +#ifdef __APPLE__ +#define TRACY_OPENGL_DISABLE +#warning "OpenGL timestamps are unreliable on Apple devices that still run OpenGL." +#endif + +#if !defined TRACY_ENABLE || defined TRACY_OPENGL_DISABLE #define TracyGpuContext #define TracyGpuContextName(x,y) @@ -98,17 +103,25 @@ public: , m_head( 0 ) , m_tail( 0 ) { + ZoneScopedC( Color::Red4 ); + assert( m_context != 255 ); - glGenQueries( QueryCount, m_query ); + GLint bits; + glGetQueryiv( GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &bits ); + if( bits == 0 ) + { + // all timestamp queries would resolve to 0 (and produce 0ns GPU zones). + // (this is the case for many TBDR GPUs, including Apple Silicon) + Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Warning, Color::Tomato, 0, + "OpenGL driver does not implement GL_TIMESTAMP precision." ); + } + assert( bits > 0 ); int64_t tgpu; glGetInteger64v( GL_TIMESTAMP, &tgpu ); int64_t tcpu = Profiler::GetTime(); - GLint bits; - glGetQueryiv( GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &bits ); - #ifdef TRACY_OPENGL_AUTO_CALIBRATION // The anchor above is never refreshed; advertise calibration and emit periodic // GpuCalibration events to correct CPU/GPU drift (see Recalibrate). Opt-in, @@ -117,6 +130,8 @@ public: m_prevCalibration = GetHostTimeNs(); #endif + glGenQueries( QueryCount, m_query ); + const float period = 1.f; const auto thread = GetThreadHandle(); TracyLfqPrepare( QueueType::GpuNewContext );