【发布时间】:2017-10-04 21:21:04
【问题描述】:
我认为这是一个与 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)。
本文底部的第 1 和第 3 个 gif 是上述 GLFW 应用的截屏视频。
我认为是什么
这里的问题一定是显示延迟,即应用渲染帧和显示器点亮像素之间的时间。目标是最小化它。
我为什么这么认为
鉴于启用了垂直同步并且您的渲染很简单并且在比帧周期短得多的时间内完成,这个显示延迟通常是两帧。这是因为应用程序是三重缓冲的:一个缓冲区正在显示,一个是要在下一次翻转时显示的前缓冲区,一个是应用程序正在绘制的后缓冲区。一旦后台缓冲区可用,应用程序就会渲染下一帧。相反,如果应用在显示前等待并在大约半帧时渲染该帧,则此延迟可能小于 8.3 毫秒,而不是 33.3-25.0 毫秒(在 60 fps 时)。
我通过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)上的操作似乎会阻塞,直到缓冲区交换并可用于同步,但以这种迂回的方式同步可能会出现其他问题。
任何可以在这个三缓冲队列上返回一些状态以及消耗了多少状态的函数对于实现这种同步非常有帮助。
图片
相同的 gif,只是放慢和修剪:
【问题讨论】:
-
glFinsih实际上有一些效果,但有一些注意事项,尤其是与缓冲区交换结合使用时。 GL 通过sync objects 提供了更好的同步选项。也看看WGL_NV_delay_before_swap。