【问题标题】:strange opengl rendering stutter奇怪的opengl渲染口吃
【发布时间】:2023-03-16 23:02:01
【问题描述】:

我在我的简单 opengl(通过 GLFW3)应用程序中遇到了奇怪的口吃。虽然启用了垂直同步(帧速率几乎稳定为 60 fps),但旋转三角形的运动并不总是平滑 - 就像有时会跳过某些帧一样。我尝试查看对 glSwapBuffers() 的连续调用之间的时间差,但这些看起来非常一致。

我做错了吗?我应该使用某种运动模糊过滤使其看起来更平滑吗?

代码:

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

好的。我回到家做了更多的测试。

首先我尝试禁用 V-Sync,但我做不到!我必须禁用 Windows 的桌面效果(Aero)才能这样做,你瞧——一旦禁用 Aero,口吃就消失了(打开 V-Sync)。

然后我在关闭垂直同步的情况下对其进行了测试,当然,我得到了更高的帧速率,偶尔会出现预期的撕裂。

然后我全屏测试它。使用 Aero 和不使用 Aero 时,渲染都很流畅。

我找不到其他有此问题的人。你认为这是 GLFW3 的错误吗?驱动程序/硬件问题(我有带有最新驱动程序的 GTS450)?

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

【问题讨论】:

  • 你可以让你的三角速度不依赖于帧率,即使跳过帧你也不会看到问题。
  • @Luke,三角形的配置取决于系统时间——大部分时间该位置在物理上是正确的。
  • @liorda 我不确定仅使用时间是否足够,您不应该在着色器中使用 DT 而不是时间吗?

标签: c++ windows opengl


【解决方案1】:

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

  • 全屏显示
  • 禁用 dwm 窗口组合(请参阅我对 Linear movement stutter 的回答)
  • 启用多重采样:glfwWindowHint(GLFW_SAMPLES, 4);

【讨论】:

    【解决方案2】:

    如果没有看到这个口吃问题,就很难说出问题所在。但是您的程序的第一印象还可以。
    所以我猜你会观察到一个帧偶尔会显示两次。导致非常小的口吃。当您尝试使用 vsync 在 60Hz 显示器上输出 60 帧时,通常会发生这种情况。
    在这样的设置中,您不能错过一个垂直同步周期,否则您会看到卡顿,因为帧显示了两次。
    另一方面,几乎不可能保证这一点,因为 Windows 平台上的调度程序将线程调度为 15 毫秒(我不知道正确的值)。
    因此,更高优先级的线程可能会使用 CPU,而您的呈现线程无法及时将缓冲区交换为新帧。当您增加值时,例如在 120 Hz 显示器上显示 120 帧,您会更频繁地看到这些卡顿。
    所以我不知道如何在 Windows 平台上防止这种情况的任何解决方案。但如果其他人知道,我也很乐意知道。

    【讨论】:

    • 根据我的经验,Windows NT 的前景窗口有 10-15 毫秒的时间间隔。默认的调度器行为是优先考虑前台窗口并给出更大的时间片,当然它是先发制人的。如果您将线程优先级设置为实时,它会进一步修改规则,但几乎从来没有必要。
    • 所以基本上我的代码没有问题,只是抢占式系统中的行为方式?
    【解决方案3】:

    如果不将问题可视化就很难判断,但除非我们谈论的是一些严重的口吃,否则它很少是渲染问题。程序中的运动/物理由 CPU 处理/处理。您实现动画的方式完全取决于 CPU。

    这意味着:

    假设您在每个 CPU 周期将三角形旋转固定量。这很大程度上取决于完成一个 CPU 周期所需的时间。诸如 cpu 工作负载之类的事情会对您的屏幕结果产生巨大影响(尽管不一定)。甚至不需要占用大量 CPU 就可以注意到差异。只需要一个后台进程来唤醒和查询更新。这可能会导致“尖峰”,在您的动画流程中可能会被观察为一个微小的暂停(由于 CPU 可能会在您的动画周期中造成微小的延迟)。这可以理解为口吃。

    现在了解上述内容后,有几种方法可以解决您的问题(但在我看来,不值得为您在上面尝试做的事情进行投资)。您需要找到一种方法来获得一致的动画步骤(变化幅度很小)。

    这是一篇值得探索的好文章: http://gafferongames.com/game-physics/fix-your-timestep/

    最终,上面实现的大多数方法都会产生更好的渲染流程。但并非所有这些都能保证物理渲染精度。在没有亲自尝试的情况下,我会说一个人必须尽可能在他/她的渲染过程中实现插值,以尽可能保证平滑的绘制。

    现在我最想向您解释的是,口吃通常是由 CPU 引起的,因为它直接干预了您处理物理的方式。但总的来说,使用时间来处理物理和在渲染周期内进行插值是绝对值得探讨的话题。

    【讨论】:

    • 将运动模糊作为一种“视觉插值”的方式是一种常见的做法吗?
    • 我不确定你在说什么。这是一个相当大的话题。看看这个问题gamedev.stackexchange.com/questions/12754/…它可以更好地理解上述内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    相关资源
    最近更新 更多