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?
Odpowiedzi:
Jednym z przykładów „natychmiastowym trybie” używa glBegin
i glEnd
z glVertex
pomiędzy nimi. Innym przykładem „trybu bezpośredniego” jest użycie glDrawArrays
z 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 glDrawArrays
i 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 glDrawArrays
lub glDrawElements
wywoł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:
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();
Edycja: na
zwykłe żądanie to samo w trybie zachowanym wyglądałoby mniej więcej tak:
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);
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.
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 ( vertexShaderSource
i 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ść ourColor
jako 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:
ERROR::SHADER::VERTEX::COMPILATION_FAILED
możesz go naprawić, glfwWindowHint
jak pokazano na: stackoverflow.com/questions/52592309/… Nie mogę jednak odtworzyć.
export MESA_GL_VERSION_OVERRIDE=3.3
przed uruchomieniem main.out (Debian 8), ponieważ jedna z odpowiedzi wskazuje w tym samym poście, który udostępniłeś.