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