GPU Module Overview (Legacy)¶
This document is being replaced by GPU Module Overview, but still here since it needs to be checked if any information here should be merged into that page.
Drawing pipeline¶
This section gives an overview of the drawing pipeline of the GPU module.
Textures¶
Textures are used to hold pixel data. Textures can be 1, 2 or 3 dimensional, cubemap and an array of 2d textures/cubemaps. The internal storage of a texture (how the pixels are stored in memory on the GPU) can be set when creating a texture.
/* Create an empty texture with HD resolution where pixels are stored as half floats. */
GPUTexture *texture = GPU_texture_create_2d("MyTexture", 1920, 1080, 1,0, GPU_RGBA16F, NULL);
Frame buffer¶
A frame buffer is a group of textures you can render onto. These textures are arranged in a fixed set of slots. The first slot is reserved for a depth/stencil buffer. The other slots can be filled with regular textures, cube maps, or layer textures.
GPU_framebuffer_ensure_config
is used to create/update a
framebuffer.
GPUFramebuffer *fb = NULL;
GPU_framebuffer_ensure_config(&fb, {
GPU_ATTACHMENT_NONE, // Slot reserved for depth/stencil buffer.
GPU_ATTACHMENT_TEXTURE(texture),
})
Shader program¶
A GPUShader is a program that runs on the GPU. The program can have several stages depending on the its usage. When rendering geometry it should at least have a vertex and fragment stage, it can have an optional geometry stage. It is not recommended to use geometry stages as Apple doesn't have support for it.
The order of execution of stages have a fixed order. When drawing geometry, first the vertex stage is performed, then the geometry stage (when available), and then the fragment stage. The logic of these stages can be loaded with GLSL-code.
GPUShader *sh_depth = GPU_shader_create_from_arrays({
.vert = (const char *[]) {my_vert_glsl_code, NULL},
.frag = (const char *[]) {my_frag_glsl_code, NULL},
});
This will create a GPUShader load and compile the vertex and fragment stage and link the stages into a program that can be used on the GPU. It also generates a GPUShaderInterface that handles lookup to input parameters (attributes, uniforms, uniform buffers, textures and shader storage buffer objects).
Cross Compilation¶
Our target is to cross compile GLSL to OpenGL3/4 and Vulkan. To create shaders that can be cross compiled the GPUShaderCreateInfo should be used when creating shaders.
This mechanism is introduced in Blender 3.1 and we are currently in the process of migrating all internal shaders we expect that all internal shaders will be migrated in Blender 3.2.
See GLSL Cross Compilation for more details.
Geometry¶
Geometry is defined by a GPUPrimType
, one index buffer (IBO) and one
or more vertex buffers (VBOs). The GPUPrimType defines how the index
buffer should be interpreted.
Indices inside the index buffer define the order how to read elements from the vertex buffer(s). Vertex buffers are a table where each row contains the data of an element. When multiple vertex buffers are used they are considered to be different columns of the same table. This matches how GL backends organize geometry on GPUs.
Index buffers¶
Index buffers can be created by using a GPUIndexBufferBuilder
GPUIndexBufBuilder ibuf
/* Construct a builder to create an index buffer that has 6 indexes.
* And the number of elements in the vertex buffer is 12. */
GPU_indexbuf_init(&ibuf GPU_PRIM_TRIS, 6, 12);
GPU_indexbuf_add_tri_verts(&ibuf, 0, 1, 2);
GPU_indexbuf_add_tri_verts(&ibuf, 2, 1, 3);
GPU_indexbuf_add_tri_verts(&ibuf, 4, 5, 6);
GPU_indexbuf_add_tri_verts(&ibuf, 6, 5, 7);
GPU_indexbuf_add_tri_verts(&ibuf, 8, 9, 10);
GPU_indexbuf_add_tri_verts(&ibuf, 10, 9, 11);
GPUIndexBuf *ibo = GPU_indexbuf_build(&builder)
Vertex buffer¶
Vertex buffers contain data and attributes inside vertex buffers should match the attributes of the shader. Before a buffer can be created, the format of the buffer should be defined.
static GPUVertFormat format = {0};
GPU_vertformat_clear(&format);
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_32, 2, GPU_FETCH_FLOAT);
Create a vertex buffer with this format and allocate the elements.
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(vbo, 12);
Fill the buffer with the data.
for (int i = 0; i < 12; i ++) {
GPU_vertbuf_attr_set(vbo, pos.id, i, positions[i]);
}
Batch¶
Use GPUBatches to draw geometry. A GPUBatch combines the geometry with a shader and its parameters and has functions to perform a draw call. To perform a draw call the next steps should be taken.
- Construct its geometry.
- Construct a GPUBatch with the geometry.
- Attach a GPUShader to the GPUBatch with the
GPU_batch_set_shader
function or attach a built in shader using theGPU_batch_program*
functions. - Set the parameters of the GPUShader using the
GPU_batch_uniform*
/GPU_batch_texture_bind
functions. - Perform a
GPU_batch_draw*
function.
This will draw on the geometry on the active frame buffer using the shader and the loaded parameters.
Feedback
GPUTextures can be used as render target or as input of a shader, but not inside the same drawing call.
Immediate mode and built in shaders¶
To ease development for drawing panels/UI buttons the GPU module provides an immediate mode. This is a wrapper on top of what is explained above, but in a more legacy opengl fashion.
Blender provides builtin shaders. This is widely used to draw the user
interface. A shader can be activated by calling
immBindBuiltinProgram
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
This shader program needs a vertex buffer with a pos attribute, and a color can be set as uniform.
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
/* Set the color attribute of the shader. */
immUniformColor4f(0.0f, 0.5f, 0.0f, 1.0f);
Fill the vertex buffer with the starting and ending position of the line to draw.
/* Construct a line index buffer with 2 elements (start point and end point to draw) */
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, 0.0, 100.0);
immVertex2f(pos, 100.0, 0.0);
immEnd();
By calling immEnd
the data drawn on the GPU.
Performance
Use GPUBatches directly in cases where performance matters. Immediate mode buffers aren't cached, which can lead to poor performance.
Compute pipeline¶
Next to drawing geometry on a texture you can also use the GPU module for computational tasks. Currently the compute pipeline should only be used after checking if the platform can handle compute tasks. There should always be a fallback implemented for the CPU if the platform doesn't support compute. We expect that in 2022 all platforms will support compute capabilities.
GPU_compute_shader_support
can be called to check if the platform
supports compute tasks.
A compute task is a variant of a GPUShader that only has a compute stage.
GPUShader *shader = GPU_shader_create_compute(compute_glsl, nullptr, nullptr, "gpu_shader_compute_2d");
To activate the program the shader should be bound to the GPU device.
GPU_shader_bind(shader);
After the bind the parameters can be loaded with the
GPU_texture_(image_)bind
, GPU_shader_uniform*
, functions.
The compute task can be called by the GPU_compute_dispatch
function.
In source/blender/gpu/tests/gpu_shader_test.cc
there are several
examples on how to use compute pipeline.
GPU_compute_dispatch(shader, 100, 100, 1);
Debugging¶
Debugging on GPUs can be difficult as you cannot step through your code with a debugger. Tools like renderdoc help to detect what a call actually does on the GPU by recording the state before and after each call.
Starting blender with the --debug-gpu
parameter blender will add
more context to ease debugging.