Rendering and performance
How Strut draws, how it behaves under shader packs, and how to read the F9 overlay.
How Strut draws
Strut poses and draws models on the GPU: entities that share a model and a texture are drawn together in one instanced call, with bone matrices computed on the graphics card. This is what lets a hundred skinned characters cost a few draw calls instead of thousands. The GPU is the only path: entities, block entities, held and dropped items, worn armour and the models worn by another (armour, a hat) are all drawn by it, in the world, in a screen and under a shader pack. There is no CPU render path to fall back to, so a computer that cannot run it (OpenGL 4.3 with shader storage buffers) draws no Strut models.
Shader packs (Iris)
With Iris and a shader pack active, Strut draws through the pack's own programs with the pack's own vertex format, so entities are lit, shaded, and post-processed like any other entity. Nothing to configure and no toggle. Entities also cast shadows, because Strut draws them again into the shadow map from the sun's point of view, inside Iris's own shadow pass.
- Per-bone texture overrides are not redrawn in the shadow pass (they change colour, not silhouette).
- The F9 overlay counts each entity twice under Iris: once for the main pass and once for the shadow pass. That is correct.
- If a shadow flush is ever missed, the shadows of that frame are lost and the overlay says so (shadow flush unreliable); the entities are still drawn.
Translucency and overlays
Models draw cutout by default: opaque, with pixels below a small alpha discarded. Three things change that, all working the same with and without a shader pack.
A translucent model. Override these on your model. renderMode blends the whole model after everything opaque; shadingMode chooses how the model is shaded by the way its surfaces face (below), FLAT for none; translucentDepthBias and translucentCullsBackFaces tune the blended draws.
@Override
public RenderMode renderMode(HeroEntity creature) {
return RenderMode.TRANSLUCENT; // the whole model blends, after everything opaque
}
@Override
public ShadingMode shadingMode(HeroEntity creature) {
return ShadingMode.FLAT; // no shading by direction; the world's own light still applies
}
@Override
public DepthBias translucentDepthBias(HeroEntity creature) {
return new DepthBias(-1f, -2f); // pull translucent bones and overlays towards the camera; not the model itself
}
@Override
public boolean translucentCullsBackFaces(HeroEntity creature) {
return true; // hide a closed cube's inside faces; a one-sided plane vanishes from behind
}
Per-bone layers. On an opaque model, flag one bone translucent, or draw a second texture over a bone. Both are bone overrides, so they inherit down the tree, a child can opt out, and calling them on the client makes them this viewer's own.
// A model that is opaque by default, with one bone drawn blended
creature.overrides().setTranslucent("blush", true);
// A second texture over a bone, translucent, on top of its own. Layers 0 to 3, a higher layer draws later
creature.overrides().setOverlay("head", 0, freckles);
// On the client the same calls are this viewer's own, never sent anywhere: a skin, a player's setting
creature.overrides().setTexture("head", skin);
creature.overrides().clearOverlay("head", 0);
- Opaque and see-through pixels are drawn apart. A translucent model's opaque pixels (alpha 255 after the bone and instance colours) are drawn first and hide what is behind them; its see-through pixels then blend over that, and of those only the nearest surface shows. So a shell of 60% alpha over an opaque core shows the core through it, a head does not show its own back faces through its front (turn on
translucentCullsBackFacesfor a closed shell whose texture is see-through on both sides), and a see-through bone behind a nearer see-through one is hidden, not blended. Under a shader pack the pack's own translucent blending is used instead. - Colour overrides carry alpha. A bone colour with alpha below 255 makes that bone see-through when it is drawn blended.
- Depth bias. Translucent bones and overlays sit on geometry that is already drawn (a blush on a snout, a skin overlay on its bone), so they are pulled slightly towards the camera to win the tie. It is not applied to a translucent model's own draw, where it would pull neighbouring quads apart and show as seams. The default suits most models; tune it if a layer flickers or floats.
- Shadows. A translucent model or bone still casts a shadow, as if it were cutout. Overlays and per-bone texture swaps do not affect the shadow.
- No sorting between entities. Two overlapping translucent entities blend in the order they were submitted, as in vanilla.
Shading
How a model is shaded by the way its surfaces face is a choice of the model, StrutModel#shadingMode(animatable):
SOFT(the default): the light is wrapped round the surface and tinted from the sky side to the ground side, in a range of 0.55 to 1, so no face goes dark and the many small cubes and meshes of a character blend.VANILLA: the game's own entity shading, two directional lights over a base of 0.4.TOON: an anime look onSOFT's light. The shadow is a deeper, more saturated and slightly cool version of the texture's own colour (not the same colour darker), with a middle tone, soft edges between the tones, and a thin soft rim of light at the lit outline, and the world's own light (block and sky light) kept above a floor, so a model in the dark is still readable.FLAT: no shading by direction, every surface lit alike. The world's own light still applies.
SOFT and TOON are lit by the light that is really there: the sun by day and the moon by night (through what is above the model), and the blocks that glow round it. Up to four lights act at once, the sky's and the three strongest of the others (the weaker ones are added together into the last), each lighting the side of the model that faces it and tinting it with its colour (white unless a mod sets one, see Coloured lights). The invisible light block is not counted. The side of a character facing a torch is its lit side, in the open the sun's side is, and in a dim place the model is flatter. A model drawn into a screen, or under a shader pack, has no such light and uses fixed ones.
The world's own light (block and sky light) darkens the model in every mode, and is eased over about a fifth of a second, so walking toward a torch brightens it smoothly instead of a level at a time. The faces of a flat cube (a plane or a card, with two faces on one plane) are lit by the side the eye is on, so the two look alike. Under a shader pack the pack lights the model and the mode has no effect.
A mod can change the shader itself, add coloured lights and run code around the draw: see Changing the render pipeline.
Animation level of detail
Animations are evaluated less often the farther an entity is from the camera. In a crowd (more than about two dozen entities drawn) the distances shrink, so the nearest entities stay smooth every frame and the rest take turns. It affects only how often a pose is recomputed, never server-side events. To keep an animatable frame-exact at any distance, override useAnimationLod() to return false.
The F9 overlay
Press F9 to toggle. Every line is a total for the last completed frame unless it says cumulative.
| Line | Meaning |
|---|---|
Iris: active=…, shadow flush unreliable=… | Whether a shader pack is driving Strut's Iris path, and whether the safety fallback has tripped. |
events fired: server N / client N, unbound keys, capped sweeps, binding sets | Cumulative since launch. Compare before and after an action to see events fire. Unbound keys are sound cues no binding defines. |
asset sync: … | Whether server assets are active, how many files, and what the last join had to download. |
shadow pass: submitted, flushes, instances drawn, skipped, pose failed | The shadow pass stage by stage: entities queued for the shadow map, flushes that ran, instances actually drawn, draws skipped because the pack supplied no program, and pose passes that failed. A missing shadow shows up as the stage where the numbers stop matching. |
GPU instances drawn, GPU draw calls, Iris GPU draw calls | The GPU path's work. Instances count entities; draw calls count batches. |
blended: model instances, translucent bones, overlays | The blended passes' work: entities drawn as a translucent model, translucent bone ranges, and overlay ranges. All zero when nothing uses translucency. |
animation, animations evaluated / skipped (LOD) | Animation cost, and how many were skipped by level of detail. |
submission, gpu flush (CPU), gpu draw (GPU) | CPU time spent submitting and flushing, and GPU time spent drawing, in milliseconds. |
render-thread alloc | Memory allocated on the render thread per frame. Steady state should be small. |
physics | Whether physics is on and at which quality, how many instances and spring bones are simulated, and the steps taken and their cost. |
lights (you), lights (last drawn) | The lights the shading is given, for your player and for the model queued last: each is [direction |strength| #colour], in the order sky, strongest glowing block, the others together. |
strongest block light | The glowing block that is the strongest light round the last model looked at, and where it is. |
When a shadow is missing
Read the shadow pass line as a funnel:
- submitted is 0: nothing reached the shadow queue.
- submitted above 0 and flushes 0: the flush point is not being hit.
- instances drawn 0 and skipped above 0: the pack supplied no shadow program for entities.
- pose failed above 0: a pose pass failed.
- submitted equals drawn: the draws happen, so look at the pack and at GL state. The first shadow flushes also write the GL state they found to
latest.logas[Strut shadow]lines.