Skip to content

Commit 9f179aa

Browse files
authored
Indigo and Renderer API fixes related to fallback consumers (#2775)
* Fix #2639: Indigo fallback consumer does not respect BlendMode or emissivity * Change renderer testmod to test material change * Remove presumably unneeded `quad.geometryFlags()` call * Also test emissivity * Call emitBlockQuads in the testmod * Allow passing the block state explicitly to the fallback consumer. Fixes #1871 * Expand testmod to also test item models * Also fix fallback consumer ignoring material for items * Slight changes * Introduce new interface for the expanded fallback consumer * Add javadoc to ModelHelper
1 parent b5d379b commit 9f179aa

File tree

12 files changed

+220
-115
lines changed

12 files changed

+220
-115
lines changed

fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/model/FabricBakedModel.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
* Can also be used to generate or customize outputs based on world state instead of
3737
* or in addition to block state when render chunks are rebuilt.
3838
*
39+
* <p>Implementors should have a look at {@link ModelHelper} as it contains many useful functions.
40+
*
3941
* <p>Note for {@link Renderer} implementors: Fabric causes BakedModel to extend this
4042
* interface with {@link #isVanillaAdapter()} == true and to produce standard vertex data.
4143
* This means any BakedModel instance can be safely cast to this interface without an instanceof check.
@@ -82,7 +84,7 @@ public interface FabricBakedModel {
8284
* parameter is normally initialized with the same seed prior to each face layer.
8385
* Model authors should note this method is called only once per block, and call the provided
8486
* Random supplier multiple times if re-seeding is necessary. For wrapped vanilla baked models,
85-
* it will probably be easier to use {@link RenderContext#fallbackConsumer} which handles
87+
* it will probably be easier to use {@link RenderContext#bakedModelConsumer()} which handles
8688
* re-seeding per face automatically.
8789
*
8890
* @param blockView Access to world state. Cast to {@code RenderAttachedBlockView} to

fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/render/RenderContext.java

+57-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import java.util.function.Consumer;
2020

21+
import org.jetbrains.annotations.Nullable;
22+
23+
import net.minecraft.block.BlockState;
2124
import net.minecraft.client.render.model.BakedModel;
2225

2326
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
@@ -38,12 +41,65 @@ public interface RenderContext {
3841
*/
3942
Consumer<Mesh> meshConsumer();
4043

44+
/**
45+
* Fallback consumer that can process a vanilla {@link BakedModel}.
46+
* Fabric causes vanilla baked models to send themselves
47+
* via this interface. Can also be used by compound models that contain a mix
48+
* of vanilla baked models, packaged quads and/or dynamic elements.
49+
*/
50+
default BakedModelConsumer bakedModelConsumer() {
51+
// Default implementation is provided for compat with older renderer implementations,
52+
// but they should always override this function.
53+
Consumer<BakedModel> fallback = fallbackConsumer();
54+
return new BakedModelConsumer() {
55+
@Override
56+
public void accept(BakedModel model) {
57+
fallback.accept(model);
58+
}
59+
60+
@Override
61+
public void accept(BakedModel model, @Nullable BlockState state) {
62+
fallback.accept(model);
63+
}
64+
};
65+
}
66+
67+
interface BakedModelConsumer extends Consumer<BakedModel> {
68+
/**
69+
* Render a baked model by processing its {@linkplain BakedModel#getQuads} using the rendered block state.
70+
*
71+
* <p>For block contexts, this will pass the block state being rendered to {@link BakedModel#getQuads}.
72+
* For item contexts, this will pass a {@code null} block state to {@link BakedModel#getQuads}.
73+
* {@link #accept(BakedModel, BlockState)} can be used instead to pass the block state explicitly.
74+
*/
75+
@Override
76+
void accept(BakedModel model);
77+
78+
/**
79+
* Render a baked model by processing its {@linkplain BakedModel#getQuads} with an explicit block state.
80+
*
81+
* <p>This overload allows passing the block state (or {@code null} to query the item quads).
82+
* This is useful when a model is being wrapped, and expects a different
83+
* block state than the one of the block being rendered.
84+
*
85+
* <p>For item render contexts, you can use this function if you want to render the model with a specific block state.
86+
* Otherwise, use {@linkplain #accept(BakedModel)} the other overload} to render the usual item quads.
87+
*/
88+
void accept(BakedModel model, @Nullable BlockState state);
89+
}
90+
4191
/**
4292
* Fabric causes vanilla baked models to send themselves
4393
* via this interface. Can also be used by compound models that contain a mix
4494
* of vanilla baked models, packaged quads and/or dynamic elements.
95+
*
96+
* @deprecated Prefer using the more flexible {@link #bakedModelConsumer}.
4597
*/
46-
Consumer<BakedModel> fallbackConsumer();
98+
@Deprecated
99+
default Consumer<BakedModel> fallbackConsumer() {
100+
// This default implementation relies on implementors overriding bakedModelConsumer().
101+
return bakedModelConsumer();
102+
}
47103

48104
/**
49105
* Returns a {@link QuadEmitter} instance that emits directly to the render buffer.

fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/BakedModelMixin.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ default boolean isVanillaAdapter() {
4242

4343
@Override
4444
default void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
45-
context.fallbackConsumer().accept((BakedModel) this);
45+
context.bakedModelConsumer().accept((BakedModel) this, state);
4646
}
4747

4848
@Override
4949
default void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
50-
context.fallbackConsumer().accept((BakedModel) this);
50+
// Pass null state to enforce item quads in block render contexts
51+
context.bakedModelConsumer().accept((BakedModel) this, null);
5152
}
5253
}

fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameBakedModel.java

+76-22
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import net.minecraft.block.Block;
2626
import net.minecraft.block.BlockState;
27+
import net.minecraft.block.Blocks;
2728
import net.minecraft.client.MinecraftClient;
2829
import net.minecraft.client.render.model.BakedModel;
2930
import net.minecraft.client.render.model.BakedQuad;
@@ -36,20 +37,29 @@
3637
import net.minecraft.world.BlockRenderView;
3738
import net.minecraft.util.math.random.Random;
3839

40+
import net.fabricmc.fabric.api.renderer.v1.Renderer;
41+
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
42+
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
43+
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
3944
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
40-
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
41-
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
4245
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
46+
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
4347
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
4448
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
4549

4650
final class FrameBakedModel implements BakedModel, FabricBakedModel {
4751
private final Mesh frameMesh;
4852
private final Sprite frameSprite;
53+
private final RenderMaterial translucentMaterial;
54+
private final RenderMaterial translucentEmissiveMaterial;
4955

5056
FrameBakedModel(Mesh frameMesh, Sprite frameSprite) {
5157
this.frameMesh = frameMesh;
5258
this.frameSprite = frameSprite;
59+
60+
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
61+
this.translucentMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).find();
62+
this.translucentEmissiveMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).emissive(0, true).find();
5363
}
5464

5565
@Override
@@ -69,7 +79,7 @@ public boolean hasDepth() {
6979

7080
@Override
7181
public boolean isSideLit() {
72-
return false;
82+
return true; // we want the block to be lit from the side when rendered as an item
7383
}
7484

7585
@Override
@@ -84,7 +94,7 @@ public Sprite getParticleSprite() {
8494

8595
@Override
8696
public ModelTransformation getTransformation() {
87-
return ModelTransformation.NONE;
97+
return ModelHelper.MODEL_TRANSFORM_BLOCK;
8898
}
8999

90100
@Override
@@ -112,27 +122,71 @@ public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos
112122
return; // No inner block to render
113123
}
114124

115-
Sprite sprite = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelManager().getBlockModels().getModelParticleSprite(data.getDefaultState());
116-
QuadEmitter emitter = context.getEmitter();
117-
118-
// We can emit our quads outside of the mesh as the block being put in the frame is very much dynamic.
119-
// Emit the quads for each face of the block inside the frame
120-
for (Direction direction : Direction.values()) {
121-
// Add a face, with an inset to give the appearance of the block being in a frame.
122-
emitter.square(direction, 0.1F, 0.1F, 0.9F, 0.9F, 0.1F)
123-
// Set the sprite of the fact, use whole texture via BAKE_LOCK_UV
124-
.spriteBake(0, sprite, MutableQuadView.BAKE_LOCK_UV)
125-
// Allow textures
126-
// TODO: the magic values here are not documented at all and probably should be
127-
.spriteColor(0, -1, -1, -1, -1)
128-
// Emit the quad
129-
.emit();
130-
}
125+
BlockState innerState = data.getDefaultState();
126+
127+
// Now, we emit a transparent scaled-down version of the inner model
128+
// Try both emissive and non-emissive versions of the translucent material
129+
RenderMaterial material = pos.getX() % 2 == 0 ? translucentMaterial : translucentEmissiveMaterial;
130+
131+
emitInnerQuads(context, material, () -> {
132+
// Use emitBlockQuads to allow for Renderer API features
133+
((FabricBakedModel) MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState)).emitBlockQuads(blockView, innerState, pos, randomSupplier, context);
134+
});
131135
}
132136

133137
@Override
134138
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
135-
// TODO: Implement an item test.
136-
// For now we will just leave this as I have not added a block item yet
139+
// Emit our frame mesh
140+
context.meshConsumer().accept(this.frameMesh);
141+
142+
// Emit a scaled-down fence for testing, trying both materials again.
143+
RenderMaterial material = stack.hasCustomName() ? translucentEmissiveMaterial : translucentMaterial;
144+
145+
BlockState innerState = Blocks.OAK_FENCE.getDefaultState();
146+
147+
emitInnerQuads(context, material, () -> {
148+
// Need to use the fallback consumer directly:
149+
// - we can't use emitBlockQuads because we don't have a blockView
150+
// - we can't use emitItemQuads because multipart models don't have item quads
151+
context.bakedModelConsumer().accept(MinecraftClient.getInstance().getBlockRenderManager().getModel(innerState), innerState);
152+
});
153+
}
154+
155+
/**
156+
* Emit a scaled-down version of the inner model.
157+
*/
158+
private void emitInnerQuads(RenderContext context, RenderMaterial material, Runnable innerModelEmitter) {
159+
// Let's push a transform to scale the model down and make it transparent
160+
context.pushTransform(quad -> {
161+
// Scale model down
162+
for (int vertex = 0; vertex < 4; ++vertex) {
163+
float x = quad.x(vertex) * 0.8f + 0.1f;
164+
float y = quad.y(vertex) * 0.8f + 0.1f;
165+
float z = quad.z(vertex) * 0.8f + 0.1f;
166+
quad.pos(vertex, x, y, z);
167+
}
168+
169+
// Make the quad partially transparent
170+
// Change material to translucent
171+
quad.material(material);
172+
173+
// Change vertex colors to be partially transparent
174+
for (int vertex = 0; vertex < 4; ++vertex) {
175+
int color = quad.spriteColor(vertex, 0);
176+
int alpha = (color >> 24) & 0xFF;
177+
alpha = alpha * 3 / 4;
178+
color = (color & 0xFFFFFF) | (alpha << 24);
179+
quad.spriteColor(vertex, 0, color);
180+
}
181+
182+
// Return true because we want the quad to be rendered
183+
return true;
184+
});
185+
186+
// Emit the inner block model
187+
innerModelEmitter.run();
188+
189+
// Let's not forget to pop the transform!
190+
context.popTransform();
137191
}
138192
}

fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/FrameModelResourceProvider.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package net.fabricmc.fabric.test.renderer.simple.client;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1922
import org.jetbrains.annotations.Nullable;
2023

2124
import net.minecraft.client.render.model.UnbakedModel;
@@ -28,12 +31,12 @@
2831
* Provides the unbaked model for use with the frame block.
2932
*/
3033
final class FrameModelResourceProvider implements ModelResourceProvider {
31-
private static final Identifier FRAME_MODEL_ID = new Identifier("fabric-renderer-api-v1-testmod", "block/frame");
34+
static final Set<Identifier> FRAME_MODELS = new HashSet<>();
3235

3336
@Nullable
3437
@Override
3538
public UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) {
36-
if (resourceId.equals(FRAME_MODEL_ID)) {
39+
if (FRAME_MODELS.contains(resourceId)) {
3740
return new FrameUnbakedModel();
3841
}
3942

fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client/RendererClientTest.java

+10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package net.fabricmc.fabric.test.renderer.simple.client;
1818

19+
import static net.fabricmc.fabric.test.renderer.simple.RendererTest.id;
20+
1921
import net.minecraft.client.render.RenderLayer;
22+
import net.minecraft.registry.Registries;
2023

2124
import net.fabricmc.api.ClientModInitializer;
2225
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
@@ -31,7 +34,14 @@ public void onInitializeClient() {
3134
ModelLoadingRegistry.INSTANCE.registerVariantProvider(manager -> new PillarModelVariantProvider());
3235

3336
for (FrameBlock frameBlock : RendererTest.FRAMES) {
37+
// We don't specify a material for the frame mesh,
38+
// so it will use the default material, i.e. the one from BlockRenderLayerMap.
3439
BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped());
40+
41+
String itemPath = Registries.ITEM.getId(frameBlock.asItem()).getPath();
42+
FrameModelResourceProvider.FRAME_MODELS.add(id("item/" + itemPath));
3543
}
44+
45+
FrameModelResourceProvider.FRAME_MODELS.add(id("block/frame"));
3646
}
3747
}

fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java

+2-42
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
2727
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
2828
import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
29-
import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
3029
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
3130
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat;
3231
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshImpl;
@@ -55,7 +54,7 @@ private class Maker extends MutableQuadViewImpl {
5554
@Override
5655
public Maker emit() {
5756
computeGeometry();
58-
renderQuad(this);
57+
renderQuad(this, false);
5958
clear();
6059
return this;
6160
}
@@ -74,51 +73,12 @@ public void accept(Mesh mesh) {
7473
System.arraycopy(data, index, editorQuad.data(), 0, EncodingFormat.TOTAL_STRIDE);
7574
editorQuad.load();
7675
index += EncodingFormat.TOTAL_STRIDE;
77-
renderQuad(editorQuad);
76+
renderQuad(editorQuad, false);
7877
}
7978
}
8079

8180
public QuadEmitter getEmitter() {
8281
editorQuad.clear();
8382
return editorQuad;
8483
}
85-
86-
private void renderQuad(MutableQuadViewImpl quad) {
87-
if (!transform.transform(quad)) {
88-
return;
89-
}
90-
91-
if (!blockInfo.shouldDrawFace(quad.cullFace())) {
92-
return;
93-
}
94-
95-
tessellateQuad(quad, 0);
96-
}
97-
98-
/**
99-
* Determines color index and render layer, then routes to appropriate
100-
* tessellate routine based on material properties.
101-
*/
102-
private void tessellateQuad(MutableQuadViewImpl quad, int textureIndex) {
103-
final RenderMaterialImpl.Value mat = quad.material();
104-
final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex();
105-
final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex));
106-
107-
if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) {
108-
// needs to happen before offsets are applied
109-
aoCalc.compute(quad, false);
110-
111-
if (mat.emissive(textureIndex)) {
112-
tessellateSmoothEmissive(quad, renderLayer, colorIndex);
113-
} else {
114-
tessellateSmooth(quad, renderLayer, colorIndex);
115-
}
116-
} else {
117-
if (mat.emissive(textureIndex)) {
118-
tessellateFlatEmissive(quad, renderLayer, colorIndex);
119-
} else {
120-
tessellateFlat(quad, renderLayer, colorIndex);
121-
}
122-
}
123-
}
12484
}

0 commit comments

Comments
 (0)