Materia Profiling GuideMateria includes a comprehensive profiling system designed to help you identify performancebottlenecks and optimize your 3D applications. The profiling infrastructure provides:

Materia Profiling Guide

Overview

Materia includes a comprehensive profiling system designed to help you identify performance bottlenecks and optimize your 3D applications. The profiling infrastructure provides:

  • Zero-cost when disabled: Profiling adds no overhead when turned off
  • Cross-platform support: Works on JVM, JS, Native, Android, and iOS
  • Multiple verbosity levels: From minimal to detailed profiling
  • Real-time dashboard: Monitor performance during development
  • Comprehensive reports: HTML, JSON, and text export formats
  • Automatic recommendations: Get optimization suggestions

Quick Start

Basic Profiling

import io.materia.profiling.*

// Enable profiling
PerformanceProfiler.configure(ProfilerConfig(enabled = true))

// In your render loop
fun renderFrame() {
    PerformanceProfiler.startFrame()

    // Your rendering code
    renderer.render(scene, camera)

    PerformanceProfiler.endFrame()
}

// Get statistics
val stats = PerformanceProfiler.getFrameStats()
println("Average FPS: ${stats.averageFps}")
println("Average frame time: ${stats.averageFrameTime / 1_000_000}ms")

Using the Profiling Dashboard

val dashboard = ProfilingDashboard()

// Enable with default configuration
dashboard.enable()

// Check performance status
if (!dashboard.isPerformanceAcceptable()) {
    println("Performance issues detected!")
    println(dashboard.getFormattedText())
}

// Get performance grade
val grade = dashboard.getPerformanceGrade() // A, B, C, D, or F

Measuring Specific Operations

// Measure a code block
PerformanceProfiler.measure("myOperation", ProfileCategory.RENDERING) {
    // Your code here
    processGeometry()
}

// Use a scope for more control
val scope = PerformanceProfiler.startScope("complexOperation", ProfileCategory.GEOMETRY)
try {
    doComplexWork()
} finally {
    scope.end()
}

Advanced Usage

Profiling Sessions

Create focused profiling sessions to analyze specific scenarios:

val session = ProfilingHelpers.createSession("LoadingBenchmark")

// Run your scenario
loadAllAssets()
renderTestScene()

// End session and get summary
val summary = session.end()
summary.printSummary()

Profiled Renderer

Wrap your renderer for automatic instrumentation:

val baseRenderer = createRenderer().getOrThrow()
val renderer = baseRenderer.withProfiling()

// All render calls are now profiled automatically
renderer.render(scene, camera)

Scene Graph Profiling

Profile scene operations:

// Profile scene traversal
scene.traverseProfiled { obj ->
    // Process each object
    processObject(obj)
}

// Analyze scene complexity
val complexity = scene.getComplexity()
if (complexity.isComplex()) {
    println("Scene has ${complexity.totalNodes} nodes at depth ${complexity.maxDepth}")
}

Geometry Profiling

Profile geometry operations:

// Analyze geometry complexity
val complexity = geometry.analyzeComplexity()
println("Geometry has ${complexity.vertexCount} vertices, ${complexity.triangleCount} triangles")
println("Memory usage: ${complexity.getMemoryUsageMB()}MB")

// Get optimization recommendations
complexity.getRecommendations().forEach { recommendation ->
    println("- $recommendation")
}

// Profile specific operations
GeometryProfiler.profileNormalCalculation {
    geometry.computeVertexNormals()
}

Animation Profiling

Profile animation systems:

// Profile animation mixer update
AnimationProfiler.profileMixerUpdate(deltaTime, mixer.getActionCount()) {
    mixer.update(deltaTime)
}

// Analyze animation complexity
val complexity = AnimationProfiler.analyzeAnimationComplexity(
    trackCount = clip.tracks.size,
    keyframeCount = totalKeyframes,
    duration = clip.duration,
    boneCount = skeleton.bones.size
)

if (complexity.isComplex()) {
    complexity.getRecommendations().forEach { println(it) }
}

Physics Profiling

Profile physics simulation:

// Profile physics step
PhysicsProfiler.profilePhysicsStep(deltaTime, world.bodyCount) {
    world.step(deltaTime)
}

// Analyze physics complexity
val complexity = PhysicsProfiler.analyzePhysicsComplexity(
    bodyCount = world.bodies.size,
    constraintCount = world.constraints.size,
    contactCount = world.contacts.size
)

println("Physics complexity score: ${complexity.getComplexityScore()}")
println("Estimated CPU usage: ${complexity.estimateCPUUsage()}%")

Configuration Options

Profiler Configuration

PerformanceProfiler.configure(ProfilerConfig(
    enabled = true,                           // Enable/disable profiling
    trackMemory = true,                       // Track memory usage
    frameHistorySize = 300,                   // Keep 300 frames in history
    memoryHistorySize = 60,                   // Keep 60 memory snapshots
    frameStatsWindow = 60,                    // Calculate stats over 60 frames
    memorySnapshotInterval = 10,              // Take memory snapshot every 10 frames
    verbosity = ProfileVerbosity.NORMAL       // Profiling detail level
))

Verbosity Levels

  • MINIMAL: Only frame statistics, minimal overhead
  • NORMAL: Frame stats + hotspot detection
  • DETAILED: Everything including individual measurements
  • TRACE: Maximum detail with memory tracking

Dashboard Configuration

dashboard.enable(DashboardConfig(
    updateIntervalMs = 1000,                  // Update every second
    showHotspots = true,                      // Show performance hotspots
    showMemory = true,                        // Show memory statistics
    showFrameGraph = true,                    // Show frame time graph
    showRecommendations = true,               // Show optimization suggestions
    maxHotspots = 10,                         // Show top 10 hotspots
    maxRecommendations = 5,                   // Show top 5 recommendations
    verbosity = ProfileVerbosity.NORMAL
))

Generating Reports

Text Report

val textReport = ProfilingReport.generateTextReport()
println(textReport)

HTML Report

val htmlReport = ProfilingReport.generateHtmlReport()
File("performance-report.html").writeText(htmlReport)

JSON Export

val json = PerformanceProfiler.export(ExportFormat.JSON)
File("profiling-data.json").writeText(json)

CSV Export

val csv = PerformanceProfiler.export(ExportFormat.CSV)
File("profiling-data.csv").writeText(csv)

Chrome Trace Format

val trace = PerformanceProfiler.export(ExportFormat.CHROME_TRACE)
File("trace.json").writeText(trace)
// Open in chrome://tracing

Performance Targets

Constitutional Requirements

Materia has constitutional performance requirements:

  • 60 FPS: Must maintain 60 FPS (16.67ms per frame)
  • 5MB Size: Base library must be under 5MB
  • 100k Triangles: Should handle 100k triangles at 60 FPS

Checking Compliance

val stats = PerformanceProfiler.getFrameStats()

// Check if meeting 60 FPS target
if (stats.meetsTargetFps(60)) {
    println("✓ Meeting 60 FPS constitutional requirement")
} else {
    println("✗ NOT meeting 60 FPS requirement")
    println("  Average FPS: ${stats.averageFps}")
    println("  95th percentile frame time: ${stats.percentile95 / 1_000_000}ms")
}

// Get recommendations
val report = ProfilingReport.generateReport()
report.recommendations
    .filter { it.severity == Severity.HIGH }
    .forEach { println("⚠ ${it.message}") }

Best Practices

1. Profile in Release Mode

Always profile in release mode with optimizations enabled:

// Only enable profiling in debug builds if needed
if (BuildConfig.DEBUG) {
    ProfilingHelpers.enableDevelopmentProfiling()
} else {
    // Lightweight profiling in production
    ProfilingHelpers.enableProductionProfiling()
}

2. Use Profiling Sessions

Group related profiling data into sessions:

val session = ProfilingHelpers.createSession("AssetLoadingTest")

// Run scenario
loadModels()
loadTextures()
compileShaders()

val summary = session.end()
summary.printSummary()

3. Focus on Hotspots

Concentrate optimization efforts on the biggest hotspots:

val hotspots = PerformanceProfiler.getHotspots()

hotspots.filter { it.percentage > 10f }.forEach { hotspot ->
    println("⚠ ${hotspot.name} consuming ${hotspot.percentage}% of frame time")
    println("  Called ${hotspot.callCount} times")
    println("  Average time: ${hotspot.averageTime / 1_000_000}ms")
}

4. Monitor Memory

Track memory usage to prevent leaks:

val memoryStats = PerformanceProfiler.getMemoryStats()
memoryStats?.let { memory ->
    if (memory.trend > 10 * 1024 * 1024) { // 10MB growth
        println("⚠ Memory trending upward: ${memory.trend / (1024 * 1024)}MB")
    }

    if (memory.gcPressure > 0.5f) {
        println("⚠ High GC pressure: ${memory.gcPressure * 100}%")
    }
}

5. Automate Performance Testing

Integrate profiling into your CI/CD:

@Test
fun testPerformanceRegression() {
    ProfilingHelpers.enableDevelopmentProfiling()

    val session = ProfilingHelpers.createSession("PerformanceTest")

    // Run test scenario
    repeat(100) {
        renderComplexScene()
    }

    val summary = session.end()

    // Assert performance requirements
    assertTrue(summary.averageFps >= 58.0, "Must maintain near 60 FPS")

    val hotspots = summary.hotspots
    hotspots.forEach { hotspot ->
        assertTrue(
            hotspot.percentage < 30f,
            "${hotspot.name} consuming too much time: ${hotspot.percentage}%"
        )
    }
}

Common Profiling Scenarios

Game Loop Profiling

fun gameLoop(deltaTime: Float) {
    ProfilingHelpers.profileGameLoop(
        deltaTime = deltaTime,
        updatePhase = {
            updatePhysics(deltaTime)
            updateAnimations(deltaTime)
            updateAI(deltaTime)
        },
        renderPhase = {
            renderer.render(scene, camera)
        }
    )
}

Scene Rendering Profiling

ProfilingHelpers.profileSceneRender(
    sceneName = "MainScene",
    culling = { frustumCulling() },
    sorting = { depthSort() },
    drawCalls = { executeDraw() }
)

Asset Loading Profiling

GeometryProfiler.profilePrimitiveGeneration("SphereGeometry") {
    SphereGeometry(radius = 1f, segments = 32)
}

val geometry = GeometryProfiler.profileBufferUpload(bufferSize) {
    uploadGeometryToGPU(geometry)
}

Troubleshooting

High Frame Times

val hotspots = PerformanceProfiler.getHotspots()
hotspots.take(5).forEach { hotspot ->
    when (hotspot.category) {
        ProfileCategory.RENDERING -> println("Rendering bottleneck: ${hotspot.name}")
        ProfileCategory.PHYSICS -> println("Physics bottleneck: ${hotspot.name}")
        ProfileCategory.SCENE_GRAPH -> println("Scene graph bottleneck: ${hotspot.name}")
        else -> println("Other bottleneck: ${hotspot.name}")
    }
}

Memory Issues

val memoryStats = PerformanceProfiler.getMemoryStats()
memoryStats?.let { memory ->
    println("Current: ${memory.current / (1024 * 1024)}MB")
    println("Peak: ${memory.peak / (1024 * 1024)}MB")
    println("Allocation rate: ${memory.allocations / (1024 * 1024)}MB/s")

    if (memory.gcPressure > 0.3f) {
        println("Reduce object allocations:")
        println("- Use object pooling")
        println("- Reuse buffers")
        println("- Avoid temporary objects in hot paths")
    }
}

Profiler Overhead

The profiler is designed to have minimal overhead:

  • Disabled: Zero overhead (inlined no-ops)
  • MINIMAL: < 1% overhead
  • NORMAL: 1-3% overhead
  • DETAILED: 3-5% overhead
  • TRACE: 5-10% overhead

Choose the appropriate verbosity level for your needs.

Platform-Specific Notes

JVM

  • Full memory tracking via Runtime API
  • High-precision timing via System.nanoTime()
  • JFR integration available

JavaScript

  • Memory tracking via performance.memory (when available)
  • High-precision timing via performance.now()
  • Chrome DevTools integration

Native

  • Limited memory tracking
  • Platform-specific timing
  • Best used with external profilers

Android

  • Memory tracking via Runtime API
  • Android Profiler integration
  • Battery impact monitoring recommended

iOS

  • Limited memory tracking
  • Instruments integration recommended
  • Consider energy impact

Further Resources

IBL Convolution Profiling

Use IBLConvolutionProfiler to inspect the CPU cost of irradiance and prefilter generation. The profiler records the most recent durations and sample counts:

val metrics = IBLConvolutionProfiler.snapshot()
println("Prefilter: ${metrics.prefilterMs} ms, samples=${metrics.prefilterSamples}")
println("Irradiance: ${metrics.irradianceMs} ms")

These values bubble up into RenderStats (iblCpuMs, iblPrefilterMipCount, iblLastRoughness) so the WebGPU renderer can surface lighting diagnostics without additional instrumentation.

On this page
Architected in Kotlin. Rendered with Materia. Powered by Aether.
© 2026 Yousef.