Buffer is a dedicated area of GPU memory. Can store anything in bytes.

Creating a buffer is essentially allocating an area of memory into which data is later written. Therefore, when creating an empty buffer, it may contain memory fragments of deleted objects. Example of creating an empty buffer:

buf = ctx.buffer(reserve=1)

The reserve parameter specifies how many bytes should be allocated, the minimum number is 1.

The buffer is cleared by writing zeros to the allocated memory area. However, this must be done manually:


ModernGL allows you to create 2 types of buffer: dynamic and non-dynamic. To do this, when creating a buffer, use the keyword argument dynamic=True/False (by default False):

buf = ctx.buffer(reserve=32, dynamic=True)


Using the dynamic=True parameter tells the GPU that actions with this Buffer will be performed very often. This parameter is optional, but is recommended if the Buffer is used frequently.

Later, using the Buffer.orphan() function, you can change the buffer size at any time:


After changing the buffer size, you will need to write data there. This is done using the Buffer.write() function. This function will write data from RAM to GPU memory:

buf.write(b'any bytes data')

However, if the size of this buffer was changed after it was added to VertexArray, then when calling VertexArray.render() you will need to specify the new number of vertices in the vertices parameter. For example:

# If from the contents of this buffer every 12 bytes fall on one vertex.
vao.render(vertices=buf.size // 4 // 3)

The same will need to be done when calling the VertexArray.transform() function:

# If from the contents of this buffer every 12 bytes fall on one vertex.
# output_buf - the buffer into which the transformation will be performed.
vao.transform(output_buf, vertices=buf.size // 4 // 3)

After VertexArray.transform() writes data to output_buf using a vertex shader, you may need to read it — use Buffer.read(). This function will read data from GPU memory into RAM:

bytes_data = buf.read()


Transferring data between RAM and GPU memory comes at a huge performance cost. It is recommended to use Buffer.write() and Buffer.read() as little as possible.

If you just need to copy data between buffers, look towards the Context.copy_buffer() function:

ctx.copy_buffer(destination_buf, source_buf)

In our example, we simply create a static buffer and write data immediately when it is created:

vbo = ctx.buffer(vertices.astype("f4").tobytes())

We called it VBO (Vertex Buffer Object) because we will store vertex data in this buffer.

Entire source

 1import moderngl
 2import numpy as np
 4ctx = moderngl.create_context(standalone=True)
 6prog = ctx.program(
 7    vertex_shader="""
 8        #version 330
10        in vec2 in_vert;
11        in vec3 in_color;
13        out vec3 v_color;
15        void main() {
16            v_color = in_color;
17            gl_Position = vec4(in_vert, 0.0, 1.0);
18        }
19    """,
20    fragment_shader="""
21        #version 330
23        in vec3 v_color;
25        out vec3 f_color;
27        void main() {
28            f_color = v_color;
29        }
30    """,
33x = np.linspace(-1.0, 1.0, 50)
34y = np.random.rand(50) - 0.5
35r = np.zeros(50)
36g = np.ones(50)
37b = np.zeros(50)
39vertices = np.dstack([x, y, r, g, b])
41vbo = ctx.buffer(vertices.astype("f4").tobytes())

Proceed to the next step.