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