adding OpenGL example (spinning triangle)

This commit is contained in:
Marcos Slomp
2026-06-10 14:14:54 -07:00
parent 9ab39d8af3
commit 1b207d3e2a
6 changed files with 668 additions and 0 deletions

View File

@@ -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=<path> -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})

View File

@@ -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 <OpenGL/gl3.h>
#else
# include <GL/glew.h>
#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)());

View File

@@ -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 <Cocoa/Cocoa.h>
#import <OpenGL/OpenGL.h>
#include <CoreFoundation/CFDate.h>
#include <cstdio>
#include "platform.h"
static NSOpenGLView* sGLView = nullptr;
static CFAbsoluteTime sStartTime = 0;
static void (*sRenderCb)() = nullptr;
static void (*sShutdownCb)() = nullptr;
@interface AppDelegate : NSObject <NSApplicationDelegate>
@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]; }
}

View File

@@ -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<glew-include> <glew-lib>/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 <windows.h>
#include <GL/gl.h>
#include <cstdio>
#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);
}

View File

@@ -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 <X11/Xlib.h>
#include <GL/glx.h>
#include <cstdio>
#include <time.h>
#include "platform.h"
// GLX_ARB_create_context token values (from <GL/glxext.h>)
#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);
}

View File

@@ -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 <cmath>
#include <cstdio>
#include <tracy/Tracy.hpp>
#include <tracy/TracyOpenGL.hpp>
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;
}