diff options
Diffstat (limited to 'examples/pugl_gl3_demo.c')
-rw-r--r-- | examples/pugl_gl3_demo.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/examples/pugl_gl3_demo.c b/examples/pugl_gl3_demo.c new file mode 100644 index 0000000..52c5dd3 --- /dev/null +++ b/examples/pugl_gl3_demo.c @@ -0,0 +1,398 @@ +/* + Copyright 2012-2019 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_gl3_demo.c An example of drawing with OpenGL 3. + + 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. +*/ + +#define GL_SILENCE_DEPRECATION 1 + +#include "demo_utils.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 <math.h> +#include <stdbool.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 +{ + float pos[2]; + float size[2]; + float fillColor[4]; +} Rect; + +// clang-format off +static const GLfloat rectVertices[] = { + 0.0f, 0.0f, // TL + 1.0f, 0.0f, // TR + 0.0f, 1.0f, // BL + 1.0f, 1.0f, // BR +}; +// clang-format on + +static const GLuint rectIndices[4] = {0, 1, 2, 3}; + +typedef struct +{ + PuglTestOptions opts; + PuglWorld* world; + PuglView* view; + size_t numRects; + Rect* rects; + Program drawRect; + GLuint vao; + GLuint vbo; + GLuint instanceVbo; + GLuint ibo; + GLint u_projection; + unsigned framesDrawn; + int quit; +} PuglTestApp; + +static void +onConfigure(PuglView* view, double width, double height) +{ + (void)view; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + 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 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); + + // Set projection matrix uniform + glUniformMatrix4fv(app->u_projection, 1, GL_FALSE, (const GLfloat*)&proj); + + for (size_t i = 0; i < app->numRects; ++i) { + Rect* rect = &app->rects[i]; + const float normal = i / (float)app->numRects; + const float offset[2] = {normal * 128.0f, normal * 128.0f}; + + // Move rect around in an arbitrary way that looks cool + rect->pos[0] = + (float)(frame.width - rect->size[0] + offset[0]) * + (sinf((float)time * rect->size[0] / 64.0f + normal) + 1.0f) / + 2.0f; + rect->pos[1] = + (float)(frame.height - rect->size[1] + offset[1]) * + (cosf((float)time * rect->size[1] / 64.0f + normal) + 1.0f) / + 2.0f; + } + + 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_CONFIGURE: + onConfigure(view, event->configure.width, event->configure.height); + 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) +{ + const float minSize = (float)defaultWidth / 64.0f; + const float maxSize = (float)defaultWidth / 6.0f; + const float boxAlpha = 0.2f; + + Rect* rects = (Rect*)calloc(numRects, sizeof(Rect)); + for (size_t i = 0; i < numRects; ++i) { + const float s = (sinf((float)i) / 2.0f + 0.5f); + const float c = (cosf((float)i) / 2.0f + 0.5f); + + rects[i].size[0] = minSize + s * maxSize; + rects[i].size[1] = minSize + c * maxSize; + rects[i].fillColor[1] = s / 2.0f + 0.25f; + rects[i].fillColor[2] = c / 2.0f + 0.25f; + rects[i].fillColor[3] = boxAlpha; + } + + 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; +} + +int +main(int argc, char** argv) +{ + PuglTestApp app; + memset(&app, 0, sizeof(app)); + + const PuglRect frame = {0, 0, defaultWidth, defaultHeight}; + + // Parse command line options + app.numRects = 1024; + app.opts = puglParseTestOptions(&argc, &argv); + if (app.opts.help) { + puglPrintTestUsage("pugl_gl3_test", "[NUM_RECTS]"); + return 1; + } + + // Parse number of rectangles argument, if given + if (argc == 1) { + char* endptr = NULL; + app.numRects = (size_t)strtol(argv[0], &endptr, 10); + if (endptr != argv[0] + strlen(argv[0])) { + puglPrintTestUsage("pugl_gl3_test", "[NUM_RECTS]"); + return 1; + } + } + + // Create world, view, and rect data + app.world = puglNewWorld(); + app.view = puglNewView(app.world); + app.rects = makeRects(app.numRects); + + // Set up world and view + puglSetClassName(app.world, "PuglGL3Test"); + 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, 3); + puglSetViewHint(app.view, PUGL_CONTEXT_VERSION_MINOR, 3); + 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.doubleBuffer); + puglSetViewHint(app.view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE); + puglSetHandle(app.view, &app); + puglSetEventFunc(app.view, onEvent); + + const PuglStatus st = puglCreateWindow(app.view, "Pugl OpenGL 3"); + if (st) { + return logError("Failed to create window (%s)\n", puglStrerror(st)); + } + + // Enter context to set up GL stuff + puglEnterContext(app.view, false); + + // Load GL functions via GLAD + if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) { + logError("Failed to load GL\n"); + puglFreeView(app.view); + puglFreeWorld(app.world); + return 1; + } + + // Load shader sources + char* const vertexSource = loadShader("shaders/rect.vert"); + char* const fragmentSource = loadShader("shaders/rect.frag"); + if (!vertexSource || !fragmentSource) { + logError("Failed to load shader sources\n"); + puglFreeView(app.view); + puglFreeWorld(app.world); + return 1; + } + + // Compile rectangle shaders and program + app.drawRect = compileProgram(vertexSource, fragmentSource); + free(fragmentSource); + free(vertexSource); + if (!app.drawRect.program) { + puglFreeView(app.view); + puglFreeWorld(app.world); + return 1; + } + + // Get location of rectangle shader uniforms + app.u_projection = + glGetUniformLocation(app.drawRect.program, "u_projection"); + + // 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); + + // Finally ready to go, leave GL context and show the window + puglLeaveContext(app.view, false); + puglShowWindow(app.view); + + // Grind away, drawing continuously + PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)}; + while (!app.quit) { + puglPostRedisplay(app.view); + puglDispatchEvents(app.world); + puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn); + } + + // Delete GL stuff + puglEnterContext(app.view, false); + glDeleteBuffers(1, &app.ibo); + glDeleteBuffers(1, &app.vbo); + glDeleteBuffers(1, &app.instanceVbo); + glDeleteVertexArrays(1, &app.vao); + deleteProgram(app.drawRect); + puglLeaveContext(app.view, false); + + // Tear down view and world + puglFreeView(app.view); + puglFreeWorld(app.world); + return 0; +} |