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 int
main(int argc,char ** argv)402 main(int argc, char** argv)
403 {
404 PuglTestApp app = {0};
405
406 app.programPath = argv[0];
407 app.glMajorVersion = 3;
408 app.glMinorVersion = 3;
409
410 // Parse command line options
411 if (parseOptions(&app, argc, argv)) {
412 puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
413 return 1;
414 }
415
416 // Create and configure world and view
417 setupPugl(&app);
418
419 // Create window (which will send a PUGL_CREATE event)
420 const PuglStatus st = puglRealize(app.view);
421 if (st) {
422 return logError("Failed to create window (%s)\n", puglStrerror(st));
423 }
424
425 // Show window
426 printViewHints(app.view);
427 puglShow(app.view);
428
429 // Calculate ideal frame duration to drive the main loop at a good rate
430 const int refreshRate = puglGetViewHint(app.view, PUGL_REFRESH_RATE);
431 const double frameDuration = 1.0 / (double)refreshRate;
432
433 // Grind away, drawing continuously
434 const double startTime = puglGetTime(app.world);
435 PuglFpsPrinter fpsPrinter = {startTime};
436 while (!app.quit) {
437 /* To minimize input latency and get smooth performance during window
438 resizing, we want to poll for events as long as possible before
439 starting to draw the next frame. This ensures that as many events
440 are consumed as possible before starting to draw, or, equivalently,
441 that the next rendered frame represents the latest events possible.
442 This is particularly important for mouse input and "live" window
443 resizing, where many events tend to pile up within a frame.
444
445 To do this, we keep track of the time when the last frame was
446 finished drawing, and how long it took to expose (and assume this is
447 relatively stable). Then, we can calculate how much time there is
448 from now until the time when we should start drawing to not miss the
449 deadline, and use that as the timeout for puglUpdate().
450 */
451
452 const double now = puglGetTime(app.world);
453 const double nextFrameEndTime = app.lastFrameEndTime + frameDuration;
454 const double nextExposeTime = nextFrameEndTime - app.lastDrawDuration;
455 const double timeUntilNext = nextExposeTime - now;
456 const double timeout = app.opts.sync ? timeUntilNext : 0.0;
457
458 puglUpdate(app.world, fmax(0.0, timeout));
459 puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
460 }
461
462 // Destroy window (which will send a PUGL_DESTROY event)
463 puglFreeView(app.view);
464
465 // Free everything else
466 puglFreeWorld(app.world);
467 free(app.rects);
468
469 return 0;
470 }
471