Skip to main content

Overview

Filament uses a structured rendering loop where each frame follows a specific sequence: setup, rendering, and presentation. Understanding this loop is essential for building efficient real-time rendering applications.

Basic Rendering Loop

A typical Filament rendering loop consists of three main phases executed each frame:
1

Begin Frame

Signal to the renderer that a new frame is starting and acquire rendering resources.
2

Render View

Execute all rendering commands for the current view and scene.
3

End Frame

Complete the frame and present the results to the swap chain.

Minimal Render Loop

Here’s the minimal structure from the Engine documentation:
Engine.h:132-138
do {
    // typically we wait for VSYNC and user input events
    if (renderer->beginFrame(swapChain)) {
        renderer->render(view);
        renderer->endFrame();
    }
} while (!quit);

Complete Setup Example

A full application setup includes engine initialization, resource creation, and the render loop:
hellotriangle.cpp:125-161
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
    app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
    scene->setSkybox(app.skybox);
    view->setPostProcessingEnabled(false);
    static_assert(sizeof(Vertex) == 12, "Strange vertex size.");
    app.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);
    app.vb->setBufferAt(*engine, 0,
            VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));
    app.ib = IndexBuffer::Builder()
            .indexCount(3)
            .bufferType(IndexBuffer::IndexType::USHORT)
            .build(*engine);
    app.ib->setBuffer(*engine,
            IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));
    app.mat = Material::Builder()
            .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
            .build(*engine);
    app.renderable = EntityManager::get().create();
    RenderableManager::Builder(1)
            .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
            .material(0, app.mat->getDefaultInstance())
            .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)
            .culling(false)
            .receiveShadows(false)
            .castShadows(false)
            .build(*engine, app.renderable);
    scene->addEntity(app.renderable);
    app.camera = utils::EntityManager::get().create();
    app.cam = engine->createCamera(app.camera);
    view->setCamera(app.cam);
};

Frame Timing and Updates

Filament provides timing information for animation and updates:
hellotriangle.cpp:173-184
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
    constexpr float ZOOM = 1.5f;
    const uint32_t w = view->getViewport().width;
    const uint32_t h = view->getViewport().height;
    const float aspect = (float) w / h;
    app.cam->setProjection(Camera::Projection::ORTHO,
        -aspect * ZOOM, aspect * ZOOM,
        -ZOOM, ZOOM, 0, 1);
    auto& tcm = engine->getTransformManager();
    tcm.setTransform(tcm.getInstance(app.renderable),
            filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 }));
});

Key Rendering Components

Engine

The Engine is the main entry point and manages all rendering resources:
Engine.h:99-106
Engine* engine = Engine::create();
The Engine keeps track of all resources created by the user. Leaked resources are automatically freed when the engine instance is destroyed, but a warning is emitted.

SwapChain

Represents the operating system’s native renderable surface (window or view):
Engine.h:125
SwapChain* swapChain = engine->createSwapChain(nativeWindow);

Renderer

Handles the actual rendering operations for a window:
Engine.h:126
Renderer* renderer = engine->createRenderer();

Scene

A flat container of renderable entities and lights:
Engine.h:127
Scene* scene = engine->createScene();

View

Defines what to render and how to render it:
Engine.h:128-130
View* view = engine->createView();
view->setScene(scene);

Thread Safety

An Engine instance is not thread-safe. If multi-threading is needed, synchronization must be external.

Multi-threading Support

When created, the Engine automatically starts:
  • A render thread with elevated priority
  • Multiple worker threads (automatically chosen based on platform)
From Engine.h:165-172:
When created, the Engine instance starts a render thread as well as multiple worker threads, these threads have an elevated priority appropriate for rendering, based on the platform’s best practices. The number of worker threads depends on the platform and is automatically chosen for best performance.

Resource Management

Creating Resources

All Filament resources are created through the Engine:
  • createSwapChain() - Window surfaces
  • createRenderer() - Rendering contexts
  • createView() - Render views
  • createScene() - Entity containers
  • createCamera() - Camera components

Destroying Resources

Resources must be explicitly destroyed:
hellotriangle.cpp:163-171
auto cleanup = [&app](Engine* engine, View*, Scene*) {
    engine->destroy(app.skybox);
    engine->destroy(app.renderable);
    engine->destroy(app.mat);
    engine->destroy(app.vb);
    engine->destroy(app.ib);
    engine->destroyCameraComponent(app.camera);
    utils::EntityManager::get().destroy(app.camera);
};
Engine.destroy() should be called last and after all other resources have been destroyed.

Flushing and Synchronization

Flush

Kick the hardware thread without waiting:
engine->flush();
Useful after creating many objects to start draining the command queue.

Flush and Wait

Block until all commands are executed:
engine->flushAndWait();
Typically used after destroying the SwapChain when you need guaranteed destruction timing.

Best Practices

Always check the return value of beginFrame() before rendering. It returns false when the swap chain is not ready.
Filament uses command buffering for efficiency. Commands are queued and executed asynchronously on the render thread.
Configure memory footprint using Engine::Config to balance performance and memory usage based on your application’s needs.
Wait for VSYNC and process user input events before calling beginFrame() for smooth rendering.

Platform-Specific Considerations

Mobile Platforms

Filament is optimized for mobile with:
  • Automatic core selection on asymmetric architectures (ARM Big.Little)
  • Configurable command buffer sizes
  • Metal-specific upload buffer configuration

Single-Threaded Platforms

For platforms without threading support:
engine->execute();
Call this method to invoke one iteration of the render loop manually.

Entity-Component System

Learn how entities and components work together

Materials Overview

Understanding the material system

Build docs developers (and LLMs) love