Skip to main content

Overview

VertexBuffer holds a set of buffers that define the geometry of a Renderable. The geometry is defined by vertex attributes such as position, color, normals, tangents, etc. There is no need to have a 1-to-1 mapping between attributes and buffers. A buffer can hold data for several attributes (interleaved attributes). The buffers themselves are GPU resources, so mutating their data can be relatively slow.

Key Concepts

  • Attributes: Vertex data like position, color, normals, UVs
  • Buffers: GPU memory regions holding attribute data
  • Interleaving: Storing multiple attributes in a single buffer
  • Buffer Objects: Shared GPU data that can be swapped between VertexBuffers
It is possible, and even encouraged, to use a single vertex buffer for several Renderables.

Builder

The Builder class is used to construct VertexBuffer objects.

Constructor

Builder() noexcept
Creates a new VertexBuffer::Builder instance.

Configuration Methods

bufferCount

Builder& bufferCount(uint8_t bufferCount) noexcept
Defines how many buffers will be created in this vertex buffer set.
bufferCount
uint8_t
required
Number of buffers in this vertex buffer set (maximum is 8, default is 0)
Returns: Reference to this Builder for chaining calls.
This call is mandatory. Buffers are later referenced by index from 0 to bufferCount - 1.

vertexCount

Builder& vertexCount(uint32_t vertexCount) noexcept
Sets the size of each buffer in the set in vertices.
vertexCount
uint32_t
required
Number of vertices in each buffer in this set
Returns: Reference to this Builder for chaining calls.

enableBufferObjects

Builder& enableBufferObjects(bool enabled = true) noexcept
Allows buffers to be swapped out and shared using BufferObject.
enabled
bool
If true, enables buffer object mode (default is false)
Returns: Reference to this Builder for chaining calls.
If buffer objects mode is enabled, clients must call setBufferObjectAt() rather than setBufferAt(). This allows sharing of data between VertexBuffer objects.

Attribute Methods

attribute

Builder& attribute(
    VertexAttribute attribute,
    uint8_t bufferIndex,
    AttributeType attributeType,
    uint32_t byteOffset = 0,
    uint8_t byteStride = 0
) noexcept
Sets up an attribute for this vertex buffer set.
attribute
VertexAttribute
required
The attribute to set up
bufferIndex
uint8_t
required
The index of the buffer containing the data for this attribute (0 to bufferCount - 1)
attributeType
AttributeType
required
The type of the attribute data (e.g., FLOAT2, FLOAT3, UBYTE4)
byteOffset
uint32_t
Offset in bytes into the buffer (default 0)
byteStride
uint8_t
Stride in bytes to the next element of this attribute. When set to 0, the attribute size is used (default 0)
Returns: Reference to this Builder for chaining calls. VertexAttribute enum values:
  • POSITION - Vertex position (required)
  • TANGENTS - Tangent space quaternion (also encodes normals)
  • COLOR - Vertex color
  • UV0 - Texture coordinates set 0
  • UV1 - Texture coordinates set 1
  • BONE_INDICES - Skinning bone indices (up to 4)
  • BONE_WEIGHTS - Skinning bone weights (up to 4)
  • CUSTOM0 through CUSTOM7 - Custom attributes
AttributeType (ElementType) enum values:
  • BYTE - Signed 8-bit integer
  • BYTE2 - 2-component signed 8-bit integer
  • BYTE3 - 3-component signed 8-bit integer
  • BYTE4 - 4-component signed 8-bit integer
  • UBYTE - Unsigned 8-bit integer
  • UBYTE2 - 2-component unsigned 8-bit integer
  • UBYTE3 - 3-component unsigned 8-bit integer
  • UBYTE4 - 4-component unsigned 8-bit integer
  • SHORT - Signed 16-bit integer
  • SHORT2 - 2-component signed 16-bit integer
  • SHORT3 - 3-component signed 16-bit integer
  • SHORT4 - 4-component signed 16-bit integer
  • USHORT - Unsigned 16-bit integer
  • USHORT2 - 2-component unsigned 16-bit integer
  • USHORT3 - 3-component unsigned 16-bit integer
  • USHORT4 - 4-component unsigned 16-bit integer
  • INT - Signed 32-bit integer
  • UINT - Unsigned 32-bit integer
  • FLOAT - 32-bit float
  • FLOAT2 - 2-component 32-bit float
  • FLOAT3 - 3-component 32-bit float
  • FLOAT4 - 4-component 32-bit float
  • HALF - 16-bit float
  • HALF2 - 2-component 16-bit float
  • HALF3 - 3-component 16-bit float
  • HALF4 - 4-component 16-bit float
TANGENTS must be specified as a quaternion. Not all backends support 3-component attributes that are not floats.
Example:
// Interleaved position and color in buffer 0
struct Vertex {
    float position[2];  // 8 bytes
    uint32_t color;     // 4 bytes
};  // Total: 12 bytes

VertexBuffer::Builder()
    .vertexCount(3)
    .bufferCount(1)
    .attribute(VertexAttribute::POSITION, 0,
               VertexBuffer::AttributeType::FLOAT2, 0, 12)
    .attribute(VertexAttribute::COLOR, 0,
               VertexBuffer::AttributeType::UBYTE4, 8, 12)
    .normalized(VertexAttribute::COLOR)
    .build(*engine);

normalized

Builder& normalized(
    VertexAttribute attribute,
    bool normalize = true
) noexcept
Sets whether a given attribute should be normalized.
attribute
VertexAttribute
required
Attribute to set the normalization flag for
normalize
bool
true to automatically normalize the attribute (default true)
Returns: Reference to this Builder for chaining calls.
By default, attributes are not normalized. A normalized attribute is mapped between 0 and 1 in the shader. This applies only to integer types.

Advanced Features

advancedSkinning

Builder& advancedSkinning(bool enabled) noexcept
Sets advanced skinning mode.
enabled
bool
required
If true, enables advanced skinning mode (default is false)
Returns: Reference to this Builder for chaining calls.
When enabled, bone data, indices and weights are set using RenderableManager::Builder::boneIndicesAndWeights(). Works with or without buffer objects.

Asynchronous Creation

async

Builder& async(
    backend::CallbackHandler* handler,
    AsyncCompletionCallback callback = nullptr,
    void* user = nullptr
) noexcept
Specifies a callback that will execute once the resource’s data has been fully allocated within GPU memory.
handler
CallbackHandler*
Handler to dispatch the callback or nullptr for the default handler
callback
AsyncCompletionCallback
Function to be called upon completion of asynchronous creation
user
void*
Custom data passed as second argument to the callback
Returns: Reference to this Builder for chaining calls.
To use this method, the engine must be configured for asynchronous operation.

build

VertexBuffer* build(Engine& engine)
Creates the VertexBuffer object and returns a pointer to it.
engine
Engine&
required
Reference to the filament::Engine to associate this VertexBuffer with
Returns: Pointer to the newly created VertexBuffer object.

Instance Methods

getVertexCount

size_t getVertexCount() const noexcept
Returns the vertex count. Returns: Number of vertices in this vertex buffer set.

Setting Buffer Data

setBufferAt

void setBufferAt(
    Engine& engine,
    uint8_t bufferIndex,
    BufferDescriptor&& buffer,
    uint32_t byteOffset = 0
)
Copy-initializes the specified buffer from the given buffer data.
engine
Engine&
required
Reference to the filament::Engine
bufferIndex
uint8_t
required
Index of the buffer to initialize (0 to bufferCount - 1)
buffer
BufferDescriptor&&
required
BufferDescriptor pointing to raw, untyped data to be copied into the buffer
byteOffset
uint32_t
Offset in bytes into the buffer (must be multiple of 4, default 0)
Do not use this if you called enableBufferObjects() on the Builder.
Example:
struct Vertex {
    float position[2];
    uint32_t color;
};

Vertex vertices[] = {
    {{1.0f, 0.0f}, 0xffff0000u},
    {{0.0f, 1.0f}, 0xff00ff00u},
    {{-1.0f, 0.0f}, 0xff0000ffu}
};

vb->setBufferAt(*engine, 0,
    VertexBuffer::BufferDescriptor(
        vertices,
        sizeof(vertices),
        nullptr  // no callback needed
    )
);

setBufferAtAsync

AsyncCallId setBufferAtAsync(
    Engine& engine,
    uint8_t bufferIndex,
    BufferDescriptor&& buffer,
    uint32_t byteOffset,
    backend::CallbackHandler* handler,
    AsyncCompletionCallback callback,
    void* user = nullptr
)
Asynchronous version of setBufferAt().
handler
CallbackHandler*
Handler to dispatch the callback or nullptr for the default handler
callback
AsyncCompletionCallback
required
Function to be called upon completion
user
void*
Custom data passed to the callback
Returns: AsyncCallId that can be used to cancel the operation.
To use this method, the engine must be configured for asynchronous operation. Do not use if enableBufferObjects() was called.

setBufferObjectAt

void setBufferObjectAt(
    Engine& engine,
    uint8_t bufferIndex,
    BufferObject const* bufferObject
)
Swaps in the given buffer object.
engine
Engine&
required
Reference to the filament::Engine
bufferIndex
uint8_t
required
Index of the buffer slot (0 to bufferCount - 1)
bufferObject
BufferObject const*
required
The handle to the GPU data for this buffer slot
To use this, you must first call enableBufferObjects() on the Builder.
Example:
// Create shared buffer object
BufferObject* bufferObject = BufferObject::Builder()
    .size(sizeof(vertices))
    .build(*engine);

bufferObject->setBuffer(*engine,
    BufferObject::BufferDescriptor(
        vertices, sizeof(vertices), nullptr
    )
);

// Share between multiple vertex buffers
vb1->setBufferObjectAt(*engine, 0, bufferObject);
vb2->setBufferObjectAt(*engine, 0, bufferObject);

setBufferObjectAtAsync

AsyncCallId setBufferObjectAtAsync(
    Engine& engine,
    uint8_t bufferIndex,
    BufferObject const* bufferObject,
    backend::CallbackHandler* handler,
    AsyncCompletionCallback callback,
    void* user = nullptr
)
Asynchronous version of setBufferObjectAt().
handler
CallbackHandler*
Handler to dispatch the callback or nullptr for the default handler
callback
AsyncCompletionCallback
required
Function to be called upon completion
user
void*
Custom data passed to the callback
Returns: AsyncCallId that can be used to cancel the operation.

Creation Status

isCreationComplete

bool isCreationComplete() const noexcept
Checks if the resource has finished creation. Returns: true if the resource is fully created and ready for use.
For resources created asynchronously, this returns true only after all related asynchronous tasks are complete. For normally created resources, this always returns true.

Buffer Descriptor

using BufferDescriptor = backend::BufferDescriptor;
Describes a buffer and provides a callback for when the data is consumed. Constructor:
BufferDescriptor(
    void const* buffer,
    size_t size,
    Callback callback = nullptr,
    void* user = nullptr
)
buffer
void const*
required
Pointer to the buffer data
size
size_t
required
Size of the buffer in bytes
callback
Callback
Optional callback invoked when the data has been consumed
user
void*
Optional user pointer passed to the callback

Example Usage

Simple Interleaved Vertex Buffer

#include <filament/Engine.h>
#include <filament/VertexBuffer.h>

struct Vertex {
    float position[2];
    uint32_t color;
};

Vertex vertices[] = {
    {{1.0f, 0.0f}, 0xffff0000u},   // Red
    {{0.0f, 1.0f}, 0xff00ff00u},   // Green
    {{-1.0f, 0.0f}, 0xff0000ffu}   // Blue
};

// Create vertex buffer
VertexBuffer* vb = VertexBuffer::Builder()
    .vertexCount(3)
    .bufferCount(1)
    .attribute(VertexAttribute::POSITION, 0,
               VertexBuffer::AttributeType::FLOAT2, 0, 12)
    .attribute(VertexAttribute::COLOR, 0,
               VertexBuffer::AttributeType::UBYTE4, 8, 12)
    .normalized(VertexAttribute::COLOR)
    .build(*engine);

// Upload data
vb->setBufferAt(*engine, 0,
    VertexBuffer::BufferDescriptor(
        vertices, sizeof(vertices), nullptr
    )
);

// Cleanup
engine->destroy(vb);

Non-Interleaved Vertex Buffer

// Separate position and color buffers
float positions[] = {
    1.0f, 0.0f,   // vertex 0
    0.0f, 1.0f,   // vertex 1
    -1.0f, 0.0f   // vertex 2
};

uint32_t colors[] = {
    0xffff0000u,  // Red
    0xff00ff00u,  // Green
    0xff0000ffu   // Blue
};

// Create vertex buffer with 2 buffers
VertexBuffer* vb = VertexBuffer::Builder()
    .vertexCount(3)
    .bufferCount(2)
    .attribute(VertexAttribute::POSITION, 0,
               VertexBuffer::AttributeType::FLOAT2)
    .attribute(VertexAttribute::COLOR, 1,
               VertexBuffer::AttributeType::UBYTE4)
    .normalized(VertexAttribute::COLOR)
    .build(*engine);

// Upload position data to buffer 0
vb->setBufferAt(*engine, 0,
    VertexBuffer::BufferDescriptor(
        positions, sizeof(positions), nullptr
    )
);

// Upload color data to buffer 1
vb->setBufferAt(*engine, 1,
    VertexBuffer::BufferDescriptor(
        colors, sizeof(colors), nullptr
    )
);

Using Buffer Objects for Sharing

// Create vertex buffer with buffer objects enabled
VertexBuffer* vb1 = VertexBuffer::Builder()
    .vertexCount(100)
    .bufferCount(1)
    .enableBufferObjects(true)
    .attribute(VertexAttribute::POSITION, 0,
               VertexBuffer::AttributeType::FLOAT3)
    .build(*engine);

VertexBuffer* vb2 = VertexBuffer::Builder()
    .vertexCount(100)
    .bufferCount(1)
    .enableBufferObjects(true)
    .attribute(VertexAttribute::POSITION, 0,
               VertexBuffer::AttributeType::FLOAT3)
    .build(*engine);

// Create shared buffer object
BufferObject* sharedBuffer = BufferObject::Builder()
    .size(100 * sizeof(float) * 3)
    .build(*engine);

// Upload data once
sharedBuffer->setBuffer(*engine,
    BufferObject::BufferDescriptor(
        vertexData, dataSize, nullptr
    )
);

// Share between vertex buffers
vb1->setBufferObjectAt(*engine, 0, sharedBuffer);
vb2->setBufferObjectAt(*engine, 0, sharedBuffer);

Dynamic Vertex Buffer with Callback

void onBufferConsumed(void* buffer, size_t size, void* user) {
    // Buffer has been consumed by GPU, safe to free
    delete[] static_cast<uint8_t*>(buffer);
}

// Allocate dynamic buffer
uint8_t* dynamicVertices = new uint8_t[vertexDataSize];
// ... fill dynamicVertices ...

vb->setBufferAt(*engine, 0,
    VertexBuffer::BufferDescriptor(
        dynamicVertices,
        vertexDataSize,
        onBufferConsumed,  // Callback to free memory
        nullptr            // User data
    )
);

Build docs developers (and LLMs) love