1 /*
2 Copyright 2012-2020 David Robillard <d@drobilla.net>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /*
18 An example of drawing with OpenGL 3/4.
19
20 This is an example of using OpenGL for pixel-perfect 2D drawing. It uses
21 pixel coordinates for positions and sizes so that things work roughly like a
22 typical 2D graphics API.
23
24 The program draws a bunch of rectangles with borders, using instancing.
25 Each rectangle has origin, size, and fill color attributes, which are shared
26 for all four vertices. On each frame, a single buffer with all the
27 rectangle data is sent to the GPU, and everything is drawn with a single
28 draw call.
29
30 This is not particularly realistic or optimal, but serves as a decent rough
31 benchmark for how much simple geometry you can draw. The number of
32 rectangles can be given on the command line. For reference, it begins to
33 struggle to maintain 60 FPS on my machine (1950x + Vega64) with more than
34 about 100000 rectangles.
35 */
36
37 #include "demo_utils.h"
38 #include "file_utils.h"
39 #include "rects.h"
40 #include "shader_utils.h"
41 #include "test/test_utils.h"
42
43 #include "glad/glad.h"
44
45 #include "pugl/gl.h"
46 #include "pugl/pugl.h"
47
48 #include <math.h>
49 #include <stddef.h>
50 #include <stdint.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54
55 static const int defaultWidth = 512;
56 static const int defaultHeight = 512;
57 static const uintptr_t resizeTimerId = 1u;
58
59 typedef struct {
60 mat4 projection;
61 } RectUniforms;
62
63 typedef struct {
64 const char* programPath;
65 PuglWorld* world;
66 PuglView* view;
67 PuglTestOptions opts;
68 size_t numRects;
69 Rect* rects;
70 Program drawRect;
71 GLuint vao;
72 GLuint vbo;
73 GLuint instanceVbo;
74 GLuint ibo;
75 double lastDrawDuration;
76 double lastFrameEndTime;
77 unsigned framesDrawn;
78 int glMajorVersion;
79 int glMinorVersion;
80 int quit;
81 } PuglTestApp;
82
83 static PuglStatus
84 setupGl(PuglTestApp* app);
85
86 static void
87 teardownGl(PuglTestApp* app);
88
89 static void
onConfigure(PuglView * view,double width,double height)90 onConfigure(PuglView* view, double width, double height)
91 {
92 (void)view;
93
94 glEnable(GL_BLEND);
95 glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
96 glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
97 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
98 glViewport(0, 0, (int)width, (int)height);
99 }
100
101 static void
onExpose(PuglView * view)102 onExpose(PuglView* view)
103 {
104 PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
105 const PuglRect frame = puglGetFrame(view);
106 const float width = (float)frame.width;
107 const float height = (float)frame.height;
108 const double time = puglGetTime(puglGetWorld(view));
109
110 // Construct projection matrix for 2D window surface (in pixels)
111 mat4 proj;
112 mat4Ortho(
113 proj, 0.0f, (float)frame.width, 0.0f, (float)frame.height, -1.0f, 1.0f);
114
115 // Clear and bind everything that is the same for every rect
116 glClear(GL_COLOR_BUFFER_BIT);
117 glUseProgram(app->drawRect.program);
118 glBindVertexArray(app->vao);
119
120 for (size_t i = 0; i < app->numRects; ++i) {
121 moveRect(&app->rects[i], i, app->numRects, width, height, time);
122 }
123
124 glBufferData(GL_UNIFORM_BUFFER, sizeof(proj), &proj, GL_STREAM_DRAW);
125
126 glBufferSubData(
127 GL_ARRAY_BUFFER, 0, (GLsizeiptr)(app->numRects * sizeof(Rect)), app->rects);
128
129 glDrawElementsInstanced(
130 GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, NULL, (GLsizei)(app->numRects * 4));
131
132 ++app->framesDrawn;
133
134 app->lastFrameEndTime = puglGetTime(puglGetWorld(view));
135 app->lastDrawDuration = app->lastFrameEndTime - time;
136 }
137
138 static PuglStatus
onEvent(PuglView * view,const PuglEvent * event)139 onEvent(PuglView* view, const PuglEvent* event)
140 {
141 PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
142
143 printEvent(event, "Event: ", app->opts.verbose);
144
145 switch (event->type) {
146 case PUGL_CREATE:
147 setupGl(app);
148 break;
149 case PUGL_DESTROY:
150 teardownGl(app);
151 break;
152 case PUGL_CONFIGURE:
153 onConfigure(view, event->configure.width, event->configure.height);
154 break;
155 case PUGL_UPDATE:
156 puglPostRedisplay(view);
157 break;
158 case PUGL_EXPOSE:
159 onExpose(view);
160 break;
161 case PUGL_CLOSE:
162 app->quit = 1;
163 break;
164 case PUGL_LOOP_ENTER:
165 puglStartTimer(view,
166 resizeTimerId,
167 1.0 / (double)puglGetViewHint(view, PUGL_REFRESH_RATE));
168 break;
169 case PUGL_LOOP_LEAVE:
170 puglStopTimer(view, resizeTimerId);
171 break;
172 case PUGL_KEY_PRESS:
173 if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
174 app->quit = 1;
175 }
176 break;
177 case PUGL_TIMER:
178 if (event->timer.id == resizeTimerId) {
179 puglPostRedisplay(view);
180 }
181 break;
182 default:
183 break;
184 }
185
186 return PUGL_SUCCESS;
187 }
188
189 static Rect*
makeRects(const size_t numRects)190 makeRects(const size_t numRects)
191 {
192 Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
193 for (size_t i = 0; i < numRects; ++i) {
194 rects[i] = makeRect(i, (float)defaultWidth);
195 }
196
197 return rects;
198 }
199
200 static char*
loadShader(const char * const programPath,const char * const name)201 loadShader(const char* const programPath, const char* const name)
202 {
203 char* const path = resourcePath(programPath, name);
204 fprintf(stderr, "Loading shader %s\n", path);
205
206 FILE* const file = fopen(path, "r");
207 if (!file) {
208 logError("Failed to open '%s'\n", path);
209 return NULL;
210 }
211
212 free(path);
213 fseek(file, 0, SEEK_END);
214 const size_t fileSize = (size_t)ftell(file);
215
216 fseek(file, 0, SEEK_SET);
217 char* source = (char*)calloc(1, fileSize + 1u);
218
219 fread(source, 1, fileSize, file);
220 fclose(file);
221
222 return source;
223 }
224
225 static int
parseOptions(PuglTestApp * app,int argc,char ** argv)226 parseOptions(PuglTestApp* app, int argc, char** argv)
227 {
228 char* endptr = NULL;
229
230 // Parse command line options
231 app->numRects = 1024;
232 app->opts = puglParseTestOptions(&argc, &argv);
233 if (app->opts.help) {
234 return 1;
235 }
236
237 // Parse number of rectangles argument, if given
238 if (argc >= 1) {
239 app->numRects = (size_t)strtol(argv[0], &endptr, 10);
240 if (endptr != argv[0] + strlen(argv[0])) {
241 logError("Invalid number of rectangles: %s\n", argv[0]);
242 return 1;
243 }
244 }
245
246 // Parse OpenGL major version argument, if given
247 if (argc >= 2) {
248 app->glMajorVersion = (int)strtol(argv[1], &endptr, 10);
249 if (endptr != argv[1] + strlen(argv[1])) {
250 logError("Invalid GL major version: %s\n", argv[1]);
251 return 1;
252 }
253
254 if (app->glMajorVersion == 4) {
255 app->glMinorVersion = 2;
256 } else if (app->glMajorVersion != 3) {
257 logError("Unsupported GL major version %d\n", app->glMajorVersion);
258 return 1;
259 }
260 }
261
262 return 0;
263 }
264
265 static void
setupPugl(PuglTestApp * app)266 setupPugl(PuglTestApp* app)
267 {
268 // Create world, view, and rect data
269 app->world = puglNewWorld(PUGL_PROGRAM, 0);
270 app->view = puglNewView(app->world);
271 app->rects = makeRects(app->numRects);
272
273 // Set up world and view
274 puglSetClassName(app->world, "PuglGL3Demo");
275 puglSetWindowTitle(app->view, "Pugl OpenGL 3");
276 puglSetDefaultSize(app->view, defaultWidth, defaultHeight);
277 puglSetMinSize(app->view, defaultWidth / 4, defaultHeight / 4);
278 puglSetMaxSize(app->view, defaultWidth * 4, defaultHeight * 4);
279 puglSetAspectRatio(app->view, 1, 1, 16, 9);
280 puglSetBackend(app->view, puglGlBackend());
281 puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
282 puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
283 puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, app->glMajorVersion);
284 puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, app->glMinorVersion);
285 puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
286 puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
287 puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);
288 puglSetViewHint(app->view, PUGL_SWAP_INTERVAL, app->opts.sync);
289 puglSetViewHint(app->view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE);
290 puglSetHandle(app->view, app);
291 puglSetEventFunc(app->view, onEvent);
292 }
293
294 static PuglStatus
setupGl(PuglTestApp * app)295 setupGl(PuglTestApp* app)
296 {
297 // Load GL functions via GLAD
298 if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) {
299 logError("Failed to load GL\n");
300 return PUGL_FAILURE;
301 }
302
303 const char* const headerFile =
304 (app->glMajorVersion == 3 ? "shaders/header_330.glsl"
305 : "shaders/header_420.glsl");
306
307 // Load shader sources
308 char* const headerSource = loadShader(app->programPath, headerFile);
309
310 char* const vertexSource = loadShader(app->programPath, "shaders/rect.vert");
311
312 char* const fragmentSource =
313 loadShader(app->programPath, "shaders/rect.frag");
314
315 if (!vertexSource || !fragmentSource) {
316 logError("Failed to load shader sources\n");
317 return PUGL_FAILURE;
318 }
319
320 // Compile rectangle shaders and program
321 app->drawRect = compileProgram(headerSource, vertexSource, fragmentSource);
322 free(fragmentSource);
323 free(vertexSource);
324 free(headerSource);
325 if (!app->drawRect.program) {
326 return PUGL_FAILURE;
327 }
328
329 // Get location of rectangle shader uniform block
330 const GLuint globalsIndex =
331 glGetUniformBlockIndex(app->drawRect.program, "UniformBufferObject");
332
333 // Generate/bind a uniform buffer for setting rectangle properties
334 GLuint uboHandle = 0;
335 glGenBuffers(1, &uboHandle);
336 glBindBuffer(GL_UNIFORM_BUFFER, uboHandle);
337 glBindBufferBase(GL_UNIFORM_BUFFER, globalsIndex, uboHandle);
338
339 // Generate/bind a VAO to track state
340 glGenVertexArrays(1, &app->vao);
341 glBindVertexArray(app->vao);
342
343 // Generate/bind a VBO to store vertex position data
344 glGenBuffers(1, &app->vbo);
345 glBindBuffer(GL_ARRAY_BUFFER, app->vbo);
346 glBufferData(
347 GL_ARRAY_BUFFER, sizeof(rectVertices), rectVertices, GL_STATIC_DRAW);
348
349 // Attribute 0 is position, 2 floats from the VBO
350 glEnableVertexAttribArray(0);
351 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL);
352
353 // Generate/bind a VBO to store instance attribute data
354 glGenBuffers(1, &app->instanceVbo);
355 glBindBuffer(GL_ARRAY_BUFFER, app->instanceVbo);
356 glBufferData(GL_ARRAY_BUFFER,
357 (GLsizeiptr)(app->numRects * sizeof(Rect)),
358 app->rects,
359 GL_STREAM_DRAW);
360
361 // Attribute 1 is Rect::position
362 glEnableVertexAttribArray(1);
363 glVertexAttribDivisor(1, 4);
364 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), NULL);
365
366 // Attribute 2 is Rect::size
367 glEnableVertexAttribArray(2);
368 glVertexAttribDivisor(2, 4);
369 glVertexAttribPointer(
370 2, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (const void*)offsetof(Rect, size));
371
372 // Attribute 3 is Rect::fillColor
373 glEnableVertexAttribArray(3);
374 glVertexAttribDivisor(3, 4);
375 glVertexAttribPointer(3,
376 4,
377 GL_FLOAT,
378 GL_FALSE,
379 sizeof(Rect),
380 (const void*)offsetof(Rect, fillColor));
381
382 // Set up the IBO to index into the VBO
383 glGenBuffers(1, &app->ibo);
384 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo);
385 glBufferData(
386 GL_ELEMENT_ARRAY_BUFFER, sizeof(rectIndices), rectIndices, GL_STATIC_DRAW);
387
388 return PUGL_SUCCESS;
389 }
390
391 static void
teardownGl(PuglTestApp * app)392 teardownGl(PuglTestApp* app)
393 {
394 glDeleteBuffers(1, &app->ibo);
395 glDeleteBuffers(1, &app->vbo);
396 glDeleteBuffers(1, &app->instanceVbo);
397 glDeleteVertexArrays(1, &app->vao);
398 deleteProgram(app->drawRect);
399 }
400
401 static double
updateTimeout(const PuglTestApp * const app)402 updateTimeout(const PuglTestApp* const app)
403 {
404 if (!puglGetVisible(app->view)) {
405 return -1.0; // View is invisible (minimized), wait until something happens
406 }
407
408 if (!app->opts.sync) {
409 return 0.0; // VSync explicitly disabled, run as fast as possible
410 }
411
412 /* To minimize input latency and get smooth performance during window
413 resizing, we want to poll for events as long as possible before starting
414 to draw the next frame. This ensures that as many events are consumed as
415 possible before starting to draw, or, equivalently, that the next rendered
416 frame represents the latest events possible. This is particularly
417 important for mouse input and "live" window resizing, where many events
418 tend to pile up within a frame.
419
420 To do this, we keep track of the time when the last frame was finished
421 drawing, and how long it took to expose (and assume this is relatively
422 stable). Then, we can calculate how much time there is from now until the
423 time when we should start drawing to not miss the deadline, and use that
424 as the timeout for puglUpdate().
425 */
426
427 const int refreshRate = puglGetViewHint(app->view, PUGL_REFRESH_RATE);
428 const double now = puglGetTime(app->world);
429 const double nextFrameEndTime = app->lastFrameEndTime + (1.0 / refreshRate);
430 const double nextExposeTime = nextFrameEndTime - app->lastDrawDuration;
431 const double timeUntilNext = nextExposeTime - now;
432
433 return timeUntilNext;
434 }
435
436 int
main(int argc,char ** argv)437 main(int argc, char** argv)
438 {
439 PuglTestApp app = {0};
440
441 app.programPath = argv[0];
442 app.glMajorVersion = 3;
443 app.glMinorVersion = 3;
444
445 // Parse command line options
446 if (parseOptions(&app, argc, argv)) {
447 puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
448 return 1;
449 }
450
451 // Create and configure world and view
452 setupPugl(&app);
453
454 // Create window (which will send a PUGL_CREATE event)
455 const PuglStatus st = puglRealize(app.view);
456 if (st) {
457 return logError("Failed to create window (%s)\n", puglStrerror(st));
458 }
459
460 // Show window
461 printViewHints(app.view);
462 puglShow(app.view);
463
464 // Grind away, drawing continuously
465 const double startTime = puglGetTime(app.world);
466 PuglFpsPrinter fpsPrinter = {startTime};
467 while (!app.quit) {
468 puglUpdate(app.world, fmax(0.0, updateTimeout(&app)));
469 puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
470 }
471
472 // Destroy window (which will send a PUGL_DESTROY event)
473 puglFreeView(app.view);
474
475 // Free everything else
476 puglFreeWorld(app.world);
477 free(app.rects);
478
479 return 0;
480 }
481