簡體   English   中英

最大限度地減少 GL 桌面應用程序上的鼠標輸入延遲?

[英]Minimize mouse input lag on GL desktop app?

我認為這是一個常見問題,它與 OpenGL 管道以及它如何將渲染幀排隊以供顯示。

看起來如何

在 Android 上的這個視頻中可以看到一個極端的例子。

最簡單的桌面應用程序中存在鼠標延遲。 如果您運行我在 C++ 中用 GLFW編寫的小應用程序之一,您會發現它真的很明顯:

#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);
}

...或在 C 中使用 GLUT

#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;
}

這里也有預編譯的二進制文件(Linux x64)。

這篇文章底部的第一個和第三個 gif 是上述 GLFW 應用程序的截屏視頻。

我認為是什么

這里的問題一定是顯示延遲,即應用程序渲染幀和顯示器點亮像素之間的時間。 目標是將其最小化。

為什么我認為是

鑒於垂直同步已打開並且您的渲染很簡單並且在比幀周期短得多的時間內完成,此顯示延遲通常為兩幀。 這是因為該應用程序是三重緩沖的:一個緩沖區正在顯示,一個是要在下一次翻轉時顯示的前緩沖區,一個是應用程序正在繪制的后緩沖區。 一旦后台緩沖區可用,應用程序就會渲染下一幀。 相反,如果應用程序在顯示幀之前等待並渲染幀大約半幀,則此延遲可能小於 8.3 毫秒,而不是 33.3-25.0 毫秒(60 幀/秒)。

我通過每幀執行一個睡眠函數 17 毫秒(比一幀多一點點) 確認這一點。 通過這種方式,顯示每秒左右抖動一次,但鼠標延遲明顯更小,因為幀發送顯示更快,因為隊列“飢餓”,即沒有預渲染的幀。 下面的第二個和第四個 gif 顯示了這一點。 如果您將此應用程序置於全屏模式,則從操作系統光標幾乎察覺不到延遲。

所以問題就變成了如何同步幀渲染以在相對於它在監視器上顯示的時間(例如 T-8ms)開始(例如 T-8ms)。 例如,T 前半幀或我們估計渲染所需的時間。

有沒有通用的方法來解決這個問題?

我發現了什么

  • 我只能在 Android 上找到一個類似的問題here ,它展示了如何將兩幀延遲縮短半幀周期,但僅限於 Android。
  • 另一個適用於此處的桌面應用程序,但解決方案是僅在有鼠標事件時才渲染幀。 當鼠標開始移動時,這減少了第一幀或兩幀的延遲,但幀隊列很快填滿,兩幀延遲再次出現。

我什至找不到用於查詢渲染是否落后於監視器的幀消耗的 GL 函數。 在前后緩沖區交換之前都不會阻塞的函數( 文檔說它的 glFinish ,但在我的實驗中,它總是比后緩沖區可用時更快地返回)。 幀緩沖區上的操作(特別是 CopyTexImage2D)似乎會阻塞,直到緩沖區交換並可以用於同步,但是以這種迂回的方式同步可能會出現其他問題。

任何可以在這個三重緩沖隊列上返回一些狀態以及它消耗了多少的函數對於實現這種同步非常有幫助。

圖片

方形跟隨鼠標 - GLFW

方形跟隨鼠標 - GLFW - 睡眠

相同的 gif,只是放慢了速度並進行了修剪:

慢 - GLFW

慢 - GLFW - 睡眠

用原生的東西替換輸入處理,在 glfw 輸入識別是通過回調過程完成的,它們最初的性能比輪詢低,但它們涉及到你正在經歷的延遲。 回調很笨拙,您想通過 WinAPI 直接從 Windows 上的系統進行輪詢,這非常簡單,而在 Linux 上,它的 X11 不像 WinAPI 那樣簡單但可行。 請注意,您可以決定投票率。

看起來您正在使用 Linux 如何獲取當前鼠標(指針)在 X 中的位置坐標

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM