“Chế độ tức thì” trong OpenGL có nghĩa là gì?


81

"Chế độ tức thì" là gì? Đưa ra một ví dụ về mã.

Khi nào tôi phải sử dụng chế độ ngay lập tức thay vì chế độ giữ lại? Ưu và nhược điểm của việc sử dụng từng phương pháp là gì?

Câu trả lời:


142

Một ví dụ về "chế độ ngay lập tức" đang sử dụng glBeginglEndvới glVertexở giữa chúng. Một ví dụ khác về "chế độ tức thời" là sử dụng glDrawArraysvới mảng đỉnh máy khách (tức là không phải đối tượng bộ đệm đỉnh).

Thông thường, bạn sẽ không bao giờ muốn sử dụng chế độ tức thì (ngoại trừ có thể cho chương trình "hello world" đầu tiên của bạn) vì nó không dùng chức năng nữa và không mang lại hiệu suất tối ưu.

Lý do tại sao chế độ tức thời không tối ưu là do card đồ họa được liên kết trực tiếp với luồng chương trình của bạn. Trình điều khiển không thể yêu cầu GPU bắt đầu hiển thị trước glEndvì nó không biết khi nào bạn sẽ hoàn tất việc gửi dữ liệu và nó cũng cần chuyển dữ liệu đó (điều này chỉ có thể thực hiện sau đó glEnd).
Tương tự, với mảng đỉnh máy khách, trình điều khiển chỉ có thể kéo một bản sao của mảng của bạn vào thời điểm bạn gọi glDrawArraysvà nó phải chặn ứng dụng của bạn trong khi làm như vậy. Lý do là nếu không, bạn có thể sửa đổi (hoặc giải phóng) bộ nhớ của mảng trước khi trình điều khiển chiếm nó. Nó không thể lên lịch cho hoạt động đó sớm hơn hay muộn hơn, bởi vì nó chỉ biết rằng dữ liệu là hợp lệ chính xác tại một thời điểm.

Ngược lại với điều đó, nếu bạn sử dụng ví dụ như một đối tượng bộ đệm đỉnh, bạn điền vào bộ đệm với dữ liệu và giao nó cho OpenGL. Quy trình của bạn không còn sở hữu dữ liệu này nữa và do đó không thể sửa đổi nó nữa. Người lái xe có thể dựa vào thực tế này và có thể (thậm chí suy đoán) tải dữ liệu lên bất cứ khi nào xe buýt rảnh.
Bất kỳ cuộc gọi sau glDrawArrayshoặc glDrawElementscuộc gọi nào của bạn sẽ chỉ đi vào hàng đợi công việc và trả về ngay lập tức (trước khi thực sự kết thúc!), Vì vậy chương trình của bạn tiếp tục gửi lệnh trong khi cùng lúc trình điều khiển hoạt động từng cái một. Họ cũng có thể sẽ không cần đợi dữ liệu đến vì người lái xe đã có thể làm điều đó sớm hơn nhiều.
Do đó, luồng kết xuất và GPU chạy không đồng bộ, mọi thành phần đều bận rộn mọi lúc, điều này mang lại hiệu suất tốt hơn.

Chế độ tức thời có lợi thế là sử dụng đơn giản, nhưng sau đó sử dụng lại OpenGL đúng cách theo cách không bị phản đối cũng không phải là khoa học tên lửa chính xác - nó chỉ mất rất ít công việc bổ sung.

Đây là mã OpenGL "Hello World" điển hình ở chế độ ngay lập tức:

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();

Chỉnh sửa:
Theo yêu cầu chung, điều tương tự ở chế độ được giữ lại sẽ giống như sau:

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); 

1
Cảm ơn Damon rất nhiều, so sánh rất thú vị. Điều đó không nhìn rõ rệt hơn phức tạp đối với tôi, nhưng tôi đoán một khi tôi đúng hiểu đường ống nó sẽ trở nên rõ ràng hơn ...
mallardz

6
@mallardz: Thật khó để làm bất cứ điều gì với OpenGL hiện đại, nhưng trên thực tế sẽ dễ dàng hơn khi bạn vượt qua rào cản ban đầu (và nhanh hơn nhiều). Chế độ ngay lập tức là tốt vì rào cản gia nhập cực kỳ thấp. Ví dụ của tôi vẫn còn thiếu các trình tạo bóng đỉnh và phân mảnh mà bạn cũng cần cung cấp (những cái khá cơ bản). Một ví dụ đang chạy hoàn chỉnh về thứ gì đó thực sự biên dịch và hoạt động khá dài. :-)
Damon

19

Ví dụ được giữ lại Runnable

Damon đã cung cấp các phần quan trọng, nhưng những người mới như tôi sẽ tìm kiếm một ví dụ đầy đủ có thể chạy được.

nhập mô tả hình ảnh ở đây

C chính

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

Phỏng theo Tìm hiểu OpenGL , GitHub ngược dòng của tôi .

Biên dịch và chạy trên 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

Từ đó chúng tôi thấy cách:

Khi sử dụng shader:

  • các chương trình tô bóng đỉnh và phân đoạn đang được biểu diễn dưới dạng chuỗi kiểu C chứa ngôn ngữ GLSL ( vertexShaderSourcefragmentShaderSource) bên trong chương trình C thông thường chạy trên CPU

  • chương trình C này thực hiện các lệnh gọi OpenGL biên dịch các chuỗi đó thành mã GPU, ví dụ:

    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    
  • shader xác định các đầu vào dự kiến ​​của chúng và chương trình C cung cấp chúng thông qua một con trỏ tới bộ nhớ cho mã GPU. Ví dụ, trình đổ bóng phân mảnh xác định các đầu vào dự kiến ​​của nó là một mảng các vị trí và màu sắc đỉnh:

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

    và cũng định nghĩa một trong các đầu ra của nó ourColorlà một mảng màu, sau đó trở thành đầu vào cho bộ đổ bóng phân mảnh:

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

    Sau đó, chương trình C cung cấp mảng chứa các vị trí đỉnh và màu sắc từ CPU đến GPU

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

Tuy nhiên, trên ví dụ không đổ bóng ngay lập tức, chúng ta thấy rằng các lệnh gọi API ma thuật được thực hiện để cung cấp các vị trí và màu sắc một cách rõ ràng:

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

Do đó, chúng tôi hiểu rằng điều này đại diện cho một mô hình bị hạn chế hơn nhiều, vì các vị trí và màu sắc không phải là các mảng do người dùng xác định tùy ý trong bộ nhớ nữa, mà chỉ là đầu vào cho một mô hình giống như Phong.

Trong cả hai trường hợp, đầu ra được kết xuất thường đi thẳng đến video mà không chuyển ngược lại qua CPU, mặc dù có thể đọc cho CPU, ví dụ như nếu bạn muốn lưu chúng vào một tệp: Cách sử dụng GLUT / OpenGL để kết xuất một tập tin?

Hầu hết các hướng dẫn OpenGL "hiện đại" thường được giữ lại ở chế độ và GLFW, bạn sẽ tìm thấy nhiều ví dụ tại:


1
Tôi đã có một báo cáo rằng nếu bạn gặp lỗi ERROR::SHADER::VERTEX::COMPILATION_FAILEDthì bạn có thể sửa nó bằng glfwWindowHintcách được hiển thị tại: stackoverflow.com/questions/52592309/… Tuy nhiên, tôi không thể tạo lại.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Tôi vừa giải quyết vấn đề bằng cách chạy lệnh export MESA_GL_VERSION_OVERRIDE=3.3trước khi chạy main.out (Debian 8) như một trong những câu trả lời chỉ ra trên cùng bài đăng mà bạn đã chia sẻ.
Ivanzinho
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.