Skip to content

Commit 45576f6

Browse files
authored
Merge pull request #1400 from wolfpld/slomp/gl-example
adding OpenGL example (spinning triangle)
2 parents 343567a + 17e13bc commit 45576f6

5 files changed

Lines changed: 370 additions & 5 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# CMakeLists.txt — OpenGL spinning triangle demo
2+
#
3+
# macOS:
4+
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
5+
# cmake --build build/ninja
6+
#
7+
# Linux (requires libsdl3-dev libgl1-mesa-dev):
8+
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
9+
# cmake --build build/ninja
10+
#
11+
# Windows:
12+
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
13+
# cmake --build build/ninja
14+
15+
cmake_minimum_required(VERSION 3.16)
16+
project(gl_spinning_triangle LANGUAGES C CXX)
17+
18+
# ---------------------------------------------------------------------------
19+
# Tracy root — defaults to three directories above this CMakeLists.txt.
20+
# ---------------------------------------------------------------------------
21+
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
22+
option(TRACY_ENABLE "Enable Tracy profiling" ON)
23+
24+
# ---------------------------------------------------------------------------
25+
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
26+
# ---------------------------------------------------------------------------
27+
find_package(SDL3 REQUIRED)
28+
29+
# ---------------------------------------------------------------------------
30+
# GL extension loader — GLEW (Windows + Linux, fetched automatically)
31+
# ---------------------------------------------------------------------------
32+
if(NOT APPLE)
33+
include(FetchContent)
34+
set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "" FORCE)
35+
set(ONLY_LIBS ON CACHE BOOL "" FORCE)
36+
FetchContent_Declare(glew
37+
GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git
38+
GIT_TAG master # pin to a specific commit for reproducible builds
39+
GIT_SHALLOW TRUE
40+
)
41+
FetchContent_MakeAvailable(glew)
42+
endif()
43+
44+
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
45+
46+
if(APPLE)
47+
set(PLATFORM_LIBS SDL3::SDL3 "-framework OpenGL")
48+
elseif(WIN32)
49+
set(PLATFORM_LIBS SDL3::SDL3 opengl32 libglew_static)
50+
else()
51+
set(PLATFORM_LIBS SDL3::SDL3 GL libglew_static)
52+
endif()
53+
54+
# ---------------------------------------------------------------------------
55+
# Target
56+
# ---------------------------------------------------------------------------
57+
add_executable(gl_spinning_triangle
58+
spinning_triangle.cpp
59+
"${TRACY_DIR}/public/TracyClient.cpp"
60+
${PLATFORM_SOURCES}
61+
)
62+
63+
# Suppress upstream warnings from TracyClient.cpp
64+
if(MSVC)
65+
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
66+
PROPERTIES COMPILE_FLAGS "/w"
67+
)
68+
else()
69+
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
70+
PROPERTIES COMPILE_FLAGS "-w"
71+
)
72+
endif()
73+
74+
target_compile_features(gl_spinning_triangle PRIVATE cxx_std_17)
75+
76+
if(TRACY_ENABLE)
77+
target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_ENABLE)
78+
endif()
79+
80+
target_include_directories(gl_spinning_triangle PRIVATE
81+
"${TRACY_DIR}/public"
82+
)
83+
target_link_libraries(gl_spinning_triangle PRIVATE ${PLATFORM_LIBS})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// platform.h — interface between platform-agnostic code and platform backends
2+
//
3+
// Each platform_*.mm / platform_*.cpp file implements these four functions.
4+
// Exactly one backend must be linked into the final binary.
5+
6+
#pragma once
7+
8+
#ifdef __APPLE__
9+
# include <OpenGL/gl3.h>
10+
#else
11+
# include <GL/glew.h>
12+
#endif
13+
14+
// Initialize the windowing system, create a window, and make an OpenGL 3.3
15+
// Core Profile context current on the calling thread.
16+
// Returns true on success.
17+
bool platformInit(int width, int height, const char* title);
18+
19+
// Load OpenGL function pointers (no-op on macOS where the framework exports them directly).
20+
// Must be called after platformInit() while the GL context is current.
21+
// Returns true on success.
22+
bool platformInitGL();
23+
24+
// Elapsed wall-clock time in seconds since platformInit().
25+
double platformGetTime();
26+
27+
// Swap front and back buffers (present the rendered frame).
28+
void platformSwapBuffers();
29+
30+
// Pixel scaling factor relative to the logical window size (1.0 on non-HiDPI displays).
31+
// Must be called after platformInit().
32+
void platformGetPixelDensityScale(float* x, float* y);
33+
34+
// Enter the platform event/render loop.
35+
// Calls render() each frame at ~60 fps.
36+
// Calls shutdown() exactly once before returning.
37+
void platformRunLoop(void (*render)(), void (*shutdown)());
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// platform_sdl3.cpp — SDL3 windowing backend (cross-platform)
2+
#include "platform.h" // GL headers first (gl3.h / glew.h) so SDL sees guards set
3+
4+
#define SDL_MAIN_HANDLED // we don't want SDL_main
5+
#include <SDL3/SDL.h>
6+
7+
#include <chrono>
8+
#include <cstdio>
9+
10+
static SDL_Window* sWin = nullptr;
11+
static SDL_GLContext sCtx = nullptr;
12+
static std::chrono::steady_clock::time_point sStartTime;
13+
14+
bool platformInit(int width, int height, const char* title) {
15+
if (!SDL_Init(SDL_INIT_VIDEO)) {
16+
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
17+
return false;
18+
}
19+
20+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
21+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
22+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
23+
24+
sWin = SDL_CreateWindow(title, width, height, SDL_WINDOW_OPENGL);
25+
if (!sWin) {
26+
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
27+
SDL_Quit();
28+
return false;
29+
}
30+
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
31+
32+
sCtx = SDL_GL_CreateContext(sWin);
33+
if (!sCtx) {
34+
fprintf(stderr, "ERROR: SDL_GL_CreateContext failed: %s\n", SDL_GetError());
35+
SDL_DestroyWindow(sWin);
36+
SDL_Quit();
37+
return false;
38+
}
39+
40+
SDL_GL_SetSwapInterval(1);
41+
sStartTime = std::chrono::steady_clock::now();
42+
return true;
43+
}
44+
45+
bool platformInitGL() {
46+
#ifndef __APPLE__
47+
glewExperimental = GL_TRUE;
48+
if (glewInit() != GLEW_OK) {
49+
fprintf(stderr, "Failed to initialize GLEW\n");
50+
return false;
51+
}
52+
#endif
53+
return true;
54+
}
55+
56+
double platformGetTime() {
57+
return std::chrono::duration<double>(
58+
std::chrono::steady_clock::now() - sStartTime).count();
59+
}
60+
61+
void platformSwapBuffers() { SDL_GL_SwapWindow(sWin); }
62+
63+
void platformGetPixelDensityScale(float* x, float* y) {
64+
int pw, ph, ww, wh;
65+
SDL_GetWindowSizeInPixels(sWin, &pw, &ph);
66+
SDL_GetWindowSize(sWin, &ww, &wh);
67+
*x = (ww > 0) ? (float)pw / (float)ww : 1.0f;
68+
*y = (wh > 0) ? (float)ph / (float)wh : 1.0f;
69+
}
70+
71+
void platformRunLoop(void (*render)(), void (*shutdown)()) {
72+
bool running = true;
73+
while (running) {
74+
SDL_Event e;
75+
while (SDL_PollEvent(&e)) {
76+
if (e.type == SDL_EVENT_QUIT) running = false;
77+
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
78+
}
79+
if (running) render();
80+
}
81+
shutdown();
82+
SDL_GL_DestroyContext(sCtx);
83+
SDL_DestroyWindow(sWin);
84+
SDL_Quit();
85+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling.
2+
3+
#ifdef __APPLE__
4+
// NOTE: OpenGL is only available on MacOS (no iOS support)
5+
// Including and using anything related to OpenGL on Apple (like <OpenGL/gl3.h>)
6+
// will emit deprecation warnings, unless GL_SILENCE_DEPRECATION is defined
7+
#define GL_SILENCE_DEPRECATION
8+
// NOTE: TracyOpenGL.hpp will not work as expected even on Apple devices that
9+
// support OpenGL, because the OpenGL drivers do not implement ARB_timer_query
10+
// properly (querying GL_TIMESTAMP always resolves to 0). TracyOpenGL.hpp will
11+
// emit a compiler warning, and a Tracy message to the trace/profiler, but the
12+
// program will still run.
13+
#endif
14+
15+
#include "platform/platform.h" // also includes OpenGL headers
16+
17+
#include <tracy/Tracy.hpp>
18+
19+
// NOTE: opt-in toggle for periodic recalibrations during Collect()
20+
#define TRACY_OPENGL_AUTO_CALIBRATION
21+
#include <tracy/TracyOpenGL.hpp>
22+
23+
static const int kWidth = 800;
24+
static const int kHeight = 600;
25+
26+
static GLuint gProgram = 0;
27+
static GLuint gVao = 0;
28+
static GLint gAngleLoc = -1;
29+
30+
// Vertex colors and positions are baked in; rotation is driven by a uniform.
31+
static const char* kVertSrc = R"(
32+
#version 150 core
33+
uniform float uAngle;
34+
const vec2 kPos[3] = vec2[3](
35+
vec2( 0.0, 0.5 ),
36+
vec2(-0.433, -0.25 ),
37+
vec2( 0.433, -0.25 )
38+
);
39+
const vec3 kCol[3] = vec3[3](
40+
vec3(1.0, 0.0, 0.0),
41+
vec3(0.0, 1.0, 0.0),
42+
vec3(0.0, 0.0, 1.0)
43+
);
44+
out vec3 vColor;
45+
void main() {
46+
float c = cos(uAngle);
47+
float s = sin(uAngle);
48+
vec2 p = kPos[gl_VertexID];
49+
gl_Position = vec4(p.x*c - p.y*s, p.x*s + p.y*c, 0.0, 1.0);
50+
vColor = kCol[gl_VertexID];
51+
}
52+
)";
53+
54+
static const char* kFragSrc = R"(
55+
#version 150 core
56+
in vec3 vColor;
57+
out vec4 fragColor;
58+
void main() { fragColor = vec4(vColor, 1.0); }
59+
)";
60+
61+
static GLuint compileShader(GLenum type, const char* src) {
62+
GLuint s = glCreateShader(type);
63+
glShaderSource(s, 1, &src, nullptr);
64+
glCompileShader(s);
65+
GLint ok = 0;
66+
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
67+
if (!ok) {
68+
char log[512];
69+
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
70+
fprintf(stderr, "Shader compile error: %s\n", log);
71+
glDeleteShader(s);
72+
return 0;
73+
}
74+
return s;
75+
}
76+
77+
static int initGL() {
78+
if (!platformInitGL()) return 1;
79+
80+
TracyGpuContext;
81+
TracyGpuContextName("OpenGL", 6);
82+
83+
GLuint vert = compileShader(GL_VERTEX_SHADER, kVertSrc);
84+
GLuint frag = compileShader(GL_FRAGMENT_SHADER, kFragSrc);
85+
if (!vert || !frag) return 1;
86+
87+
gProgram = glCreateProgram();
88+
glAttachShader(gProgram, vert);
89+
glAttachShader(gProgram, frag);
90+
glLinkProgram(gProgram);
91+
glDeleteShader(vert);
92+
glDeleteShader(frag);
93+
94+
GLint ok = 0;
95+
glGetProgramiv(gProgram, GL_LINK_STATUS, &ok);
96+
if (!ok) {
97+
char log[512];
98+
glGetProgramInfoLog(gProgram, sizeof(log), nullptr, log);
99+
fprintf(stderr, "Program link error: %s\n", log);
100+
return 1;
101+
}
102+
103+
gAngleLoc = glGetUniformLocation(gProgram, "uAngle");
104+
105+
// Core profile requires a bound VAO even with no vertex attributes.
106+
glGenVertexArrays(1, &gVao);
107+
glBindVertexArray(gVao);
108+
109+
glClearColor(0.05f, 0.05f, 0.08f, 1.0f);
110+
float scaleX, scaleY;
111+
platformGetPixelDensityScale(&scaleX, &scaleY);
112+
glViewport(0, 0, (int)(kWidth * scaleX), (int)(kHeight * scaleY));
113+
return 0;
114+
}
115+
116+
static void renderFrame() {
117+
ZoneScoped;
118+
119+
glClear(GL_COLOR_BUFFER_BIT);
120+
glUseProgram(gProgram);
121+
122+
{
123+
TracyGpuZone("triangle draw");
124+
glUniform1f(gAngleLoc, (float)platformGetTime());
125+
glDrawArrays(GL_TRIANGLES, 0, 3);
126+
}
127+
128+
platformSwapBuffers();
129+
TracyGpuCollect;
130+
}
131+
132+
static void shutdown() {
133+
fprintf(stderr, "application is shutting down...\n");
134+
glDeleteVertexArrays(1, &gVao);
135+
glDeleteProgram(gProgram);
136+
}
137+
138+
int main() {
139+
if (!platformInit(kWidth, kHeight, "OpenGL Spinning Triangle"))
140+
return 1;
141+
if (initGL() != 0)
142+
return 2;
143+
platformRunLoop(renderFrame, shutdown);
144+
return 0;
145+
}

0 commit comments

Comments
 (0)