2013-08-12 8 views
5

Mam dziwne jąkanie w mojej prostej aplikacji OpenGL (przez GLFW3). Mimo że włączona jest funkcja vsync (szybkość klatek jest prawie stała 60 fps), ruch obracającego się trójkąta nie zawsze jest płynny - jest prawie tak, że niektóre ramki są czasami pomijane. Próbowałem sprawdzić różnicę czasu między kolejnymi wywołaniami funkcji glSwapBuffers(), ale te wydają się całkiem spójne.dziwne robaczkowanie openglingu

Czy robię coś nie tak? Czy powinienem używać filtrowania ruchu, aby wyglądał na bardziej płynny?

Kod:

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

OK. Wróciłem do domu i zrobiłem więcej testów.

Najpierw spróbowałem wyłączyć V-Sync, ale nie mogłem! Musiałem wyłączyć efekty pulpitu Windows (Aero), aby móc to zrobić, a oto i oto - po wyłączeniu Aero, jąkanie zniknęło (przy włączonym V-Sync).

Potem przetestowałem to z wyłączonym V-Sync, i oczywiście, dostałem znacznie większą liczbę klatek na sekundę z okazjonalnym oczekiwanym rozdarciem.

Potem przetestowałem to na pełnym ekranie. Renderowanie było płynne z Aero i bez niego.

Nie mogłem znaleźć nikogo, kto miałby ten problem. Czy myślisz, że to błąd GLFW3? problem ze sterownikiem/sprzętem (mam GTS450 z najnowszymi sterownikami)?

Dziękuję wszystkim za odpowiedzi. Wiele się nauczyłem, ale mój problem wciąż nie został rozwiązany.

+0

Mogłabyś spraw, by prędkość trójkąta nie była zależna od szybkości wyświetlania klatek, nawet jeśli ramki są pomijane, problem nie zostanie zauważony. –

+0

@Luke, rozmieszczenie trójkąta zależy od czasu systemu - pozycja jest fizycznie poprawna przez większość czasu. – liorda

+0

@liorda Nie jestem pewien, czy wystarczy użyć czasu na to, czy nie należy używać DT zamiast czasu w module cieniującym? –

Odpowiedz

3

Bez patrzenia na ten problem jąkania trudno jest powiedzieć, jaki jest problem. Ale pierwsze wrażenie z twojego programu jest w porządku.
Sądzę, że zauważyłeś, że klatka raz na jakiś czas jest wyświetlana dwa razy. Prowadząc do bardzo małego jąkania. Zdarza się to zazwyczaj przy próbie wyprowadzenia 60 klatek na monitorze 60 Hz z funkcją vsync.
W takim ustawieniu nie wolno przegapić ani jednego okresu porównywania z innymi, ponieważ pojawi się dwukrotne zacinanie się ramki.
Z drugiej strony jest prawie niemożliwe, aby to zagwarantować, ponieważ program planujący na platformach Windows planuje wątki na 15 ms (o tym nie wiem, która wartość jest poprawna na pamięć).
Możliwe jest, że wątek o wyższym priorytecie będzie używał procesora, a prezentowany wątek nie będzie w stanie zamienić buforów na nową ramkę w czasie. Po zwiększeniu wartości, np. 120 klatek na monitorze 120 Hz te zacierki będą jeszcze częściej pojawiać się.
Więc nie znam żadnego rozwiązania, w jaki sposób można temu zapobiec na platformie Windows. Ale jeśli ktoś inny wie, chętnie bym się o tym dowiedział.

+0

Z mojego doświadczenia wynika, że ​​Windows NT ma od 10 do 15 ms kwantów dla okien na pierwszym planie. Domyślnym działaniem programu planującego jest nadanie priorytetu oknie pierwszego planu i zapewnienie większych przedziałów czasu, i oczywiście jest to z wyprzedzeniem. Jeśli ustawisz priorytet wątku w czasie rzeczywistym, to dalej modyfikuje reguły, ale prawie nigdy nie jest to konieczne. –

+0

Więc zasadniczo nie ma nic złego w moim kodzie, tak po prostu zachowuje się system prewencyjny? – liorda

2

Trudno powiedzieć bez wizualizacji problemu, ale jeśli nie mówimy o poważnym jąkaniu, rzadko jest to problem z renderowaniem. Ruch/fizyka w twoim programie jest obsługiwana/przetwarzana przez CPU. Sposób, w jaki realizujesz swoją animację, jest obsługiwany w sposób zależny wyłącznie od procesora.

Oznacza to, że:

Załóżmy, że obracają swoje trójkąta o stałej kwocie każdym cyklu procesora. Jest to bardzo zależne od czasu ukończenia cyklu procesora. Rzeczy takie jak obciążenie pracą cpu mogą mieć ogromny wpływ na wynik twojego ekranu (niekoniecznie). I nawet nie wymaga dużego zajętości procesora, aby zauważyć różnicę. Wystarczy proces w tle, aby się obudzić i zapytać o aktualizacje.Może to spowodować "skok", który można zaobserwować jako niewielką przerwę w ruchu animacji (ze względu na niewielkie opóźnienie, jakie procesor może spowodować w cyklu animacji). Można to interpretować jako jąkanie.

Teraz zrozumienie powyższego jest możliwe na kilka sposobów rozwiązania problemu (ale moim zdaniem nie warto inwestować w to, co próbujesz zrobić powyżej). Musisz znaleźć sposób na spójne etapy animacji (z niewielkim marginesem dla odmiany).

To jest wielki artykuł na zwiedzanie: http://gafferongames.com/game-physics/fix-your-timestep/

Ostatecznie większość metod realizowanych powyżej spowoduje przepływ lepiej renderowania. Ale wciąż nie wszystkie z nich gwarantują dokładność renderowania fizyki. Nie wypróbowawszy tego jeszcze, powiedziałbym, że trzeba by przejść do implementacji interpolacji w jego procesie renderingu, aby zagwarantować jak najsprytniejszy rysunek.

Teraz, co chciałem ci wyjaśnić, jest to, że jąkanie jest zwykle spowodowane przez procesor, ponieważ interweniuje bezpośrednio w twoim sposobie obchodzenia się z fizyką. Ale ogólnie rzecz biorąc, czas na poradzenie sobie z fizyką i interpolację w cyklach renderowania jest zdecydowanie wart uwagi.

+0

Czy często stosowaną metodą motion blur jest "interpolacja wizualna"? – liorda

+0

Nie jestem pewien, o czym mówisz. Jest to dość duży temat do opracowania. Spójrz na to pytanie http://gamedev.stackexchange.com/questions/12754/how-to-interpolate-between- two-game-states. Daje to lepsze zrozumienie powyższego. – Bisder

4

To dziwny tryb kompozycji DWM (Desktop Window Manager) i problem z interakcją glfwSwapBuffers(). Nie dotarłem jeszcze do źródła problemu. Ale można obejść się jąkanie, wykonując jedną z następujących czynności:

  • iść pełnoekranowy
  • wyłączyć okno DWM składu (patrz moja odpowiedź na Linear movement stutter)
  • umożliwić wielu próbkowanie: glfwWindowHint(GLFW_SAMPLES, 4);