Skip to main content

Debugging Tools

Filament provides several tools and features for debugging rendering issues, shader problems, and performance bottlenecks.

matdbg - Material Debugger

The Material Debugger (matdbg) is a web-based tool for debugging and live-editing Filament shaders.

Capabilities

  • OpenGL: Edit GLSL shaders in real-time
  • Metal: Edit MSL (Metal Shading Language)
  • Vulkan: Edit transpiled GLSL, view disassembled SPIR-V
  • WebGPU: Edit WGSL shaders

Setup for Desktop

  1. Build with matdbg enabled:
./build.sh -fd debug gltf_viewer
The -d flag enables FILAMENT_ENABLE_MATDBG and -f forces CMake to regenerate.
  1. Set the debug port:
export FILAMENT_MATDBG_PORT=8080
  1. Launch your application and open http://localhost:8080 in your browser.

Setup for Android

  1. Enable matdbg in CMake by setting FILAMENT_ENABLE_MATDBG=ON in your build configuration.
  2. Add internet permissions to your AndroidManifest.xml:
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- ... -->
</manifest>
  1. Forward the debug port:
adb forward tcp:8081 tcp:8081
  1. Access the debugger at http://localhost:8081

Using matdbg

Selecting Materials

  1. Open the matdbg page in your browser
  2. Select a material from the upper-left pane
  3. Select a shader variant (active variants are shown in bold)
  4. View or edit shader code in the main editor

Editing Shaders

You can modify GLSL or MSL code directly:
  1. Make your edits in the shader editor
  2. Click [rebuild] or press Cmd+S (Mac) / Ctrl+S (Windows/Linux)
  3. Changes apply immediately if the shader compiles successfully
Important: Shader inputs and uniforms must remain intact. Edits are lost when you close the browser.

Keyboard Shortcuts

ActionShortcut
Save/Rebuild shaderCmd+S / Ctrl+S
Navigate materialsShift+Ctrl+Up/Down
Navigate variantsShift+Ctrl+Left/Right

Architecture

matdbg consists of:

C++ Server API

class DebugServer {
public:
    // Add a material to the debugger
    void addMaterial(MaterialPackage const& package);
    
    // Set callback for shader edits
    void setEditCallback(EditCallback callback);
    
    // Set callback for queries
    void setQueryCallback(QueryCallback callback);
};

HTTP API

GET /api/matids Returns array of material IDs:
["e4c41141", "44ae2b62", "9dab8a03"]
GET /api/materials Returns all material information (except shader source):
[{
    "matid": "e4c41141",
    "name": "uiBlit",
    "version": 4,
    "shading": { "model": "unlit" },
    "opengl": [
        { "index": "0", "shaderModel": "gl41", 
          "pipelineStage": "vertex", "variant": 0 }
    ]
}]
GET /api/active Returns active shader variants:
{
    "b38d4ad0": ["opengl", 5],
    "44ae2b62": ["opengl", 1, 4]
}
GET /api/shader Query parameters:
  • matid={id} - Material ID
  • type=[glsl|spirv|msl|wgsl] - Shader language
  • glindex={n} / vkindex={n} / metalindex={n} - Variant index
Returns the shader source code as plain text.

Debug Registry

Filament provides a debug registry for runtime inspection:
DebugRegistry& registry = engine->getDebugRegistry();

// Get property value
auto* property = registry.getPropertyInfo("d.lighting.debug_directional_shadows");
if (property) {
    bool value = *static_cast<bool*>(property->addr);
}

Validation and Assertions

Build Configurations

Filament supports different build types:
# Debug build with full validation
./build.sh debug

# Release build (optimized, minimal checks)
./build.sh release

ASAN/UBSAN Builds

Detect memory errors and undefined behavior:
# Build with AddressSanitizer
./build.sh -b debug

# Build with UndefinedBehaviorSanitizer  
./build.sh -y debug
See ASAN/UBSAN documentation for details.

Resource Tracking

Leak Detection

Filament tracks all resources and warns about leaks:
Engine* engine = Engine::create();

// Create resources
Texture* texture = Texture::Builder()...build(*engine);
VertexBuffer* vb = VertexBuffer::Builder()...build(*engine);

// ... use resources ...

// Clean up (or leak warnings will appear)
engine->destroy(texture);
engine->destroy(vb);

Engine::destroy(&engine);
Console output on leaks:
WARNING: 2 Texture objects leaked
WARNING: 1 VertexBuffer object leaked

Resource Counts

Query resource counts:
void printResourceStats(Engine* engine) {
    printf("Textures: %zu\n", engine->getTextureCount());
    printf("Materials: %zu\n", engine->getMaterialCount());
    printf("VertexBuffers: %zu\n", engine->getVertexBufferCount());
    printf("IndexBuffers: %zu\n", engine->getIndexBufferCount());
    printf("Views: %zu\n", engine->getViewCount());
    printf("Scenes: %zu\n", engine->getSceneCount());
}

Handle Validation

Check if resources are valid:
if (!engine->isValid(texture)) {
    // Texture was destroyed or never created
}

if (!engine->isValid(material, materialInstance)) {
    // Material instance is invalid
}

Backend Debugging

Command Logging

Enable command logging in debug builds by modifying CommandStream.h:
// Set to true to print every command
#define DEBUG_COMMAND_STREAM true

Driver Debugging

Backend drivers support debug hooks:
virtual void debugCommandBegin(
    CommandStream* cmds,
    bool synchronous, 
    const char* methodName) noexcept = 0;

virtual void debugCommandEnd(
    CommandStream* cmds,
    bool synchronous,
    const char* methodName) noexcept = 0;

Frame Capture

Use platform-specific tools:

RenderDoc (OpenGL, Vulkan)

  1. Launch your app through RenderDoc
  2. Press F12 to capture a frame
  3. Analyze draw calls, shaders, and resources

Xcode (Metal)

  1. Run your app with Metal frame capture enabled
  2. Click the camera icon in Xcode
  3. Inspect GPU workload and shader performance

Android GPU Inspector (Vulkan)

  1. Install Android GPU Inspector
  2. Connect to your device
  3. Capture and analyze frames

Common Debug Scenarios

Shader Compilation Errors

// Check material creation
Material* material = Material::Builder()
    .package(package, packageSize)
    .build(*engine);

if (!material) {
    // Check console for compilation errors
    printf("Material creation failed\n");
}

Black Screen Issues

  1. Check clear color:
    renderer->setClearOptions({
        .clearColor = {0.1f, 0.1f, 0.1f, 1.0f},
        .clear = true
    });
    
  2. Verify view setup:
    view->setViewport({0, 0, width, height});
    view->setCamera(camera);
    view->setScene(scene);
    
  3. Check renderable bounds:
    // Make sure bounding box is correct
    RenderableManager::Builder(1)
        .boundingBox({{-1,-1,-1}, {1,1,1}})
        // ...
    

Performance Issues

Use frame timing:
auto frames = renderer->getFrameInfoHistory(60);
for (const auto& frame : frames) {
    if (frame.gpuFrameDuration > 16'666'666) {  // 16.6ms
        printf("Frame %u exceeded 60fps budget\n", frame.frameId);
    }
}

Build Options

CMake Options

Key debugging options:
# Enable matdbg
-DFILAMENT_ENABLE_MATDBG=ON

# Enable LTO (release builds)
-DFILAMENT_ENABLE_LTO=ON

# Skip samples
-DFILAMENT_SKIP_SAMPLES=ON

Easy Build Script

# Debug build
./build.sh debug

# Release build
./build.sh release

# Clean build
./build.sh -c debug

# Install to out/debug or out/release
./build.sh -i debug

# With matdbg
./build.sh -d debug

# Force CMake regeneration
./build.sh -f debug

Best Practices

Do’s

Use debug builds during development
Enable matdbg for shader debugging
Check resource counts regularly
Destroy resources properly
Use validation layers (Vulkan)
Profile with platform tools

Don’ts

Don’t ignore warnings about leaked resources
Don’t disable handle validation in development
Don’t skip proper cleanup
Don’t ignore frame timing information

Example: Complete Debug Setup

#include <filament/Engine.h>
#include <filament/Renderer.h>
#include <filament/View.h>

using namespace filament;

class DebugApp {
public:
    DebugApp() {
        Engine::Config config;
        config.commandBufferSizeMB = 3;
        
        mEngine = Engine::Builder()
            .config(&config)
            .build();
            
        mRenderer = mEngine->createRenderer();
        
        // Print backend info
        Backend backend = mEngine->getBackend();
        printf("Backend: %s\n", to_string(backend).data());
    }
    
    ~DebugApp() {
        // Clean up
        printResourceStats();
        
        mEngine->destroy(mRenderer);
        Engine::destroy(&mEngine);
    }
    
    void renderFrame() {
        if (mRenderer->beginFrame(mSwapChain)) {
            mRenderer->render(mView);
            mRenderer->endFrame();
            
            // Check frame timing
            auto info = mRenderer->getFrameInfoHistory(1);
            if (!info.empty()) {
                float ms = info[0].gpuFrameDuration / 1e6f;
                if (ms > 16.6f) {
                    printf("Frame %u: %.2f ms (dropped!)\n",
                        info[0].frameId, ms);
                }
            }
        }
    }
    
private:
    void printResourceStats() {
        printf("\nResource Stats:\n");
        printf("  Textures: %zu\n", mEngine->getTextureCount());
        printf("  Materials: %zu\n", mEngine->getMaterialCount());
        printf("  Views: %zu\n", mEngine->getViewCount());
    }
    
    Engine* mEngine;
    Renderer* mRenderer;
    SwapChain* mSwapChain;
    View* mView;
};

See Also

Build docs developers (and LLMs) love