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