From ad39159e3ada8086ad8385226c0361f4ff51f90d Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 7 Dec 2019 21:42:52 +0100 Subject: GL3 Test: Use instancing --- shaders/rect.frag | 13 +++--- shaders/rect.vert | 24 +++++++++-- test/pugl_gl3_test.c | 111 ++++++++++++++++++++++++++++----------------------- 3 files changed, 88 insertions(+), 60 deletions(-) diff --git a/shaders/rect.frag b/shaders/rect.frag index c5dedf9..5e3af9d 100644 --- a/shaders/rect.frag +++ b/shaders/rect.frag @@ -10,10 +10,9 @@ specified precisely in pixels to draw sharp lines. The border width is just hardcoded, but could be made a uniform or vertex attribute easily enough. */ -uniform vec2 u_size; -uniform vec4 u_fillColor; - noperspective in vec2 f_uv; +noperspective in vec2 f_size; +noperspective in vec4 f_fillColor; layout(location = 0) out vec4 FragColor; @@ -22,14 +21,14 @@ main() { const float borderWidth = 2.0; - vec4 borderColor = u_fillColor + vec4(0.0, 0.4, 0.4, 0.0); + vec4 borderColor = f_fillColor + vec4(0.0, 0.4, 0.4, 0.0); float t = step(borderWidth, f_uv[1]); - float r = step(borderWidth, u_size.x - f_uv[0]); - float b = step(borderWidth, u_size.y - f_uv[1]); + float r = step(borderWidth, f_size.x - f_uv[0]); + float b = step(borderWidth, f_size.y - f_uv[1]); float l = step(borderWidth, f_uv[0]); float fillMix = t * r * b * l; float borderMix = 1.0 - fillMix; - vec4 fill = fillMix * u_fillColor; + vec4 fill = fillMix * f_fillColor; vec4 border = borderMix * borderColor; FragColor = fill + border; diff --git a/shaders/rect.vert b/shaders/rect.vert index de74fa7..bf2e951 100644 --- a/shaders/rect.vert +++ b/shaders/rect.vert @@ -3,16 +3,32 @@ /* The vertex shader is trivial, but forwards scaled UV coordinates (in pixels) to the fragment shader for drawing the border. */ -uniform mat4 MVP; -uniform vec2 u_size; +uniform mat4 u_projection; -in vec2 v_position; +layout(location = 0) in vec2 v_position; +layout(location = 1) in vec2 v_origin; +layout(location = 2) in vec2 v_size; +layout(location = 3) in vec4 v_fillColor; noperspective out vec2 f_uv; +noperspective out vec2 f_size; +noperspective out vec4 f_fillColor; void main() { - f_uv = v_position * u_size; + // clang-format off + mat4 m = mat4(v_size[0], 0.0, 0.0, 0.0, + 0.0, v_size[1], 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + v_origin[0], v_origin[1], 0.0, 1.0); + // clang-format on + + mat4 MVP = u_projection * m; + + f_uv = v_position * v_size; + f_size = v_size; + f_fillColor = v_fillColor; + gl_Position = MVP * vec4(v_position, 0.0, 1.0); } diff --git a/test/pugl_gl3_test.c b/test/pugl_gl3_test.c index 95fa54d..abcb977 100644 --- a/test/pugl_gl3_test.c +++ b/test/pugl_gl3_test.c @@ -21,17 +21,17 @@ 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, with one draw call per - rectangle (the shader draws the borders). Rectangle attributes are - controlled via uniform variables. This is certainly not the fastest way to - do this: it is probably CPU and/or I/O bound, but serves as a decent very - rough benchmark for how many draw calls you can get away with. - - A better (if slightly more GPU memory intensive) way to do this would be to - put everything in vertex attributes, jam all the rectangle data into a - single buffer, and draw the whole thing with a single draw call. That way - would probably be GPU bound instead, and show a difference between alpha - blending and depth testing for many overlapped rectangles. + 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 @@ -82,10 +82,9 @@ typedef struct Program drawRect; GLuint vao; GLuint vbo; + GLuint instanceVbo; GLuint ibo; - GLint u_MVP; - GLint u_size; - GLint u_fillColor; + GLint u_projection; unsigned framesDrawn; int quit; } PuglTestApp; @@ -101,33 +100,6 @@ onConfigure(PuglView* view, double width, double height) glViewport(0, 0, (int)width, (int)height); } -static void -drawRect(const PuglTestApp* app, const Rect* rect, mat4 projection) -{ - /* The vertex data is always the same: a normalized rectangle from (0, 0) - to (1, 1). We use the MVP matrix to scale and translate this to the - desired screen coordinates. */ - - // Construct model matrix to scale/translate to screen coordinates - mat4 m; - mat4Identity(m); - mat4Translate(m, rect->pos[0], rect->pos[1], 0); - m[0][0] = rect->size[0]; - m[1][1] = rect->size[1]; - - // Combine them into the final MVP matrix and set uniform - mat4 mvp; - mat4Mul(mvp, projection, m); - glUniformMatrix4fv(app->u_MVP, 1, GL_FALSE, (const GLfloat*)&mvp); - - // Set uniforms for the various rectangle attributes - glUniform2fv(app->u_size, 1, rect->size); - glUniform4fv(app->u_fillColor, 1, rect->fillColor); - - // Draw - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, 0); -} - static void onExpose(PuglView* view) { @@ -149,7 +121,9 @@ onExpose(PuglView* view) glClear(GL_COLOR_BUFFER_BIT); glUseProgram(app->drawRect.program); glBindVertexArray(app->vao); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo); + + // 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]; @@ -165,10 +139,16 @@ onExpose(PuglView* view) (float)(frame.height - rect->size[1] + offset[1]) * (cosf((float)time * rect->size[1] / 64.0f + normal) + 1.0f) / 2.0f; - - drawRect(app, rect, proj); } + glBufferSubData(GL_ARRAY_BUFFER, + 0, + app->numRects * sizeof(Rect), + app->rects); + + glDrawElementsInstanced( + GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, NULL, app->numRects * 4); + ++app->framesDrawn; } @@ -201,7 +181,7 @@ makeRects(const size_t numRects) { const float minSize = (float)defaultWidth / 64.0f; const float maxSize = (float)defaultWidth / 6.0f; - const float boxAlpha = 0.25f; + const float boxAlpha = 0.2f; Rect* rects = (Rect*)calloc(numRects, sizeof(Rect)); for (size_t i = 0; i < numRects; ++i) { @@ -325,9 +305,8 @@ main(int argc, char** argv) } // Get location of rectangle shader uniforms - app.u_MVP = glGetUniformLocation(app.drawRect.program, "MVP"); - app.u_size = glGetUniformLocation(app.drawRect.program, "u_size"); - app.u_fillColor = glGetUniformLocation(app.drawRect.program, "u_fillColor"); + app.u_projection = + glGetUniformLocation(app.drawRect.program, "u_projection"); // Generate/bind a VAO to track state glGenVertexArrays(1, &app.vao); @@ -341,10 +320,43 @@ main(int argc, char** argv) rectVertices, GL_STATIC_DRAW); - // Set up the first/only attribute, position, as 2 floats from the VBO + // 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, + 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); @@ -369,6 +381,7 @@ main(int argc, char** argv) 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); -- cgit v1.2.1