# Godot Shader Pipeline Warm-Up ![Cover](cover.png) ## Description This folder contains the **Godot project** and all the code/assets used to implement and benchmark a deterministic warm-up workflow to reduce **first-use shader/pipeline stutter**. Also a report.pdf is available to check the results of my implementation and experiments ## System overview The core idea is: 1. **Editor-time discovery**: scan real scenes to find materials that will likely trigger pipeline creation; 2. **Manifest**: write a versionable JSON file listing those materials; 3. **Runtime warm-up**: at startup, a lightweight C++ GDExtension loads the manifest and *touches* those materials under a time budget, spreading work across frames; 4. **Benchmarking**: log frame times and compare Baseline vs Warm-up. ## Project layout (high level) - `project.godot` — Godot project - `addons/shader_warmup_tool/` — **Editor plugin** to generate the warm-up manifest and (optionally) generate synthetic stress scenes - `autoloads/` - `WarmupAutoloads.gd` — startup integration (reads settings, applies CLI overrides, spawns the warm-up manager) - `FrameTimeLogger.gd` — CSV frame-time logger for experiments - `bench/` - `scenes/` — ready-to-run benchmark scenes - `generated/` — generated shaders/materials/scenes produced by the plugin - `scripts/BenchmarkController.gd` — deterministic reveal + fairness: starts logging only after warm-up - `shaders/` / `materials/` — benchmark resources - `gdext/` — **C++ GDExtension** (WarmupManager) + SCons build - `src/` — `warmup_manager.*`, `register_types.cpp` - `SConstruct` — builds the extension for Windows/MSVC - `warmup.gdextension` — tells Godot which DLL to load - `godot-cpp/` — official C++ bindings (must match Godot version) - `json_warmups/` — example manifests used in some runs - `shader_warmup_manifest.json` — default manifest path used by the autoload ## How it works ### 1) Editor plugin → JSON manifest The plugin (`addons/shader_warmup_tool/plugin.gd`) is enabled inside the Godot Editor. You select one or more `.tscn` scenes and click **Generate Manifest** The plugin: - instantiates each selected scene *in the editor*; - traverses the node tree; - extracts **material resource paths** (e.g. `res://...`) from common renderable nodes (`MeshInstance3D`, `GPUParticles3D`, etc.); - **deduplicates** by resource path; - writes a JSON manifest. Note: embedded (in-scene) materials without a stable `resource_path` are intentionally skipped, because runtime warm-up must be able to load resources deterministically by path. ### 2) Runtime warm-up (C++ GDExtension) At runtime, the warm-up phase is executed by a small GDExtension node (class `WarmupManager`). The manager: - reads the JSON manifest; - loads each material using standard Godot APIs (e.g. `ResourceLoader`); - attaches the material to a small temporary renderable: - for spatial materials: a `MeshInstance3D`; - for particle process materials: a temporary `GPUParticles3D` with a minimal draw pass; - keeps the node alive for at least one rendered frame to encourage pipeline creation. ### 3) Non-blocking scheduling + time budget Warm-up is intentionally **non-blocking**: - it runs incrementally from `_process(delta)`; - it processes a limited number of manifest entries per frame; - it stops when either all entries are processed or a configurable **time budget** is exceeded. When done, the manager emits a `warmup_finished` signal containing: - `touched` / `total` entries, - `elapsed seconds`, - whether it stopped due to the budget. ### 4) Startup integration (autoload) `autoloads/WarmupAutoloads.gd` is responsible for: - exposing ProjectSettings keys: - `shader_warmup/enabled` - `shader_warmup/manifest_path` - `shader_warmup/max_seconds` - applying command line overrides; - instantiating `WarmupManager` only if the class exists (defensive check if the extension is not loaded); - emitting its own `warmup_phase_finished` signal for game/benchmark code. Command line overrides supported by the autoload: - `--baseline` or `--no-warmup` - `--warmup=0` / `--warmup=1` (Use Godot’s “user args” section: arguments after `--` on the command line.) ### 5) Benchmarking (fair Baseline vs Warm-up) `bench/scripts/BenchmarkController.gd` implements a repeatable protocol: - hides all nodes in group `bench_reveal` at startup; - waits for warm-up completion (if enabled); - starts CSV logging **only after warm-up** (so warm-up cost is not counted as a regression); - reveals objects at a fixed cadence; - stops after a fixed window. `autoloads/FrameTimeLogger.gd` writes: - `user://frametimes_baseline.csv` or `user://frametimes_warmup.csv` - columns: `ms_since_start,frame_time_ms` The Python analysis tools live outside this folder under `ProjectReport/analysis/`. ## Build the GDExtension (Windows / MSVC) Prerequisites: - Godot **4.3** (project target) - Visual Studio Build Tools (MSVC) - Python + SCons - `godot-cpp` matching the same Godot version (4.3) ### 1) Build godot-cpp From `ProjectCode/gdext/godot-cpp/`: ```powershell scons platform=windows target=template_debug arch=x86_64 scons platform=windows target=template_release arch=x86_64 ``` ### 2) Build this extension From `ProjectCode/gdext/`: ```powershell scons platform=windows target=template_debug arch=x86_64 scons platform=windows target=template_release arch=x86_64 ``` Expected outputs: - `gdext/bin/warmup.windows.template_debug.x86_64.dll` - `gdext/bin/warmup.windows.template_release.x86_64.dll` `gdext/warmup.gdextension` already points to these names. ## Use in the Godot Editor 1. Enable the plugin: - **Project → Project Settings → Plugins** → enable “Shader Warm-Up”. 2. Generate a manifest: - open the “Shader Warm-Up” dock - add one or more scenes - click **Generate Manifest** - default output: `res://shader_warmup_manifest.json` 3. Enable the extension: - **Project → Project Settings → GDExtension** → enable `gdext/warmup.gdextension`. 4. Add autoloads: - `autoloads/WarmupAutoloads.gd` - (optional) `autoloads/FrameTimeLogger.gd` ## Notes / practical tips - Warm-up is a trade-off: small startup cost for fewer in-run outliers - Coverage depends on the manifest and the time budget. If the content set is huge and `max_seconds` is small, warm-up may stop early and some first-use stalls can still happen later - For comparable runs, use cold-cache conditions (clear Godot shader cache / pipeline cache and keep the environment consistent)