Co oznacza „tryb natychmiastowy” w OpenGL?

82

Co to jest „tryb natychmiastowy”? Podaj przykład kodu.

Kiedy muszę używać trybu natychmiastowego zamiast trybu zachowanego? Jakie są wady i zalety każdej metody?

Dmitriy
źródło

Odpowiedzi:

143

Jednym z przykładów „natychmiastowym trybie” używa glBegini glEndz glVertexpomiędzy nimi. Innym przykładem „trybu bezpośredniego” jest użycie glDrawArraysz tablicą wierzchołków klienta (tj. Nie jest to obiekt bufora wierzchołków).

Zwykle nigdy nie będziesz chciał używać trybu natychmiastowego (z wyjątkiem być może pierwszego programu „hello world”), ponieważ jest to przestarzała funkcja i nie oferuje optymalnej wydajności.

Powodem, dla którego tryb natychmiastowy nie jest optymalny, jest to, że karta graficzna jest bezpośrednio połączona z przepływem programu. Sterownik nie może wcześniej nakazać procesorowi GPU rozpoczęcia renderowania glEnd, ponieważ nie wie, kiedy zakończysz przesyłanie danych, i musi je również przesłać (co może zrobić dopiero później glEnd).
Podobnie, w przypadku tablicy wierzchołków klienta, sterownik może pobrać kopię tablicy tylko w momencie wywołania glDrawArraysi musi podczas tego blokować aplikację. Powodem jest to, że w przeciwnym razie można by zmodyfikować (lub zwolnić) pamięć tablicy, zanim sterownik ją przechwycił. Nie może zaplanować tej operacji wcześniej ani później, ponieważ wie tylko, że dane są prawidłowe dokładnie w jednym momencie.

W przeciwieństwie do tego, jeśli używasz na przykład obiektu bufora wierzchołków, wypełniasz bufor danymi i przekazujesz je do OpenGL. Twój proces nie jest już właścicielem tych danych i dlatego nie może ich już modyfikować. Kierowca może polegać na tym fakcie i może (nawet spekulacyjnie) przesłać dane, gdy autobus jest wolny.
Każde z twoich późniejszych glDrawArrayslub glDrawElementswywołań po prostu przejdzie do kolejki roboczej i natychmiast wróci (przed faktycznym zakończeniem!), Więc twój program będzie nadal wysyłać polecenia, podczas gdy w tym samym czasie sterownik będzie pracował jeden po drugim. Prawdopodobnie nie będą też musieli czekać na nadejście danych, ponieważ kierowca mógł to zrobić już znacznie wcześniej.
W ten sposób renderuj wątek i procesor graficzny działają asynchronicznie, każdy komponent jest cały czas zajęty, co zapewnia lepszą wydajność.

Tryb natychmiastowy ma tę zaletę, że jest śmiertelnie prosty w użyciu, ale z drugiej strony prawidłowe używanie OpenGL w sposób, który nie jest przestarzały, również nie jest dokładnie nauką o rakietach - zajmuje tylko bardzo mało dodatkowej pracy.

Oto typowy kod „Hello World” w OpenGL w trybie natychmiastowym:

Edycja: na
zwykłe żądanie to samo w trybie zachowanym wyglądałoby mniej więcej tak:

Damon
źródło
1
Wielkie dzięki Damon, bardzo ciekawe porównanie.
Wydaje
6
@mallardz: O wiele trudniej jest cokolwiek zrobić z nowoczesnym OpenGL, ale w rzeczywistości jest to łatwiejsze, gdy pokonasz początkową przeszkodę (i znacznie szybciej). Tryb natychmiastowy jest fajny, ponieważ bariera wejścia jest wyjątkowo niska. W moim przykładzie nadal brakuje shaderów wierzchołków i fragmentów, które będziesz musiał dostarczyć (całkiem podstawowe). Kompletny działający przykład czegoś, co faktycznie kompiluje się i działa, jest dość długi. :-)
Damon
19

Zachowany przykład do wykonania

Damon dostarczył kluczowe części, ale nowicjusze tacy jak ja będą szukać w pełni działającego przykładu.

wprowadź opis obrazu tutaj

main.c

#include <stdio.h>
#include <stdlib.h>

#define GLEW_STATIC
#include <GL/glew.h>

#include <GLFW/glfw3.h>

#define INFOLOG_LEN 512

static const GLuint WIDTH = 512, HEIGHT = 512;
/* vertex data is passed as input to this shader
 * ourColor is passed as input to the to the fragment shader. */
static const GLchar* vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 ourColor;\n"
    "void main() {\n"
    "    gl_Position = vec4(position, 1.0f);\n"
    "    ourColor = color;\n"
    "}\n";
static const GLchar* fragmentShaderSource =
    "#version 330 core\n"
    "in vec3 ourColor;\n"
    "out vec4 color;\n"
    "void main() {\n"
    "    color = vec4(ourColor, 1.0f);\n"
    "}\n";
GLfloat vertices[] = {
/*   Positions            Colors */
     0.5f, -0.5f, 0.0f,   1.0f, 0.0f, 0.0f,
    -0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,
     0.0f,  0.5f, 0.0f,   0.0f, 0.0f, 1.0f
};

int main(int argc, char **argv) {
    int immediate = (argc > 1) && argv[1][0] == '1';

    /* Used in !immediate only. */
    GLuint vao, vbo;
    GLint shaderProgram;

    glfwInit();
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
    glfwMakeContextCurrent(window);
    glewExperimental = GL_TRUE;
    glewInit();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glViewport(0, 0, WIDTH, HEIGHT);
    if (immediate) {
        float ratio;
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        ratio = width / (float) height;
        glClear(GL_COLOR_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glBegin(GL_TRIANGLES);
        glColor3f(  1.0f,  0.0f, 0.0f);
        glVertex3f(-0.5f, -0.5f, 0.0f);
        glColor3f(  0.0f,  1.0f, 0.0f);
        glVertex3f( 0.5f, -0.5f, 0.0f);
        glColor3f(  0.0f,  0.0f, 1.0f);
        glVertex3f( 0.0f,  0.5f, 0.0f);
        glEnd();
    } else {
        /* Build and compile shader program. */
        /* Vertex shader */
        GLint vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);
        GLint success;
        GLchar infoLog[INFOLOG_LEN];
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(vertexShader, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog);
        }
        /* Fragment shader */
        GLint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(fragmentShader, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog);
        }
        /* Link shaders */
        shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        if (!success) {
            glGetProgramInfoLog(shaderProgram, INFOLOG_LEN, NULL, infoLog);
            printf("ERROR::SHADER::PROGRAM::LINKING_FAILED\n%s\n", infoLog);
        }
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        glGenVertexArrays(1, &vao);
        glGenBuffers(1, &vbo);
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        /* Position attribute */
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        /* Color attribute */
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glBindVertexArray(0);
        glUseProgram(shaderProgram);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
    }
    glfwSwapBuffers(window);

    /* Main loop. */
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }

    if (!immediate) {
        glDeleteVertexArrays(1, &vao);
        glDeleteBuffers(1, &vbo);
        glDeleteProgram(shaderProgram);
    }
    glfwTerminate();
    return EXIT_SUCCESS;
}

Zaadaptowano z Learn OpenGL , mojego upstreamu na GitHub .

Skompiluj i uruchom na Ubuntu 20.04:

sudo apt install libglew-dev libglfw3-dev
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -lGL -lGLEW -lglfw
# Shader
./main.out
# Immediate
./main.out 1

Z tego widzimy, jak:

Podczas korzystania z shaderów:

  • programy Vertex Shader i Fragment Shader są reprezentowane jako łańcuchy w stylu C zawierające język GLSL ( vertexShaderSourcei fragmentShaderSource) wewnątrz zwykłego programu w C, który działa na CPU

  • ten program C wykonuje wywołania OpenGL, które kompilują te ciągi do kodu GPU, np .:

    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    
  • moduł cieniujący definiuje oczekiwane dane wejściowe, a program w języku C udostępnia je za pośrednictwem wskaźnika do pamięci do kodu GPU. Na przykład Fragment Shader definiuje swoje oczekiwane dane wejściowe jako tablicę pozycji wierzchołków i kolorów:

    "layout (location = 0) in vec3 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 ourColor;\n"
    

    a także definiuje jedno ze swoich wyjść ourColorjako tablicę kolorów, która następnie staje się danymi wejściowymi do Fragment Shadera:

    static const GLchar* fragmentShaderSource =
        "#version 330 core\n"
        "in vec3 ourColor;\n"
    

    Następnie program w C dostarcza tablicę zawierającą pozycje wierzchołków i kolory z CPU do GPU

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    

Jednak w bezpośrednim przykładzie bez modułu cieniującego widzimy, że wykonywane są magiczne wywołania interfejsu API, które jawnie podają pozycje i kolory:

glColor3f(  1.0f,  0.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);

Rozumiemy zatem, że reprezentuje to znacznie bardziej ograniczony model, ponieważ pozycje i kolory nie są już dowolnymi tablicami zdefiniowanymi przez użytkownika w pamięci, ale raczej danymi wejściowymi do modelu podobnego do Phonga.

W obu przypadkach renderowane dane wyjściowe normalnie trafiają bezpośrednio do wideo, bez przechodzenia z powrotem przez procesor, chociaż można je odczytać do procesora, np. Jeśli chcesz zapisać je do pliku: Jak używać GLUT / OpenGL do renderowania plik?

Większość "nowoczesnych" samouczków OpenGL normalnie zachowanych trybów i GLFW, znajdziesz wiele przykładów na:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
Otrzymałem zgłoszenie, że jeśli pojawi się błąd, ERROR::SHADER::VERTEX::COMPILATION_FAILEDmożesz go naprawić, glfwWindowHintjak pokazano na: stackoverflow.com/questions/52592309/… Nie mogę jednak odtworzyć.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Właśnie rozwiązałem problem, uruchamiając polecenie export MESA_GL_VERSION_OVERRIDE=3.3przed uruchomieniem main.out (Debian 8), ponieważ jedna z odpowiedzi wskazuje w tym samym poście, który udostępniłeś.
Ivanzinho