Overview
IndexBuffer contains vertex indices into a VertexBuffer. Indices can be 16-bit or 32-bit integers. The buffer itself is a GPU resource, therefore mutating the data can be relatively slow. Typically these buffers are constant.
It is possible, and even encouraged, to use a single index buffer for several Renderables.
IndexType Enum
enum class IndexType : uint8_t {
USHORT, // 16-bit indices
UINT // 32-bit indices
}
Specifies the data type of indices in the buffer.
USHORT - 16-bit unsigned integer indices (maximum 65,535 vertices)
UINT - 32-bit unsigned integer indices (billions of vertices)
Builder
The Builder class is used to construct IndexBuffer objects.
Constructor
Creates a new IndexBuffer::Builder instance.
Configuration Methods
indexCount
Builder& indexCount(uint32_t indexCount) noexcept
Sets the size of the index buffer in elements.
Number of indices the IndexBuffer can hold
Returns: Reference to this Builder for chaining calls.
Example:
// For a mesh with 100 triangles
Builder().indexCount(300) // 100 triangles * 3 indices each
bufferType
Builder& bufferType(IndexType indexType) noexcept
Sets the type of the index buffer, 16-bit or 32-bit.
Type of indices stored in the IndexBuffer
Returns: Reference to this Builder for chaining calls.
Use USHORT (16-bit) when your vertex count is less than 65,536. Use UINT (32-bit) for larger meshes.
Example:
Builder()
.indexCount(300)
.bufferType(IndexBuffer::IndexType::USHORT)
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 to dispatch the callback or nullptr for the default handler
Function to be called upon completion of asynchronous creation
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
IndexBuffer* build(Engine& engine)
Creates the IndexBuffer object and returns a pointer to it.
Reference to the filament::Engine to associate this IndexBuffer with
Returns: Pointer to the newly created IndexBuffer object.
After creation, the index buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize it.
Example:
IndexBuffer* ib = IndexBuffer::Builder()
.indexCount(3)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
Instance Methods
setBuffer
void setBuffer(
Engine& engine,
BufferDescriptor&& buffer,
uint32_t byteOffset = 0
)
Copy-initializes a region of this IndexBuffer from the data provided.
Reference to the filament::Engine this IndexBuffer is associated with
buffer
BufferDescriptor&&
required
BufferDescriptor containing the index data. Data will be interpreted as either 16-bit or 32-bit indices based on the buffer’s IndexType
Offset in bytes into the IndexBuffer (must be multiple of 4, default is 0)
Example with 16-bit indices:
uint16_t indices[] = {0, 1, 2};
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
indices,
sizeof(indices),
nullptr // no callback
)
);
Example with 32-bit indices:
uint32_t indices[] = {0, 1, 2, 2, 3, 0};
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
indices,
sizeof(indices),
nullptr
)
);
setBufferAsync
AsyncCallId setBufferAsync(
Engine& engine,
BufferDescriptor&& buffer,
uint32_t byteOffset,
backend::CallbackHandler* handler,
AsyncCompletionCallback callback,
void* user = nullptr
)
Asynchronous version of setBuffer().
Reference to the filament::Engine
buffer
BufferDescriptor&&
required
BufferDescriptor containing the index data
Offset in bytes into the IndexBuffer (must be multiple of 4)
Handler to dispatch the callback or nullptr for the default handler
callback
AsyncCompletionCallback
required
Function to be called upon completion
Custom data passed to the callback
Returns: AsyncCallId that can be used to cancel the operation via Engine::cancelAsyncCall().
To use this method, the engine must be configured for asynchronous operation.
getIndexCount
size_t getIndexCount() const noexcept
Returns the size of this IndexBuffer in elements.
Returns: The number of indices the IndexBuffer holds.
Example:
size_t count = ib->getIndexCount();
std::cout << "Index buffer contains " << count << " indices" << std::endl;
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
)
Pointer to the index data
Size of the buffer in bytes
Optional callback invoked when the data has been consumed by the GPU
Optional user pointer passed to the callback
Example Usage
Simple Triangle (16-bit indices)
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/VertexBuffer.h>
#include <filament/RenderableManager.h>
// Define triangle indices (counter-clockwise winding)
uint16_t indices[] = {0, 1, 2};
// Create index buffer
IndexBuffer* ib = IndexBuffer::Builder()
.indexCount(3)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
// Upload index data
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
indices,
sizeof(indices),
nullptr
)
);
// Use with renderable
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
vertexBuffer, ib, 0, 3)
.build(*engine, entity);
// Cleanup
engine->destroy(ib);
Quad with Triangle Strip (16-bit indices)
// Triangle strip: 0-1-2, 1-3-2
uint16_t indices[] = {0, 1, 2, 3};
IndexBuffer* ib = IndexBuffer::Builder()
.indexCount(4)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(indices, sizeof(indices), nullptr)
);
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, 0 }, { 1, 1, 0 }})
.geometry(0, RenderableManager::PrimitiveType::TRIANGLE_STRIP,
vertexBuffer, ib, 0, 4)
.build(*engine, entity);
Cube (16-bit indices)
// Cube with 6 faces, 2 triangles per face, 3 indices per triangle
uint16_t cubeIndices[] = {
// Front face
0, 1, 2, 2, 3, 0,
// Back face
4, 5, 6, 6, 7, 4,
// Top face
8, 9, 10, 10, 11, 8,
// Bottom face
12, 13, 14, 14, 15, 12,
// Right face
16, 17, 18, 18, 19, 16,
// Left face
20, 21, 22, 22, 23, 20
};
IndexBuffer* cubeIB = IndexBuffer::Builder()
.indexCount(36) // 6 faces * 2 triangles * 3 indices
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
cubeIB->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
cubeIndices,
sizeof(cubeIndices),
nullptr
)
);
Large Mesh (32-bit indices)
// For meshes with more than 65,535 vertices
std::vector<uint32_t> indices;
// ... generate or load indices ...
IndexBuffer* largeIB = IndexBuffer::Builder()
.indexCount(indices.size())
.bufferType(IndexBuffer::IndexType::UINT) // 32-bit
.build(*engine);
largeIB->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
indices.data(),
indices.size() * sizeof(uint32_t),
nullptr
)
);
Dynamic Index Buffer with Callback
void onIndexBufferConsumed(void* buffer, size_t size, void* user) {
// Buffer has been consumed by GPU, safe to free
delete[] static_cast<uint16_t*>(buffer);
std::cout << "Index buffer freed" << std::endl;
}
// Allocate dynamic buffer
uint16_t* dynamicIndices = new uint16_t[indexCount];
// ... fill dynamicIndices ...
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
dynamicIndices,
indexCount * sizeof(uint16_t),
onIndexBufferConsumed, // Callback to free memory
nullptr // User data
)
);
Partial Update
// Initial indices
uint16_t initialIndices[] = {0, 1, 2, 3, 4, 5};
IndexBuffer* ib = IndexBuffer::Builder()
.indexCount(6)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
initialIndices, sizeof(initialIndices), nullptr
)
);
// Later, update only the last 2 indices
uint16_t partialIndices[] = {4, 5};
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
partialIndices, sizeof(partialIndices), nullptr
),
4 * sizeof(uint16_t) // Offset to index 4
);
Sharing Index Buffer Across Renderables
// Create one index buffer
IndexBuffer* sharedIB = IndexBuffer::Builder()
.indexCount(36)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
sharedIB->setBuffer(*engine,
IndexBuffer::BufferDescriptor(cubeIndices, sizeof(cubeIndices), nullptr)
);
// Use same index buffer for multiple renderables
RenderableManager::Builder(1)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
vertexBuffer1, sharedIB, 0, 36)
.build(*engine, entity1);
RenderableManager::Builder(1)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
vertexBuffer2, sharedIB, 0, 36)
.build(*engine, entity2);
// Both renderables share the same index buffer
Best Practices
Choosing Index Type
// Use 16-bit indices when possible (more efficient)
if (vertexCount < 65536) {
builder.bufferType(IndexBuffer::IndexType::USHORT);
} else {
builder.bufferType(IndexBuffer::IndexType::UINT);
}
Index Winding Order
Filament uses counter-clockwise (CCW) winding order by default for front-facing triangles. Ensure your indices follow this convention unless you’ve changed the material’s culling mode.
// Counter-clockwise triangle (front-facing)
uint16_t ccwTriangle[] = {0, 1, 2};
// Clockwise triangle (back-facing, will be culled by default)
uint16_t cwTriangle[] = {0, 2, 1};
Memory Management
// Stack-allocated data (no callback needed)
uint16_t stackIndices[] = {0, 1, 2};
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(stackIndices, sizeof(stackIndices), nullptr)
);
// Heap-allocated data (use callback to free)
uint16_t* heapIndices = new uint16_t[count];
ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
heapIndices,
count * sizeof(uint16_t),
[](void* buf, size_t, void*) { delete[] static_cast<uint16_t*>(buf); },
nullptr
)
);