From 92fab85f88676c3339e9a657421b8e4cc88c8554 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:26:16 -0700 Subject: [PATCH 1/6] Improve model builders - Additionally provide AO to material function - Optimize mesh count by not preserving quad order between blocks - Optimize mesh count by not splitting meshes when different inputs to the material function (except RenderType) produce the same material --- .../flywheel/lib/material/Materials.java | 16 +-- .../flywheel/lib/model/ModelUtil.java | 5 + .../lib/model/baked/BakedModelBuilder.java | 14 +- .../model/baked/BlockMaterialFunction.java | 11 ++ .../lib/model/baked/BlockModelBuilder.java | 14 +- .../flywheel/lib/model/baked/MeshEmitter.java | 87 ++++++++++++ .../lib/model/baked/MeshEmitterManager.java | 78 ++++++++++ .../baked/ModelBuilderResultConsumer.java | 37 +++++ .../lib/model/baked/ResultConsumer.java | 14 ++ .../lib/model/baked/BakedModelBufferer.java | 88 ++++-------- .../baked/ChunkLayerSortedListBuilder.java | 58 -------- .../model/baked/FabricBakedModelBuilder.java | 33 +++-- .../model/baked/FabricBlockModelBuilder.java | 25 ++-- ...ter.java => FabricMeshEmitterManager.java} | 84 +++++++---- .../flywheel/lib/model/baked/MeshEmitter.java | 58 -------- forge/build.gradle.kts | 1 + .../lib/model/baked/BakedModelBufferer.java | 84 ++++++----- .../baked/ChunkLayerSortedListBuilder.java | 54 ------- .../model/baked/ForgeBakedModelBuilder.java | 25 ++-- .../model/baked/ForgeBlockModelBuilder.java | 25 ++-- .../lib/model/baked/ForgeMeshEmitter.java | 131 +++++++++++++++++ .../model/baked/ForgeMeshEmitterManager.java | 13 ++ .../flywheel/lib/model/baked/MeshEmitter.java | 134 ------------------ .../mixin/forge/ModelBlockRendererMixin.java | 30 ++++ .../resources/flywheel.impl.forge.mixins.json | 13 ++ 25 files changed, 624 insertions(+), 508 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java delete mode 100644 fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java rename fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/{UniversalMeshEmitter.java => FabricMeshEmitterManager.java} (64%) delete mode 100644 fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java delete mode 100644 forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java create mode 100644 forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java create mode 100644 forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java delete mode 100644 forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java create mode 100644 forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java create mode 100644 forge/src/main/resources/flywheel.impl.forge.mixins.json diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java index 4f56e36db..932191dd4 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java @@ -10,15 +10,14 @@ public final class Materials { public static final Material SOLID_BLOCK = SimpleMaterial.builder() .build(); - public static final Material SOLID_UNSHADED_BLOCK = SimpleMaterial.builder() + public static final Material SOLID_UNSHADED_BLOCK = SimpleMaterial.builderOf(SOLID_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); public static final Material CUTOUT_MIPPED_BLOCK = SimpleMaterial.builder() .cutout(CutoutShaders.HALF) .build(); - public static final Material CUTOUT_MIPPED_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.HALF) + public static final Material CUTOUT_MIPPED_UNSHADED_BLOCK = SimpleMaterial.builderOf(CUTOUT_MIPPED_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); @@ -26,17 +25,14 @@ public final class Materials { .cutout(CutoutShaders.ONE_TENTH) .mipmap(false) .build(); - public static final Material CUTOUT_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.ONE_TENTH) - .mipmap(false) + public static final Material CUTOUT_UNSHADED_BLOCK = SimpleMaterial.builderOf(CUTOUT_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); public static final Material TRANSLUCENT_BLOCK = SimpleMaterial.builder() .transparency(Transparency.ORDER_INDEPENDENT) .build(); - public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builder() - .transparency(Transparency.ORDER_INDEPENDENT) + public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builderOf(TRANSLUCENT_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); @@ -44,9 +40,7 @@ public final class Materials { .cutout(CutoutShaders.ONE_TENTH) .transparency(Transparency.ORDER_INDEPENDENT) .build(); - public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.ONE_TENTH) - .transparency(Transparency.ORDER_INDEPENDENT) + public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builderOf(TRIPWIRE_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java index 48acb921f..5bfc8d660 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java @@ -24,6 +24,11 @@ private ModelUtil() { @Nullable public static Material getMaterial(RenderType chunkRenderType, boolean shaded) { + return getMaterial(chunkRenderType, shaded, true); + } + + @Nullable + public static Material getMaterial(RenderType chunkRenderType, boolean shaded, boolean ambientOcclusion) { if (chunkRenderType == RenderType.solid()) { return shaded ? Materials.SOLID_BLOCK : Materials.SOLID_UNSHADED_BLOCK; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java index 3eed07db2..2e6130fb5 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java @@ -25,7 +25,7 @@ public abstract class BakedModelBuilder { @Nullable PoseStack poseStack; @Nullable - BiFunction materialFunc; + BlockMaterialFunction materialFunc; BakedModelBuilder(BakedModel bakedModel) { this.bakedModel = bakedModel; @@ -50,7 +50,17 @@ public BakedModelBuilder poseStack(@Nullable PoseStack poseStack) { return this; } - public BakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public BakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + if (materialFunc != null) { + this.materialFunc = (chunkRenderType, shaded, ambientOcclusion) -> materialFunc.apply(chunkRenderType, shaded); + } else { + this.materialFunc = null; + } + return this; + } + + public BakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { this.materialFunc = materialFunc; return this; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java new file mode 100644 index 000000000..c8b177ec8 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java @@ -0,0 +1,11 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.material.Material; +import net.minecraft.client.renderer.RenderType; + +public interface BlockMaterialFunction { + @Nullable + Material apply(RenderType chunkRenderType, boolean shaded, boolean ambientOcclusion); +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java index 67f2eb0a6..060168d2d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java @@ -22,7 +22,7 @@ public abstract class BlockModelBuilder { PoseStack poseStack; boolean renderFluids = false; @Nullable - BiFunction materialFunc; + BlockMaterialFunction materialFunc; BlockModelBuilder(BlockAndTintGetter level, Iterable positions) { this.level = level; @@ -43,7 +43,17 @@ public BlockModelBuilder renderFluids(boolean renderFluids) { return this; } - public BlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public BlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + if (materialFunc != null) { + this.materialFunc = (chunkRenderType, shaded, ambientOcclusion) -> materialFunc.apply(chunkRenderType, shaded); + } else { + this.materialFunc = null; + } + return this; + } + + public BlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { this.materialFunc = materialFunc; return this; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java new file mode 100644 index 000000000..15451c592 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java @@ -0,0 +1,87 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.jetbrains.annotations.UnknownNullability; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +class MeshEmitter { + private final Supplier bufferBuilderSupplier; + + private final List activeBufferBuilders = new ArrayList<>(); + private final List activeKeys = new ArrayList<>(); + + @UnknownNullability + ResultConsumer resultConsumer; + + private int currentIndex = -1; + @UnknownNullability + private BufferBuilder currentBufferBuilder; + @UnknownNullability + private Object currentKey; + + MeshEmitter(Supplier bufferBuilderSupplier) { + this.bufferBuilderSupplier = bufferBuilderSupplier; + } + + @SuppressWarnings("unchecked") + public void prepare(ResultConsumer resultConsumer) { + this.resultConsumer = (ResultConsumer) resultConsumer; + } + + public void prepareForBlock() { + currentIndex = -1; + currentBufferBuilder = null; + currentKey = null; + } + + public void end() { + for (int index = 0; index < activeBufferBuilders.size(); index++) { + var renderedBuffer = activeBufferBuilders.get(index).endOrDiscardIfEmpty(); + + if (renderedBuffer != null) { + resultConsumer.accept(activeKeys.get(index), renderedBuffer); + renderedBuffer.release(); + } + } + + activeBufferBuilders.clear(); + activeKeys.clear(); + resultConsumer = null; + currentIndex = -1; + currentBufferBuilder = null; + currentKey = null; + } + + public BufferBuilder getBuffer(Object key) { + if (currentIndex < 0 || !key.equals(currentKey)) { + while (true) { + currentIndex++; + + if (currentIndex >= activeBufferBuilders.size()) { + BufferBuilder bufferBuilder = bufferBuilderSupplier.get(); + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); + activeBufferBuilders.add(bufferBuilder); + activeKeys.add(key); + currentBufferBuilder = bufferBuilder; + currentKey = key; + break; + } + + Object searchKey = activeKeys.get(currentIndex); + if (key.equals(searchKey)) { + currentBufferBuilder = activeBufferBuilders.get(currentIndex); + currentKey = searchKey; + break; + } + } + } + + return currentBufferBuilder; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java new file mode 100644 index 000000000..07813afc0 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java @@ -0,0 +1,78 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import com.mojang.blaze3d.vertex.BufferBuilder; + +import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.minecraft.client.renderer.RenderType; + +class MeshEmitterManager { + private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); + + final Reference2ReferenceMap emitterMap = new Reference2ReferenceLinkedOpenHashMap<>(); + private final ReferenceArrayList bufferBuilders = new ReferenceArrayList<>(); + + @UnknownNullability + private ResultConsumer resultConsumer; + private int nextBufferBuilderIndex; + + MeshEmitterManager(BiFunction, RenderType, T> meshEmitterFactory) { + for (RenderType renderType : CHUNK_LAYERS) { + T emitter = meshEmitterFactory.apply(this::getOrCreateBufferBuilder, renderType); + emitterMap.put(renderType, emitter); + } + } + + public void prepare(ResultConsumer resultConsumer) { + this.resultConsumer = resultConsumer; + nextBufferBuilderIndex = 0; + + for (MeshEmitter emitter : emitterMap.values()) { + emitter.prepare(resultConsumer); + } + } + + public void prepareForBlock() { + for (MeshEmitter emitter : emitterMap.values()) { + emitter.prepareForBlock(); + } + } + + public void end() { + resultConsumer = null; + nextBufferBuilderIndex = 0; + + for (MeshEmitter emitter : emitterMap.values()) { + emitter.end(); + } + } + + @Nullable + public BufferBuilder getBuffer(RenderType renderType, boolean shade, boolean ao) { + Object key = resultConsumer.createKey(renderType, shade, ao); + if (key != null) { + return emitterMap.get(renderType).getBuffer(key); + } else { + return null; + } + } + + private BufferBuilder getOrCreateBufferBuilder() { + BufferBuilder bufferBuilder; + if (nextBufferBuilderIndex < bufferBuilders.size()) { + bufferBuilder = bufferBuilders.get(nextBufferBuilderIndex); + } else { + bufferBuilder = new BufferBuilder(0); + bufferBuilders.add(bufferBuilder); + } + nextBufferBuilderIndex++; + return bufferBuilder; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java new file mode 100644 index 000000000..6c832e282 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java @@ -0,0 +1,37 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.BufferBuilder; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.SimpleModel; +import net.minecraft.client.renderer.RenderType; + +class ModelBuilderResultConsumer implements ResultConsumer { + private final BlockMaterialFunction materialFunc; + private final ImmutableList.Builder meshes = ImmutableList.builder(); + + ModelBuilderResultConsumer(BlockMaterialFunction materialFunc) { + this.materialFunc = materialFunc; + } + + @Override + @Nullable + public Material createKey(RenderType renderType, boolean shade, boolean ambientOcclusion) { + return materialFunc.apply(renderType, shade, ambientOcclusion); + } + + @Override + public void accept(Material material, BufferBuilder.RenderedBuffer data) { + Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=ModelBuilder" + ",material=" + material); + meshes.add(new Model.ConfiguredMesh(material, mesh)); + } + + public SimpleModel build() { + return new SimpleModel(meshes.build()); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java new file mode 100644 index 000000000..8d815b50a --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java @@ -0,0 +1,14 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.BufferBuilder; + +import net.minecraft.client.renderer.RenderType; + +interface ResultConsumer { + @Nullable + T createKey(RenderType renderType, boolean shade, boolean ambientOcclusion); + + void accept(T key, BufferBuilder.RenderedBuffer data); +} diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index e97c5154e..2a9114d4d 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -4,11 +4,9 @@ import org.jetbrains.annotations.Nullable; -import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -24,84 +22,78 @@ import net.minecraft.world.level.material.FluidState; final class BakedModelBufferer { - static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length; - private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) { + public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; - UniversalMeshEmitter universalEmitter = objects.universalEmitter; + FabricMeshEmitterManager emitters = objects.emitters; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(resultConsumer); long seed = state.getSeed(pos); RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state); - universalEmitter.prepare(defaultLayer); - model = universalEmitter.wrapModel(model); + boolean useAo = Minecraft.useAmbientOcclusion(); + boolean defaultAo = useAo && state.getLightEmission() == 0 && model.useAmbientOcclusion(); + model = emitters.prepareForModel(model, defaultLayer, useAo, defaultAo); poseStack.pushPose(); Minecraft.getInstance() .getBlockRenderer() .getModelRenderer() - .tesselateBlock(level, model, state, pos, poseStack, universalEmitter, false, random, seed, OverlayTexture.NO_OVERLAY); + .tesselateBlock(level, model, state, pos, poseStack, emitters, false, random, seed, OverlayTexture.NO_OVERLAY); poseStack.popPose(); - universalEmitter.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } + emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ResultConsumer resultConsumer) { + public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; - Reference2ReferenceMap emitterMap = objects.emitterMap; - UniversalMeshEmitter universalEmitter = objects.universalEmitter; + FabricMeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(resultConsumer); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); - ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer.enableCaching(); + boolean useAo = Minecraft.useAmbientOcclusion(); + while (posIterator.hasNext()) { BlockPos pos = posIterator.next(); BlockState state = level.getBlockState(pos); + emitters.prepareForBlock(); + if (renderFluids) { FluidState fluidState = state.getFluidState(); if (!fluidState.isEmpty()) { RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState); - transformingWrapper.prepare(emitterMap.get(renderType).getBuffer(true), poseStack); + BufferBuilder bufferBuilder = emitters.getBuffer(renderType, true, false); - poseStack.pushPose(); - poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); - renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); - poseStack.popPose(); + if (bufferBuilder != null) { + transformingWrapper.prepare(bufferBuilder, poseStack); + + poseStack.pushPose(); + poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); + renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); + poseStack.popPose(); + } } } @@ -110,46 +102,26 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett BakedModel model = renderDispatcher.getBlockModel(state); RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state); - universalEmitter.prepare(defaultLayer); - model = universalEmitter.wrapModel(model); + boolean defaultAo = useAo && state.getLightEmission() == 0 && model.useAmbientOcclusion(); + model = emitters.prepareForModel(model, defaultLayer, useAo, defaultAo); poseStack.pushPose(); poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); - blockRenderer.tesselateBlock(level, model, state, pos, poseStack, universalEmitter, true, random, seed, OverlayTexture.NO_OVERLAY); + blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitters, true, random, seed, OverlayTexture.NO_OVERLAY); poseStack.popPose(); } } ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - universalEmitter.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } - } - - public interface ResultConsumer { - void accept(RenderType renderType, boolean shaded, RenderedBuffer data); + emitters.end(); } private static class ThreadLocalObjects { public final PoseStack identityPoseStack = new PoseStack(); public final RandomSource random = RandomSource.createNewThreadLocalInstance(); - public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; - public final Reference2ReferenceMap emitterMap = new Reference2ReferenceOpenHashMap<>(); - public final UniversalMeshEmitter universalEmitter; + public final FabricMeshEmitterManager emitters = new FabricMeshEmitterManager(); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); - - { - for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = CHUNK_LAYERS[layerIndex]; - MeshEmitter emitter = new MeshEmitter(renderType); - emitters[layerIndex] = emitter; - emitterMap.put(renderType, emitter); - } - universalEmitter = new UniversalMeshEmitter(emitterMap); - } } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java deleted file mode 100644 index eae9dad15..000000000 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -import net.minecraft.client.renderer.RenderType; - -class ChunkLayerSortedListBuilder { - private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(ChunkLayerSortedListBuilder::new); - - @SuppressWarnings("unchecked") - private final ObjectArrayList[] lists = new ObjectArrayList[BakedModelBufferer.CHUNK_LAYER_AMOUNT]; - private final Reference2ReferenceMap> map = new Reference2ReferenceOpenHashMap<>(); - - private ChunkLayerSortedListBuilder() { - for (int layerIndex = 0; layerIndex < BakedModelBufferer.CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = BakedModelBufferer.CHUNK_LAYERS[layerIndex]; - ObjectArrayList list = new ObjectArrayList<>(); - lists[layerIndex] = list; - map.put(renderType, list); - } - } - - @SuppressWarnings("unchecked") - public static ChunkLayerSortedListBuilder getThreadLocal() { - return (ChunkLayerSortedListBuilder) THREAD_LOCAL.get(); - } - - public void add(RenderType renderType, T obj) { - List list = map.get(renderType); - if (list == null) { - throw new IllegalArgumentException("RenderType '" + renderType + "' is not a chunk layer"); - } - list.add(obj); - } - - @SuppressWarnings("unchecked") - public ImmutableList build() { - int size = 0; - for (ObjectArrayList list : lists) { - size += list.size(); - } - - T[] array = (T[]) new Object[size]; - int destPos = 0; - for (ObjectArrayList list : lists) { - System.arraycopy(list.elements(), 0, array, destPos, list.size()); - destPos += list.size(); - list.clear(); - } - - return ImmutableList.copyOf(array); - } -} diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java index acf01934c..d6465ddc8 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java @@ -2,11 +2,11 @@ import java.util.function.BiFunction; +import org.jetbrains.annotations.Nullable; + import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -21,25 +21,32 @@ public FabricBakedModelBuilder(BakedModel bakedModel) { } @Override - public FabricBakedModelBuilder level(BlockAndTintGetter level) { + public FabricBakedModelBuilder level(@Nullable BlockAndTintGetter level) { super.level(level); return this; } @Override - public FabricBakedModelBuilder pos(BlockPos pos) { + public FabricBakedModelBuilder pos(@Nullable BlockPos pos) { super.pos(pos); return this; } @Override - public FabricBakedModelBuilder poseStack(PoseStack poseStack) { + public FabricBakedModelBuilder poseStack(@Nullable PoseStack poseStack) { super.poseStack(poseStack); return this; } @Override - public FabricBakedModelBuilder materialFunc(BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public FabricBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public FabricBakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -57,16 +64,8 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); + BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, resultConsumer); + return resultConsumer.build(); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java index 6e3bb0911..7d5479c4a 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java @@ -7,8 +7,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -33,7 +31,14 @@ public FabricBlockModelBuilder renderFluids(boolean renderFluids) { } @Override - public FabricBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public FabricBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public FabricBlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -44,16 +49,8 @@ public SimpleModel build() { materialFunc = ModelUtil::getMaterial; } - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); + BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, resultConsumer); + return resultConsumer.build(); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java similarity index 64% rename from fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java rename to fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java index feb560bcc..c6dfa9de4 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java @@ -9,11 +9,11 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.resources.model.BakedModel; @@ -22,108 +22,136 @@ import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -class UniversalMeshEmitter implements VertexConsumer { - private final Reference2ReferenceMap emitterMap; +class FabricMeshEmitterManager extends MeshEmitterManager implements VertexConsumer { private final WrapperModel wrapperModel = new WrapperModel(); @UnknownNullability private RenderType defaultLayer; - @UnknownNullability + private boolean useAo; + private boolean defaultAo; + @Nullable private BufferBuilder currentDelegate; - UniversalMeshEmitter(Reference2ReferenceMap emitterMap) { - this.emitterMap = emitterMap; - } + FabricMeshEmitterManager() { + super((bufferBuilderSupplier, renderType) -> new MeshEmitter(bufferBuilderSupplier)); + } - public void prepare(RenderType defaultLayer) { + public BakedModel prepareForModel(BakedModel model, RenderType defaultLayer, boolean useAo, boolean defaultAo) { this.defaultLayer = defaultLayer; + this.useAo = useAo; + this.defaultAo = defaultAo; + wrapperModel.setWrapped(model); + return wrapperModel; } - public void clear() { + @Override + public void end() { wrapperModel.setWrapped(null); - } - - public BakedModel wrapModel(BakedModel model) { - wrapperModel.setWrapped(model); - return wrapperModel; + super.end(); } private void prepareForGeometry(RenderMaterial material) { BlendMode blendMode = material.blendMode(); RenderType layer = blendMode == BlendMode.DEFAULT ? defaultLayer : blendMode.blockRenderLayer; boolean shade = !material.disableDiffuse(); - currentDelegate = emitterMap.get(layer).getBuffer(shade); + TriState aoMode = material.ambientOcclusion(); + boolean ao = useAo && aoMode.orElse(defaultAo); + currentDelegate = getBuffer(layer, shade, ao); } @Override public VertexConsumer vertex(double x, double y, double z) { - currentDelegate.vertex(x, y, z); + if (currentDelegate != null) { + currentDelegate.vertex(x, y, z); + } return this; } @Override public VertexConsumer color(int red, int green, int blue, int alpha) { - currentDelegate.color(red, green, blue, alpha); + if (currentDelegate != null) { + currentDelegate.color(red, green, blue, alpha); + } return this; } @Override public VertexConsumer uv(float u, float v) { - currentDelegate.uv(u, v); + if (currentDelegate != null) { + currentDelegate.uv(u, v); + } return this; } @Override public VertexConsumer overlayCoords(int u, int v) { - currentDelegate.overlayCoords(u, v); + if (currentDelegate != null) { + currentDelegate.overlayCoords(u, v); + } return this; } @Override public VertexConsumer uv2(int u, int v) { - currentDelegate.uv2(u, v); + if (currentDelegate != null) { + currentDelegate.uv2(u, v); + } return this; } @Override public VertexConsumer normal(float x, float y, float z) { - currentDelegate.normal(x, y, z); + if (currentDelegate != null) { + currentDelegate.normal(x, y, z); + } return this; } @Override public void endVertex() { - currentDelegate.endVertex(); + if (currentDelegate != null) { + currentDelegate.endVertex(); + } } @Override public void defaultColor(int red, int green, int blue, int alpha) { - currentDelegate.defaultColor(red, green, blue, alpha); + if (currentDelegate != null) { + currentDelegate.defaultColor(red, green, blue, alpha); + } } @Override public void unsetDefaultColor() { - currentDelegate.unsetDefaultColor(); + if (currentDelegate != null) { + currentDelegate.unsetDefaultColor(); + } } @Override public void vertex(float x, float y, float z, float red, float green, float blue, float alpha, float u, float v, int overlay, int light, float normalX, float normalY, float normalZ) { - currentDelegate.vertex(x, y, z, red, green, blue, alpha, u, v, overlay, light, normalX, normalY, normalZ); + if (currentDelegate != null) { + currentDelegate.vertex(x, y, z, red, green, blue, alpha, u, v, overlay, light, normalX, normalY, normalZ); + } } @Override public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { - currentDelegate.putBulkData(pose, quad, red, green, blue, light, overlay); + if (currentDelegate != null) { + currentDelegate.putBulkData(pose, quad, red, green, blue, light, overlay); + } } @Override public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { - currentDelegate.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + if (currentDelegate != null) { + currentDelegate.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + } } private class WrapperModel extends ForwardingBakedModel { private final RenderContext.QuadTransform quadTransform = quad -> { - UniversalMeshEmitter.this.prepareForGeometry(quad.material()); + FabricMeshEmitterManager.this.prepareForGeometry(quad.material()); return true; }; diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java deleted file mode 100644 index 13bdfc521..000000000 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.UnknownNullability; - -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.VertexFormat; - -import net.minecraft.client.renderer.RenderType; - -class MeshEmitter { - private final RenderType renderType; - private final BufferBuilder bufferBuilder; - - private BakedModelBufferer.@UnknownNullability ResultConsumer resultConsumer; - private boolean currentShade; - - MeshEmitter(RenderType renderType) { - this.renderType = renderType; - this.bufferBuilder = new BufferBuilder(renderType.bufferSize()); - } - - public void prepare(BakedModelBufferer.ResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; - } - - public void end() { - if (bufferBuilder.building()) { - emit(); - } - resultConsumer = null; - } - - public BufferBuilder getBuffer(boolean shade) { - prepareForGeometry(shade); - return bufferBuilder; - } - - private void prepareForGeometry(boolean shade) { - if (!bufferBuilder.building()) { - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } else if (shade != currentShade) { - emit(); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } - - currentShade = shade; - } - - private void emit() { - var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty(); - - if (renderedBuffer != null) { - resultConsumer.accept(renderType, currentShade, renderedBuffer); - renderedBuffer.release(); - } - } -} diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 220ff8b50..45d75cffb 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -119,6 +119,7 @@ loom { forge { mixinConfig("flywheel.backend.mixins.json") mixinConfig("flywheel.impl.mixins.json") + mixinConfig("flywheel.impl.forge.mixins.json") } runs { diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 996144805..b9487d09a 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.Nullable; -import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Minecraft; @@ -25,81 +25,86 @@ import net.minecraftforge.client.model.data.ModelData; final class BakedModelBufferer { - static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length; - private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) { + public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; + ForgeMeshEmitterManager emitters = objects.emitters; + + emitters.prepare(resultConsumer); + + ModelBlockRenderer blockRenderer = Minecraft.getInstance() + .getBlockRenderer() + .getModelRenderer(); long seed = state.getSeed(pos); modelData = model.getModelData(level, pos, state, modelData); random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); - ModelBlockRenderer blockRenderer = Minecraft.getInstance() - .getBlockRenderer() - .getModelRenderer(); + boolean defaultAo = Minecraft.useAmbientOcclusion() && state.getLightEmission(level, pos) == 0; for (RenderType renderType : renderTypes) { - int layerIndex = renderType.getChunkLayerId(); - MeshEmitter emitter = emitters[layerIndex]; + boolean defaultAoLayer = defaultAo && model.useAmbientOcclusion(state, renderType); - emitter.prepare(resultConsumer); + ForgeMeshEmitter emitter = emitters.getEmitter(renderType); + emitter.prepareForModelLayer(defaultAoLayer); poseStack.pushPose(); blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitter, false, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); poseStack.popPose(); - - emitter.end(); } + + emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) { + public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; + ForgeMeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(resultConsumer); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); - ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer.enableCaching(); + boolean useAo = Minecraft.useAmbientOcclusion(); + while (posIterator.hasNext()) { BlockPos pos = posIterator.next(); BlockState state = level.getBlockState(pos); + emitters.prepareForBlock(); + if (renderFluids) { FluidState fluidState = state.getFluidState(); if (!fluidState.isEmpty()) { RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState); - int layerIndex = renderType.getChunkLayerId(); - transformingWrapper.prepare(emitters[layerIndex].unwrap(true), poseStack); + BufferBuilder bufferBuilder = emitters.getBuffer(renderType, true, false); - poseStack.pushPose(); - poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); - renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); - poseStack.popPose(); + if (bufferBuilder != null) { + transformingWrapper.prepare(bufferBuilder, poseStack); + + poseStack.pushPose(); + poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); + renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); + poseStack.popPose(); + } } } @@ -111,12 +116,17 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); + boolean defaultAo = useAo && state.getLightEmission(level, pos) == 0; + for (RenderType renderType : renderTypes) { - int layerIndex = renderType.getChunkLayerId(); + boolean defaultAoLayer = defaultAo && model.useAmbientOcclusion(state, renderType); + + ForgeMeshEmitter emitter = emitters.getEmitter(renderType); + emitter.prepareForModelLayer(defaultAoLayer); poseStack.pushPose(); poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); - blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitters[layerIndex], true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); + blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitter, true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); poseStack.popPose(); } } @@ -124,28 +134,14 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } - } - - public interface ResultConsumer { - void accept(RenderType renderType, boolean shaded, RenderedBuffer data); + emitters.end(); } private static class ThreadLocalObjects { public final PoseStack identityPoseStack = new PoseStack(); public final RandomSource random = RandomSource.createNewThreadLocalInstance(); - public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; + public final ForgeMeshEmitterManager emitters = new ForgeMeshEmitterManager(); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); - - { - for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = CHUNK_LAYERS[layerIndex]; - emitters[layerIndex] = new MeshEmitter(renderType); - } - } } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java deleted file mode 100644 index 4026ff008..000000000 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.minecraft.client.renderer.RenderType; - -class ChunkLayerSortedListBuilder { - private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(ChunkLayerSortedListBuilder::new); - - @SuppressWarnings("unchecked") - private final ObjectArrayList[] lists = new ObjectArrayList[BakedModelBufferer.CHUNK_LAYER_AMOUNT]; - - private ChunkLayerSortedListBuilder() { - for (int layerIndex = 0; layerIndex < BakedModelBufferer.CHUNK_LAYER_AMOUNT; layerIndex++) { - ObjectArrayList list = new ObjectArrayList<>(); - lists[layerIndex] = list; - } - } - - @SuppressWarnings("unchecked") - public static ChunkLayerSortedListBuilder getThreadLocal() { - return (ChunkLayerSortedListBuilder) THREAD_LOCAL.get(); - } - - public void add(RenderType renderType, T obj) { - int layerIndex = renderType.getChunkLayerId(); - if (layerIndex == -1) { - throw new IllegalArgumentException("RenderType '" + renderType + "' is not a chunk layer"); - } - List list = lists[layerIndex]; - list.add(obj); - } - - @SuppressWarnings("unchecked") - public ImmutableList build() { - int size = 0; - for (ObjectArrayList list : lists) { - size += list.size(); - } - - T[] array = (T[]) new Object[size]; - int destPos = 0; - for (ObjectArrayList list : lists) { - System.arraycopy(list.elements(), 0, array, destPos, list.size()); - destPos += list.size(); - list.clear(); - } - - return ImmutableList.copyOf(array); - } -} diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java index 2162dd634..a032c98b4 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java @@ -7,8 +7,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -46,7 +44,14 @@ public ForgeBakedModelBuilder poseStack(@Nullable PoseStack poseStack) { } @Override - public ForgeBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public ForgeBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public ForgeBakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -73,16 +78,8 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); + BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, resultConsumer); + return resultConsumer.build(); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java index 06da266a4..fd243fb26 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java @@ -8,8 +8,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -39,7 +37,14 @@ public ForgeBlockModelBuilder renderFluids(boolean renderFluids) { } @Override - public ForgeBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public ForgeBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public ForgeBlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -61,16 +66,8 @@ public SimpleModel build() { }; } - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); + BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, resultConsumer); + return resultConsumer.build(); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java new file mode 100644 index 000000000..64e4e7d9b --- /dev/null +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java @@ -0,0 +1,131 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.ModelBlockRenderer; +import net.minecraft.client.renderer.block.model.BakedQuad; + +@ApiStatus.Internal +public class ForgeMeshEmitter extends MeshEmitter implements VertexConsumer { + private final RenderType renderType; + + private boolean defaultAo; + + ForgeMeshEmitter(Supplier bufferBuilderSupplier, RenderType renderType) { + super(bufferBuilderSupplier); + this.renderType = renderType; + } + + /** + * Some mods, like FramedBlocks, have custom hooks to determine the default AO. This method is invoked a second time + * from within a mixin to {@link ModelBlockRenderer} after the accurate value is computed, so we don't need to + * support those custom hooks manually. It is possible that the mixin injector will never run (primarily due to + * implementations of Fabric Renderer API on Forge, like Indigo in Forgified Fabric API), so we always compute the + * value manually beforehand too. + */ + public void prepareForModelLayer(boolean defaultAo) { + this.defaultAo = defaultAo; + } + + @Nullable + private BufferBuilder getBuffer(boolean shade, boolean ao) { + Object key = resultConsumer.createKey(renderType, shade, ao); + if (key != null) { + return getBuffer(key); + } else { + return null; + } + } + + @Nullable + private BufferBuilder getBuffer(BakedQuad quad) { + boolean shade = quad.isShade(); + boolean ao = quad.hasAmbientOcclusion() && defaultAo; + return getBuffer(shade, ao); + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, red, green, blue, light, overlay); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, float alpha, int light, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, red, green, blue, alpha, light, overlay, readExistingColor); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, float alpha, int[] lights, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, alpha, lights, overlay, readExistingColor); + } + } + + @Override + public VertexConsumer vertex(double x, double y, double z) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer uv(float u, float v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer overlayCoords(int u, int v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer uv2(int u, int v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer normal(float x, float y, float z) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void endVertex() { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void defaultColor(int red, int green, int blue, int alpha) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void unsetDefaultColor() { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } +} diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java new file mode 100644 index 000000000..831ea1b10 --- /dev/null +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java @@ -0,0 +1,13 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import net.minecraft.client.renderer.RenderType; + +class ForgeMeshEmitterManager extends MeshEmitterManager { + ForgeMeshEmitterManager() { + super(ForgeMeshEmitter::new); + } + + public ForgeMeshEmitter getEmitter(RenderType renderType) { + return emitterMap.get(renderType); + } +} diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java deleted file mode 100644 index e878ae95c..000000000 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ /dev/null @@ -1,134 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.UnknownNullability; - -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.blaze3d.vertex.VertexFormat; - -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedQuad; - -class MeshEmitter implements VertexConsumer { - private final RenderType renderType; - private final BufferBuilder bufferBuilder; - - private BakedModelBufferer.@UnknownNullability ResultConsumer resultConsumer; - private boolean currentShade; - - MeshEmitter(RenderType renderType) { - this.renderType = renderType; - this.bufferBuilder = new BufferBuilder(renderType.bufferSize()); - } - - public void prepare(BakedModelBufferer.ResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; - } - - public void end() { - if (bufferBuilder.building()) { - emit(); - } - resultConsumer = null; - } - - public BufferBuilder unwrap(boolean shade) { - prepareForGeometry(shade); - return bufferBuilder; - } - - private void prepareForGeometry(boolean shade) { - if (!bufferBuilder.building()) { - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } else if (shade != currentShade) { - emit(); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } - - currentShade = shade; - } - - private void prepareForGeometry(BakedQuad quad) { - prepareForGeometry(quad.isShade()); - } - - private void emit() { - var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty(); - - if (renderedBuffer != null) { - resultConsumer.accept(renderType, currentShade, renderedBuffer); - renderedBuffer.release(); - } - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, red, green, blue, light, overlay); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, float alpha, int light, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, red, green, blue, alpha, light, overlay, readExistingColor); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, float alpha, int[] lights, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, alpha, lights, overlay, readExistingColor); - } - - @Override - public VertexConsumer vertex(double x, double y, double z) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer color(int red, int green, int blue, int alpha) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer uv(float u, float v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer overlayCoords(int u, int v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer uv2(int u, int v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer normal(float x, float y, float z) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void endVertex() { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void defaultColor(int red, int green, int blue, int alpha) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void unsetDefaultColor() { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } -} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java new file mode 100644 index 000000000..b69dad0c2 --- /dev/null +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java @@ -0,0 +1,30 @@ +package dev.engine_room.flywheel.impl.mixin.forge; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import dev.engine_room.flywheel.lib.model.baked.ForgeMeshEmitter; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.ModelBlockRenderer; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.client.model.data.ModelData; + +@Mixin(ModelBlockRenderer.class) +abstract class ModelBlockRendererMixin { + @Inject(method = "tesselateBlock(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;ZLnet/minecraft/util/RandomSource;JILnet/minecraftforge/client/model/data/ModelData;Lnet/minecraft/client/renderer/RenderType;)V", at = @At(value = "INVOKE", target = "net/minecraft/world/level/block/state/BlockState.getOffset(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;"), locals = LocalCapture.CAPTURE_FAILSOFT, require = 0) + private void onTesselateBlock(BlockAndTintGetter level, BakedModel model, BlockState state, BlockPos pos, PoseStack poseStack, VertexConsumer consumer, boolean checkSides, RandomSource random, long seed, int packedOverlay, ModelData modelData, RenderType renderType, CallbackInfo ci, boolean ao) { + if (consumer instanceof ForgeMeshEmitter meshEmitter) { + meshEmitter.prepareForModelLayer(ao); + } + } +} diff --git a/forge/src/main/resources/flywheel.impl.forge.mixins.json b/forge/src/main/resources/flywheel.impl.forge.mixins.json new file mode 100644 index 000000000..c918f0fd6 --- /dev/null +++ b/forge/src/main/resources/flywheel.impl.forge.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.engine_room.flywheel.impl.mixin.forge", + "compatibilityLevel": "JAVA_17", + "refmap": "flywheel.refmap.json", + "client": [ + "ModelBlockRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} From 15cef3fa3e8425047bfb62e8ad759fb6c3b42925 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 1 Nov 2025 17:10:48 -0700 Subject: [PATCH 2/6] self review --- .../flywheel/api/material/Material.java | 37 ++++++++++++++++ .../lib/model/baked/BufferBuilderStack.java | 30 +++++++++++++ .../flywheel/lib/model/baked/MeshEmitter.java | 44 ++++++------------- .../lib/model/baked/MeshEmitterManager.java | 43 +++++++----------- .../baked/ModelBuilderResultConsumer.java | 4 +- .../lib/model/baked/ResultConsumer.java | 14 ------ .../lib/model/baked/BakedModelBufferer.java | 4 +- .../lib/model/baked/BakedModelBufferer.java | 10 ++--- .../lib/model/baked/ForgeMeshEmitter.java | 9 ++-- .../model/baked/ForgeMeshEmitterManager.java | 13 ------ 10 files changed, 109 insertions(+), 99 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java delete mode 100644 forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java diff --git a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java index ae72a2fb3..c20a51122 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java @@ -1,5 +1,7 @@ package dev.engine_room.flywheel.api.material; +import org.jetbrains.annotations.Nullable; + import net.minecraft.resources.ResourceLocation; public interface Material { @@ -52,4 +54,39 @@ public interface Material { * @return The cardinal lighting mode. */ CardinalLightingMode cardinalLightingMode(); + + /** + * Check for field-wise equality between this Material and another. + * + * @param other The nullable material to check equality against. + * @return True if the materials represent the same configuration. + */ + default boolean equals(@Nullable Material other) { + if (this == other) { + return true; + } + + if (other == null) { + return false; + } + + // @formatter:off + return this.blur() == other.blur() + && this.mipmap() == other.mipmap() + && this.backfaceCulling() == other.backfaceCulling() + && this.polygonOffset() == other.polygonOffset() + && this.depthTest() == other.depthTest() + && this.transparency() == other.transparency() + && this.writeMask() == other.writeMask() + && this.useOverlay() == other.useOverlay() + && this.useLight() == other.useLight() + && this.cardinalLightingMode() == other.cardinalLightingMode() + && this.shaders().fragmentSource().equals(other.shaders().fragmentSource()) + && this.shaders().vertexSource().equals(other.shaders().vertexSource()) + && this.fog().source().equals(other.fog().source()) + && this.cutout().source().equals(other.cutout().source()) + && this.light().source().equals(other.light().source()) + && this.texture().equals(other.texture()); + // @formatter:on + } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java new file mode 100644 index 000000000..a419b6663 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java @@ -0,0 +1,30 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import com.mojang.blaze3d.vertex.BufferBuilder; + +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; + +class BufferBuilderStack { + private static final int INITIAL_CAPACITY_VERTICES = 256; + + private int nextBufferBuilderIndex = 0; + private final ReferenceArrayList bufferBuilders = new ReferenceArrayList<>(); + + BufferBuilder getOrCreateBufferBuilder() { + BufferBuilder bufferBuilder; + if (nextBufferBuilderIndex < bufferBuilders.size()) { + bufferBuilder = bufferBuilders.get(nextBufferBuilderIndex); + } else { + // Need to allocate at least some memory up front, as BufferBuilder internally + // only calls `ensureCapacity` after writing a vertex. + bufferBuilder = new BufferBuilder(INITIAL_CAPACITY_VERTICES); + bufferBuilders.add(bufferBuilder); + } + nextBufferBuilderIndex++; + return bufferBuilder; + } + + public void reset() { + nextBufferBuilderIndex = 0; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java index 15451c592..aa4932529 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java @@ -2,42 +2,33 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; - -import org.jetbrains.annotations.UnknownNullability; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; +import dev.engine_room.flywheel.api.material.Material; + class MeshEmitter { - private final Supplier bufferBuilderSupplier; + private final BufferBuilderStack bufferBuilderStack; private final List activeBufferBuilders = new ArrayList<>(); - private final List activeKeys = new ArrayList<>(); + private final List activeKeys = new ArrayList<>(); - @UnknownNullability - ResultConsumer resultConsumer; + ModelBuilderResultConsumer resultConsumer; private int currentIndex = -1; - @UnknownNullability - private BufferBuilder currentBufferBuilder; - @UnknownNullability - private Object currentKey; - MeshEmitter(Supplier bufferBuilderSupplier) { - this.bufferBuilderSupplier = bufferBuilderSupplier; + MeshEmitter(BufferBuilderStack bufferBuilderStack) { + this.bufferBuilderStack = bufferBuilderStack; } - @SuppressWarnings("unchecked") - public void prepare(ResultConsumer resultConsumer) { - this.resultConsumer = (ResultConsumer) resultConsumer; + public void prepare(ModelBuilderResultConsumer resultConsumer) { + this.resultConsumer = resultConsumer; } public void prepareForBlock() { currentIndex = -1; - currentBufferBuilder = null; - currentKey = null; } public void end() { @@ -54,34 +45,27 @@ public void end() { activeKeys.clear(); resultConsumer = null; currentIndex = -1; - currentBufferBuilder = null; - currentKey = null; } - public BufferBuilder getBuffer(Object key) { - if (currentIndex < 0 || !key.equals(currentKey)) { + public BufferBuilder getBuffer(Material key) { + if (currentIndex < 0 || !key.equals(activeKeys.get(currentIndex))) { while (true) { currentIndex++; if (currentIndex >= activeBufferBuilders.size()) { - BufferBuilder bufferBuilder = bufferBuilderSupplier.get(); + BufferBuilder bufferBuilder = bufferBuilderStack.getOrCreateBufferBuilder(); bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); activeBufferBuilders.add(bufferBuilder); activeKeys.add(key); - currentBufferBuilder = bufferBuilder; - currentKey = key; break; } - Object searchKey = activeKeys.get(currentIndex); - if (key.equals(searchKey)) { - currentBufferBuilder = activeBufferBuilders.get(currentIndex); - currentKey = searchKey; + if (key.equals(activeKeys.get(currentIndex))) { break; } } } - return currentBufferBuilder; + return activeBufferBuilders.get(currentIndex); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java index 07813afc0..af0db96b3 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java @@ -1,38 +1,39 @@ package dev.engine_room.flywheel.lib.model.baked; import java.util.function.BiFunction; -import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; import com.mojang.blaze3d.vertex.BufferBuilder; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; +import dev.engine_room.flywheel.api.material.Material; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -import it.unimi.dsi.fastutil.objects.ReferenceArrayList; import net.minecraft.client.renderer.RenderType; class MeshEmitterManager { private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - final Reference2ReferenceMap emitterMap = new Reference2ReferenceLinkedOpenHashMap<>(); - private final ReferenceArrayList bufferBuilders = new ReferenceArrayList<>(); + final Reference2ReferenceMap emitterMap = new Reference2ReferenceArrayMap<>(); - @UnknownNullability - private ResultConsumer resultConsumer; - private int nextBufferBuilderIndex; + private ModelBuilderResultConsumer resultConsumer; - MeshEmitterManager(BiFunction, RenderType, T> meshEmitterFactory) { + private final BufferBuilderStack bufferBuilderStack = new BufferBuilderStack(); + + MeshEmitterManager(BiFunction meshEmitterFactory) { for (RenderType renderType : CHUNK_LAYERS) { - T emitter = meshEmitterFactory.apply(this::getOrCreateBufferBuilder, renderType); + T emitter = meshEmitterFactory.apply(bufferBuilderStack, renderType); emitterMap.put(renderType, emitter); } } - public void prepare(ResultConsumer resultConsumer) { + public T getEmitter(RenderType renderType) { + return emitterMap.get(renderType); + } + + public void prepare(ModelBuilderResultConsumer resultConsumer) { this.resultConsumer = resultConsumer; - nextBufferBuilderIndex = 0; + bufferBuilderStack.reset(); for (MeshEmitter emitter : emitterMap.values()) { emitter.prepare(resultConsumer); @@ -47,7 +48,7 @@ public void prepareForBlock() { public void end() { resultConsumer = null; - nextBufferBuilderIndex = 0; + bufferBuilderStack.reset(); for (MeshEmitter emitter : emitterMap.values()) { emitter.end(); @@ -56,23 +57,11 @@ public void end() { @Nullable public BufferBuilder getBuffer(RenderType renderType, boolean shade, boolean ao) { - Object key = resultConsumer.createKey(renderType, shade, ao); + Material key = resultConsumer.createKey(renderType, shade, ao); if (key != null) { return emitterMap.get(renderType).getBuffer(key); } else { return null; } } - - private BufferBuilder getOrCreateBufferBuilder() { - BufferBuilder bufferBuilder; - if (nextBufferBuilderIndex < bufferBuilders.size()) { - bufferBuilder = bufferBuilders.get(nextBufferBuilderIndex); - } else { - bufferBuilder = new BufferBuilder(0); - bufferBuilders.add(bufferBuilder); - } - nextBufferBuilderIndex++; - return bufferBuilder; - } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java index 6c832e282..14bd5ef53 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java @@ -11,7 +11,7 @@ import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; -class ModelBuilderResultConsumer implements ResultConsumer { +class ModelBuilderResultConsumer { private final BlockMaterialFunction materialFunc; private final ImmutableList.Builder meshes = ImmutableList.builder(); @@ -19,13 +19,11 @@ class ModelBuilderResultConsumer implements ResultConsumer { this.materialFunc = materialFunc; } - @Override @Nullable public Material createKey(RenderType renderType, boolean shade, boolean ambientOcclusion) { return materialFunc.apply(renderType, shade, ambientOcclusion); } - @Override public void accept(Material material, BufferBuilder.RenderedBuffer data) { Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=ModelBuilder" + ",material=" + material); meshes.add(new Model.ConfiguredMesh(material, mesh)); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java deleted file mode 100644 index 8d815b50a..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ResultConsumer.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.Nullable; - -import com.mojang.blaze3d.vertex.BufferBuilder; - -import net.minecraft.client.renderer.RenderType; - -interface ResultConsumer { - @Nullable - T createKey(RenderType renderType, boolean shade, boolean ambientOcclusion); - - void accept(T key, BufferBuilder.RenderedBuffer data); -} diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 2a9114d4d..de598a752 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -27,7 +27,7 @@ final class BakedModelBufferer { private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) { + public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelBuilderResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -54,7 +54,7 @@ public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGette emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ResultConsumer resultConsumer) { + public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ModelBuilderResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index b9487d09a..46c6a4511 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -30,13 +30,13 @@ final class BakedModelBufferer { private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) { + public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ModelBuilderResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - ForgeMeshEmitterManager emitters = objects.emitters; + MeshEmitterManager emitters = objects.emitters; emitters.prepare(resultConsumer); @@ -65,13 +65,13 @@ public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGette emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) { + public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ModelBuilderResultConsumer resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - ForgeMeshEmitterManager emitters = objects.emitters; + MeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; emitters.prepare(resultConsumer); @@ -141,7 +141,7 @@ private static class ThreadLocalObjects { public final PoseStack identityPoseStack = new PoseStack(); public final RandomSource random = RandomSource.createNewThreadLocalInstance(); - public final ForgeMeshEmitterManager emitters = new ForgeMeshEmitterManager(); + public final MeshEmitterManager emitters = new MeshEmitterManager<>(ForgeMeshEmitter::new); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java index 64e4e7d9b..16567452d 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java @@ -1,7 +1,5 @@ package dev.engine_room.flywheel.lib.model.baked; -import java.util.function.Supplier; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -9,6 +7,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import dev.engine_room.flywheel.api.material.Material; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.ModelBlockRenderer; import net.minecraft.client.renderer.block.model.BakedQuad; @@ -19,8 +18,8 @@ public class ForgeMeshEmitter extends MeshEmitter implements VertexConsumer { private boolean defaultAo; - ForgeMeshEmitter(Supplier bufferBuilderSupplier, RenderType renderType) { - super(bufferBuilderSupplier); + ForgeMeshEmitter(BufferBuilderStack bufferBuilderStack, RenderType renderType) { + super(bufferBuilderStack); this.renderType = renderType; } @@ -37,7 +36,7 @@ public void prepareForModelLayer(boolean defaultAo) { @Nullable private BufferBuilder getBuffer(boolean shade, boolean ao) { - Object key = resultConsumer.createKey(renderType, shade, ao); + Material key = resultConsumer.createKey(renderType, shade, ao); if (key != null) { return getBuffer(key); } else { diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java deleted file mode 100644 index 831ea1b10..000000000 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitterManager.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import net.minecraft.client.renderer.RenderType; - -class ForgeMeshEmitterManager extends MeshEmitterManager { - ForgeMeshEmitterManager() { - super(ForgeMeshEmitter::new); - } - - public ForgeMeshEmitter getEmitter(RenderType renderType) { - return emitterMap.get(renderType); - } -} From 93006e8018b7117eaa00ab0c181d42d6ae94a81c Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 2 Nov 2025 13:06:01 -0800 Subject: [PATCH 3/6] Direct result - Completely inline result consumer - MeshEmitterManager now directly creates the SimpleModel --- .../flywheel/lib/model/baked/MeshEmitter.java | 26 ++++++++++---- .../lib/model/baked/MeshEmitterManager.java | 30 ++++++++++------ .../baked/ModelBuilderResultConsumer.java | 35 ------------------- .../lib/model/baked/BakedModelBufferer.java | 9 ++--- .../model/baked/FabricBakedModelBuilder.java | 4 +-- .../model/baked/FabricBlockModelBuilder.java | 4 +-- .../model/baked/FabricMeshEmitterManager.java | 5 +-- .../lib/model/baked/BakedModelBufferer.java | 13 +++---- .../model/baked/ForgeBakedModelBuilder.java | 4 +-- .../model/baked/ForgeBlockModelBuilder.java | 4 +-- .../lib/model/baked/ForgeMeshEmitter.java | 2 +- 11 files changed, 58 insertions(+), 78 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java index aa4932529..ed04b8167 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java @@ -3,11 +3,16 @@ import java.util.ArrayList; import java.util.List; +import org.jetbrains.annotations.UnknownNullability; + +import com.google.common.collect.ImmutableList; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; class MeshEmitter { private final BufferBuilderStack bufferBuilderStack; @@ -15,7 +20,8 @@ class MeshEmitter { private final List activeBufferBuilders = new ArrayList<>(); private final List activeKeys = new ArrayList<>(); - ModelBuilderResultConsumer resultConsumer; + @UnknownNullability + BlockMaterialFunction blockMaterialFunction; private int currentIndex = -1; @@ -23,27 +29,33 @@ class MeshEmitter { this.bufferBuilderStack = bufferBuilderStack; } - public void prepare(ModelBuilderResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; + public void prepare(BlockMaterialFunction blockMaterialFunction) { + this.blockMaterialFunction = blockMaterialFunction; } public void prepareForBlock() { + // Quad render order within blocks must be preserved for correctness, however between blocks we should try to + // reduce the number of generated meshes as much as possible. Here we reset the head index without flushing + // any buffers, so that the next block can start over scanning through the parallel arrays looking for a + // matching Material, BufferBuilder pair. currentIndex = -1; } - public void end() { + public void end(ImmutableList.Builder out) { for (int index = 0; index < activeBufferBuilders.size(); index++) { - var renderedBuffer = activeBufferBuilders.get(index).endOrDiscardIfEmpty(); + var renderedBuffer = activeBufferBuilders.get(index) + .endOrDiscardIfEmpty(); if (renderedBuffer != null) { - resultConsumer.accept(activeKeys.get(index), renderedBuffer); + Material material = activeKeys.get(index); + Mesh mesh = MeshHelper.blockVerticesToMesh(renderedBuffer, "source=ModelBuilder" + ",material=" + material); + out.add(new Model.ConfiguredMesh(material, mesh)); renderedBuffer.release(); } } activeBufferBuilders.clear(); activeKeys.clear(); - resultConsumer = null; currentIndex = -1; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java index af0db96b3..949eb5cc2 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java @@ -3,10 +3,14 @@ import java.util.function.BiFunction; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; +import com.google.common.collect.ImmutableList; import com.mojang.blaze3d.vertex.BufferBuilder; import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.SimpleModel; import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; import net.minecraft.client.renderer.RenderType; @@ -14,12 +18,12 @@ class MeshEmitterManager { private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - final Reference2ReferenceMap emitterMap = new Reference2ReferenceArrayMap<>(); - - private ModelBuilderResultConsumer resultConsumer; - + private final Reference2ReferenceMap emitterMap = new Reference2ReferenceArrayMap<>(); private final BufferBuilderStack bufferBuilderStack = new BufferBuilderStack(); + @UnknownNullability + private BlockMaterialFunction blockMaterialFunction; + MeshEmitterManager(BiFunction meshEmitterFactory) { for (RenderType renderType : CHUNK_LAYERS) { T emitter = meshEmitterFactory.apply(bufferBuilderStack, renderType); @@ -31,12 +35,12 @@ public T getEmitter(RenderType renderType) { return emitterMap.get(renderType); } - public void prepare(ModelBuilderResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; + public void prepare(BlockMaterialFunction blockMaterialFunction) { + this.blockMaterialFunction = blockMaterialFunction; bufferBuilderStack.reset(); for (MeshEmitter emitter : emitterMap.values()) { - emitter.prepare(resultConsumer); + emitter.prepare(blockMaterialFunction); } } @@ -46,18 +50,22 @@ public void prepareForBlock() { } } - public void end() { - resultConsumer = null; + public SimpleModel end() { + blockMaterialFunction = null; bufferBuilderStack.reset(); + ImmutableList.Builder meshes = ImmutableList.builder(); + for (MeshEmitter emitter : emitterMap.values()) { - emitter.end(); + emitter.end(meshes); } + + return new SimpleModel(meshes.build()); } @Nullable public BufferBuilder getBuffer(RenderType renderType, boolean shade, boolean ao) { - Material key = resultConsumer.createKey(renderType, shade, ao); + Material key = blockMaterialFunction.apply(renderType, shade, ao); if (key != null) { return emitterMap.get(renderType).getBuffer(key); } else { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java deleted file mode 100644 index 14bd5ef53..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ModelBuilderResultConsumer.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.ImmutableList; -import com.mojang.blaze3d.vertex.BufferBuilder; - -import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.lib.model.SimpleModel; -import net.minecraft.client.renderer.RenderType; - -class ModelBuilderResultConsumer { - private final BlockMaterialFunction materialFunc; - private final ImmutableList.Builder meshes = ImmutableList.builder(); - - ModelBuilderResultConsumer(BlockMaterialFunction materialFunc) { - this.materialFunc = materialFunc; - } - - @Nullable - public Material createKey(RenderType renderType, boolean shade, boolean ambientOcclusion) { - return materialFunc.apply(renderType, shade, ambientOcclusion); - } - - public void accept(Material material, BufferBuilder.RenderedBuffer data) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=ModelBuilder" + ",material=" + material); - meshes.add(new Model.ConfiguredMesh(material, mesh)); - } - - public SimpleModel build() { - return new SimpleModel(meshes.build()); - } -} diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index de598a752..8aeddb972 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -7,6 +7,7 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -27,7 +28,7 @@ final class BakedModelBufferer { private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelBuilderResultConsumer resultConsumer) { + public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, BlockMaterialFunction resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -51,10 +52,10 @@ public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGette .tesselateBlock(level, model, state, pos, poseStack, emitters, false, random, seed, OverlayTexture.NO_OVERLAY); poseStack.popPose(); - emitters.end(); + return emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ModelBuilderResultConsumer resultConsumer) { + public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, BlockMaterialFunction resultConsumer) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -114,7 +115,7 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - emitters.end(); + return emitters.end(); } private static class ThreadLocalObjects { diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java index d6465ddc8..3b9f4f46b 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java @@ -64,8 +64,6 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, resultConsumer); - return resultConsumer.build(); + return BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, materialFunc); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java index 7d5479c4a..32c6b2904 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java @@ -49,8 +49,6 @@ public SimpleModel build() { materialFunc = ModelUtil::getMaterial; } - ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, resultConsumer); - return resultConsumer.build(); + return BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, materialFunc); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java index c6dfa9de4..2aab5561a 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java @@ -9,6 +9,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; @@ -45,9 +46,9 @@ public BakedModel prepareForModel(BakedModel model, RenderType defaultLayer, boo } @Override - public void end() { + public SimpleModel end() { wrapperModel.setWrapped(null); - super.end(); + return super.end(); } private void prepareForGeometry(RenderMaterial material) { diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 46c6a4511..778f7dd83 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -8,6 +8,7 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -30,7 +31,7 @@ final class BakedModelBufferer { private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ModelBuilderResultConsumer resultConsumer) { + public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -38,7 +39,7 @@ public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGette RandomSource random = objects.random; MeshEmitterManager emitters = objects.emitters; - emitters.prepare(resultConsumer); + emitters.prepare(blockMaterialFunction); ModelBlockRenderer blockRenderer = Minecraft.getInstance() .getBlockRenderer() @@ -62,10 +63,10 @@ public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGette poseStack.popPose(); } - emitters.end(); + return emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ModelBuilderResultConsumer resultConsumer) { + public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -74,7 +75,7 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett MeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - emitters.prepare(resultConsumer); + emitters.prepare(blockMaterialFunction); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); @@ -134,7 +135,7 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - emitters.end(); + return emitters.end(); } private static class ThreadLocalObjects { diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java index a032c98b4..8632001a9 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java @@ -78,8 +78,6 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, resultConsumer); - return resultConsumer.build(); + return BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, materialFunc); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java index fd243fb26..ca0064288 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java @@ -66,8 +66,6 @@ public SimpleModel build() { }; } - ModelBuilderResultConsumer resultConsumer = new ModelBuilderResultConsumer(materialFunc); - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, resultConsumer); - return resultConsumer.build(); + return BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, materialFunc); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java index 16567452d..c8c24394c 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java @@ -36,7 +36,7 @@ public void prepareForModelLayer(boolean defaultAo) { @Nullable private BufferBuilder getBuffer(boolean shade, boolean ao) { - Material key = resultConsumer.createKey(renderType, shade, ao); + Material key = blockMaterialFunction.apply(renderType, shade, ao); if (key != null) { return getBuffer(key); } else { From f5b0bd4d286fd24307badee26f5b799eaab67811 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 2 Nov 2025 13:38:21 -0800 Subject: [PATCH 4/6] Array of builders - Inline ArrayList and directly use parallel arrays in MeshEmitter --- .../flywheel/lib/model/baked/MeshEmitter.java | 89 ++++++++++++------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java index ed04b8167..6ade6cb4e 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.lib.model.baked; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; import org.jetbrains.annotations.UnknownNullability; @@ -15,15 +14,20 @@ import dev.engine_room.flywheel.api.model.Model; class MeshEmitter { + private static final int INITIAL_CAPACITY = 4; + private final BufferBuilderStack bufferBuilderStack; - private final List activeBufferBuilders = new ArrayList<>(); - private final List activeKeys = new ArrayList<>(); + private Material @UnknownNullability [] materials = new Material[INITIAL_CAPACITY]; + private BufferBuilder @UnknownNullability [] bufferBuilders = new BufferBuilder[INITIAL_CAPACITY]; + + // The number of valid elements in the above parallel arrays. + private int numBufferBuildersPopulated = 0; @UnknownNullability BlockMaterialFunction blockMaterialFunction; - private int currentIndex = -1; + private int currentIndex = 0; MeshEmitter(BufferBuilderStack bufferBuilderStack) { this.bufferBuilderStack = bufferBuilderStack; @@ -37,47 +41,72 @@ public void prepareForBlock() { // Quad render order within blocks must be preserved for correctness, however between blocks we should try to // reduce the number of generated meshes as much as possible. Here we reset the head index without flushing // any buffers, so that the next block can start over scanning through the parallel arrays looking for a - // matching Material, BufferBuilder pair. - currentIndex = -1; + // matching Material/BufferBuilder pair. + currentIndex = 0; } public void end(ImmutableList.Builder out) { - for (int index = 0; index < activeBufferBuilders.size(); index++) { - var renderedBuffer = activeBufferBuilders.get(index) + for (int index = 0; index < numBufferBuildersPopulated; index++) { + var renderedBuffer = bufferBuilders[index] .endOrDiscardIfEmpty(); if (renderedBuffer != null) { - Material material = activeKeys.get(index); + Material material = materials[index]; Mesh mesh = MeshHelper.blockVerticesToMesh(renderedBuffer, "source=ModelBuilder" + ",material=" + material); out.add(new Model.ConfiguredMesh(material, mesh)); renderedBuffer.release(); } } - activeBufferBuilders.clear(); - activeKeys.clear(); - currentIndex = -1; + // Not strictly necessary to clear the arrays, but best not to hold on to references for too long here. + Arrays.fill(bufferBuilders, 0, numBufferBuildersPopulated, null); + Arrays.fill(materials, 0, numBufferBuildersPopulated, null); + + currentIndex = 0; + numBufferBuildersPopulated = 0; } - public BufferBuilder getBuffer(Material key) { - if (currentIndex < 0 || !key.equals(activeKeys.get(currentIndex))) { - while (true) { - currentIndex++; - - if (currentIndex >= activeBufferBuilders.size()) { - BufferBuilder bufferBuilder = bufferBuilderStack.getOrCreateBufferBuilder(); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - activeBufferBuilders.add(bufferBuilder); - activeKeys.add(key); - break; - } - - if (key.equals(activeKeys.get(currentIndex))) { - break; - } + public BufferBuilder getBuffer(Material material) { + // First, scan through and try to find a matching Material. + while (currentIndex < numBufferBuildersPopulated) { + if (material.equals(materials[currentIndex])) { + // Return the matching BufferBuilder, but do not increment as we + // may still be able to use the same BufferBuilder in the next quad. + return bufferBuilders[currentIndex]; } + ++currentIndex; } - return activeBufferBuilders.get(currentIndex); + // Nothing matched so we need to grab a new BufferBuilder. + // Make sure we have room to represent it in the arrays. + if (currentIndex >= materials.length) { + // Only technically need to grow one at a time here, but doubling is + // fine and should reduce the number of reallocations. + resize(materials.length * 2); + } + + BufferBuilder bufferBuilder = bufferBuilderStack.getOrCreateBufferBuilder(); + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); + + // currentIndex == numBufferBuildersPopulated here. + materials[currentIndex] = material; + bufferBuilders[currentIndex] = bufferBuilder; + + // Again, do not increment currentIndex so we can re-use the new + // BufferBuilder for the next quad if it matches. + ++numBufferBuildersPopulated; + + return bufferBuilder; + } + + private void resize(int capacity) { + BufferBuilder[] newBufferBuilders = new BufferBuilder[capacity]; + Material[] newMaterials = new Material[capacity]; + + System.arraycopy(bufferBuilders, 0, newBufferBuilders, 0, numBufferBuildersPopulated); + System.arraycopy(materials, 0, newMaterials, 0, numBufferBuildersPopulated); + + bufferBuilders = newBufferBuilders; + materials = newMaterials; } } From b9e6d4612409ce742d345cbb8c8d9b9c6a34f349 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 2 Nov 2025 15:15:32 -0800 Subject: [PATCH 5/6] Material AO --- .../flywheel/api/material/Material.java | 10 ++++ .../backend/engine/MaterialEncoder.java | 6 +- .../backend/engine/MaterialRenderState.java | 2 +- .../flywheel/flywheel/internal/material.glsl | 1 + .../flywheel/internal/packed_material.glsl | 6 +- .../flywheel/lib/material/SimpleMaterial.java | 22 +++++++ .../flywheel/lib/model/ModelUtil.java | 57 ++++++++++++++----- .../lib/model/baked/BlockModelBuilder.java | 4 +- .../flywheel/lib/model/baked/MeshEmitter.java | 3 +- .../flywheel/flywheel/light/smooth.glsl | 4 +- .../flywheel/light/smooth_when_embedded.glsl | 4 +- .../lib/model/baked/BakedModelBufferer.java | 8 +-- 12 files changed, 100 insertions(+), 27 deletions(-) diff --git a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java index c20a51122..35d04b9b7 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java @@ -55,6 +55,15 @@ public interface Material { */ CardinalLightingMode cardinalLightingMode(); + /** + * Whether this material should receive ambient occlusion from nearby chunk geometry. + * + * @return {@code true} if this material should receive ambient occlusion. + */ + default boolean ambientOcclusion() { + return true; + } + /** * Check for field-wise equality between this Material and another. * @@ -81,6 +90,7 @@ default boolean equals(@Nullable Material other) { && this.useOverlay() == other.useOverlay() && this.useLight() == other.useLight() && this.cardinalLightingMode() == other.cardinalLightingMode() + && this.ambientOcclusion() == other.ambientOcclusion() && this.shaders().fragmentSource().equals(other.shaders().fragmentSource()) && this.shaders().vertexSource().equals(other.shaders().vertexSource()) && this.fog().source().equals(other.fog().source()) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java index 52dab8ad2..28ee88019 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java @@ -21,6 +21,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_LENGTH = 1; private static final int USE_LIGHT_LENGTH = 1; private static final int CARDINAL_LIGHTING_MODE_LENGTH = Mth.ceillog2(CardinalLightingMode.values().length); + private static final int AMBIENT_OCCLUSION_LENGTH = 1; // The bit offset of each property private static final int BLUR_OFFSET = 0; @@ -33,6 +34,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_OFFSET = WRITE_MASK_OFFSET + WRITE_MASK_LENGTH; private static final int USE_LIGHT_OFFSET = USE_OVERLAY_OFFSET + USE_OVERLAY_LENGTH; private static final int CARDINAL_LIGHTING_MODE_OFFSET = USE_LIGHT_OFFSET + USE_LIGHT_LENGTH; + private static final int AMBIENT_OCCLUSION_OFFSET = CARDINAL_LIGHTING_MODE_OFFSET + CARDINAL_LIGHTING_MODE_LENGTH; // The bit mask for each property private static final int BLUR_MASK = bitMask(BLUR_LENGTH, BLUR_OFFSET); @@ -45,6 +47,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_MASK = bitMask(USE_OVERLAY_LENGTH, USE_OVERLAY_OFFSET); private static final int USE_LIGHT_MASK = bitMask(USE_LIGHT_LENGTH, USE_LIGHT_OFFSET); private static final int CARDINAL_LIGHTING_MODE_MASK = bitMask(CARDINAL_LIGHTING_MODE_LENGTH, CARDINAL_LIGHTING_MODE_OFFSET); + private static final int AMBIENT_OCCLUSION_MASK = bitMask(AMBIENT_OCCLUSION_LENGTH, AMBIENT_OCCLUSION_OFFSET); private MaterialEncoder() { } @@ -60,7 +63,7 @@ public static int packUberShader(Material material) { } // Packed format: - // cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] + // ambientOcclusion[1] | cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] public static int packProperties(Material material) { int bits = 0; @@ -75,6 +78,7 @@ public static int packProperties(Material material) { if (material.useLight()) bits |= USE_LIGHT_MASK; bits |= (material.cardinalLightingMode() .ordinal() << CARDINAL_LIGHTING_MODE_OFFSET) & CARDINAL_LIGHTING_MODE_MASK; + if (material.ambientOcclusion()) bits |= AMBIENT_OCCLUSION_MASK; return bits; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java index 74ddc681d..5d4eec2b2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java @@ -188,7 +188,7 @@ public static boolean materialEquals(Material lhs, Material rhs) { return true; } - // Not here because ubershader: useLight, useOverlay, diffuse, fog shader + // Not here because ubershader: useLight, useOverlay, diffuse, fog shader, ambient occlusion // Everything in the comparator should be here. // @formatter:off return lhs.blur() == rhs.blur() diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl index 990129b54..041d87b1e 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl @@ -34,4 +34,5 @@ struct FlwMaterial { bool useOverlay; bool useLight; uint cardinalLightingMode; + bool ambientOcclusion; }; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl index 55c53a135..1493031b1 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl @@ -9,6 +9,7 @@ const uint _FLW_WRITE_MASK_LENGTH = 2u; const uint _FLW_USE_OVERLAY_LENGTH = 1u; const uint _FLW_USE_LIGHT_LENGTH = 1u; const uint _FLW_CARDINAL_LIGHTING_MODE_LENGTH = 2u; +const uint _FLW_AMBIENT_OCCLUSION_LENGTH = 1u; // The bit offset of each property const uint _FLW_BLUR_OFFSET = 0u; @@ -21,6 +22,7 @@ const uint _FLW_WRITE_MASK_OFFSET = _FLW_TRANSPARENCY_OFFSET + _FLW_TRANSPARENCY const uint _FLW_USE_OVERLAY_OFFSET = _FLW_WRITE_MASK_OFFSET + _FLW_WRITE_MASK_LENGTH; const uint _FLW_USE_LIGHT_OFFSET = _FLW_USE_OVERLAY_OFFSET + _FLW_USE_OVERLAY_LENGTH; const uint _FLW_CARDINAL_LIGHTING_MODE_OFFSET = _FLW_USE_LIGHT_OFFSET + _FLW_USE_LIGHT_LENGTH; +const uint _FLW_AMBIENT_OCCLUSION_OFFSET = _FLW_CARDINAL_LIGHTING_MODE_OFFSET + _FLW_CARDINAL_LIGHTING_MODE_LENGTH; // The bit mask for each property const uint _FLW_BLUR_MASK = ((1u << _FLW_BLUR_LENGTH) - 1u) << _FLW_BLUR_OFFSET; @@ -33,9 +35,10 @@ const uint _FLW_WRITE_MASK_MASK = ((1u << _FLW_WRITE_MASK_LENGTH) - 1u) << _FLW_ const uint _FLW_USE_OVERLAY_MASK = ((1u << _FLW_USE_OVERLAY_LENGTH) - 1u) << _FLW_USE_OVERLAY_OFFSET; const uint _FLW_USE_LIGHT_MASK = ((1u << _FLW_USE_LIGHT_LENGTH) - 1u) << _FLW_USE_LIGHT_OFFSET; const uint _FLW_CARDINAL_LIGHTING_MODE_MASK = ((1u << _FLW_CARDINAL_LIGHTING_MODE_LENGTH) - 1u) << _FLW_CARDINAL_LIGHTING_MODE_OFFSET; +const uint _FLW_AMBIENT_OCCLUSION_MASK = ((1u << _FLW_AMBIENT_OCCLUSION_LENGTH) - 1u) << _FLW_AMBIENT_OCCLUSION_OFFSET; // Packed format: -// cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] +// ambientOcclusion[1] | cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] void _flw_unpackMaterialProperties(uint p, out FlwMaterial m) { m.blur = (p & _FLW_BLUR_MASK) != 0u; m.mipmap = (p & _FLW_MIPMAP_MASK) != 0u; @@ -47,6 +50,7 @@ void _flw_unpackMaterialProperties(uint p, out FlwMaterial m) { m.useOverlay = (p & _FLW_USE_OVERLAY_MASK) != 0u; m.useLight = (p & _FLW_USE_LIGHT_MASK) != 0u; m.cardinalLightingMode = (p & _FLW_CARDINAL_LIGHTING_MODE_MASK) >> _FLW_CARDINAL_LIGHTING_MODE_OFFSET; + m.ambientOcclusion = (p & _FLW_AMBIENT_OCCLUSION_MASK) != 0; } void _flw_unpackUint2x16(uint s, out uint hi, out uint lo) { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java index 70e137d74..ef5a2298a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java @@ -32,6 +32,8 @@ public class SimpleMaterial implements Material { protected final boolean useLight; protected final CardinalLightingMode cardinalLightingMode; + protected final boolean ambientOcclusion; + protected SimpleMaterial(Builder builder) { shaders = builder.shaders(); fog = builder.fog(); @@ -48,6 +50,7 @@ protected SimpleMaterial(Builder builder) { useOverlay = builder.useOverlay(); useLight = builder.useLight(); cardinalLightingMode = builder.cardinalLightingMode(); + ambientOcclusion = builder.ambientOcclusion(); } public static Builder builder() { @@ -133,6 +136,11 @@ public CardinalLightingMode cardinalLightingMode() { return cardinalLightingMode; } + @Override + public boolean ambientOcclusion() { + return ambientOcclusion; + } + public static class Builder implements Material { protected MaterialShaders shaders; protected FogShader fog; @@ -153,6 +161,8 @@ public static class Builder implements Material { protected boolean useLight; protected CardinalLightingMode cardinalLightingMode; + protected boolean ambientOcclusion; + public Builder() { shaders = StandardMaterialShaders.DEFAULT; fog = FogShaders.LINEAR; @@ -169,6 +179,7 @@ public Builder() { useOverlay = true; useLight = true; cardinalLightingMode = CardinalLightingMode.ENTITY; + ambientOcclusion = true; } public Builder(Material material) { @@ -191,6 +202,7 @@ public Builder copyFrom(Material material) { useOverlay = material.useOverlay(); useLight = material.useLight(); cardinalLightingMode = material.cardinalLightingMode(); + ambientOcclusion = material.ambientOcclusion(); return this; } @@ -355,5 +367,15 @@ public CardinalLightingMode cardinalLightingMode() { public SimpleMaterial build() { return new SimpleMaterial(this); } + + public Builder ambientOcclusion(boolean ambientOcclusion) { + this.ambientOcclusion = ambientOcclusion; + return this; + } + + @Override + public boolean ambientOcclusion() { + return ambientOcclusion; + } } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java index 5bfc8d660..874095110 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java @@ -6,11 +6,13 @@ import org.joml.Vector3f; import org.joml.Vector4f; +import dev.engine_room.flywheel.api.material.CardinalLightingMode; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.vertex.VertexList; import dev.engine_room.flywheel.lib.material.Materials; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.memory.MemoryBlock; import dev.engine_room.flywheel.lib.vertex.PosVertexView; import net.minecraft.client.renderer.RenderType; @@ -19,6 +21,36 @@ public final class ModelUtil { private static final float BOUNDING_SPHERE_EPSILON = 1e-4f; + private static final RenderType[] CHUNK_LAYERS = new RenderType[]{RenderType.solid(), RenderType.cutoutMipped(), RenderType.cutout(), RenderType.translucent(), RenderType.tripwire()}; + + // Array of chunk materials to make lookups easier. + // Index by (renderTypeIdx * 4 + shaded * 2 + ambientOcclusion). + private static final Material[] CHUNK_MATERIALS = new Material[20]; + + static { + Material[] baseChunkMaterials = new Material[]{Materials.SOLID_BLOCK, Materials.CUTOUT_MIPPED_BLOCK, Materials.CUTOUT_BLOCK, Materials.TRANSLUCENT_BLOCK, Materials.TRIPWIRE_BLOCK,}; + for (int chunkLayerIdx = 0; chunkLayerIdx < CHUNK_LAYERS.length; chunkLayerIdx++) { + int baseMaterialIdx = chunkLayerIdx * 4; + Material baseChunkMaterial = baseChunkMaterials[chunkLayerIdx]; + + // shaded: false, ambientOcclusion: false + CHUNK_MATERIALS[baseMaterialIdx] = SimpleMaterial.builderOf(baseChunkMaterial) + .cardinalLightingMode(CardinalLightingMode.OFF) + .ambientOcclusion(false) + .build(); + // shaded: false, ambientOcclusion: true + CHUNK_MATERIALS[baseMaterialIdx + 1] = SimpleMaterial.builderOf(baseChunkMaterial) + .cardinalLightingMode(CardinalLightingMode.OFF) + .build(); + // shaded: true, ambientOcclusion: false + CHUNK_MATERIALS[baseMaterialIdx + 2] = SimpleMaterial.builderOf(baseChunkMaterial) + .ambientOcclusion(false) + .build(); + // shaded: true, ambientOcclusion: true + CHUNK_MATERIALS[baseMaterialIdx + 3] = baseChunkMaterial; + } + } + private ModelUtil() { } @@ -29,27 +61,22 @@ public static Material getMaterial(RenderType chunkRenderType, boolean shaded) { @Nullable public static Material getMaterial(RenderType chunkRenderType, boolean shaded, boolean ambientOcclusion) { - if (chunkRenderType == RenderType.solid()) { - return shaded ? Materials.SOLID_BLOCK : Materials.SOLID_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.cutoutMipped()) { - return shaded ? Materials.CUTOUT_MIPPED_BLOCK : Materials.CUTOUT_MIPPED_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.cutout()) { - return shaded ? Materials.CUTOUT_BLOCK : Materials.CUTOUT_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.translucent()) { - return shaded ? Materials.TRANSLUCENT_BLOCK : Materials.TRANSLUCENT_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.tripwire()) { - return shaded ? Materials.TRIPWIRE_BLOCK : Materials.TRIPWIRE_UNSHADED_BLOCK; + for (int chunkLayerIdx = 0; chunkLayerIdx < CHUNK_LAYERS.length; ++chunkLayerIdx) { + if (chunkRenderType == CHUNK_LAYERS[chunkLayerIdx]) { + int shadedIdx = shaded ? 1 : 0; + int ambientOcclusionIdx = ambientOcclusion ? 1 : 0; + + int materialIdx = chunkLayerIdx * 4 + shadedIdx * 2 + ambientOcclusionIdx; + + return CHUNK_MATERIALS[materialIdx]; + } } return null; } @Nullable public static Material getItemMaterial(RenderType renderType) { - var chunkMaterial = getMaterial(renderType, true); + var chunkMaterial = getMaterial(renderType, true, false); if (chunkMaterial != null) { return chunkMaterial; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java index 060168d2d..8307441aa 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java @@ -53,10 +53,10 @@ public BlockModelBuilder materialFunc(@Nullable BiFunction out) { currentIndex = 0; numBufferBuildersPopulated = 0; + blockMaterialFunction = null; } public BufferBuilder getBuffer(Material material) { diff --git a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl index 4844308d9..c481ae6d7 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl +++ b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl @@ -3,6 +3,8 @@ void flw_shaderLight() { if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, light)) { flw_fragLight = max(flw_fragLight, light.light); - flw_fragColor.rgb *= light.ao; + if (flw_material.ambientOcclusion) { + flw_fragColor.rgb *= light.ao; + } } } diff --git a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl index 035c6e9ad..439eac214 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl +++ b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl @@ -4,7 +4,9 @@ void flw_shaderLight() { if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, light)) { flw_fragLight = max(flw_fragLight, light.light); - flw_fragColor.rgb *= light.ao; + if (flw_material.ambientOcclusion) { + flw_fragColor.rgb *= light.ao; + } } #endif } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 8aeddb972..4a07695e6 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -28,7 +28,7 @@ final class BakedModelBufferer { private BakedModelBufferer() { } - public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, BlockMaterialFunction resultConsumer) { + public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -36,7 +36,7 @@ public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTi RandomSource random = objects.random; FabricMeshEmitterManager emitters = objects.emitters; - emitters.prepare(resultConsumer); + emitters.prepare(blockMaterialFunction); long seed = state.getSeed(pos); @@ -55,7 +55,7 @@ public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTi return emitters.end(); } - public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, BlockMaterialFunction resultConsumer) { + public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; @@ -64,7 +64,7 @@ public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndT FabricMeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - emitters.prepare(resultConsumer); + emitters.prepare(blockMaterialFunction); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); From 2282d5830e81fb72fd890a0c53b271c62ba3dbe3 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 8 Nov 2025 14:20:04 -0800 Subject: [PATCH 6/6] Move around and comment --- .../flywheel/lib/material/SimpleMaterial.java | 18 +++++++++--------- .../lib/model/baked/BlockModelBuilder.java | 4 ++-- .../lib/model/baked/BakedModelBufferer.java | 3 +++ .../lib/model/baked/BakedModelBufferer.java | 2 ++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java index ef5a2298a..46aab259b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java @@ -289,6 +289,11 @@ public Builder cardinalLightingMode(CardinalLightingMode value) { return this; } + public Builder ambientOcclusion(boolean ambientOcclusion) { + this.ambientOcclusion = ambientOcclusion; + return this; + } + @Override public MaterialShaders shaders() { return shaders; @@ -364,18 +369,13 @@ public CardinalLightingMode cardinalLightingMode() { return cardinalLightingMode; } - public SimpleMaterial build() { - return new SimpleMaterial(this); - } - - public Builder ambientOcclusion(boolean ambientOcclusion) { - this.ambientOcclusion = ambientOcclusion; - return this; - } - @Override public boolean ambientOcclusion() { return ambientOcclusion; } + + public SimpleMaterial build() { + return new SimpleMaterial(this); + } } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java index 8307441aa..060168d2d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java @@ -53,10 +53,10 @@ public BlockModelBuilder materialFunc(@Nullable BiFunction posIterator, BlockAndT BakedModel model = renderDispatcher.getBlockModel(state); RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state); + + // See ModelBlockRenderer#tesselateBlock boolean defaultAo = useAo && state.getLightEmission() == 0 && model.useAmbientOcclusion(); model = emitters.prepareForModel(model, defaultLayer, useAo, defaultAo); diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 778f7dd83..9351aa4bc 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -50,6 +50,7 @@ public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTi random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); + // See ModelBlockRenderer#tesselateBlock boolean defaultAo = Minecraft.useAmbientOcclusion() && state.getLightEmission(level, pos) == 0; for (RenderType renderType : renderTypes) { @@ -117,6 +118,7 @@ public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndT random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); + // See ModelBlockRenderer#tesselateBlock boolean defaultAo = useAo && state.getLightEmission(level, pos) == 0; for (RenderType renderType : renderTypes) {