Buffers API

The buffers module provides OpenGL buffer management functionality for PicoGL, including vertex buffers, element buffers, and vertex array objects.

Core Classes

VertexArrayObject

The VertexArrayObject class manages OpenGL Vertex Array Objects (VAOs) for modern OpenGL rendering.

Example:

from picogl.backend.modern.core.vertex.array.object import VertexArrayObject

# Create VAO
vao = VertexArrayObject()

# Add vertex buffer
vao.add_vbo(index=0, data=vertices, size=3)

# Add color buffer
vao.add_vbo(index=1, data=colors, size=3)

# Add element buffer
vao.add_ebo(data=indices)

# Draw
vao.draw(mode=GL_TRIANGLES, index_count=len(indices))

VertexBufferGroup

The VertexBufferGroup class provides legacy OpenGL buffer management for systems without VAO support.

Example:

from picogl.buffers.vertex.legacy import VertexBufferGroup

# Create vertex buffer group
vbg = VertexBufferGroup()

# Add vertex buffer
vbg.add_vbo("position", vertices, 3)

# Add color buffer
vbg.add_vbo("color", colors, 3)

# Add element buffer
vbg.add_ebo(indices)

# Bind and draw
vbg.bind()
vbg.draw(mode=GL_TRIANGLES)

Buffer Classes

Modern Buffers

ModernVBO

ModernEBO

class picogl.backend.modern.core.vertex.buffer.element.ModernEBO(handle: int | None = None, data: ndarray | None = None, size: int = 3, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ELEMENT_ARRAY_BUFFER)[source]

Bases: VertexBuffer

OpenGL element data buffer (also known as an index buffer)

__init__(handle: int | None = None, data: ndarray | None = None, size: int = 3, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ELEMENT_ARRAY_BUFFER)[source]
set_element_attributes(data: ndarray, size: int, dtype: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_STATIC_DRAW)[source]
Parameters:
  • data – np.ndarray

  • size – int

  • dtype – int

Returns:

None

configure()[source]
Returns:

None

Legacy Buffers

LegacyVBO

class picogl.backend.legacy.core.vertex.buffer.vertex.LegacyVBO(handle: int | None = None, data: ndarray | None = None, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ARRAY_BUFFER, configure: bool = True, size: int = 3, stride: int = 0, dtype: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_FLOAT, pointer: c_void_p = c_void_p(None))[source]

Bases: VertexBuffer

Legacy OpenGL Vertex Buffer Object (VBO) or Element Buffer Object (EBO).

__init__(handle: int | None = None, data: ndarray | None = None, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ARRAY_BUFFER, configure: bool = True, size: int = 3, stride: int = 0, dtype: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_FLOAT, pointer: c_void_p = c_void_p(None))[source]
set_data(data: ndarray, usage: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_STATIC_DRAW) None[source]

Upload data to the GPU.

Parameters:
  • data – NumPy array containing the data to upload.

  • usage – OpenGL usage hint (e.g., GL_STATIC_DRAW).

Raises:

ValueError – If the data is not a NumPy array.

allocate(data: ndarray, usage: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_STATIC_DRAW)[source]

Initial allocation (glBufferData)

update_data(data: ndarray, offset: int = 0)[source]

Fast path update (glBufferSubData)

configure()[source]

Configure the buffer (default implementation does nothing).

draw(index_count: int, index_type: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_UNSIGNED_INT, mode: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_LINES)[source]

Draw call (must be implemented in subclasses).

LegacyPositionVBO

class picogl.backend.legacy.core.vertex.buffer.position.LegacyPositionVBO(handle: int | None = None, data: ndarray | None = None, size: int = 3, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ARRAY_BUFFER, dtype: int = OpenGL.raw.GL._types.GL_FLOAT)[source]

Bases: LegacyVBO

OpenGL buffer class specialized for storing and managing position data, commonly used for rendering ribbons_legacy-like meshdata.

Inherits from LegacyVBO and adds behavior specific to position data, such as setting up the vertex pointer and handling data uploads.

SUPPORTED_GL_TYPES = {OpenGL.raw.GL.VERSION.GL_1_1.GL_DOUBLE, OpenGL.raw.GL._types.GL_BYTE, OpenGL.raw.GL._types.GL_FLOAT, OpenGL.raw.GL._types.GL_INT, OpenGL.raw.GL._types.GL_SHORT}[source]
__init__(handle: int | None = None, data: ndarray | None = None, size: int = 3, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ARRAY_BUFFER, dtype: int = OpenGL.raw.GL._types.GL_FLOAT)[source]

Initialize the position VBO.

draw_arrays(count: int | None = None, mode: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_TRIANGLES)[source]
draw(index_count: int | None = None, index_type: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_UNSIGNED_INT, mode: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_TRIANGLES)[source]

Draw the buffer.

Parameters:
  • index_count – Number of indices to draw (default: self.index_count).

  • index_type – Data type of indices (e.g., GL_UNSIGNED_INT).

  • mode – OpenGL drawing mode (e.g., GL_TRIANGLES).

configure()[source]

Configure the vertex pointer for the position buffer.

LegacyVBO.__enter__ has already bound this buffer; do not unbind here or the vertex array may lose its buffer object binding before glDraw*.

LegacyColorVBO

class picogl.backend.legacy.core.vertex.buffer.color.LegacyColorVBO(handle: int | None = None, data: ndarray | None = None, size: int = 3, dtype: int = OpenGL.GL.GL_FLOAT)[source]

Bases: LegacyVBO

Specialized VBO class for colour attributes.

__init__(handle: int | None = None, data: ndarray | None = None, size: int = 3, dtype: int = OpenGL.GL.GL_FLOAT)[source]

Initialize a colour VBO.

Parameters:
  • handle – Existing OpenGL buffer handle (optional).

  • data – Numpy array with colour data (optional).

  • size – Number of components per colour (3=RGB, 4=RGBA).

configure()[source]

Configure vertex attribute pointer for colors.

LegacyNormalVBO

class picogl.backend.legacy.core.vertex.buffer.normal.LegacyNormalVBO(handle: int | None = None, data: ndarray | None = None, size: int = 3, dtype: int = OpenGL.raw.GL._types.GL_FLOAT)[source]

Bases: LegacyVBO

Specialized Class for Position Buffers

__init__(handle: int | None = None, data: ndarray | None = None, size: int = 3, dtype: int = OpenGL.raw.GL._types.GL_FLOAT)[source]

constructor

configure()[source]

Configure attributes specific to position atoms_buffers

LegacyEBO

class picogl.backend.legacy.core.vertex.buffer.element.LegacyEBO(handle: int | None = None, data: ndarray | None = None, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ELEMENT_ARRAY_BUFFER, size: int = 3, dtype: int = OpenGL.GL.GL_FLOAT)[source]

Bases: LegacyVBO

Legacy Element Buffer Object (EBO)

__init__(handle: int | None = None, data: ndarray | None = None, target: int = OpenGL.raw.GL.VERSION.GL_1_5.GL_ELEMENT_ARRAY_BUFFER, size: int = 3, dtype: int = OpenGL.GL.GL_FLOAT)[source]

constructor

draw(index_count: int, index_type: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_UNSIGNED_INT, mode: int = OpenGL.raw.GL.VERSION.GL_1_0.GL_LINES, pointer: int = c_void_p(None))[source]
Parameters:
  • index_count – int

  • index_type – int e.g. GL_UNSIGNED_INT

  • mode – int e.g. GL_LINES

  • pointer – int

Returns:

None

configure()[source]
Returns:

None

Element Buffers don’t use vertex attributes—nothing to configure.

Base Classes

VertexBase

class picogl.buffers.base.VertexBase(handle: int | None = None)[source]

Bases: AbstractVertexGroup

Generic OpenGL object interface with binding lifecycle.

Provides handle + context manager, leaves binding to subclasses.

__init__(handle: int | None = None)[source]
bind()[source]

Bind the underlying VAO/state for rendering.

unbind()[source]

Optionally unbind the VAO/state.

delete()[source]

Release resources (VAO or equivalent).

attach_buffers(nbo=None, cbo=None, vbo=None, ebo=None) None[source]

Attach the buffers that the VAO/group should coordinate.

set_layout(layout: LayoutDescriptor) None[source]

Define the attribute layout for this VAO/group.

The VertexBase class provides the base functionality for all vertex buffer classes.

VertexBuffer

class picogl.buffers.vertex.modern.ModernVertexArrayGroup(handle: int | None = None)[source]

Bases: VertexBase

Modern backend (uses a real VAO)

init()[source]
attach_buffers(nbo=None, cbo=None, vbo=None, ebo=None) None[source]

Attach the buffers that the VAO/group should coordinate.

set_layout(layout: LayoutDescriptor) None[source]
Parameters:

layout – LayoutDescriptor: The layout descriptor to define the vertex attribute format.

Raises:

None

Sets the layout for the rendering setup by binding the buffers and configuring the attributes. The state is stored in the Vertex Array Object (VAO). This method assumes a single Vertex Buffer Object (VBO) holds all position data but can be adapted as required. Handles optional usage of Normal Buffer Object (NBO) and Element Buffer Object (EBO) if present.

bind() None[source]

Bind the underlying VAO/state for rendering.

unbind() None[source]

Optionally unbind the VAO/state.

delete() None[source]

Release resources (VAO or equivalent).

The VertexBuffer class provides the base functionality for vertex buffer objects.

Data Structures

LayoutDescriptor

class picogl.buffers.factory.layout.LayoutDescriptor(attributes: List[AttributeSpec], _cache: dict[VBOType, AttributeSpec] | None = None)[source]

Bases: object

Layout descriptor.

attributes: List[AttributeSpec]
get_attr(vbo_type: VBOType) AttributeSpec[source]
has_attr(vbo_type: VBOType) bool[source]
as_dict() dict[VBOType, AttributeSpec][source]
property attr_dict: dict[VBOType, AttributeSpec]

dict

__init__(attributes: List[AttributeSpec], _cache: dict[VBOType, AttributeSpec] | None = None) None

The LayoutDescriptor class describes the layout of vertex attributes in a buffer.

Example:

from picogl.buffers.factory.layout import create_layout

# Create layout descriptor
layout = create_layout([
    AttributeSpec(name="position", size=3, type=GL_FLOAT),
    AttributeSpec(name="color", size=3, type=GL_FLOAT),
    AttributeSpec(name="normal", size=3, type=GL_FLOAT)
])

AttributeSpec

class picogl.buffers.attributes.AttributeSpec(name: str, index: int, size: int, type: int, normalized: bool, stride: int, offset: int, vbo_type: VBOType = VBOType.VBO)[source]

Bases: object

Attribute specification.

name: str[source]
index: int[source]
size: int[source]
type: int[source]
normalized: bool[source]
stride: int[source]
offset: int[source]
vbo_type: VBOType = 'vbo'[source]
__init__(name: str, index: int, size: int, type: int, normalized: bool, stride: int, offset: int, vbo_type: VBOType = VBOType.VBO) None

The AttributeSpec class describes a single vertex attribute.

Example:

from picogl.buffers.attributes import AttributeSpec

# Create attribute specification
position_attr = AttributeSpec(
    name="position",
    size=3,
    type=GL_FLOAT,
    normalized=False,
    stride=0,
    offset=0
)

Buffer Management

Creating Buffers

Modern OpenGL (VAO/VBO):

from picogl.backend.modern.core.vertex.array.object import VertexArrayObject

# Create VAO
vao = VertexArrayObject()

# Add vertex buffer
vao.add_vbo(index=0, data=vertices, size=3)

# Add color buffer
vao.add_vbo(index=1, data=colors, size=3)

# Add element buffer
vao.add_ebo(data=indices)

Legacy OpenGL (VBO only):

from picogl.buffers.vertex.legacy import VertexBufferGroup

# Create vertex buffer group
vbg = VertexBufferGroup()

# Add vertex buffer
vbg.add_vbo(name="position", data=vertices, size=3)

# Add color buffer
vbg.add_vbo(name="color", data=colors, size=3)

# Add element buffer
vbg.add_ebo(data=indices)

Binding Buffers

Modern OpenGL:

# Bind VAO (automatically binds all VBOs)
with vao:
    # Draw calls here
    vao.draw(mode=GL_TRIANGLES, index_count=len(indices))

Legacy OpenGL:

# Bind vertex buffer group
vbg.bind()

# Draw calls here
vbg.draw(mode=GL_TRIANGLES)

# Unbind
vbg.unbind()

Buffer Types

Vertex Buffers

Vertex buffers store vertex data such as positions, colors, normals, and texture coordinates.

Position Buffer:

# 3D positions (N, 3) array
vertices = np.array([
    [0, 0, 0],  # Vertex 0
    [1, 0, 0],  # Vertex 1
    [0, 1, 0]   # Vertex 2
], dtype=np.float32)

Color Buffer:

# RGB colors (N, 3) array
colors = np.array([
    [1, 0, 0],  # Red
    [0, 1, 0],  # Green
    [0, 0, 1]   # Blue
], dtype=np.float32)

Normal Buffer:

# Surface normals (N, 3) array
normals = np.array([
    [0, 0, 1],  # Normal 0
    [0, 0, 1],  # Normal 1
    [0, 0, 1]   # Normal 2
], dtype=np.float32)

UV Buffer:

# Texture coordinates (N, 2) array
uvs = np.array([
    [0, 0],  # UV 0
    [1, 0],  # UV 1
    [0, 1]   # UV 2
], dtype=np.float32)

Element Buffers

Element buffers store indices that define the topology of the mesh.

Element Buffer:

# Triangle indices (M, 3) array
indices = np.array([
    [0, 1, 2]  # Triangle connecting vertices 0, 1, 2
], dtype=np.uint32)

Buffer Operations

Uploading Data

Modern OpenGL:

# Upload vertex data
vao.add_vbo(index=0, data=vertices, size=3)

# Upload color data
vao.add_vbo(index=1, data=colors, size=3)

# Upload element data
vao.add_ebo(data=indices)

Legacy OpenGL:

# Upload vertex data
vbg.add_vbo(index=0, vbo_name="position", data=vertices, size=3)

# Upload color data
vbg.add_vbo(index=1, vbo_name="color", data=colors, size=3)

# Upload element data
vbg.add_ebo(data=indices)

Drawing

Modern OpenGL:

# Draw with VAO
with vao:
    vao.draw(mode=GL_TRIANGLES, index_count=len(indices))

Legacy OpenGL:

# Draw with vertex buffer group
vbg.bind()
vbg.draw(mode=GL_TRIANGLES)
vbg.unbind()

Cleanup

Modern OpenGL:

# Cleanup VAO
vao.delete()

Legacy OpenGL:

# Cleanup vertex buffer group
vbg.delete()

Error Handling

Buffer operations include comprehensive error handling:

OpenGL Context Errors: Check for valid OpenGL context Buffer Creation Errors: Handle OpenGL buffer creation failures Data Upload Errors: Validate data before uploading Drawing Errors: Handle OpenGL drawing failures

Example:

try:
    vao.add_vbo(index=0, data=vertices, size=3)
except OpenGLError as e:
    print(f"OpenGL error: {e}")
    # Handle gracefully
except ValueError as e:
    print(f"Data error: {e}")
    # Validate data
except Exception as e:
    print(f"Unexpected error: {e}")
    # Handle gracefully

Performance Considerations

Modern Buffers (VAO/VBO): * Best performance with modern OpenGL * Efficient GPU memory usage * Fast drawing operations * Requires OpenGL 3.0+ support

Legacy Buffers (VBO only): * Compatible with older OpenGL versions * Good performance on older hardware * Uses client state management * Limited feature set

Immediate Mode: * Maximum compatibility * Uses glBegin/glEnd for rendering * Lower performance but works everywhere * No buffer management

Best Practices

  1. Use appropriate buffer type for your target platform

  2. Upload data once and reuse buffers

  3. Use VAOs for modern OpenGL when possible

  4. Handle errors gracefully with fallbacks

  5. Clean up resources when done

Example:

# Choose buffer type based on OpenGL support
if has_modern_opengl():
    vao = VertexArrayObject()
    vao.add_vbo(index=0, data=vertices, size=3)
elif has_legacy_opengl():
    vbg = VertexBufferGroup()
    vbg.add_vbo("position", vertices, 3)
else:
    # Use immediate mode fallback
    pass