Diagnostics & Tuning
Nebulon exposes live overlays, stats APIs, and JVM flags for development and performance tuning.
The F8 overlay
Press F8 in a development client to toggle color-coded live diagnostics covering renderers, materials, render graphs, effects, particles (simulation and render stats), collision/attractor/trail budgets, compositor passes, shared color/depth copy totals, and lights.
Launch with -Dnebulon.debug=true to make the overlay visible immediately at startup.
A healthy scene reports zero material errors, capacity drops, simulation-budget skips, and light-budget culls.
The showcase
-Dnebulon.showcase=true spawns a full example gallery (fog, magic circle, arc chain, billboards, SDF shapes including rounded box and polygon, pooled-particle patterns, a bounded snow storm, and every light and shadow path) in the look direction on the first world join. The flag has no effect unless explicitly set.
Loom regenerates IDE run configurations on every Gradle sync and wipes hand-added VM arguments. Put dev flags in loom { runs { client { ... } } } in build.gradle instead — Nebulon's bundled dev run configuration already sets the showcase flag there.
Stats APIs
| API | Reports |
|---|---|
NebulonParticles.effects().stats() | Active, queued, spawned, dropped, expired particles; collision/attractor/trail budget skips |
NebulonParticles.renderStats() | Render candidates, submitted/culled particles, alpha/additive counts, draw calls, alpha batches |
NebulonLights.lights().stats() | Active/visible/rendered lights, distance and budget culls, shadow usage, depth copies |
ReloadableMaterial.pending() / .lastError() | Material reload state |
JVM tuning flags
| Flag | Default | Purpose |
|---|---|---|
-Dnebulon.showcase=true | off | Spawn the example gallery on world join |
-Dnebulon.debug=true | off | Show the F8 overlay at startup |
-Dnebulon.particles.collisionBudget=<checks> | 4096 | Collision-shape checks per client tick |
-Dnebulon.particles.attractorBudget=<checks> | 32768 | Particle/attractor force tests per tick |
-Dnebulon.lights.maxVisible=<count> | 16 | Light budget per frame (hard cap 32) |
-Dnebulon.lights.maxShadowed=<count> | 2 | Shadowed lights per frame (hard cap 8) |
-Dnebulon.lights.worldShadowRefreshTicks=<ticks> | 20 | World-shadow atlas fallback refresh interval |
Shader packs: how compatibility works
Understanding the timing helps debug "my effect disappeared with a shader pack" reports:
- Without a pack, effects draw through Fabric world render events at their normal stages.
- With an Iris pack active, Nebulon defers custom draws to a narrow window after the pack's final composite (so composite passes can't erase them) but before vanilla clears world depth for the hand (so block and first-person occlusion stay correct).
- Drawing custom passes mid-frame while a pack is active does not work — Iris intercepts unmapped render passes (log:
Missing program ... in override list). - Materials that opt into
INHERIT_SHADER_PACKare instead mapped to a pack program by Iris and stay inside its pipeline.
If an effect is invisible only with a specific pack, test with PRESERVE_CUSTOM (the default), then check the F8 overlay for material errors.
Debugging rendering issues
- Shader compile/link problems appear in
logs/latest.logasShaderProgramlink messages. CallShaderMaterial.precompile()inprepare()so failures surface at startup rather than first draw. DynamicUniforms.write(...)must run beforecreateRenderPass(...), never inside an open pass (Close the existing render pass before performing additional commands).- Never create GPU objects in constructors or static initializers — the device may not exist yet (
Can't getDevice() before it was initialized). Create lazily inprepare(), release inclose().