¿Qué significa "modo inmediato" en OpenGL?

82

¿Qué es el "modo inmediato"? Da un ejemplo de código.

¿Cuándo tengo que usar el modo inmediato en lugar del modo retenido? ¿Cuáles son las ventajas y desventajas de utilizar cada método?

Dmitriy
fuente

Respuestas:

143

Un ejemplo de "modo inmediato" es usar glBeginy glEndcon glVertexentre ellos. Otro ejemplo de "modo inmediato" es utilizarlo glDrawArrayscon una matriz de vértices de cliente (es decir, no un objeto de búfer de vértice).

Por lo general, nunca querrá usar el modo inmediato (excepto tal vez para su primer programa "hola mundo") porque es una funcionalidad obsoleta y no ofrece un rendimiento óptimo.

La razón por la que el modo inmediato no es óptimo es que la tarjeta gráfica está vinculada directamente con el flujo de su programa. El controlador no puede decirle a la GPU que comience a renderizar antes glEnd, porque no sabe cuándo terminará de enviar datos, y también necesita transferir esos datos (lo que solo puede hacer después glEnd).
De manera similar, con una matriz de vértices de cliente, el controlador solo puede extraer una copia de su matriz en el momento en que llama glDrawArraysy debe bloquear su aplicación mientras lo hace. La razón es que, de lo contrario, podría modificar (o liberar) la memoria de la matriz antes de que el controlador la haya capturado. No puede programar esa operación antes o después, porque solo sabe que los datos son válidos exactamente en un momento determinado.

En contraste con eso, si usa, por ejemplo, un objeto de búfer de vértice, llena un búfer con datos y lo entrega a OpenGL. Su proceso ya no posee estos datos y, por lo tanto, ya no puede modificarlos. El conductor puede confiar en este hecho y puede (incluso especulativamente) cargar los datos siempre que el autobús esté libre.
Cualquiera de sus posteriores glDrawArrayso glDrawElementslas llamadas se acaba de entrar en una cola de trabajo y volver inmediatamente (antes de realmente terminar!), Por lo que su programa mantiene el envío de comandos al mismo tiempo que el controlador funciona uno por uno. También es probable que no necesiten esperar a que lleguen los datos, porque el controlador ya podría hacerlo mucho antes.
Por lo tanto, el hilo de procesamiento y la GPU se ejecutan de forma asíncrona, cada componente está ocupado en todo momento, lo que produce un mejor rendimiento.

El modo inmediato tiene la ventaja de ser muy fácil de usar, pero usar OpenGL correctamente de una manera no obsoleta tampoco es precisamente ciencia espacial, solo requiere muy poco trabajo adicional.

Aquí está el código típico de OpenGL "Hello World" en modo inmediato:

Editar:
por solicitud común, lo mismo en modo retenido se vería algo así:

Damon
fuente
1
Muchas gracias Damon, comparación muy interesante. Eso me parece significativamente más complicado, pero supongo que una vez que entienda adecuadamente la tubería, se volverá más claro ...
mallardz
6
@mallardz: Es mucho más difícil hacer algo con OpenGL moderno, pero de hecho es más fácil una vez que se supera el obstáculo inicial (y mucho más rápido). El modo inmediato es bueno porque la barrera de entrada es extremadamente baja. En mi ejemplo todavía faltan los sombreadores de vértices y fragmentos que necesitará proporcionar también (bastante básicos). Un ejemplo completo de ejecución de algo que realmente se compila y funciona es bastante largo. :-)
Damon
19

Ejemplo retenido ejecutable

Damon ha proporcionado las partes clave, pero los novatos como yo buscarán un ejemplo completo.

ingrese la descripción de la imagen aquí

C Principal

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

Adaptado de Learn OpenGL , mi GitHub upstream .

Compile y ejecute en 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

De eso vemos cómo:

Al usar sombreadores:

  • los programas de sombreado de vértices y fragmentos se representan como cadenas de estilo C que contienen lenguaje GLSL ( vertexShaderSourcey fragmentShaderSource) dentro de un programa C normal que se ejecuta en la CPU

  • este programa C realiza llamadas OpenGL que compilan esas cadenas en código GPU, por ejemplo:

    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    
  • el sombreador define sus entradas esperadas y el programa C las proporciona a través de un puntero a la memoria al código de la GPU. Por ejemplo, el sombreador de fragmentos define sus entradas esperadas como una matriz de posiciones y colores de vértices:

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

    y también define una de sus salidas ourColorcomo una matriz de colores, que luego se convierte en una entrada para el sombreador de fragmentos:

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

    El programa C luego proporciona la matriz que contiene las posiciones de los vértices y los colores de la CPU a la GPU

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

Sin embargo, en el ejemplo inmediato sin sombreador, vemos que se realizan llamadas a API mágicas que dan explícitamente posiciones y colores:

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

Por lo tanto, entendemos que esto representa un modelo mucho más restringido, ya que las posiciones y colores ya no son matrices arbitrarias definidas por el usuario en la memoria, sino más bien entradas a un modelo similar a Phong.

En ambos casos, la salida renderizada normalmente va directamente al video, sin pasar por la CPU, aunque es posible leer en la CPU, por ejemplo, si desea guardarlos en un archivo: Cómo usar GLUT / OpenGL para renderizar a ¿un archivo?

La mayoría de los tutoriales de OpenGL "modernos" normalmente conservan el modo y GLFW, encontrará muchos ejemplos en:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
Recibí un informe de que, si recibe un error ERROR::SHADER::VERTEX::COMPILATION_FAILED, puede solucionarlo glfwWindowHintcomo se muestra en: stackoverflow.com/questions/52592309/… Sin embargo, no puedo reproducirlo.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Acabo de resolver el problema ejecutando el comando export MESA_GL_VERSION_OVERRIDE=3.3antes de ejecutar main.out (Debian 8) como indica una de las respuestas en la misma publicación que compartió.
Ivanzinho