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 puglSetAspectRatio(app->view, 1, 1, 16, 9);
279 puglSetBackend(app->view, puglGlBackend());
280 puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
281 puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
282 puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, app->glMajorVersion);
283 puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, app->glMinorVersion);
284 puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
285 puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
286 puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);
287 puglSetViewHint(app->view, PUGL_SWAP_INTERVAL, app->opts.sync);
288 puglSetViewHint(app->view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE);
289 puglSetHandle(app->view, app);
290 puglSetEventFunc(app->view, onEvent);
291 }
292
293 static PuglStatus
setupGl(PuglTestApp * app)294 setupGl(PuglTestApp* app)
295 {
296 // Load GL functions via GLAD
297 if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) {
298 logError("Failed to load GL\n");
299 return PUGL_FAILURE;
300 }
301
302 const char* const headerFile =
303 (app->glMajorVersion == 3 ? "shaders/header_330.glsl"
304 : "shaders/header_420.glsl");
305
306 // Load shader sources
307 char* const headerSource = loadShader(app->programPath, headerFile);
308
309 char* const vertexSource = loadShader(app->programPath, "shaders/rect.vert");
310
311 char* const fragmentSource =
312 loadShader(app->programPath, "shaders/rect.frag");
313
314 if (!vertexSource || !fragmentSource) {
315 logError("Failed to load shader sources\n");
316 return PUGL_FAILURE;
317 }
318
319 // Compile rectangle shaders and program
320 app->drawRect = compileProgram(headerSource, vertexSource, fragmentSource);
321 free(fragmentSource);
322 free(vertexSource);
323 free(headerSource);
324 if (!app->drawRect.program) {
325 return PUGL_FAILURE;
326 }
327
328 // Get location of rectangle shader uniform block
329 const GLuint globalsIndex =
330 glGetUniformBlockIndex(app->drawRect.program, "UniformBufferObject");
331
332 // Generate/bind a uniform buffer for setting rectangle properties
333 GLuint uboHandle = 0;
334 glGenBuffers(1, &uboHandle);
335 glBindBuffer(GL_UNIFORM_BUFFER, uboHandle);
336 glBindBufferBase(GL_UNIFORM_BUFFER, globalsIndex, uboHandle);
337
338 // Generate/bind a VAO to track state
339 glGenVertexArrays(1, &app->vao);
340 glBindVertexArray(app->vao);
341
342 // Generate/bind a VBO to store vertex position data
343 glGenBuffers(1, &app->vbo);
344 glBindBuffer(GL_ARRAY_BUFFER, app->vbo);
345 glBufferData(
346 GL_ARRAY_BUFFER, sizeof(rectVertices), rectVertices, GL_STATIC_DRAW);
347
348 // Attribute 0 is position, 2 floats from the VBO
349 glEnableVertexAttribArray(0);
350 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL);
351
352 // Generate/bind a VBO to store instance attribute data
353 glGenBuffers(1, &app->instanceVbo);
354 glBindBuffer(GL_ARRAY_BUFFER, app->instanceVbo);
355 glBufferData(GL_ARRAY_BUFFER,
356 (GLsizeiptr)(app->numRects * sizeof(Rect)),
357 app->rects,
358 GL_STREAM_DRAW);
359
360 // Attribute 1 is Rect::position
361 glEnableVertexAttribArray(1);
362 glVertexAttribDivisor(1, 4);
363 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), NULL);
364
365 // Attribute 2 is Rect::size
366 glEnableVertexAttribArray(2);
367 glVertexAttribDivisor(2, 4);
368 glVertexAttribPointer(
369 2, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (const void*)offsetof(Rect, size));
370
371 // Attribute 3 is Rect::fillColor
372 glEnableVertexAttribArray(3);
373 glVertexAttribDivisor(3, 4);
374 glVertexAttribPointer(3,
375 4,
376 GL_FLOAT,
377 GL_FALSE,
378 sizeof(Rect),
379 (const void*)offsetof(Rect, fillColor));
380
381 // Set up the IBO to index into the VBO
382 glGenBuffers(1, &app->ibo);
383 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo);
384 glBufferData(
385 GL_ELEMENT_ARRAY_BUFFER, sizeof(rectIndices), rectIndices, GL_STATIC_DRAW);
386
387 return PUGL_SUCCESS;
388 }
389
390 static void
teardownGl(PuglTestApp * app)391 teardownGl(PuglTestApp* app)
392 {
393 glDeleteBuffers(1, &app->ibo);
394 glDeleteBuffers(1, &app->vbo);
395 glDeleteBuffers(1, &app->instanceVbo);
396 glDeleteVertexArrays(1, &app->vao);
397 deleteProgram(app->drawRect);
398 }
399
400 int
main(int argc,char ** argv)401 main(int argc, char** argv)
402 {
403 PuglTestApp app = {0};
404
405 app.programPath = argv[0];
406 app.glMajorVersion = 3;
407 app.glMinorVersion = 3;
408
409 // Parse command line options
410 if (parseOptions(&app, argc, argv)) {
411 puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
412 return 1;
413 }
414
415 // Create and configure world and view
416 setupPugl(&app);
417
418 // Create window (which will send a PUGL_CREATE event)
419 const PuglStatus st = puglRealize(app.view);
420 if (st) {
421 return logError("Failed to create window (%s)\n", puglStrerror(st));
422 }
423
424 // Show window
425 printViewHints(app.view);
426 puglShow(app.view);
427
428 // Calculate ideal frame duration to drive the main loop at a good rate
429 const int refreshRate = puglGetViewHint(app.view, PUGL_REFRESH_RATE);
430 const double frameDuration = 1.0 / (double)refreshRate;
431
432 // Grind away, drawing continuously
433 const double startTime = puglGetTime(app.world);
434 PuglFpsPrinter fpsPrinter = {startTime};
435 while (!app.quit) {
436 /* To minimize input latency and get smooth performance during window
437 resizing, we want to poll for events as long as possible before
438 starting to draw the next frame. This ensures that as many events
439 are consumed as possible before starting to draw, or, equivalently,
440 that the next rendered frame represents the latest events possible.
441 This is particularly important for mouse input and "live" window
442 resizing, where many events tend to pile up within a frame.
443
444 To do this, we keep track of the time when the last frame was
445 finished drawing, and how long it took to expose (and assume this is
446 relatively stable). Then, we can calculate how much time there is
447 from now until the time when we should start drawing to not miss the
448 deadline, and use that as the timeout for puglUpdate().
449 */
450
451 const double now = puglGetTime(app.world);
452 const double nextFrameEndTime = app.lastFrameEndTime + frameDuration;
453 const double nextExposeTime = nextFrameEndTime - app.lastDrawDuration;
454 const double timeUntilNext = nextExposeTime - now;
455 const double timeout = app.opts.sync ? timeUntilNext : 0.0;
456
457 puglUpdate(app.world, fmax(0.0, timeout));
458 puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
459 }
460
461 // Destroy window (which will send a PUGL_DESTROY event)
462 puglFreeView(app.view);
463
464 // Free everything else
465 puglFreeWorld(app.world);
466 free(app.rects);
467
468 return 0;
469 }
470