Skills/godot-performance/SKILL.md

9.8 KiB

name description license compatibility metadata
godot-performance Performance optimization for Godot 4.x: profiling workflow (built-in profiler, CPU/GPU usage), memory management with ResourceCache and instance pooling, instancing optimization with packed scenes and LOD, render passes, draw calls, mobile-specific optimizations, Perfetto tracing, and nearest-neighbor viewport scaling. Use when debugging performance, optimizing games, or targeting low-end hardware. MIT
godot-4.0
godot-4.1
godot-4.2
godot-4.3
godot-4.4
godot-4.5
godot-4.6
godot-4.7
author version tags created
vl4dt 0.1.0
godot
performance
profiling
optimization
memory
mobile
2026-06-20

Performance Optimization for Godot 4.x

Systematic approach to profiling, optimizing, and debugging performance in Godot 4.x.

Profiling Workflow

Built-in Profiler

Access the profiler via Debug > Start Profiling or press Ctrl+Shift+P.

# Enable profiling in your project settings:
# Project Settings > Debug > Profile Every Frame = true

# Use time measure for specific code sections
var start_time = Time.get_ticks_usec()
# ... expensive operation ...
var elapsed = Time.get_ticks_usec() - start_time
print("Operation took: ", elapsed, " us")

CPU/GPU Usage Monitoring

# Display FPS and frame time in debug overlay
# Project Settings > Debug > Show FPS = true

# Monitor specific metrics
func get_performance_stats():
    var stats = {
        fps: Performance.get_monitor(Performance.TIME_FPS),
        frame_time: Performance.get_monitor(Performance.TIME_FRAME_DURATION),
        draw_calls: Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME),
        active_objects: Performance.get_monitor(Performance.OBJECT_COUNT),
        mem_alloc: Performance.get_monitor(Performance.MEMORY_STATIC),
        mem_pool: Performance.get_monitor(Performance.MEMORY_STATIC_SPECIALIZATION)
    }
    return stats

Profiling Checklist

  1. Start with a clean build — Profile the exported release build, not the debug version
  2. Enable all profiler categories — CPU, GPU, Physics, Rendering, Audio
  3. Record a representative gameplay segment — 30-60 seconds of typical play
  4. Identify hotspots — Look for functions consuming >5% of frame time
  5. Profile before and after changes — Measure impact of each optimization

Memory Management

ResourceCache Pattern

Preload and cache frequently-used resources.

class_name ResourceCache extends Node

static var _cache := {}

static func preload_resource(path: String) -> Variant:
    if not _cache.has(path):
        var res = load(path)
        if res:
            _cache[path] = res
        else:
            push_warning("Failed to load resource: " + path)
    return _cache[path]

static func get_scene(path: String) -> PackedScene:
    return preload_resource(path) as PackedScene

# Usage
var bullet_scene = ResourceCache.get_scene("res://scenes/entities/bullet.tscn")
var bullet = bullet_scene.instantiate()

Instance Pooling Pattern

Reuse objects instead of creating/destroying them.

class_name ObjectPool<T> extends Node where T is RefCounted

@export var pool_size: int = 20
var _pool: Array[T] = []
var _active: Array[T] = []

func _ready():
    for i in range(pool_size):
        var instance = create_instance()
        instance.set_process_mode(PROCESS_MODE_ALWAYS)
        add_child(instance as Node)
        _pool.append(instance as T)

func get_free() -> T:
    if _pool.size() > 0:
        var obj = _pool.pop_back()
        obj.set_active(true)
        _active.append(obj)
        return obj
    return null

func release(obj: T) -> void:
    obj.set_active(false)
    _active.erase(obj)
    _pool.append(obj)

Instancing Optimization

Packed Scene Best Practices

# preload() for always-needed scenes (compile-time)
const BulletScene = preload("res://scenes/entities/bullet.tscn")

func spawn_bullet():
    var bullet = BulletScene.instantiate()
    add_child(bullet)

# load() for conditional scenes (runtime)
func load_level(level_name: String):
    var path = "res://levels/" + level_name + ".tscn"
    var scene = load(path)  # Cached by Godot internally
    if scene:
        current_level = scene.instantiate()
        add_child(current_level)

Level of Detail (LOD)

Use LOD to reduce rendering cost for distant objects.

class_name LODObject extends Node3D

@export var lod_distances: PackedFloat32Array = [10.0, 30.0, 60.0]
@onready var lod_nodes = [$HighDetail, $MediumDetail, $LowDetail]

func _process():
    var dist = global_position.distance_to(get_viewport().get_camera3D().global_position)
    for i in range(lod_nodes.size()):
        lod_nodes[i].visible = dist < lod_distances[i]

Instanced Rendering

Use MultiMeshInstance2D/3D for many similar objects.

# Draw 1000 identical trees with a single draw call
var mesh = preload("res://models/tree.tscn")
var multi_mesh = MultiMesh.new()
multi_mesh.mesh = mesh.resource
multi_mesh.transform_format = MultiMesh.TRANSFORM_3D
multi_mesh.instance_count = 1000

for i in range(1000):
    var x = randf() * 200 - 100
    var z = randf() * 200 - 100
    multi_mesh.set_instance_transform(i, Transform3D(Basis(), Vector3(x, 0, z)))

$MultiMeshInstance3D.multi_mesh = multi_mesh

Render Passes and Draw Calls

Minimizing Draw Calls

# BAD: Many small sprites (many draw calls)
for i in range(100):
    var sprite = Sprite2D.new()
    sprite.texture = grass_texture
    add_child(sprite)

# GOOD: Use TileMap or MultiMesh (single draw call)
# TileMap handles batching automatically
# Or use a custom mesh with instanced rendering

Render Pass Optimization

  • Forward+ renderer: Better for many lights, use for 3D games
  • Mobile renderer: Fewer passes, better for low-end devices
  • Stencil shadows: Use shadow maps instead of volumetric shadows when possible
  • LightmapGI: Bake static lighting to avoid real-time shadows
# Set renderer in Project Settings > Rendering > Renderer
# Forward Plus: Better quality, higher cost
# Mobile: Optimized for low-end hardware

Mobile-Specific Optimizations

Texture Compression

# Project Settings > Export > Android/iOS
# Set texture format:
# - ASTC: Best quality/size ratio (modern devices)
# - ETC2: Fallback for older Android devices
# - DXT: Desktop only
# Use compressed textures in the editor:
# Texture > Compressed = True

Mobile Rendering Tips

  • Use Mobile renderer instead of Forward+ for Android/iOS
  • Limit active lights per object (use light masks)
  • Bake static lighting with LightmapGI
  • Use smaller shadow map sizes (512x512 or 256x256)
  • Disable post-processing effects when not needed
  • Use CanvasGroup to batch UI rendering
  • Set texture_filter to TEXTURE_FILTER_NEAREST for pixel art

CPU Optimization for Mobile

  • Avoid _get_node() in _process — use @onready instead
  • Minimize _instantiate() calls — use object pooling
  • Use NavigationServer3D for pathfinding (avoids scene tree overhead)
  • Batch physics queries instead of individual raycasts
  • Use PhysicsServer3D directly for bulk operations

Godot 4.7 Performance Features

Perfetto Tracing for Android (Godot 4.7)

Perfetto provides detailed profiling data for Android builds.

# Enable in Project Settings > Debug
# Perfetto tracing captures:
# - Frame timing breakdown
# - GC pauses
# - Thread scheduling
# - GPU command queue

# After export, run:
# godot --perfetto-trace output.perfetto
# Then open in https://ui.perfetto.dev/

Nearest-Neighbor Viewport Scaling (Godot 4.7)

Pixel-perfect scaling for retro-style games.

# Project Settings > Display > Window > Stretch Mode
stretch_mode = CanvasGroup.STRETCH_MODE_SCALE
stretch_aspect = CanvasGroup.STRETCH_ASPECT_KEEP

# Godot 4.7: Use nearest-neighbor scaling for crisp pixel art
texture_filter = TextureFilter.NEAREST

# Calculate integer scale factor
func get_viewport_scale() -> int:
    var target_width = 320  # Original game resolution
    return max(1, int(DisplayServer.window_get_size().x / target_width))

Performance Checklist

Before Release

  • Profile the release build (not debug)
  • Check FPS stays above 30 (60 ideal) on target hardware
  • Verify memory usage stays within device limits
  • Test on lowest-spec target device
  • Check for GC spikes (>1ms pauses)
  • Verify texture compression is enabled
  • Reduce draw calls with instancing
  • Use lightmaps for static scenes
  • Implement object pooling for frequently spawned entities

Runtime Monitoring

# Add to debug HUD
class_name PerfHUD extends CanvasLayer

@onready var fps_label = $FPSLabel
@onready var mem_label = $MemLabel

func _process(delta):
    fps_label.text = "FPS: " + str(Performance.get_monitor(Performance.TIME_FPS))
    mem_label.text = "Mem: " + str(Performance.get_monitor(Performance.MEMORY_STATIC) / 1024 / 1024) + " MB"

MCP Bridge Tools (Optional — Live Editor Integration)

When the MCP bridge is running (Phase 2), these tools enhance performance debugging:

  • profiler_start — Start a profiling session from the editor
  • profiler_stop — Stop and display results
  • memory_stats — View current memory allocation breakdown
  • render_stats — Query draw calls, triangles, and active textures
  • perfetto_export — Export Perfetto trace for Android debugging

Note: MCP tools require the Godot editor to be running with the MCP plugin enabled. Skills work independently without the bridge.

References