Що таке "негайний режим"? Наведіть приклад коду.
Коли мені потрібно використовувати негайний режим замість збереженого режиму? Які плюси і мінуси використання кожного методу?
Відповіді:
Одним із прикладів "негайного режиму" є використання glBegin
та glEnd
перерва glVertex
між ними. Іншим прикладом "безпосереднього режиму" є використання glDrawArrays
з клієнтським масивом вершин (тобто не об'єктом буфера вершин).
Зазвичай ви ніколи не захочете використовувати негайний режим (за винятком, можливо, вашої першої програми "hello world"), оскільки вона застаріла і не забезпечує оптимальної продуктивності.
Причина, по якій безпосередній режим не є оптимальним, полягає в тому, що графічна карта пов’язана безпосередньо з потоком вашої програми. Драйвер не може сказати графічному процесору розпочати рендерінг раніше glEnd
, оскільки він не знає, коли ви закінчите подачу даних, і йому також потрібно передати ці дані (що він може зробити лише після glEnd
).
Подібним чином, з клієнтським масивом вершин драйвер може витягувати копію вашого масиву лише в той момент, коли ви телефонуєте glDrawArrays
, і при цьому він повинен блокувати вашу програму. Причина полягає в тому, що в іншому випадку ви можете змінити (або звільнити) пам’ять масиву до того, як драйвер захопить його. Він не може запланувати цю операцію ні раніше, ні пізніше, оскільки знає лише, що дані дійсні точно в один момент часу.
На відміну від цього, якщо ви використовуєте, наприклад, об'єкт буфера вершин, ви заповнюєте буфер даними та передаєте його OpenGL. Ваш процес більше не володіє цими даними і, отже, більше не може їх змінювати. Водій може покладатися на цей факт і може (навіть умоглядно) завантажувати дані, коли автобус вільний.
Будь-які ваші пізніші glDrawArrays
або glDrawElements
дзвінки просто потрапляють у робочу чергу і негайно повертаються (до того, як закінчити!), Тому ваша програма продовжує подавати команди, водночас драйвер працює по черзі. Також їм, швидше за все, не потрібно буде чекати надходження даних, оскільки водій міг це зробити вже набагато раніше.
Таким чином, візуалізація потоку та графічного процесора працює асинхронно, кожен компонент постійно зайнятий, що забезпечує кращу продуктивність.
Перевага безпосереднього режиму дійсно проста у використанні, але знову ж таки використання OpenGL належним чином у застарілому вигляді теж не є точно ракетною наукою - для цього потрібно лише дуже мало додаткової роботи.
Ось типовий код OpenGL "Hello World" у безпосередньому режимі:
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();
Редагувати:
За загальним запитом те саме в збереженому режимі буде виглядати приблизно так:
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);
Приклад з підтримкою запуску
Деймон надав ключові частини, але такі новачки, як я, будуть шукати повний приклад, яким можна керувати.
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;
}
Адаптовано з Learn OpenGL , мого GitHub вище .
Скомпілюйте та запустіть на 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
З цього ми бачимо, як:
При використанні шейдерів:
програми шейдерів вершин та фрагментів представлені у вигляді рядків у стилі C, що містять мову GLSL ( vertexShaderSource
і fragmentShaderSource
) всередині звичайної програми C, що працює на центральному процесорі
ця програма C робить виклики OpenGL, які компілюють ці рядки в код графічного процесора, наприклад:
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
шейдер визначає очікувані вхідні дані, а програма С подає їх через вказівник на пам'ять на код графічного процесора. Наприклад, шейдер фрагментів визначає свої очікувані входи як масив вершинних позицій та кольорів:
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec3 color;\n"
"out vec3 ourColor;\n"
а також визначає один з його виходів ourColor
як масив кольорів, який потім стає входом до шейдера фрагментів:
static const GLchar* fragmentShaderSource =
"#version 330 core\n"
"in vec3 ourColor;\n"
Потім програма C надає масив, що містить положення вершин і кольори від процесора до графічного процесора
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
Однак на безпосередньому нешейдерному прикладі ми бачимо, що здійснюються магічні виклики API, які явно дають позиції та кольори:
glColor3f( 1.0f, 0.0f, 0.0f);
glVertex3f(-0.5f, -0.5f, 0.0f);
Отже, ми розуміємо, що це представляє набагато більш обмежену модель, оскільки позиції та кольори вже не є довільними визначеними користувачем масивами в пам'яті, а скоріше лише входами до моделі, подібної до Фонгу.
В обох випадках виведений вихід зазвичай надходить прямо на відео, не проходячи назад через центральний процесор, хоча це можливо для читання в центральному процесорі, наприклад, якщо ви хочете зберегти їх у файл: Як використовувати GLUT / OpenGL для візуалізації в файл?
Більшість "сучасних" підручників OpenGL зазвичай зберігають режим і GLFW, багато прикладів ви знайдете тут:
ERROR::SHADER::VERTEX::COMPILATION_FAILED
ви можете виправити це, glfwWindowHint
як показано на: stackoverflow.com/questions/52592309/ ... Однак я не можу відтворити.
export MESA_GL_VERSION_OVERRIDE=3.3
перед запуском main.out (Debian 8), як вказує одна з відповідей на тому самому дописі, яким ви поділилися.