奇怪的opengl渲染口吃

我在简单的opengl(通过GLFW3)应用程序遇到一个奇怪的口吃。 尽pipe启用了vsync(帧速率几乎稳定在60 fps),但是旋转三angular形的运动并不总是平滑的 – 这就像有时会跳过某些帧。 我试图看看连续调用glSwapBuffers()之间的时间差,但这些看起来相当一致。

难道我做错了什么? 我应该使用某种运动模糊滤镜来使其看起来更stream畅吗?

代码:

#include <cstdlib> #include <cstdio> #include <cmath> #include <cfloat> #include <cassert> #include <minmax.h> #include <string> #include <iostream> #include <fstream> #include <vector> #include <Windows.h> #include <GL/glew.h> #include <gl/GLU.h> //#include <GL/GL.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #ifdef _WIN32 #pragma warning(disable:4996) #endif static int swap_interval; static double frame_rate; GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){ // Create the shaders GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER); GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); // Read the Vertex Shader code from the file std::string VertexShaderCode; std::ifstream VertexShaderStream(vertex_file_path, std::ios::in); if(VertexShaderStream.is_open()){ std::string Line = ""; while(getline(VertexShaderStream, Line)) VertexShaderCode += "\n" + Line; VertexShaderStream.close(); }else{ printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertex_file_path); return 0; } // Read the Fragment Shader code from the file std::string FragmentShaderCode; std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in); if(FragmentShaderStream.is_open()){ std::string Line = ""; while(getline(FragmentShaderStream, Line)) FragmentShaderCode += "\n" + Line; FragmentShaderStream.close(); } GLint Result = GL_FALSE; int InfoLogLength; // Compile Vertex Shader printf("Compiling shader : %s\n", vertex_file_path); char const * VertexSourcePointer = VertexShaderCode.c_str(); glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL); glCompileShader(VertexShaderID); // Check Vertex Shader glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); if (Result != GL_TRUE) { glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> VertexShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); printf("%s\n", &VertexShaderErrorMessage[0]); } } // Compile Fragment Shader printf("Compiling shader : %s\n", fragment_file_path); char const * FragmentSourcePointer = FragmentShaderCode.c_str(); glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL); glCompileShader(FragmentShaderID); // Check Fragment Shader glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); if (Result != GL_TRUE) { glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); printf("%s\n", &FragmentShaderErrorMessage[0]); } } // Link the program printf("Linking program\n"); GLuint ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); // Check the program glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); if (Result != GL_TRUE) { glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> ProgramErrorMessage(InfoLogLength+1); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); printf("%s\n", &ProgramErrorMessage[0]); } } #ifdef _DEBUG glValidateProgram(ProgramID); #endif glDeleteShader(VertexShaderID); glDeleteShader(FragmentShaderID); return ProgramID; } static void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } static void set_swap_interval(GLFWwindow* window, int interval) { swap_interval = interval; glfwSwapInterval(swap_interval); } static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (key == GLFW_KEY_SPACE && action == GLFW_PRESS) set_swap_interval(window, 1 - swap_interval); } static bool init(GLFWwindow** win) { if (!glfwInit()) exit(EXIT_FAILURE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); // creating a window using the monitor param will open it full screen const bool useFullScreen = false; GLFWmonitor* monitor = useFullScreen ? glfwGetPrimaryMonitor() : NULL; *win = glfwCreateWindow(640, 480, "", monitor, NULL); if (!(*win)) { glfwTerminate(); exit(EXIT_FAILURE); } glfwMakeContextCurrent(*win); GLenum glewError = glewInit(); if( glewError != GLEW_OK ) { printf( "Error initializing GLEW! %s\n", glewGetErrorString( glewError ) ); return false; } //Make sure OpenGL 2.1 is supported if( !GLEW_VERSION_2_1 ) { printf( "OpenGL 2.1 not supported!\n" ); return false; } glfwMakeContextCurrent(*win); glfwSetFramebufferSizeCallback(*win, framebuffer_size_callback); glfwSetKeyCallback(*win, key_callback); // get version info const GLubyte* renderer = glGetString (GL_RENDERER); // get renderer string const GLubyte* version = glGetString (GL_VERSION); // version as a string printf("Renderer: %s\n", renderer); printf("OpenGL version supported %s\n", version); return true; } std::string string_format(const std::string fmt, ...) { int size = 100; std::string str; va_list ap; while (1) { str.resize(size); va_start(ap, fmt); int n = vsnprintf((char *)str.c_str(), size, fmt.c_str(), ap); va_end(ap); if (n > -1 && n < size) { str.resize(n); return str; } if (n > -1) size = n + 1; else size *= 2; } return str; } int main(int argc, char* argv[]) { srand(9); // constant seed, for deterministic results unsigned long frame_count = 0; GLFWwindow* window; init(&window); // An array of 3 vectors which represents 3 vertices static const GLfloat g_vertex_buffer_data[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); // acclocate GPU memory and copy data glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW); unsigned int vao = 0; glGenVertexArrays (1, &vao); glBindVertexArray (vao); glEnableVertexAttribArray (0); glBindBuffer (GL_ARRAY_BUFFER, vbo); glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create and compile our GLSL program from the shaders GLuint programID = LoadShaders( "1.vert", "1.frag" ); // Use our shader glUseProgram(programID); GLint locPosition = glGetAttribLocation(programID, "vertex"); assert(locPosition != -1); glm::mat4 world(1.0f); GLint locWorld = glGetUniformLocation(programID, "gWorld"); assert(locWorld != -1 && "Error getting address (was it optimized out?)!"); glUniformMatrix4fv(locWorld, 1, GL_FALSE, glm::value_ptr(world)); GLenum err = glGetError(); GLint loc = glGetUniformLocation(programID, "time"); assert(loc != -1 && "Error getting uniform address (was it optimized out?)!"); bool isRunning = true; while (isRunning) { static float time = 0.0f; static float oldTime = 0.0f; static float fpsLastUpdateTime = 0.0f; oldTime = time; time = (float)glfwGetTime(); static std::string fps; glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram (programID); glUniform1f(loc, time); glBindVertexArray (vao); glDrawArrays (GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); isRunning = !glfwWindowShouldClose(window); float dT = time-oldTime; if (time-fpsLastUpdateTime > 0.5) { static const char* fmt = "frame rate: %.1f frames per second"; glfwSetWindowTitle(window, string_format(fmt, 1.0f/(dT)).c_str()); fpsLastUpdateTime = time; } } glfwDestroyWindow(window); glfwTerminate(); return 0; } //////////////////////////////////////// // 1.frag //////////////////////////////////////// #version 330 core // Ouput data out vec3 color; void main() { // Output color = red color = vec3(1,0,0); } ////////////////////////////////////////////// // 1.vert ////////////////////////////////////////////// #version 330 core // Input vertex data, different for all executions of this shader. in vec3 vertex; uniform mat4 gWorld; uniform float time; void main() { gl_Position = gWorld * vec4(vertex, 1.0f); gl_Position.x += sin(time); gl_Position.y += cos(time)/2.0f; gl_Position.w = 1.0; } 

好。 我回到家,做了更多的testing。

首先,我试图禁用V-Sync,但我不能! 我不得不禁用窗口的桌面效果(Aero)才能这样做,而且看啊 – 一旦Aero被禁用,口吃消失(V-Sync开启)。

然后,我用V-Syncclosures了它,当然,我得到了更高的帧速率,偶尔会有预期的撕裂。

然后我全屏testing。 Aero的渲染非常stream畅,没有它。

我找不到任何人分享这个问题。 你认为这是一个GLFW3的错误? 一个驱动程序/硬件问题(我有最新的驱动程序GTS450)?

谢谢大家的答复。 我学到了很多,但是我的问题仍然没有解决。

Solutions Collecting From Web of "奇怪的opengl渲染口吃"

没有看到这个口吃问题,很难说出什么问题。 但你的程序的第一印象是好的。
所以我想你会观察到偶尔会出现两次画面。 导致一个非常小的口吃。 这通常发生在您尝试使用vsync在60Hz Monitor上输出60帧时。
在这样的设置中,你不能错过一个vsync周期,否则你会看到一个口吃,因为显示两次。
另一方面,要保证这一点几乎是不可能的,因为Windows平台上的调度器将线程调度为15ms(关于我不知道正确的值)。
因此,更高优先级的线程可能会使用CPU,并且您的呈现线程无法及时将缓冲区交换为新帧。 当你在120赫兹的监视器上增加120帧的数值时,你会更频繁地看到这些口吃。
所以我不知道任何解决方案,你可以防止这个在Windows平台上。 但是,如果别人知道我也会很高兴知道这一点。

这是一个奇怪的Windows dwm(桌面窗口管理器)组合模式和glfwSwapBuffers()交互的问题。 我没有找到问题的根源。 但是,您可以通过执行以下操作之一来解决口吃问题:

  • 去全屏
  • 禁用dwm窗口组成(请参阅我对线性移动口吃的回答)
  • 启用多重采样: glfwWindowHint(GLFW_SAMPLES, 4);

如果没有将问题形象化,很难说清楚,除非我们谈论严重的口吃问题,否则很少会出现问题。 程序中的运动/物理是由CPU处理/处理的。 你正在实现你的动画的方式,是完全依靠CPU来处理的。

这意味着什么:

假设你每一个CPU周期都要旋转你的三角形。 这非常依赖CPU周期完成的时间。 像cpu工作负载的东西可以对你的屏幕结果产生巨大的影响(不一定)。 而且它甚至不需要占用大量的CPU就可以看到差异。 它只需要一个后台进程来唤醒并查询更新。 这可能会导致一个“尖峰”,其中可能会被视为您的动画流程中的一个微小的暂停(由于在您的动画周期CPU可能造成的小延迟)。 这可以解释为一个口吃。

现在了解上面有几种方法来解决你的问题(但在我看来,这不值得投资于你正在做的事情)。 您需要找到一种具有一致的动画步骤的方法(对于变化有一个小的余量)。

这是一个伟大的文章探索: http : //gafferongames.com/game-physics/fix-your-timestep/

最终,上面实现的大多数方法将导致更好的呈现流程。 但是并不是所有这些都能保证物理渲染精度。 我自己还没有尝试过,我想说的是,尽可能在他/她的渲染过程中实现插值,以确保顺利绘制尽可能最好。

现在我最想向你解释的是,口吃通常是由CPU引起的,因为它直接干扰你处理物理的方式。 但总体而言,利用时间来处理物理内容并在渲染周期内进行插值是一个值得探索的主题。