Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Noticable lag in a simple OpenGL program with mouse input through GLFW

Tags:

opengl

glfw

Here's a simple program that draws a triangle following the mouse cursor's position.

What I (and hopefully you) can notice, is that the triangle lags behind the cursor, it's not as tight as when dragging around even a whole window.

So my question is: What am I doing wrong? What leads to this lag?

One thing I realize is that it would suffice to shift the actual pixel values of the triangle, and not rasterize it again and again. But is rasterizing this one triangle really that expensive? I also tried using glTranslate instead of drawing at varying coordinates, but no improvement on the lag resulted. So I hope you can enlighten me on how to draw this efficiently.

#include <GLFW/glfw3.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

float x = 0.0f;
float y = 0.0f;

static void error_callback(int error, const char* description)
{
    fputs(description, stderr);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

static void cursor_callback(GLFWwindow *window, double xpos, double ypos)
{
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    float ratio = width / (float) height;
    x = ratio*(2*xpos/(float)width - 1);
    y = 2*-ypos/(float)height + 1;
}

int main(void)
{
    GLFWwindow* window;
    glfwSetErrorCallback(error_callback);
    if (!glfwInit())
        exit(EXIT_FAILURE);
    window = glfwCreateWindow(640, 480, "Following Triangle", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwMakeContextCurrent(window);
    // Callbacks
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, cursor_callback);
    // geometry for the equal sided triangle
    float r = 0.1f; // outer circle radius
    float u = r * sin(M_PI_2/3.0f);
    float l = 2.0f * r * cos(M_PI_2/3.0f);

    while (!glfwWindowShouldClose(window))
    {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        float ratio = width / (float) height;
        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-ratio, ratio, -1.0f, 1.0f, 1.f, -1.f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glBegin(GL_TRIANGLES);
            glColor3f(1.f, 0.f, 0.f);
            glVertex3f(x+0, y+r, 0.f);
            glColor3f(0.f, 1.f, 0.f);
            glVertex3f(x-l/2.0f, y-u, 0.f);
            glColor3f(0.f, 0.f, 1.f);
            glVertex3f(x+l/2.0f, y-u, 0.f);
        glEnd();

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}
like image 658
Phantrast Avatar asked Sep 30 '13 19:09

Phantrast


4 Answers

So yeah the usual approach to synchronising rendering to frame rate is with SwapInterval(1) and you weren't doing that, but that's not where the lag is coming from. In fact, typically with SwapInterval set to 0, you get less lag than with it set to 1, so I suspect it was actually set to 1 the whole time. So, I'm writing the rest of this supposing SwapInterval was set to 1.

You are seeing this lag for two independent reasons.

reason 0: your code is impatient.

Glossing over some details, the naïve method of rendering in OpenGL is with a loop like this:

while (!exitCondition()) {
    pollEvents();
    render();
    swap();
}

This loop is run once per frame. If render() is fast, most of the time is spent at swap(). swap() doesn't actually send out the new frame, though, until the moment it returns. New mouse and keyboard events may happen that entire time, but they will have no effect until next frame. This means the mouse and keyboard information is already one to two frames old by the time it reaches the screen. For better latency, you should not be polling events and rendering immediately after swap() returns. Wait for as many new events as possible, render, then send the frame just in time for it to be displayed. As little time as possible should be spent waiting for swap(). This can be achieved by adding a delay to the loop. Suppose tFrame is the amount of time between frames (1s/60 .= 16.67ms for a 60Hz screen) and tRender is an amount of time that is usually greater than the amount of time render() takes to run. The loop with the latency delay would look like this:

while(!exitCondition()) {
    sleep(tFrame - tRender);
    pollEvents();
    render();
    swap();
}

Patience is a virtue in computing too, it turns out.

reason 1: glfwSwapBuffers() does not behave like you would expect.

A newcomer would expect glfwSwapBuffers() to wait until the vsync, then send the newly-rendered frame to the screen and return, something like the swap() function I used in reason 0. What it actually does, effectively, is send the previously-rendered frame to the screen and return, leaving you with a whole frame of delay. In order to fix this, you have to obtain a separate means of synchronising your rendering, because OpenGL's mechanism isn't good enough. Such a mechanism is platform-specific. Wayland has such a mechanism, and it's called presentation-time. GLFW doesn't currently support this, but I was bothered so much by this synchronisation problem that I added it. Here's the result:

opengl rendering synchronised to system cursor

As you can see, it really is possible to synchronise rendering to the system cursor. It's just really hard.

like image 138
enigmaticPhysicist Avatar answered Nov 20 '22 07:11

enigmaticPhysicist


Your updates are purely event driven. Try replacing glfwPollEvents with glfwWaitEvents. I would then reimplement glfwSwapInterval(1). You gain nothing by updating more frequently than the monitor refresh rate - just tearing and burning through cycles.

like image 23
Brett Hale Avatar answered Nov 20 '22 07:11

Brett Hale


While the solution from enigmaticPhysicist is the best we can achieve from glfw with Wayland.

Since glfw 3.2 i was able to get smooth mouse experience by disabling vsync and manually tuning the correct timeout for

// Waits with timeout until events are queued and processes them.
GLFWAPI void glfwWaitEventsTimeout(double timeout);

Replace the usage of greedy glfwPollEvents with something like glfwWaitEventsTimeout(0.007)...

like image 1
uptoyou Avatar answered Nov 20 '22 07:11

uptoyou


I found that placing glFinish() call right after swapping buffer eliminates the lag. It is strange, because AFAIK swapping buffers must wait for OpenGL commands to complete, but nevertheless, this trick helps for whatever reason.

like image 1
igagis Avatar answered Nov 20 '22 08:11

igagis