Skip to main content

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.

IDE users

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

APIReports
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

FlagDefaultPurpose
-Dnebulon.showcase=trueoffSpawn the example gallery on world join
-Dnebulon.debug=trueoffShow the F8 overlay at startup
-Dnebulon.particles.collisionBudget=<checks>4096Collision-shape checks per client tick
-Dnebulon.particles.attractorBudget=<checks>32768Particle/attractor force tests per tick
-Dnebulon.lights.maxVisible=<count>16Light budget per frame (hard cap 32)
-Dnebulon.lights.maxShadowed=<count>2Shadowed lights per frame (hard cap 8)
-Dnebulon.lights.worldShadowRefreshTicks=<ticks>20World-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_PACK are 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.log as ShaderProgram link messages. Call ShaderMaterial.precompile() in prepare() so failures surface at startup rather than first draw.
  • DynamicUniforms.write(...) must run before createRenderPass(...), 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 in prepare(), release in close().