¿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?
Respuestas:
Un ejemplo de "modo inmediato" es usar glBegin
y glEnd
con glVertex
entre ellos. Otro ejemplo de "modo inmediato" es utilizarlo glDrawArrays
con 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 glDrawArrays
y 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 glDrawArrays
o glDrawElements
las 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:
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(-0.87f, -0.5f);
glEnd();
Editar:
por solicitud común, lo mismo en modo retenido se vería algo así:
float verts = {...};
float colors = {...};
static_assert(sizeof(verts) == sizeof(colors), "");
// not really needed for this example, but mandatory in core profile after GL 3.2
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint buf[2];
glGenBuffers(2, buf);
// assuming a layout(location = 0) for position and
// layout(location = 1) for color in the vertex shader
// vertex positions
glBindBuffer(GL_ARRAY_BUFFER, buf[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
// copy/paste for color... same code as above. A real, non-trivial program would
// normally use a single buffer for both -- usually with stride (5th param) to
// glVertexAttribPointer -- that presumes interleaving the verts and colors arrays.
// It's somewhat uglier but has better cache performance (ugly does however not
// matter for a real program, since data is loaded from a modelling-tool generated
// binary file anyway).
glBindBuffer(GL_ARRAY_BUFFER, buf[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
Ejemplo retenido ejecutable
Damon ha proporcionado las partes clave, pero los novatos como yo buscarán un ejemplo completo.
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 ( vertexShaderSource
y 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 ourColor
como 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:
ERROR::SHADER::VERTEX::COMPILATION_FAILED
, puede solucionarlo glfwWindowHint
como se muestra en: stackoverflow.com/questions/52592309/… Sin embargo, no puedo reproducirlo.
export MESA_GL_VERSION_OVERRIDE=3.3
antes de ejecutar main.out (Debian 8) como indica una de las respuestas en la misma publicación que compartió.