patterncModerate
OpenGL Instanced Rendering
Viewed 0 times
renderinginstancedopengl
Problem
I have a very basic OpenGL instanced rendering setup, which is compiling and running, however it is super slow, and even though I spent days of asking and reading how to fix it, I still have no clue, what causes the problem..
What does slow mean? At the moment as you can see, it draws
But if I start scaling the window, up to the actual size of my monitor (2560 * 1440) the FPS drops down to 1. I expect at least half a million vertices rendered at 60FPS, that would be the goal.
The setup is very simple, I use GLFW to create the window, GLEW to setup OpenGL properly. So it looks something like this:
Now, the functions from the above pseudo snippet are here, they are in the
```
#include // srand(), rand()
#include // malloc(), free()
#include // time()
#include // fprintf()
#include // GL*
/----------------------------------------------------------------------------/
typedef struct resources
{
GLuint vs_id;
GLuint fs_id;
GLuint program_id;
GLuint coords_pos;
GLuint offset_pos;
GLuint colour_pos;
GLuint colour_buffer_id;
GLuint offset_buffer_id;
} Resources;
/----------------------------------------------------------------------------/
const char *vert_shader = " \
#version 150 core \n\
\n\
in vec2 coords; \n\
in vec2 offset;
What does slow mean? At the moment as you can see, it draws
16 000 instances (48 000 vertices) @ 512*512px resolution / 38-43 FPSBut if I start scaling the window, up to the actual size of my monitor (2560 * 1440) the FPS drops down to 1. I expect at least half a million vertices rendered at 60FPS, that would be the goal.
The setup is very simple, I use GLFW to create the window, GLEW to setup OpenGL properly. So it looks something like this:
int main(void)
{
// ... init window and context
void *resources = setup();
// ... start of event loop
{
// ... clear, get buff data, viewport
draw(resources);
// ... swap buffs, poll events
}
cleanup(resources);
// ... clean up everything
return 0;
}Now, the functions from the above pseudo snippet are here, they are in the
instrender.c file. This is where the actual drawing happening:```
#include // srand(), rand()
#include // malloc(), free()
#include // time()
#include // fprintf()
#include // GL*
/----------------------------------------------------------------------------/
typedef struct resources
{
GLuint vs_id;
GLuint fs_id;
GLuint program_id;
GLuint coords_pos;
GLuint offset_pos;
GLuint colour_pos;
GLuint colour_buffer_id;
GLuint offset_buffer_id;
} Resources;
/----------------------------------------------------------------------------/
const char *vert_shader = " \
#version 150 core \n\
\n\
in vec2 coords; \n\
in vec2 offset;
Solution
Disclaimer: This is the first time I have ever really looked into using OpenGL
Keep in mind that my review may contain code that is not fully included in the question (such as the simplified
Bugs
-
I found that when I tried to resize the window, I would always crash the program.
That wasn't very fun, so I set out to fix that first and foremost. It was rather easy to fix, all I had to do was add a frame buffer sizing callback, and set it to the window with
The frame buffer callback function:
The modified
This doesn't fully fix the crashing, occasionally the program will still misbehave; however, at least I am able to scale the window now more consistently.
-
When I undefined
Optimization
-
Running my own profiling tests for a longer duration, I came up with the following data.
As we can see,
-
There are two ways to process pending events.
If instead you only need to update your rendering once you have received new input,
Keep in mind that my review may contain code that is not fully included in the question (such as the simplified
main() function).Bugs
-
I found that when I tried to resize the window, I would always crash the program.
That wasn't very fun, so I set out to fix that first and foremost. It was rather easy to fix, all I had to do was add a frame buffer sizing callback, and set it to the window with
glfwSetFramebufferSizeCallback().The frame buffer callback function:
static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height); // reset the viewport
glMatrixMode(GL_PROJECTION); // modify the projection matrix
glLoadIdentity(); // load an identity matrix into the projection matrix
glOrtho(0, width, 0, height, -1.0, 1.0); // create new projection matrix
/// Important!!! You need to switch back to the model-view matrix
/// or else your OpenGL calls are modifying the projection matrix!
glMatrixMode(GL_MODELVIEW); // return to the model matrix
glLoadIdentity(); // load an identity matrix into the model-view matrix
// OpenGL has now compensated for the resized window, and is ready to draw again.
}The modified
run() function:void
run(GLFWwindow *window)
{
void *resources = setup();
glfwSetWindowUserPointer(window, resources);
#ifdef MEASURE
glfwSwapInterval(0);
#endif
int viewport_width, viewport_height;
// set for proper resizing of window and viewport
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
while (!glfwWindowShouldClose(window))
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwGetFramebufferSize(window, &viewport_width, &viewport_height);
framebuffer_size_callback(window, viewport_width, viewport_height);
draw(resources);
glfwSwapBuffers(window);
glfwPollEvents();
#ifdef MEASURE
printfps();
#endif
}
cleanup(resources);
}This doesn't fully fix the crashing, occasionally the program will still misbehave; however, at least I am able to scale the window now more consistently.
-
When I undefined
OVERLAPPING_OFF to benchmark the code on my computer, I got some errors of an unknown type name offset. Looking at your code, you defined offset only if OVERLAPPING_OFF is defined but then try to use it if it isn't defined as well. The fix was simple enough, I just moved the declaration of offset outside of the #ifdef OVERLAPPING_OFF.// Instance offsets
int i;
GLfloat offset[OFFSET_COUNT];
#ifdef OVERLAPPING_OFF
int j, idx=0;
GLfloat x, y;
for (i=0; i<DIMi; i++)
{
y = (GLfloat)i * STEP;
for (j=0; j<DIMi; j+=2)
{
offset[idx++] = x = (GLfloat)j * STEP; // x
offset[idx++] = y; // y
}
}
#else
for (i=0; i<OFFSET_COUNT; i++)
offset[i] = genc();
#endifOptimization
-
Running my own profiling tests for a longer duration, I came up with the following data.
As we can see,
glDrawElementsInstanced() now only takes up 2% of the total run time. The big time hogs are CGLFlushDrawable and _glfwPlatformPollEvents.-
There are two ways to process pending events.
glfwPollEvents() processes only those events that have already been received and then returns immediately. This is the best choice when rendering continually, like most games do.If instead you only need to update your rendering once you have received new input,
glfwWaitEvents() is a better choice. It waits until at least one event has been received, putting the thread to sleep in the meantime, and then processes all received events just like glfwPollEvents() does. This saves a great deal of CPU cycles and is useful for, for example, many kinds of editing tools.Code Snippets
static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height); // reset the viewport
glMatrixMode(GL_PROJECTION); // modify the projection matrix
glLoadIdentity(); // load an identity matrix into the projection matrix
glOrtho(0, width, 0, height, -1.0, 1.0); // create new projection matrix
/// Important!!! You need to switch back to the model-view matrix
/// or else your OpenGL calls are modifying the projection matrix!
glMatrixMode(GL_MODELVIEW); // return to the model matrix
glLoadIdentity(); // load an identity matrix into the model-view matrix
// OpenGL has now compensated for the resized window, and is ready to draw again.
}void
run(GLFWwindow *window)
{
void *resources = setup();
glfwSetWindowUserPointer(window, resources);
#ifdef MEASURE
glfwSwapInterval(0);
#endif
int viewport_width, viewport_height;
// set for proper resizing of window and viewport
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
while (!glfwWindowShouldClose(window))
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glfwGetFramebufferSize(window, &viewport_width, &viewport_height);
framebuffer_size_callback(window, viewport_width, viewport_height);
draw(resources);
glfwSwapBuffers(window);
glfwPollEvents();
#ifdef MEASURE
printfps();
#endif
}
cleanup(resources);
}// Instance offsets
int i;
GLfloat offset[OFFSET_COUNT];
#ifdef OVERLAPPING_OFF
int j, idx=0;
GLfloat x, y;
for (i=0; i<DIMi; i++)
{
y = (GLfloat)i * STEP;
for (j=0; j<DIMi; j+=2)
{
offset[idx++] = x = (GLfloat)j * STEP; // x
offset[idx++] = y; // y
}
}
#else
for (i=0; i<OFFSET_COUNT; i++)
offset[i] = genc();
#endifContext
StackExchange Code Review Q#51654, answer score: 10
Revisions (0)
No revisions yet.