From 1b207d3e2abfd6eea69c04285b0c4c64ddb28366 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 10 Jun 2026 14:14:54 -0700 Subject: [PATCH] adding OpenGL example (spinning triangle) --- examples/opengl/triangle/CMakeLists.txt | 75 ++++++++ examples/opengl/triangle/platform/platform.h | 37 ++++ .../triangle/platform/platform_macos.mm | 113 +++++++++++++ .../triangle/platform/platform_windows.cpp | 160 ++++++++++++++++++ .../opengl/triangle/platform/platform_x11.cpp | 149 ++++++++++++++++ .../opengl/triangle/spinning_triangle.cpp | 134 +++++++++++++++ 6 files changed, 668 insertions(+) create mode 100644 examples/opengl/triangle/CMakeLists.txt create mode 100644 examples/opengl/triangle/platform/platform.h create mode 100644 examples/opengl/triangle/platform/platform_macos.mm create mode 100644 examples/opengl/triangle/platform/platform_windows.cpp create mode 100644 examples/opengl/triangle/platform/platform_x11.cpp create mode 100644 examples/opengl/triangle/spinning_triangle.cpp diff --git a/examples/opengl/triangle/CMakeLists.txt b/examples/opengl/triangle/CMakeLists.txt new file mode 100644 index 00000000..8b194ec8 --- /dev/null +++ b/examples/opengl/triangle/CMakeLists.txt @@ -0,0 +1,75 @@ +# CMakeLists.txt — OpenGL spinning triangle demo +# +# macOS: +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja . +# cmake --build build/ninja +# +# Linux (requires libx11-dev libgl1-mesa-dev libglew-dev): +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja . +# cmake --build build/ninja +# +# Windows (MSVC, requires GLEW): +# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGLEW_ROOT= -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) +option(TRACY_OPENGL_AUTO_CALIBRATION "Enable periodic GPU/CPU recalibration" ON) + +# --------------------------------------------------------------------------- +# Platform-specific sources and link settings +# --------------------------------------------------------------------------- +if(APPLE) + set(PLATFORM_SOURCES platform/platform_macos.mm) + set(PLATFORM_LIBS "-framework Cocoa" "-framework OpenGL") + set_source_files_properties(platform/platform_macos.mm + PROPERTIES COMPILE_FLAGS "-ObjC++" + ) +elseif(WIN32) + find_package(GLEW REQUIRED) + set(PLATFORM_SOURCES platform/platform_windows.cpp) + set(PLATFORM_LIBS opengl32 GLEW::GLEW) +else() + find_package(GLEW REQUIRED) + find_package(X11 REQUIRED) + set(PLATFORM_SOURCES platform/platform_x11.cpp) + set(PLATFORM_LIBS GLEW::GLEW X11::X11 GL) +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() +if(TRACY_OPENGL_AUTO_CALIBRATION) + target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_OPENGL_AUTO_CALIBRATION) +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..2d1def55 --- /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__ +// OpenGL is only available on MacOS (no iOS support) +// Anything from gl3.h will spew deprecation warnings when used, +// unless GL_SILENCE_DEPRECATION has been defined beforehand +//# define GL_SILENCE_DEPRECATION +# 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(); + +// 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_macos.mm b/examples/opengl/triangle/platform/platform_macos.mm new file mode 100644 index 00000000..ca496061 --- /dev/null +++ b/examples/opengl/triangle/platform/platform_macos.mm @@ -0,0 +1,113 @@ +// platform_macos.mm — macOS backend (Cocoa + NSOpenGLView) +// +// Note: OpenGL is deprecated on macOS 10.14+, but remains functional. +// Tracy's TracyGpuContext is a no-op on Apple platforms. +// +// Compile flags: +// clang++ -std=c++17 -ObjC++ spinning_triangle.cpp platform/platform_macos.mm \ +// -framework Cocoa -framework OpenGL -o gl_spinning_triangle + +// OpenGL is only available on MacOS (no iOS support) +// Anything from Cocoa/OpenGL will spew deprecation warnings when used, +// unless GL_SILENCE_DEPRECATION has been defined beforehand +//#define GL_SILENCE_DEPRECATION +#import +#import +#include +#include +#include "platform.h" + +static NSOpenGLView* sGLView = nullptr; +static CFAbsoluteTime sStartTime = 0; +static void (*sRenderCb)() = nullptr; +static void (*sShutdownCb)() = nullptr; + +@interface AppDelegate : NSObject +@property (strong) NSWindow* window; +@property (strong) NSTimer* timer; +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification*)notification { + self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 60.0 + target:self + selector:@selector(tick:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; + + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown + handler:^NSEvent*(NSEvent* event) { + if (event.keyCode == 53) { [NSApp terminate:nil]; return nil; } + return event; + }]; + + [self.window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; +} + +- (void)tick:(NSTimer*)t { + if (sRenderCb) { + [[sGLView openGLContext] makeCurrentContext]; + sRenderCb(); + } +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)app { return YES; } + +- (void)applicationWillTerminate:(NSNotification*)notification { + [self.timer invalidate]; + if (sShutdownCb) sShutdownCb(); +} + +@end + +bool platformInit(int width, int height, const char* title) { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + NSOpenGLPixelFormatAttribute attrs[] = { + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAccelerated, + 0 + }; + NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + if (!fmt) { fprintf(stderr, "Failed to create NSOpenGLPixelFormat\n"); return false; } + + NSRect frame = NSMakeRect(200, 200, width, height); + sGLView = [[NSOpenGLView alloc] initWithFrame:frame pixelFormat:fmt]; + if (!sGLView) { fprintf(stderr, "Failed to create NSOpenGLView\n"); return false; } + + NSWindow* window = [[NSWindow alloc] + initWithContentRect:frame + styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable) + backing:NSBackingStoreBuffered + defer:NO]; + [window setTitle:[NSString stringWithUTF8String:title]]; + [window setContentView:sGLView]; + + [[sGLView openGLContext] makeCurrentContext]; + + AppDelegate* del = [[AppDelegate alloc] init]; + del.window = window; + [NSApp setDelegate:del]; + + sStartTime = CFAbsoluteTimeGetCurrent(); + return true; +} + +bool platformInitGL() { return true; } + +double platformGetTime() { return CFAbsoluteTimeGetCurrent() - sStartTime; } + +void platformSwapBuffers() { [[sGLView openGLContext] flushBuffer]; } + +void platformRunLoop(void (*render)(), void (*shutdown)()) { + sRenderCb = render; + sShutdownCb = shutdown; + @autoreleasepool { [NSApp run]; } +} diff --git a/examples/opengl/triangle/platform/platform_windows.cpp b/examples/opengl/triangle/platform/platform_windows.cpp new file mode 100644 index 00000000..736bd7e9 --- /dev/null +++ b/examples/opengl/triangle/platform/platform_windows.cpp @@ -0,0 +1,160 @@ +// platform_windows.cpp — Windows backend (Win32 + WGL) +// +// Creates a WGL 3.3 Core Profile OpenGL context. +// GLEW must be initialized by the caller (initGL) after platformInit() returns. +// +// Compile flags (MSVC): +// cl /std:c++17 spinning_triangle.cpp platform/platform_windows.cpp \ +// /I /glew32s.lib opengl32.lib \ +// user32.lib gdi32.lib /Fe:gl_spinning_triangle.exe + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include "platform.h" + +// WGL_ARB_create_context token values +#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB +# define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +# define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +# define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +# define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#endif + +typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int*); + +static HWND sHwnd = nullptr; +static HDC sDC = nullptr; +static HGLRC sGLRC = nullptr; +static bool sRunning = false; +static LARGE_INTEGER sFreq = {}; +static LARGE_INTEGER sStart = {}; +static void (*sRenderCb)() = nullptr; +static void (*sShutdownCb)() = nullptr; + +static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + switch (msg) { + case WM_KEYDOWN: + if (wp == VK_ESCAPE) { sRunning = false; return 0; } + break; + case WM_CLOSE: + case WM_DESTROY: + sRunning = false; + PostQuitMessage(0); + return 0; + } + return DefWindowProcA(hwnd, msg, wp, lp); +} + +bool platformInit(int width, int height, const char* title) { + WNDCLASSEXA wc = {}; + wc.cbSize = sizeof(wc); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = wndProc; + wc.hInstance = GetModuleHandleA(nullptr); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszClassName = "GLSpinningTriangle"; + if (!RegisterClassExA(&wc)) { fprintf(stderr, "RegisterClassExA failed\n"); return false; } + + RECT rect = { 0, 0, width, height }; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX), FALSE); + sHwnd = CreateWindowExA(0, "GLSpinningTriangle", title, + WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX), + CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, + nullptr, nullptr, GetModuleHandleA(nullptr), nullptr); + if (!sHwnd) { fprintf(stderr, "CreateWindowExA failed\n"); return false; } + + sDC = GetDC(sHwnd); + + // Create a legacy context to get wglCreateContextAttribsARB, then replace it. + PIXELFORMATDESCRIPTOR pfd = {}; + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + SetPixelFormat(sDC, ChoosePixelFormat(sDC, &pfd), &pfd); + HGLRC dummy = wglCreateContext(sDC); + wglMakeCurrent(sDC, dummy); + + auto wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) + wglGetProcAddress("wglCreateContextAttribsARB"); + + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(dummy); + + if (!wglCreateContextAttribsARB) { + fprintf(stderr, "WGL_ARB_create_context not supported\n"); + return false; + } + + const int attribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0 + }; + sGLRC = wglCreateContextAttribsARB(sDC, nullptr, attribs); + if (!sGLRC) { fprintf(stderr, "wglCreateContextAttribsARB failed\n"); return false; } + wglMakeCurrent(sDC, sGLRC); + + ShowWindow(sHwnd, SW_SHOW); + UpdateWindow(sHwnd); + + QueryPerformanceFrequency(&sFreq); + QueryPerformanceCounter(&sStart); + return true; +} + +bool platformInitGL() { + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + fprintf(stderr, "Failed to initialize GLEW\n"); + return false; + } + return true; +} + +double platformGetTime() { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return (double)(now.QuadPart - sStart.QuadPart) / (double)sFreq.QuadPart; +} + +void platformSwapBuffers() { SwapBuffers(sDC); } + +void platformRunLoop(void (*render)(), void (*shutdown)()) { + static const double kFrameTime = 1.0 / 60.0; + sRenderCb = render; + sShutdownCb = shutdown; + sRunning = true; + + while (sRunning) { + double frameStart = platformGetTime(); + + MSG msg; + while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { sRunning = false; break; } + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + if (sRunning) render(); + + double elapsed = platformGetTime() - frameStart; + if (elapsed < kFrameTime) { + DWORD ms = (DWORD)((kFrameTime - elapsed) * 1000.0); + if (ms > 0) Sleep(ms); + } + } + + shutdown(); + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(sGLRC); + ReleaseDC(sHwnd, sDC); + DestroyWindow(sHwnd); +} diff --git a/examples/opengl/triangle/platform/platform_x11.cpp b/examples/opengl/triangle/platform/platform_x11.cpp new file mode 100644 index 00000000..3009ebb9 --- /dev/null +++ b/examples/opengl/triangle/platform/platform_x11.cpp @@ -0,0 +1,149 @@ +// platform_x11.cpp — Linux/X11 backend (GLX) +// +// Creates a GLX 3.3 Core Profile OpenGL context. +// GLEW must be initialized by the caller (initGL) after platformInit() returns. +// +// Dependencies: libX11, libGL, libGLEW +// +// Compile flags: +// g++ -std=c++17 spinning_triangle.cpp platform/platform_x11.cpp \ +// -lX11 -lGL -lGLEW -o gl_spinning_triangle + +#include +#include +#include +#include +#include "platform.h" + +// GLX_ARB_create_context token values (from ) +#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB +# define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +# define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +# define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +# define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#endif + +typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); + +static Display* sDpy = nullptr; +static Window sWin = 0; +static GLXContext sCtx = nullptr; +static Atom sWmDelete = 0; +static bool sRunning = false; +static struct timespec sStart = {}; +static void (*sRenderCb)() = nullptr; +static void (*sShutdownCb)() = nullptr; + +bool platformInit(int width, int height, const char* title) { + sDpy = XOpenDisplay(nullptr); + if (!sDpy) { fprintf(stderr, "Cannot open X display\n"); return false; } + + const int fbAttribs[] = { + GLX_X_RENDERABLE, True, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DOUBLEBUFFER, True, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + None + }; + int fbCount = 0; + GLXFBConfig* fbc = glXChooseFBConfig(sDpy, DefaultScreen(sDpy), fbAttribs, &fbCount); + if (!fbc || fbCount == 0) { fprintf(stderr, "No suitable GLXFBConfig\n"); return false; } + + XVisualInfo* vi = glXGetVisualFromFBConfig(sDpy, fbc[0]); + + XSetWindowAttributes swa = {}; + swa.colormap = XCreateColormap(sDpy, DefaultRootWindow(sDpy), vi->visual, AllocNone); + swa.event_mask = ExposureMask | KeyPressMask; + sWin = XCreateWindow(sDpy, DefaultRootWindow(sDpy), + 200, 200, width, height, 0, vi->depth, InputOutput, vi->visual, + CWColormap | CWEventMask, &swa); + XFree(vi); + + XStoreName(sDpy, sWin, title); + XMapWindow(sDpy, sWin); + + sWmDelete = XInternAtom(sDpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(sDpy, sWin, &sWmDelete, 1); + + auto glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) + glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB"); + if (!glXCreateContextAttribsARB) { + fprintf(stderr, "glXCreateContextAttribsARB not found\n"); + XFree(fbc); + return false; + } + + const int ctxAttribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + None + }; + sCtx = glXCreateContextAttribsARB(sDpy, fbc[0], nullptr, True, ctxAttribs); + XFree(fbc); + if (!sCtx) { fprintf(stderr, "Failed to create GLX context\n"); return false; } + + glXMakeCurrent(sDpy, sWin, sCtx); + clock_gettime(CLOCK_MONOTONIC, &sStart); + return true; +} + +bool platformInitGL() { + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + fprintf(stderr, "Failed to initialize GLEW\n"); + return false; + } + return true; +} + +double platformGetTime() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (double)(now.tv_sec - sStart.tv_sec) + (double)(now.tv_nsec - sStart.tv_nsec) * 1e-9; +} + +void platformSwapBuffers() { glXSwapBuffers(sDpy, sWin); } + +void platformRunLoop(void (*render)(), void (*shutdown)()) { + static const long kFrameNs = 1000000000L / 60; + sRenderCb = render; + sShutdownCb = shutdown; + sRunning = true; + + while (sRunning) { + struct timespec frameStart; + clock_gettime(CLOCK_MONOTONIC, &frameStart); + + while (XPending(sDpy)) { + XEvent ev; + XNextEvent(sDpy, &ev); + if (ev.type == KeyPress) { sRunning = false; break; } + if (ev.type == ClientMessage && (Atom)ev.xclient.data.l[0] == sWmDelete) { + sRunning = false; + break; + } + } + + if (sRunning) render(); + + struct timespec frameEnd; + clock_gettime(CLOCK_MONOTONIC, &frameEnd); + long elapsed = (frameEnd.tv_sec - frameStart.tv_sec) * 1000000000L + + (frameEnd.tv_nsec - frameStart.tv_nsec); + long remaining = kFrameNs - elapsed; + if (remaining > 0) { + struct timespec ts = { 0, remaining }; + nanosleep(&ts, nullptr); + } + } + + shutdown(); + glXMakeCurrent(sDpy, None, nullptr); + glXDestroyContext(sDpy, sCtx); + XDestroyWindow(sDpy, sWin); + XCloseDisplay(sDpy); +} diff --git a/examples/opengl/triangle/spinning_triangle.cpp b/examples/opengl/triangle/spinning_triangle.cpp new file mode 100644 index 00000000..84ec43d6 --- /dev/null +++ b/examples/opengl/triangle/spinning_triangle.cpp @@ -0,0 +1,134 @@ +// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling. +// +// Tracy GPU zones are active on non-Apple platforms when TRACY_ENABLE is defined. +// TRACY_OPENGL_AUTO_CALIBRATION (enabled by default in CMakeLists.txt) enables +// periodic GPU/CPU drift correction via glGetInteger64v(GL_TIMESTAMP). + +#include "platform/platform.h" +#include +#include + +#include +#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); + glViewport(0, 0, kWidth, kHeight); + 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; +}