最小化GL桌面应用程序上的鼠标输入延迟?

时间:2017-05-06 13:38:16

标签: c++ c opengl glut glfw

我认为这是一个常见的问题,它与OpenGL管道有关,以及它如何将渲染帧排队以进行显示。

它的样子

在Android上的这个video中可以看到一个极端的例子。

最简单的桌面应用程序上存在鼠标延迟。如果你运行我写的with GLFW in C++之一的小应用程序,你会发现它真的很明显:

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <stdlib.h>
#include <stdio.h>

const float box_size = 20;

static const struct
{
    float x, y;
} vertices[4] =
{
    { -box_size, -box_size},
    {  box_size, -box_size},
    {  box_size,  box_size},
    { -box_size,  box_size}
};

static const char* vertex_shader_text =
"#version 110\n"
"attribute vec2 vPos;\n"
"varying vec3 color;\n"
"uniform vec2 vMouse;\n"
"uniform vec2 vWindow;\n"
"void main()\n"
"{\n"
"    gl_Position = vec4(vPos/vWindow+vMouse, 0.0, 1.0);\n"
"    color = vec3(1.0, 1.0, 0.);\n"
"}\n";

static const char* fragment_shader_text =
"#version 110\n"
"varying vec3 color;\n"
"void main()\n"
"{\n"
"    gl_FragColor = vec4(color, 1.0);\n"
"}\n";

static void error_callback(int error, const char* description)
{
    fprintf(stderr, "Error: %s\n", description);
}

int main(void)
{
    GLFWwindow* window;
    GLuint vertex_buffer, vertex_shader, fragment_shader, program;
    GLint mouse_location, vpos_location, window_location;

    glfwSetErrorCallback(error_callback);

    if (!glfwInit())
        exit(EXIT_FAILURE);

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

    window = glfwCreateWindow(500, 500, "Square Follows Mouse - GLFW", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    glfwMakeContextCurrent(window);

    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        /* Problem: glewInit failed, something is seriously wrong. */
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwSwapInterval(1);

    // NOTE: OpenGL error checks have been omitted for brevity

    glGenBuffers(1, &vertex_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
    glCompileShader(vertex_shader);

    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
    glCompileShader(fragment_shader);

    program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);

    vpos_location = glGetAttribLocation(program, "vPos");
    mouse_location = glGetUniformLocation(program, "vMouse");
    window_location = glGetUniformLocation(program, "vWindow");

    glEnableVertexAttribArray(vpos_location);
    glVertexAttribPointer(vpos_location, 2, GL_FLOAT, GL_FALSE,
                          sizeof(vertices[0]), (void*) 0);

    while (!glfwWindowShouldClose(window))
    {
        float ratio;
        int width, height;

        glfwGetFramebufferSize(window, &width, &height);
        ratio = width / (float) height;

        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(program);

        double mouse_x, mouse_y;
        glfwGetCursorPos(window, &mouse_x, &mouse_y);
        glUniform2f(mouse_location, mouse_x/width*2-1, -mouse_y/height*2+1);
        glUniform2f(window_location, (float)width, (float)height);

        glDrawArrays(GL_POLYGON, 0, 4);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);

    glfwTerminate();
    exit(EXIT_SUCCESS);
}

...或with GLUT in C

#include <GL/glut.h>

int window_w, window_h = 0;
float mouse_x, mouse_y = 0.0;
float box_size = 0.02;

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glBegin(GL_POLYGON);
  glVertex3f(mouse_x+box_size, mouse_y+box_size, 0.0);
  glVertex3f(mouse_x-box_size, mouse_y+box_size, 0.0);
  glVertex3f(mouse_x-box_size, mouse_y-box_size, 0.0);
  glVertex3f(mouse_x+box_size, mouse_y-box_size, 0.0);
  glEnd();

  glutSwapBuffers();
}

void motion(int x, int y)
{
  mouse_x = (float)x/window_w - 0.5;
  mouse_y = -(float)y/window_h + 0.5;
  glutPostRedisplay();
}

void reshape(int w, int h)
{
  window_w = w;
  window_h = h;
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(-.5, .5, -.5, .5);
}

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(500, 500);
  glutCreateWindow("Square Follows Mouse - GLUT");
  glutPassiveMotionFunc(motion);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;
}

还有预编译的二进制文件here(Linux x64)。

这篇文章底部的第一个和第三个GIF是上述GLFW应用程序的截屏视频。

我认为是

此处的问题必须是显示延迟,即应用渲染帧和监视器点亮像素之间的时间。目标是尽量减少它。

为什么我认为它是

鉴于垂直同步已启用且渲染很简单并且在比帧周期少得多的情况下完成,此显示延迟通常为两帧。这是因为应用程序是三重缓冲的:正在显示一个缓冲区,一个是下一次翻转时显示的前缓冲区,另一个是应用程序引入的后台缓冲区。一旦后台缓冲区可用,app就会渲染下一帧。如果应用程序在显示之前等待并渲染帧大约半帧,则此延迟可能小于8.3ms而不是33.3-25.0ms(60fps)。

我通过executing a sleep function every frame for 17ms确认了这一点(多一点)。这样,显示器每秒左右抖动,但鼠标延迟明显更小,因为帧被更快地发送以便显示,因为队列是“饥饿的”,即没有预渲染的帧。下面的第2和第4个GIF显示了这一点。如果您将此应用程序置于全屏模式,则操作系统光标几乎察觉不到延迟。

因此,问题变成了如何使帧渲染在特定时间(例如T-8ms)开始相对于它在监视器(T)上显示的时间。例如,在T之前的半个帧或者我们估计渲染将花费的时间。

有解决这个问题的常用方法吗?

我找到了什么

  • 我只能在Android here上找到一个类似的问题,它显示了如何在两帧延迟时间内缩短半个帧周期,但仅限于Android。
  • 另一个用于桌面应用here,但解决方案是仅在有鼠标事件时渲染帧。这样可以减少鼠标开始移动时第一帧或第二帧的延迟,但帧队列很快就会填满,再次出现两帧延迟。

我甚至找不到GL函数来查询渲染是否落后于显示器的帧消耗。直到前后缓冲区交换时才阻塞的函数(the docs say its glFinish,但在我的实验中,它总是比后缓冲区变得更快时返回。帧缓冲区(特别是CopyTexImage2D)上的操作似乎确实会阻塞,直到缓冲区交换并且可以用于同步,但是可能会出现其他问题,这些问题将以这种迂回的方式同步。

任何可以在此三重缓冲区队列上返回某些状态的函数以及消耗了多少这些函数对于实现这种同步非常有用。

图片

Square Follows Mouse - GLFW

Square Follows Mouse - GLFW - Sleep

相同的GIF,只是放慢速度并修剪:

Slow - GLFW

Slow - GLFW - Sleep

1 个答案:

答案 0 :(得分:0)

用原生的东西替换输入处理,在 glfw 输入识别是通过回调过程完成的,它们最初的性能比轮询低,但它们涉及到你正在经历的延迟。回调很笨拙,您想通过 WinAPI 直接从 Windows 上的系统进行轮询,这非常简单,而在 Linux 上,它的 X11 不像 WinAPI 那样简单但可行。请注意,您可以决定投票率。

看起来你在 Linux 上工作 How can I get the current mouse (pointer) position co-ordinates in X