diff --git a/src/main/java/dev/amble/ait/client/renderers/AITRenderLayers.java b/src/main/java/dev/amble/ait/client/renderers/AITRenderLayers.java index 9b16f1ed0..8c59b8f2b 100644 --- a/src/main/java/dev/amble/ait/client/renderers/AITRenderLayers.java +++ b/src/main/java/dev/amble/ait/client/renderers/AITRenderLayers.java @@ -1,6 +1,5 @@ package dev.amble.ait.client.renderers; -import java.util.Set; import java.util.function.Function; import net.fabricmc.api.EnvType; @@ -14,13 +13,29 @@ import net.minecraft.util.Identifier; import net.minecraft.util.Util; -import dev.amble.ait.AITMod; - @Environment(EnvType.CLIENT) public class AITRenderLayers extends RenderLayer { - private static RenderLayer emissive(Identifier texture, boolean sorted) { + /** + * The emissive layer, always with the quad sorter. + * + *
The sorter is not an optimisation to be skipped when the alpha is opaque. This layer writes + * no depth ({@code COLOR_MASK}) and disables back face culling, so nothing in the batch occludes + * anything else in it and both faces of every part are submitted. Whichever quad is drawn last + * wins, which leaves submission order as the only thing deciding what ends up on top: a part's + * own back face, or a light sitting behind a panel, will paint over the front of it. + * + *
That is independent of the texture's alpha. Binary alpha makes the blend order + * independent, not the occlusion, and dropping the sorter on that reasoning put lights through + * panels and left the animated monitor glow on Renaissance and Toyota not reading as animated. + * + *
It does cost. {@code BufferBuilder.setSorter} allocates a primitive centre per quad and + * writes an explicit index buffer instead of reusing the shared sequential one, which on Copper's + * roughly 6800 quad emission pass measured about 0.6 ms a frame. That is the price of drawing it + * in the right order. + */ + private static RenderLayer emissive(Identifier texture) { RenderPhase.Texture texture2 = new RenderPhase.Texture(texture, false, false); MultiPhaseParameters multiPhaseParameters = RenderLayer.MultiPhaseParameters.builder() .program(RenderPhase.EYES_PROGRAM) @@ -33,98 +48,16 @@ private static RenderLayer emissive(Identifier texture, boolean sorted) { .depthTest(RenderPhase.LEQUAL_DEPTH_TEST) .build(false); - // The last flag is what makes RenderLayer.draw hand the buffer a sorter, and that is more than - // a sort: BufferBuilder.setSorter also builds a Vector3f per quad for the primitive centres, - // and build() then writes an explicit index buffer instead of reusing the shared sequential - // one. Copper's emission pass is around 6800 quads, so per flush that is 6800 allocations, a - // 6800-element sort and about 41000 index writes. - // - // It is separate from the blend mode set above. The layer never writes depth (COLOR_MASK), so - // ordering can only matter where emissive geometry overlaps emissive geometry inside one batch. - // - // Mostly the textures make that moot: 106 of the mod's 113 emission textures are strictly - // binary alpha, where SRC_ALPHA blending is order-independent. Seven are not, and one is not - // marginal: hourglass_default_emission.png carries 1372 partial texels including a flat run of - // 768 at alpha 100, which is deliberate semi-transparent glow. Crystalline (64 texels), - // steam_copper (24), steam_playpal (2) and bookshelf_default (34) are the rest. On those, a - // glow-over-glow overlap composites in submission order here where it used to composite in - // depth order. - // - // Vertex alpha is a separate exposure and is not covered by the texture argument at all, which - // is what the sorted layer below is for. - return RenderLayer.of(sorted ? "emissive_cull_z_offset_sorted" : "emissive_cull_z_offset_unsorted", + return RenderLayer.of("emissive_cull_z_offset", VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, VertexFormat.DrawMode.QUADS, 256, - false, sorted, multiPhaseParameters); + false, true, multiPhaseParameters); } - /** - * Emission textures whose partial alpha is large enough that draw order is visible, so they get the - * sorted layer whoever asks for them. Sortedness is a property of the texture here, not of the call - * site, which keeps one texture mapped to one layer object. - * - *
Only hourglass qualifies. Its 1372 partial texels include a flat run of 768 at alpha 100,
- * which is deliberate semi-transparent glow rather than an edge ramp. The other six partial-alpha
- * emission textures are accepted unsorted: crystalline and its two recolours have 64 texels each at
- * alpha 199, bookshelf_default 34, steam_copper 24 at alpha 4, steam_playpal 2. Crystalline was
- * checked against a sorted arm in game and showed nothing above its own animation noise.
- */
- private static final Set Returns the sorted layer anyway for the textures in {@link #SORT_SENSITIVE_EMISSION}.
- *
- * Unsorted. At alpha 1 the blend leaves nothing for the ordering to change except where opaque
- * glow overlaps opaque glow, and the sort is the single most expensive thing the console's
- * emission pass does. Measured on Copper, interior, landed, interleaved arms inside one client
- * session: the {@code monitor} zone went from 1.371-1.404 ms to 0.743-0.830 ms, disjoint ranges,
- * while the two emission zones, which are negative controls this cannot affect, moved by under
- * 0.06 ms. Frame time moved too but its per-rep ranges overlap at n=3, so it is not claimed.
- *
- * The cost lands in {@code monitor} only when monitor text is enabled and the variant overrides
- * {@code renderMonitorText}: that call is what next asks {@code Immediate} for a buffer, and the
- * flush of this layer is billed to whichever zone is open at the time. With the text off it moves
- * to {@code sonic_port} or later. It is the same work either way.
- */
+ /** One layer per emission texture. See {@link #emissive} for why the sort is not optional. */
public static RenderLayer tardisEmissiveCullZOffset(Identifier texture) {
- return SORT_SENSITIVE_EMISSION.contains(texture)
- ? EMISSIVE_SORTED.apply(texture)
- : EMISSIVE_UNSORTED.apply(texture);
- }
-
- /**
- * The emissive layer for geometry drawn at partial vertex alpha, where draw order is visible.
- *
- * Two callers. {@code TardisStar} draws two nested star models into one batch, the outer at
- * alpha 0.5 and the inner at alpha 1, with culling disabled so both faces of both shells are
- * submitted; unsorted, the opaque core would land last and paint over the shell that is supposed
- * to veil it. {@code ExteriorRenderer} draws the emission at the demat and remat fade alpha, which
- * sweeps continuously through the partial range on every takeoff and landing.
- *
- * The star is 24 quads, so sorting it costs nothing worth measuring. The exterior is around 342,
- * and it pays the sort on every landed TARDIS even though only demat and remat need it. That is
- * deliberate: picking per frame on the current alpha would hand out two different layer objects for
- * one texture, and the moment one TARDIS is fading while another sits landed that alternates every
- * frame, which costs more than the sort it saves.
- *
- * Exterior emission identifiers already reach both layers, because {@code ExteriorRenderer} is
- * here while {@code DoorRenderer}, {@code FlightTardisRenderer}, {@code FallingTardisRenderer},
- * {@code SnowGlobeRenderer} and the BOTI paths draw the same textures unsorted. {@code Immediate}
- * keys buffers on layer identity, so that costs one extra flush when both appear in a frame. It is
- * accepted: no {@code AITRenderLayers} layer has a dedicated buffer in {@code BufferBuilderStorage},
- * so every switch between them already flushes. Console emissions never collide, being a separate
- * texture directory and registry.
- */
- public static RenderLayer tardisEmissiveCullZOffsetSorted(Identifier texture) {
- return EMISSIVE_SORTED.apply(texture);
+ return EMISSIVE.apply(texture);
}
private AITRenderLayers(String name, VertexFormat vertexFormat, VertexFormat.DrawMode drawMode,
diff --git a/src/main/java/dev/amble/ait/client/renderers/TardisStar.java b/src/main/java/dev/amble/ait/client/renderers/TardisStar.java
index e8e6eba45..ef22d531e 100644
--- a/src/main/java/dev/amble/ait/client/renderers/TardisStar.java
+++ b/src/main/java/dev/amble/ait/client/renderers/TardisStar.java
@@ -64,13 +64,13 @@ public static void renderStar(WorldRenderContext context, Tardis tardis) {
.rotationDegrees(delta));
TardisStarModel.getTexturedModelData().createModel().render(matrixStack,
- provider.getBuffer(AITRenderLayers.tardisEmissiveCullZOffsetSorted(TARDIS_STAR_TEXTURE)),
+ provider.getBuffer(AITRenderLayers.tardisEmissiveCullZOffset(TARDIS_STAR_TEXTURE)),
LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV, tardis.isGrowth() ? 0.1f : 1,
tardis.isGrowth() ? 0.1f : 1, tardis.isGrowth() ? 0.1f : 1, 0.5f);
matrixStack.scale(0.9f, 0.9f, 0.9f);
TardisStarModel.getTexturedModelData().createModel().render(matrixStack,
- provider.getBuffer(AITRenderLayers.tardisEmissiveCullZOffsetSorted(TARDIS_STAR_TEXTURE)),
+ provider.getBuffer(AITRenderLayers.tardisEmissiveCullZOffset(TARDIS_STAR_TEXTURE)),
LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV, 1, tardis.isGrowth() ? 0.2f : 1,
tardis.isGrowth() ? 0f : 1, 1f);
diff --git a/src/main/java/dev/amble/ait/client/renderers/exteriors/ExteriorRenderer.java b/src/main/java/dev/amble/ait/client/renderers/exteriors/ExteriorRenderer.java
index 1516cebdc..39125adf2 100644
--- a/src/main/java/dev/amble/ait/client/renderers/exteriors/ExteriorRenderer.java
+++ b/src/main/java/dev/amble/ait/client/renderers/exteriors/ExteriorRenderer.java
@@ -310,13 +310,10 @@ private void renderExterior(Profiler profiler, ClientTardis tardis, T entity, fl
? !power ? 0.01f : 0.3f
: u - colorAlpha;
- // Sorted, not unsorted: alpha here is the demat and remat fade, which sweeps through the
- // partial range where draw order is visible.
- //
// TODO the guard above tests `emission`, which DOOM reassigns per rotation, but the layer
// below binds `variant.emission()`, the un-adjusted base. For DOOM those disagree. Left
// alone here because changing which texture DOOM binds is not part of this change.
- model.renderWithAnimations(tardis, entity, this.model.getPart(), matrices, vertexConsumers.getBuffer(AITRenderLayers.tardisEmissiveCullZOffsetSorted(variant.emission())),
+ model.renderWithAnimations(tardis, entity, this.model.getPart(), matrices, vertexConsumers.getBuffer(AITRenderLayers.tardisEmissiveCullZOffset(variant.emission())),
0xF000F0, OverlayTexture.DEFAULT_UV, red, green, blue, alpha, tickDelta);
}
if (DependencyChecker.hasIris()) {