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:
VertexBufferOpenGL 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]
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:
VertexBufferLegacy 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.
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:
LegacyVBOOpenGL 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).
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:
LegacyVBOSpecialized 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).
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:
LegacyVBOSpecialized Class for Position 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:
LegacyVBOLegacy 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
Base Classes
VertexBase
- class picogl.buffers.base.VertexBase(handle: int | None = None)[source]
Bases:
AbstractVertexGroupGeneric OpenGL object interface with binding lifecycle.
Provides handle + context manager, leaves binding to subclasses.
- 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:
VertexBaseModern backend (uses a real VAO)
- 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.
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:
objectLayout descriptor.
- attributes: List[AttributeSpec]
- get_attr(vbo_type: VBOType) AttributeSpec[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:
objectAttribute specification.
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
Use appropriate buffer type for your target platform
Upload data once and reuse buffers
Use VAOs for modern OpenGL when possible
Handle errors gracefully with fallbacks
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