Skip to content

Commit fcd5137

Browse files
committed
Fix quad render pass optimization bug with certain multi-layer models
Models such as the XyCraft Override Observer incorrectly rendered the wrong quads as the top layer. This commit fixes that bug such that they render correctly.
1 parent f8240c7 commit fcd5137

File tree

5 files changed

+115
-40
lines changed

5 files changed

+115
-40
lines changed

src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadMixin.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package org.embeddedt.embeddium.impl.mixin.core.model.quad;
22

33
import org.embeddedt.embeddium.impl.model.quad.BakedQuadView;
4+
import org.embeddedt.embeddium.impl.model.quad.ModelQuad;
45
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
56
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFlags;
7+
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
8+
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevelHolder;
69
import org.embeddedt.embeddium.impl.util.ModelQuadUtil;
710
import net.minecraft.client.renderer.block.model.BakedQuad;
811
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
912
import net.minecraft.core.Direction;
13+
import org.jetbrains.annotations.Nullable;
1014
import org.spongepowered.asm.mixin.Final;
1115
import org.spongepowered.asm.mixin.Mixin;
1216
import org.spongepowered.asm.mixin.Shadow;
@@ -98,7 +102,7 @@ public int getForgeNormal(int idx) {
98102
public int getFlags() {
99103
int f = this.flags;
100104
if ((f & ModelQuadFlags.IS_POPULATED) == 0) {
101-
this.flags = f = (f | ModelQuadFlags.getQuadFlags(this, direction));
105+
this.flags = f = ModelQuadFlags.getQuadFlags(this, direction, f);
102106
}
103107
return f;
104108
}
@@ -108,6 +112,15 @@ public void setFlags(int flags) {
108112
this.flags = flags;
109113
}
110114

115+
@Override
116+
public @Nullable SpriteTransparencyLevel getTransparencyLevel() {
117+
if (this.sprite != null && (this.flags & ModelQuadFlags.IS_TRUSTED_SPRITE) != 0) {
118+
return SpriteTransparencyLevelHolder.getTransparencyLevel(this.sprite);
119+
} else {
120+
return null;
121+
}
122+
}
123+
111124
@Override
112125
public int getColorIndex() {
113126
return this.tintIndex;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package org.embeddedt.embeddium.impl.model.quad;
22

33
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
4+
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
5+
import org.jetbrains.annotations.Nullable;
46

57
public interface BakedQuadView extends ModelQuadView {
68
ModelQuadFacing getNormalFace();
79

810
boolean hasShade();
911

1012
void setFlags(int flags);
13+
14+
@Nullable SpriteTransparencyLevel getTransparencyLevel();
1115
}

src/main/java/org/embeddedt/embeddium/impl/model/quad/properties/ModelQuadFlags.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.embeddedt.embeddium.impl.model.quad.properties;
22

3+
import org.embeddedt.embeddium.api.util.ColorABGR;
34
import org.embeddedt.embeddium.impl.model.quad.ModelQuadView;
45
import net.minecraft.client.renderer.block.model.BakedQuad;
56
import net.minecraft.core.Direction;
@@ -8,28 +9,35 @@ public class ModelQuadFlags {
89
/**
910
* Indicates that the quad does not fully cover the given face for the model.
1011
*/
11-
public static final int IS_PARTIAL = 0b001;
12+
public static final int IS_PARTIAL = (1 << 0);
1213

1314
/**
1415
* Indicates that the quad is parallel to its light face.
1516
*/
16-
public static final int IS_PARALLEL = 0b010;
17+
public static final int IS_PARALLEL = (1 << 1);
1718

1819
/**
1920
* Indicates that the quad is aligned to the block grid.
2021
* This flag is only set if {@link #IS_PARALLEL} is set.
2122
*/
22-
public static final int IS_ALIGNED = 0b100;
23+
public static final int IS_ALIGNED = (1 << 2);
2324

2425
/**
2526
* Indicates that the quad should be shaded using vanilla's getShade logic and the light face, rather than
2627
* the normals of each vertex.
2728
*/
28-
public static final int IS_VANILLA_SHADED = 0b1000;
29+
public static final int IS_VANILLA_SHADED = (1 << 3);
30+
2931
/**
3032
* Indicates that the particle sprite on this quad can be trusted to be the only sprite it shows.
3133
*/
3234
public static final int IS_TRUSTED_SPRITE = (1 << 4);
35+
36+
/**
37+
* Indicates that this quad can use a more optimal terrain render pass based on its sprite.
38+
*/
39+
public static final int IS_PASS_OPTIMIZABLE = (1 << 5);
40+
3341
/**
3442
* Indicates that the flags are populated for the quad.
3543
*/
@@ -46,7 +54,7 @@ public static boolean contains(int flags, int mask) {
4654
* Calculates the properties of the given quad. This data is used later by the light pipeline in order to make
4755
* certain optimizations.
4856
*/
49-
public static int getQuadFlags(ModelQuadView quad, Direction face) {
57+
public static int getQuadFlags(ModelQuadView quad, Direction face, int existingFlags) {
5058
float minX = 32.0F;
5159
float minY = 32.0F;
5260
float minZ = 32.0F;
@@ -61,7 +69,7 @@ public static int getQuadFlags(ModelQuadView quad, Direction face) {
6169
}
6270

6371
float lX = Float.NaN, lY = Float.NaN, lZ = Float.NaN;
64-
boolean degenerate = false;
72+
boolean degenerate = false, nonOpaqueColor = false;
6573

6674
for (int i = 0; i < numVertices; ++i) {
6775
float x = quad.getX(i);
@@ -82,6 +90,10 @@ public static int getQuadFlags(ModelQuadView quad, Direction face) {
8290
lY = y;
8391
lZ = z;
8492
}
93+
94+
if (ColorABGR.unpackAlpha(quad.getColor(i)) != 255) {
95+
nonOpaqueColor = true;
96+
}
8597
}
8698

8799
boolean partial = degenerate || (switch (face.getAxis()) {
@@ -105,7 +117,7 @@ public static int getQuadFlags(ModelQuadView quad, Direction face) {
105117
case EAST -> maxX > 0.9999F;
106118
};
107119

108-
int flags = 0;
120+
int flags = existingFlags & ~(IS_PARTIAL | IS_PARALLEL | IS_ALIGNED);
109121

110122
if (partial) {
111123
flags |= IS_PARTIAL;
@@ -119,6 +131,10 @@ public static int getQuadFlags(ModelQuadView quad, Direction face) {
119131
flags |= IS_ALIGNED;
120132
}
121133

134+
if (!nonOpaqueColor && (flags & IS_TRUSTED_SPRITE) != 0) {
135+
flags |= IS_PASS_OPTIMIZABLE;
136+
}
137+
122138
flags |= IS_POPULATED;
123139

124140
return flags;

src/main/java/org/embeddedt/embeddium/impl/render/chunk/compile/pipeline/BlockRenderer.java

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,6 @@ public class BlockRenderer {
6464

6565
private final int[] quadColors = new int[4];
6666

67-
/**
68-
* Tracks whether the MC-138211 quad reorienting fix should be applied during emission of quad geometry.
69-
* This fix must be disabled with certain modded models that use superimposed quads, as it can alter the triangulation
70-
* of some layers but not others, resulting in Z-fighting.
71-
*/
72-
private boolean useReorienting;
73-
7467
/**
7568
* The list of registered custom block renderers. These may augment or fully bypass the model system for the
7669
* block.
@@ -81,7 +74,37 @@ public class BlockRenderer {
8174

8275
private final ChunkColorWriter colorEncoder = ChunkColorWriter.get();
8376

84-
private final boolean useRenderPassOptimization;
77+
/**
78+
* If {@code true}, the {@link #QUAD_RENDER_PASS_OPTIMIZATION} flag will be enabled.
79+
*/
80+
private final boolean enableRenderPassOptimization;
81+
82+
/**
83+
* No changes should be applied when rendering quads.
84+
*/
85+
private static final int QUAD_FLAGS_NONE = 0x0;
86+
/**
87+
* If specified, the MC-138211 quad reorienting fix should be applied during emission of quad geometry.
88+
* This fix must be disabled with certain modded models that use superimposed quads, as it can alter the triangulation
89+
* of some layers but not others, resulting in Z-fighting.
90+
*/
91+
private static final int QUAD_REORIENTING = 0x1;
92+
/**
93+
* If specified, quads will be placed in differing render passes than what is specified. This can help avoid
94+
* GPU bottlenecks by running the fragment shader on pixels which aren't visible anyway.
95+
* @see <a href="https://www.khronos.org/opengl/wiki/Early_Fragment_Test">Early Fragment Test</a>
96+
*/
97+
private static final int QUAD_RENDER_PASS_OPTIMIZATION = 0x2;
98+
/**
99+
* If specified, all changes should be applied when rendering quads.
100+
*/
101+
private static final int QUAD_FLAGS_ALL = QUAD_REORIENTING | QUAD_RENDER_PASS_OPTIMIZATION;
102+
103+
/**
104+
* Quad flags used to control how quads are emitted/rendered.
105+
* These take the values of the {@code QUAD_} constants specified in this class.
106+
*/
107+
private int quadRenderingFlags = QUAD_FLAGS_NONE;
85108

86109
public BlockRenderer(ColorProviderRegistry colorRegistry, LightPipelineProvider lighters) {
87110
this.colorProviderRegistry = colorRegistry;
@@ -90,7 +113,7 @@ public BlockRenderer(ColorProviderRegistry colorRegistry, LightPipelineProvider
90113
this.occlusionCache = new BlockOcclusionCache();
91114
this.useAmbientOcclusion = Minecraft.useAmbientOcclusion();
92115
this.fabricModelRenderingHandler = FRAPIRenderHandler.INDIGO_PRESENT ? new IndigoBlockRenderContext(this.occlusionCache, lighters.getLightData()) : null;
93-
this.useRenderPassOptimization = Embeddium.options().performance.useRenderPassOptimization && !ShaderModBridge.areShadersEnabled();
116+
this.enableRenderPassOptimization = Embeddium.options().performance.useRenderPassOptimization && !ShaderModBridge.areShadersEnabled();
94117
}
95118

96119
/**
@@ -137,26 +160,22 @@ public void renderModel(BlockRenderContext ctx, ChunkBuildBuffers buffers) {
137160
return;
138161
}
139162

140-
boolean canReorientNullCullface = true;
163+
var nullCullfaceFlags = QUAD_FLAGS_ALL;
141164

142165
for (Direction face : DirectionUtil.ALL_DIRECTIONS) {
143166
List<BakedQuad> quads = this.getGeometry(ctx, face);
144167

145168
if (!quads.isEmpty() && this.isFaceVisible(ctx, face)) {
146-
this.useReorienting = true;
169+
this.quadRenderingFlags = QUAD_FLAGS_ALL;
147170
this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, buffers, meshBuilder, quads, face);
148-
if (!this.useReorienting) {
149-
// Reorienting was disabled on this side, make sure it's disabled for the null cullface too, in case
150-
// a mod layers textures in different lists
151-
canReorientNullCullface = false;
152-
}
171+
nullCullfaceFlags &= this.quadRenderingFlags;
153172
}
154173
}
155174

156175
List<BakedQuad> all = this.getGeometry(ctx, null);
157176

158177
if (!all.isEmpty()) {
159-
this.useReorienting = canReorientNullCullface;
178+
this.quadRenderingFlags = nullCullfaceFlags;
160179
this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, buffers, meshBuilder, all, null);
161180
}
162181
}
@@ -186,32 +205,54 @@ private static int computeLightFlagMask(BakedQuad quad) {
186205
return flag;
187206
}
188207

189-
/**
190-
* {@return true if all quads in the given list use similar enough lighting configuration that reorientation is
191-
* unlikely to lead to z-fighting}
192-
*/
193-
private static boolean checkQuadsHaveSameLightingConfig(List<BakedQuad> quads) {
208+
private SpriteTransparencyLevel getQuadTransparencyLevel(BakedQuadView quad) {
209+
if ((quad.getFlags() & ModelQuadFlags.IS_PASS_OPTIMIZABLE) == 0 || quad.getSprite() == null) {
210+
return SpriteTransparencyLevel.TRANSLUCENT;
211+
}
212+
213+
return SpriteTransparencyLevelHolder.getTransparencyLevel(quad.getSprite());
214+
}
215+
216+
private void checkQuadsHaveSameLightingConfig(List<BakedQuad> quads) {
194217
int quadsSize = quads.size();
195218

196219
// By definition, singleton or empty lists of quads have a common lighting config. Only check larger lists
197220
if (quadsSize >= 2) {
198221
int flagMask = -1;
222+
223+
var highestSeenLevel = SpriteTransparencyLevel.OPAQUE;
224+
199225
// noinspection ForLoopReplaceableByForEach
200226
for (int i = 0; i < quadsSize; i++) {
201-
int newFlag = computeLightFlagMask(quads.get(i));
227+
var quad = quads.get(i);
228+
int newFlag = computeLightFlagMask(quad);
202229
if (flagMask == -1) {
203230
flagMask = newFlag;
204231
} else if(newFlag != flagMask) {
205-
return false;
232+
this.quadRenderingFlags &= ~QUAD_REORIENTING;
233+
}
234+
235+
var seenLevel = getQuadTransparencyLevel((BakedQuadView)quad);
236+
237+
if (seenLevel.ordinal() < highestSeenLevel.ordinal()) {
238+
this.quadRenderingFlags &= ~QUAD_RENDER_PASS_OPTIMIZATION;
239+
} else {
240+
highestSeenLevel = seenLevel;
206241
}
207242
}
208243
}
209244

210-
return true;
245+
if (!this.enableRenderPassOptimization) {
246+
this.quadRenderingFlags &= ~QUAD_RENDER_PASS_OPTIMIZATION;
247+
}
211248
}
212249

213250
private ChunkModelBuilder chooseOptimalBuilder(Material defaultMaterial, ChunkBuildBuffers buffers, ChunkModelBuilder defaultBuilder, BakedQuadView quad) {
214-
if (defaultMaterial == DefaultMaterials.SOLID || !this.useRenderPassOptimization || (quad.getFlags() & ModelQuadFlags.IS_TRUSTED_SPRITE) == 0 || quad.getSprite() == null) {
251+
if (defaultMaterial == DefaultMaterials.SOLID ||
252+
((this.quadRenderingFlags & QUAD_RENDER_PASS_OPTIMIZATION) == 0) ||
253+
(quad.getFlags() & ModelQuadFlags.IS_PASS_OPTIMIZABLE) == 0 ||
254+
quad.getSprite() == null) {
255+
215256
// No improvement possible
216257
return defaultBuilder;
217258
}
@@ -233,11 +274,7 @@ private ChunkModelBuilder chooseOptimalBuilder(Material defaultMaterial, ChunkBu
233274
private void renderQuadList(BlockRenderContext ctx, Material material, LightPipeline lighter, ColorProvider<BlockState> colorizer, Vec3 offset,
234275
ChunkBuildBuffers buffers, ChunkModelBuilder defaultBuilder, List<BakedQuad> quads, Direction cullFace) {
235276

236-
if(!checkQuadsHaveSameLightingConfig(quads)) {
237-
// Disable reorienting if quads use different light configurations, as otherwise layered quads
238-
// may be triangulated differently from others in the stack, and that will cause z-fighting.
239-
this.useReorienting = false;
240-
}
277+
checkQuadsHaveSameLightingConfig(quads);
241278

242279
// This is a very hot allocation, iterate over it manually
243280
// noinspection ForLoopReplaceableByForEach
@@ -290,7 +327,7 @@ private void writeGeometry(BlockRenderContext ctx,
290327
int[] colors,
291328
QuadLightData light)
292329
{
293-
ModelQuadOrientation orientation = this.useReorienting ? ModelQuadOrientation.orientByBrightness(light.br, light.lm) : ModelQuadOrientation.NORMAL;
330+
ModelQuadOrientation orientation = ((this.quadRenderingFlags & QUAD_REORIENTING) != 0) ? ModelQuadOrientation.orientByBrightness(light.br, light.lm) : ModelQuadOrientation.NORMAL;
294331
var vertices = this.vertices;
295332

296333
ModelQuadFacing normalFace = quad.getNormalFace();

src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevelHolder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package org.embeddedt.embeddium.impl.render.chunk.sprite;
22

33
import net.minecraft.client.renderer.texture.SpriteContents;
4+
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
45

56
public interface SpriteTransparencyLevelHolder {
67
SpriteTransparencyLevel embeddium$getTransparencyLevel();
78

9+
static SpriteTransparencyLevel getTransparencyLevel(TextureAtlasSprite sprite) {
10+
return getTransparencyLevel(sprite.contents());
11+
}
12+
813
static SpriteTransparencyLevel getTransparencyLevel(SpriteContents contents) {
914
return ((SpriteTransparencyLevelHolder)contents).embeddium$getTransparencyLevel();
1015
}

0 commit comments

Comments
 (0)