4
$\begingroup$

I have a very basic grasp of computer graphics however I threw it all away when I wrote this simple application that I am using for scientific purposes. I didn't have a lot of time to write it correctly because my "bosses" didn't see the promise of it but now that it is done they have found it quite useful.

The problem is I built it stupidly.

Basically it takes a TON of points stored in a big array and draws them to the screen. The screen can be moved and zoomed.

Instead of using a matrix to accomplish this I am simply passing in the width, height and center of the screen making my average vertex shader look like so

#version 330 core
layout (location = 0) in float ex;
layout (location = 1) in float ey;

uniform float sizeX;
uniform float sizeY;
uniform float centerX;
uniform float centerY;
uniform float size;

out vec4 color;
out float passsize;
void main()
{
    gl_PointSize = size;
    passsize = size;
    float x = (ex - centerX) / (sizeX / 2.0);
    float y = (ey - centerY) / (sizeY / 2.0);
    gl_Position = vec4(x, y, 0.0, 1.0);

    color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

and an average draw call look about like this

glUseProgram(program);
glGenVertexArrays(1, &VAO1);
glBindVertexArray(VAO1);

glGenBuffers(1, &VBO1);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(data1), data1, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);

glGenBuffers(1, &VBO2);
glBindBuffer(GL_ARRAY_BUFFER, VBO2);
glBufferData(GL_ARRAY_BUFFER, sizeof(data2), data2, GL_STATIC_DRAW);
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);

glUniform1f(glGetUniformLocation(program, "centerX"), centerX);
glUniform1f(glGetUniformLocation(program, "centerY"), centerY);
glUniform1f(glGetUniformLocation(program, "sizeX"), scrwidth);
glUniform1f(glGetUniformLocation(program, "sizeY"), scrheight);
glUniform1f(glGetUniformLocation(program, "size"), dotSize);

glDrawArrays(GL_POINTS, 0, dot count);
glfwSwapBuffers(window);
glfwPollEvents();

This works very well but I would love to get rid of the low frame rate this causes when I am drawing a ton of points (generally over 10,000 on my small laptop that doesn't actually have a GPU).

Is there any way I can get the GPU to more intelligently clip points that are going to be off screen?

I don't have a lot of time to change things around so it would be great if it were something simple to achieve.

$\endgroup$
  • 2
    $\begingroup$ What kind of laptop does not have a GPU? $\endgroup$ – joojaa Aug 18 '17 at 4:33
  • 3
    $\begingroup$ You need to profile your code to see where the problem is. Drawing 10,000 points should be no problem for even a low-end GPU. Are you actually uploading the data on every draw call? If so, don't do that. You're using GL_STATIC_DRAW, so it looks like your data is not changing. If that's the case, upload it once and draw it on every frame. $\endgroup$ – user1118321 Aug 18 '17 at 5:15
  • 2
    $\begingroup$ Are you creating a new vertex buffer each frame? If so it would be more efficient to create it only once, then update its content when it changes. Nowadays, even a low end laptop with an integrated chipset can draw hundreds of thousands of points at a good framerate. $\endgroup$ – Julien Guertault Aug 18 '17 at 7:49
  • $\begingroup$ @joojaa I assume he means it has an Intel integrated GPU instead of a beefier off-chip one. $\endgroup$ – Dan Hulme Aug 18 '17 at 8:27
  • 1
    $\begingroup$ @DanHulme Thats my point its still a GPU! You will have hard timefinding laptops with really no GPU. $\endgroup$ – joojaa Aug 18 '17 at 9:56
8
$\begingroup$

You only need to create the VBOs and VAO once and then you can reuse them every frame.

void init(){

    glGenVertexArrays(1, &VAO1);
    glBindVertexArray(VAO1);

    glGenBuffers(1, &VBO1);
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data1), data1, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &VBO2);
    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data2), data2, GL_STATIC_DRAW);
    glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(1);

    uniformLoc_centerX = glGetUniformLocation(program, "centerX");
    uniformLoc_centerY = glGetUniformLocation(program, "centerY");
    uniformLoc_sizeX = glGetUniformLocation(program, "sizeX");
    uniformLoc_sizeY = glGetUniformLocation(program, "sizeY");
    uniformLoc_size= glGetUniformLocation(program, "size");
}

void draw(){
    glUseProgram(program);
    glBindVertexArray(VAO1);

    glUniform1f(uniformLoc_centerX, centerX);
    glUniform1f(uniformLoc_centerY, centerY);
    glUniform1f(uniformLoc_sizeX, scrwidth);
    glUniform1f(uniformLoc_sizeY, scrheight);
    glUniform1f(uniformLoc_size, dotSize);

    glDrawArrays(GL_POINTS, 0, dot count);
}

If the data ever changes then all you need to do is:

glGenBuffers(1, &VBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(data1), data1, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, VBO2);
glBufferData(GL_ARRAY_BUFFER, sizeof(data2), data2, GL_STATIC_DRAW);
$\endgroup$
2
$\begingroup$

To start with, you should (almost) never have a glGen*() call in your render loop. Do that during initialization only.

It looks like your vertex data isn't changing. Don't re-set the buffer data, just send it once during initialization.

For the application you've described, about the only thing that should be in your render loop is bindBuffer, setting your uniforms, clear, draw and present.

Fixing those issues will likely have a significant perf impact. If that still isn't enough, look at spatial partitioning and frustum culling - in 2D, your frustum is just going to be a box - to more intelligently clip your data CPU-side. Google "quad trees" to get started.

Also, depending on the use case, you MIGHT be better off just doing this in Unity or another off-the-shelf engine. They already provide much of this functionality. (However, I'm all for roll-your-own for the fun factor, and a full engine may be overkill for this.)

And never forget: the profiler is your friend. Without it, everything else is just professional guessing.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.