aboutsummaryrefslogtreecommitdiffstats
path: root/examples/pugl_shader_demo.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/pugl_shader_demo.c')
-rw-r--r--examples/pugl_shader_demo.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/examples/pugl_shader_demo.c b/examples/pugl_shader_demo.c
new file mode 100644
index 0000000..ac5fe84
--- /dev/null
+++ b/examples/pugl_shader_demo.c
@@ -0,0 +1,426 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_shader_demo.c An example of drawing with OpenGL 3/4.
+
+ This is an example of using OpenGL for pixel-perfect 2D drawing. It uses
+ pixel coordinates for positions and sizes so that things work roughly like a
+ typical 2D graphics API.
+
+ The program draws a bunch of rectangles with borders, using instancing.
+ Each rectangle has origin, size, and fill color attributes, which are shared
+ for all four vertices. On each frame, a single buffer with all the
+ rectangle data is sent to the GPU, and everything is drawn with a single
+ draw call.
+
+ This is not particularly realistic or optimal, but serves as a decent rough
+ benchmark for how much simple geometry you can draw. The number of
+ rectangles can be given on the command line. For reference, it begins to
+ struggle to maintain 60 FPS on my machine (1950x + Vega64) with more than
+ about 100000 rectangles.
+*/
+
+#include "demo_utils.h"
+#include "rects.h"
+#include "shader_utils.h"
+#include "test/test_utils.h"
+
+#include "glad/glad.h"
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const int defaultWidth = 512;
+static const int defaultHeight = 512;
+
+typedef struct
+{
+ mat4 projection;
+} RectUniforms;
+
+typedef struct
+{
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ size_t numRects;
+ Rect* rects;
+ Program drawRect;
+ GLuint vao;
+ GLuint vbo;
+ GLuint instanceVbo;
+ GLuint ibo;
+ unsigned framesDrawn;
+ int glMajorVersion;
+ int glMinorVersion;
+ int quit;
+} PuglTestApp;
+
+static PuglStatus
+setupGl(PuglTestApp* app);
+
+static void
+teardownGl(PuglTestApp* app);
+
+static void
+onConfigure(PuglView* view, double width, double height)
+{
+ (void)view;
+
+ glEnable(GL_BLEND);
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+ glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glViewport(0, 0, (int)width, (int)height);
+}
+
+static void
+onExpose(PuglView* view)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+ const PuglRect frame = puglGetFrame(view);
+ const float width = (float)frame.width;
+ const float height = (float)frame.height;
+ const double time = puglGetTime(puglGetWorld(view));
+
+ // Construct projection matrix for 2D window surface (in pixels)
+ mat4 proj;
+ mat4Ortho(proj,
+ 0.0f,
+ (float)frame.width,
+ 0.0f,
+ (float)frame.height,
+ -1.0f,
+ 1.0f);
+
+ // Clear and bind everything that is the same for every rect
+ glClear(GL_COLOR_BUFFER_BIT);
+ glUseProgram(app->drawRect.program);
+ glBindVertexArray(app->vao);
+
+ for (size_t i = 0; i < app->numRects; ++i) {
+ moveRect(&app->rects[i], i, app->numRects, width, height, time);
+ }
+
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(proj), &proj, GL_STREAM_DRAW);
+
+ glBufferSubData(GL_ARRAY_BUFFER,
+ 0,
+ (GLsizeiptr)(app->numRects * sizeof(Rect)),
+ app->rects);
+
+ glDrawElementsInstanced(GL_TRIANGLE_STRIP,
+ 4,
+ GL_UNSIGNED_INT,
+ NULL,
+ (GLsizei)(app->numRects * 4));
+
+ ++app->framesDrawn;
+}
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ", app->opts.verbose);
+
+ switch (event->type) {
+ case PUGL_CREATE:
+ setupGl(app);
+ break;
+ case PUGL_DESTROY:
+ teardownGl(app);
+ break;
+ case PUGL_CONFIGURE:
+ onConfigure(view, event->configure.width, event->configure.height);
+ break;
+ case PUGL_UPDATE:
+ puglPostRedisplay(view);
+ break;
+ case PUGL_EXPOSE: onExpose(view); break;
+ case PUGL_CLOSE: app->quit = 1; break;
+ case PUGL_KEY_PRESS:
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
+ app->quit = 1;
+ }
+ break;
+ default: break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static Rect*
+makeRects(const size_t numRects)
+{
+ Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
+ for (size_t i = 0; i < numRects; ++i) {
+ rects[i] = makeRect(i, (float)defaultWidth);
+ }
+
+ return rects;
+}
+
+static char*
+loadShader(const char* const path)
+{
+ FILE* const file = fopen(path, "r");
+ if (!file) {
+ logError("Failed to open '%s'\n", path);
+ return NULL;
+ }
+
+ fseek(file, 0, SEEK_END);
+ const size_t fileSize = (size_t)ftell(file);
+
+ fseek(file, 0, SEEK_SET);
+ char* source = (char*)calloc(1, fileSize + 1u);
+
+ fread(source, 1, fileSize, file);
+ fclose(file);
+
+ return source;
+}
+
+static int
+parseOptions(PuglTestApp* app, int argc, char** argv)
+{
+ char* endptr = NULL;
+
+ // Parse command line options
+ app->numRects = 1024;
+ app->opts = puglParseTestOptions(&argc, &argv);
+ if (app->opts.help) {
+ return 1;
+ }
+
+ // Parse number of rectangles argument, if given
+ if (argc >= 1) {
+ app->numRects = (size_t)strtol(argv[0], &endptr, 10);
+ if (endptr != argv[0] + strlen(argv[0])) {
+ logError("Invalid number of rectangles: %s\n", argv[0]);
+ return 1;
+ }
+ }
+
+ // Parse OpenGL major version argument, if given
+ if (argc >= 2) {
+ app->glMajorVersion = (int)strtol(argv[1], &endptr, 10);
+ if (endptr != argv[1] + strlen(argv[1])) {
+ logError("Invalid GL major version: %s\n", argv[1]);
+ return 1;
+ } else if (app->glMajorVersion == 4) {
+ app->glMinorVersion = 2;
+ } else if (app->glMajorVersion != 3) {
+ logError("Unsupported GL major version %d\n", app->glMajorVersion);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+setupPugl(PuglTestApp* app, const PuglRect frame)
+{
+ // Create world, view, and rect data
+ app->world = puglNewWorld(PUGL_PROGRAM, 0);
+ app->view = puglNewView(app->world);
+ app->rects = makeRects(app->numRects);
+
+ // Set up world and view
+ puglSetClassName(app->world, "PuglGL3Demo");
+ puglSetWindowTitle(app->view, "Pugl OpenGL 3");
+ puglSetFrame(app->view, frame);
+ puglSetMinSize(app->view, defaultWidth / 4, defaultHeight / 4);
+ puglSetAspectRatio(app->view, 1, 1, 16, 9);
+ puglSetBackend(app->view, puglGlBackend());
+ puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
+ puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, app->glMajorVersion);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, app->glMinorVersion);
+ puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
+ puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
+ puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);
+ puglSetViewHint(app->view, PUGL_SWAP_INTERVAL, app->opts.sync);
+ puglSetViewHint(app->view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE);
+ puglSetHandle(app->view, app);
+ puglSetEventFunc(app->view, onEvent);
+}
+
+static PuglStatus
+setupGl(PuglTestApp* app)
+{
+ // Load GL functions via GLAD
+ if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) {
+ logError("Failed to load GL\n");
+ return PUGL_FAILURE;
+ }
+
+ const char* const headerFile = (app->glMajorVersion == 3
+ ? "shaders/header_330.glsl"
+ : "shaders/header_420.glsl");
+
+ // Load shader sources
+ char* const headerSource = loadShader(headerFile);
+ char* const vertexSource = loadShader("shaders/rect.vert");
+ char* const fragmentSource = loadShader("shaders/rect.frag");
+ if (!vertexSource || !fragmentSource) {
+ logError("Failed to load shader sources\n");
+ return PUGL_FAILURE;
+ }
+
+ // Compile rectangle shaders and program
+ app->drawRect = compileProgram(headerSource, vertexSource, fragmentSource);
+ free(fragmentSource);
+ free(vertexSource);
+ free(headerSource);
+ if (!app->drawRect.program) {
+ return PUGL_FAILURE;
+ }
+
+ // Get location of rectangle shader uniform block
+ const GLuint globalsIndex = glGetUniformBlockIndex(app->drawRect.program,
+ "UniformBufferObject");
+
+ // Generate/bind a uniform buffer for setting rectangle properties
+ GLuint uboHandle;
+ glGenBuffers(1, &uboHandle);
+ glBindBuffer(GL_UNIFORM_BUFFER, uboHandle);
+ glBindBufferBase(GL_UNIFORM_BUFFER, globalsIndex, uboHandle);
+
+ // Generate/bind a VAO to track state
+ glGenVertexArrays(1, &app->vao);
+ glBindVertexArray(app->vao);
+
+ // Generate/bind a VBO to store vertex position data
+ glGenBuffers(1, &app->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, app->vbo);
+ glBufferData(GL_ARRAY_BUFFER,
+ sizeof(rectVertices),
+ rectVertices,
+ GL_STATIC_DRAW);
+
+ // Attribute 0 is position, 2 floats from the VBO
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL);
+
+ // Generate/bind a VBO to store instance attribute data
+ glGenBuffers(1, &app->instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, app->instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER,
+ (GLsizeiptr)(app->numRects * sizeof(Rect)),
+ app->rects,
+ GL_STREAM_DRAW);
+
+ // Attribute 1 is Rect::position
+ glEnableVertexAttribArray(1);
+ glVertexAttribDivisor(1, 4);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), NULL);
+
+ // Attribute 2 is Rect::size
+ glEnableVertexAttribArray(2);
+ glVertexAttribDivisor(2, 4);
+ glVertexAttribPointer(2,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Rect),
+ (const void*)offsetof(Rect, size));
+
+ // Attribute 3 is Rect::fillColor
+ glEnableVertexAttribArray(3);
+ glVertexAttribDivisor(3, 4);
+ glVertexAttribPointer(3,
+ 4,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Rect),
+ (const void*)offsetof(Rect, fillColor));
+
+ // Set up the IBO to index into the VBO
+ glGenBuffers(1, &app->ibo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ sizeof(rectIndices),
+ rectIndices,
+ GL_STATIC_DRAW);
+
+ return PUGL_SUCCESS;
+}
+
+static void
+teardownGl(PuglTestApp* app)
+{
+ glDeleteBuffers(1, &app->ibo);
+ glDeleteBuffers(1, &app->vbo);
+ glDeleteBuffers(1, &app->instanceVbo);
+ glDeleteVertexArrays(1, &app->vao);
+ deleteProgram(app->drawRect);
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTestApp app = {0};
+
+ app.glMajorVersion = 3;
+ app.glMinorVersion = 3;
+
+ const PuglRect frame = {0, 0, defaultWidth, defaultHeight};
+
+ // Parse command line options
+ if (parseOptions(&app, argc, argv)) {
+ puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
+ return 1;
+ }
+
+ // Create and configure world and view
+ setupPugl(&app, frame);
+
+ // Create window (which will send a PUGL_CREATE event)
+ const PuglStatus st = puglRealize(app.view);
+ if (st) {
+ return logError("Failed to create window (%s)\n", puglStrerror(st));
+ }
+
+ // Show window
+ puglShowWindow(app.view);
+
+ // Grind away, drawing continuously
+ PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)};
+ while (!app.quit) {
+ puglUpdate(app.world, 0.0);
+ puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
+ }
+
+ // Destroy window (which will send a PUGL_DESTROY event)
+ puglFreeView(app.view);
+
+ // Free everything else
+ puglFreeWorld(app.world);
+ free(app.rects);
+
+ return 0;
+}