Less omissions in mesh emission

- Cannibalize shade separated vertex consumer to function as the mesh
  emitter for baked model buffering
- Create one MeshEmitter per chunk RenderType to allow more coherency
  during multi block buffering
- MeshEmitters own BufferBuilders, and the buffering functions call
  #begin on the mesh emitters directly
- Each time the emitter sees a quad with a different shade flag than the
  previous run of quads, it emits a mesh and begins again
- Finally, the buffering functions call #end on the mesh emitters to
  emit trailing meshes and clear transient state
- Add diffuse debug mode
- Order indirect draws by mesh index first
- Remove non-shade separated option from buffering utilities because it
  allows you to opt in to incorrect behaviour
- Remove TessellatedModel
- Document mesh ordering contract in Model
This commit is contained in:
Jozufozu 2024-03-26 12:06:20 -07:00
parent 183ea1f0f8
commit 96eb5ea47c
12 changed files with 246 additions and 387 deletions

View file

@ -7,6 +7,16 @@ import org.joml.Vector4fc;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
public interface Model { public interface Model {
/**
* Get a list of all meshes in this model.
*
* <p>The contents of the returned list will be queried, but never modified.</p>
*
* <p>Meshes will be rendered in the order they appear in this list, though
* no render order guarantees are made for meshes between different models.</p>
*
* @return A list of meshes.
*/
List<ConfiguredMesh> meshes(); List<ConfiguredMesh> meshes();
/** /**

View file

@ -33,6 +33,7 @@ import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
public class IndirectCullingGroup<I extends Instance> { public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::stage) private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::stage)
.thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR); .thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
private static final int DRAW_BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT; private static final int DRAW_BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
@ -179,12 +180,15 @@ public class IndirectCullingGroup<I extends Instance> {
instancer.index = instancers.size(); instancer.index = instancers.size();
instancers.add(instancer); instancers.add(instancer);
for (var entry : model.meshes()) { List<Model.ConfiguredMesh> meshes = model.meshes();
MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh()); for (int i = 0; i < meshes.size(); i++) {
var draw = new IndirectDraw(instancer, entry.material(), mesh, stage); var entry = meshes.get(i);
indirectDraws.add(draw);
instancer.addDraw(draw); MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh());
} var draw = new IndirectDraw(instancer, entry.material(), mesh, stage, i);
indirectDraws.add(draw);
instancer.addDraw(draw);
}
needsDrawSort = true; needsDrawSort = true;
} }

View file

@ -13,6 +13,7 @@ public class IndirectDraw {
private final Material material; private final Material material;
private final MeshPool.PooledMesh mesh; private final MeshPool.PooledMesh mesh;
private final RenderStage stage; private final RenderStage stage;
private final int indexOfMeshInModel;
private final int materialVertexIndex; private final int materialVertexIndex;
private final int materialFragmentIndex; private final int materialFragmentIndex;
@ -20,11 +21,12 @@ public class IndirectDraw {
private final int packedMaterialProperties; private final int packedMaterialProperties;
private boolean deleted; private boolean deleted;
public IndirectDraw(IndirectInstancer<?> model, Material material, MeshPool.PooledMesh mesh, RenderStage stage) { public IndirectDraw(IndirectInstancer<?> model, Material material, MeshPool.PooledMesh mesh, RenderStage stage, int indexOfMeshInModel) {
this.model = model; this.model = model;
this.material = material; this.material = material;
this.mesh = mesh; this.mesh = mesh;
this.stage = stage; this.stage = stage;
this.indexOfMeshInModel = indexOfMeshInModel;
mesh.acquire(); mesh.acquire();
@ -50,6 +52,10 @@ public class IndirectDraw {
return stage; return stage;
} }
public int indexOfMeshInModel() {
return indexOfMeshInModel;
}
public void write(long ptr) { public void write(long ptr) {
MemoryUtil.memPutInt(ptr, mesh.indexCount()); // count MemoryUtil.memPutInt(ptr, mesh.indexCount()); // count
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader

View file

@ -7,5 +7,6 @@ public enum DebugMode {
LIGHT_LEVEL, LIGHT_LEVEL,
LIGHT_COLOR, LIGHT_COLOR,
OVERLAY, OVERLAY,
DIFFUSE,
LIGHT_VOLUME, LIGHT_VOLUME,
} }

View file

@ -7,9 +7,7 @@ import org.jetbrains.annotations.Nullable;
import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
@ -41,7 +39,7 @@ final class BakedModelBufferer {
poseStack = objects.identityPoseStack; poseStack = objects.identityPoseStack;
} }
RandomSource random = objects.random; RandomSource random = objects.random;
BufferBuilder[] buffers = objects.shadedBuffers; var consumers = objects.emitters;
modelData = model.getModelData(renderWorld, BlockPos.ZERO, state, modelData); modelData = model.getModelData(renderWorld, BlockPos.ZERO, state, modelData);
random.setSeed(42L); random.setSeed(42L);
@ -49,64 +47,18 @@ final class BakedModelBufferer {
for (RenderType renderType : renderTypes) { for (RenderType renderType : renderTypes) {
int layerIndex = renderType.getChunkLayerId(); int layerIndex = renderType.getChunkLayerId();
var consumer = consumers[layerIndex];
BufferBuilder buffer = buffers[layerIndex]; consumer.begin(resultConsumer);
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
poseStack.pushPose(); poseStack.pushPose();
blockRenderer.tesselateBlock(renderWorld, model, state, BlockPos.ZERO, poseStack, buffer, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType); blockRenderer.tesselateBlock(renderWorld, model, state, BlockPos.ZERO, poseStack, consumer, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType);
poseStack.popPose(); poseStack.popPose();
RenderedBuffer data = buffer.endOrDiscardIfEmpty(); consumer.end();
if (data != null) {
resultConsumer.accept(renderType, data);
data.release();
}
} }
} }
public static void bufferSingleShadeSeparated(ModelBlockRenderer blockRenderer, BlockAndTintGetter renderWorld, BakedModel model, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ShadeSeparatedResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
RandomSource random = objects.random;
ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
BufferBuilder[] shadedBuffers = objects.shadedBuffers;
BufferBuilder[] unshadedBuffers = objects.unshadedBuffers;
modelData = model.getModelData(renderWorld, BlockPos.ZERO, state, modelData);
random.setSeed(42L);
ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData);
for (RenderType renderType : renderTypes) {
int layerIndex = renderType.getChunkLayerId();
BufferBuilder shadedBuffer = shadedBuffers[layerIndex];
BufferBuilder unshadedBuffer = unshadedBuffers[layerIndex];
shadeSeparatingWrapper.prepare(shadedBuffer, unshadedBuffer);
shadedBuffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
unshadedBuffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
poseStack.pushPose();
blockRenderer.tesselateBlock(renderWorld, model, state, BlockPos.ZERO, poseStack, shadeSeparatingWrapper, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType);
poseStack.popPose();
RenderedBuffer shadedData = shadedBuffer.endOrDiscardIfEmpty();
if (shadedData != null) {
resultConsumer.accept(renderType, true, shadedData);
shadedData.release();
}
RenderedBuffer unshadedData = unshadedBuffer.endOrDiscardIfEmpty();
if (unshadedData != null) {
resultConsumer.accept(renderType, false, unshadedData);
unshadedData.release();
}
}
shadeSeparatingWrapper.clear();
}
public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) { public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) {
if (state.getRenderShape() != RenderShape.MODEL) { if (state.getRenderShape() != RenderShape.MODEL) {
return; return;
@ -115,14 +67,6 @@ final class BakedModelBufferer {
bufferSingle(renderDispatcher.getModelRenderer(), renderWorld, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer); bufferSingle(renderDispatcher.getModelRenderer(), renderWorld, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer);
} }
public static void bufferBlockShadeSeparated(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ShadeSeparatedResultConsumer resultConsumer) {
if (state.getRenderShape() != RenderShape.MODEL) {
return;
}
bufferSingleShadeSeparated(renderDispatcher.getModelRenderer(), renderWorld, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer);
}
public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter renderWorld, @Nullable PoseStack poseStack, Function<BlockPos, ModelData> modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) { public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter renderWorld, @Nullable PoseStack poseStack, Function<BlockPos, ModelData> modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) { if (poseStack == null) {
@ -131,9 +75,10 @@ final class BakedModelBufferer {
RandomSource random = objects.random; RandomSource random = objects.random;
TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper;
BufferBuilder[] buffers = objects.shadedBuffers; var emitters = objects.emitters;
for (BufferBuilder buffer : buffers) {
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); for (var emitter : emitters) {
emitter.begin(resultConsumer);
} }
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
@ -145,89 +90,12 @@ final class BakedModelBufferer {
if (renderFluids) { if (renderFluids) {
FluidState fluidState = state.getFluidState(); FluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty()) { if (!fluidState.isEmpty()) {
RenderType layer = ItemBlockRenderTypes.getRenderLayer(fluidState); RenderType layer = ItemBlockRenderTypes.getRenderLayer(fluidState);
int layerIndex = layer.getChunkLayerId(); int layerIndex = layer.getChunkLayerId();
transformingWrapper.prepare(buffers[layerIndex], poseStack);
poseStack.pushPose(); transformingWrapper.prepare(emitters[layerIndex], poseStack);
poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF));
renderDispatcher.renderLiquid(pos, renderWorld, transformingWrapper, state, fluidState);
poseStack.popPose();
}
}
if (state.getRenderShape() == RenderShape.MODEL) {
long seed = state.getSeed(pos);
BakedModel model = renderDispatcher.getBlockModel(state);
ModelData modelData = modelDataLookup.apply(pos);
modelData = model.getModelData(renderWorld, pos, state, modelData);
random.setSeed(seed);
ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData);
for (RenderType renderType : renderTypes) {
int layerIndex = renderType.getChunkLayerId();
BufferBuilder buffer = buffers[layerIndex];
poseStack.pushPose();
poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
blockRenderer.tesselateBlock(renderWorld, model, state, pos, poseStack, buffer, true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType);
poseStack.popPose();
}
}
}
ModelBlockRenderer.clearCache();
transformingWrapper.clear();
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
RenderType renderType = CHUNK_LAYERS[layerIndex];
BufferBuilder buffer = buffers[layerIndex];
RenderedBuffer data = buffer.endOrDiscardIfEmpty();
if (data != null) {
resultConsumer.accept(renderType, data);
data.release();
}
}
}
public static void bufferMultiBlockShadeSeparated(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter renderWorld, @Nullable PoseStack poseStack, Function<BlockPos, ModelData> modelDataLookup, boolean renderFluids, ShadeSeparatedResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
RandomSource random = objects.random;
ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
TransformingVertexConsumer transformingWrapper = objects.transformingWrapper;
BufferBuilder[] shadedBuffers = objects.shadedBuffers;
BufferBuilder[] unshadedBuffers = objects.unshadedBuffers;
for (BufferBuilder buffer : shadedBuffers) {
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
}
for (BufferBuilder buffer : unshadedBuffers) {
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
}
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
ModelBlockRenderer.enableCaching();
while (posIterator.hasNext()) {
BlockPos pos = posIterator.next();
BlockState state = renderWorld.getBlockState(pos);
if (renderFluids) {
FluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty()) {
RenderType layer = ItemBlockRenderTypes.getRenderLayer(fluidState);
int layerIndex = layer.getChunkLayerId();
transformingWrapper.prepare(shadedBuffers[layerIndex], poseStack);
poseStack.pushPose(); poseStack.pushPose();
poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF));
@ -247,11 +115,9 @@ final class BakedModelBufferer {
for (RenderType renderType : renderTypes) { for (RenderType renderType : renderTypes) {
int layerIndex = renderType.getChunkLayerId(); int layerIndex = renderType.getChunkLayerId();
shadeSeparatingWrapper.prepare(shadedBuffers[layerIndex], unshadedBuffers[layerIndex]);
poseStack.pushPose(); poseStack.pushPose();
poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
blockRenderer.tesselateBlock(renderWorld, model, state, pos, poseStack, shadeSeparatingWrapper, true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); blockRenderer.tesselateBlock(renderWorld, model, state, pos, poseStack, emitters[layerIndex], true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType);
poseStack.popPose(); poseStack.popPose();
} }
} }
@ -259,31 +125,14 @@ final class BakedModelBufferer {
ModelBlockRenderer.clearCache(); ModelBlockRenderer.clearCache();
shadeSeparatingWrapper.clear(); for (var emitter : emitters) {
transformingWrapper.clear(); emitter.end();
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
RenderType renderType = CHUNK_LAYERS[layerIndex];
BufferBuilder shadedBuffer = shadedBuffers[layerIndex];
BufferBuilder unshadedBuffer = unshadedBuffers[layerIndex];
RenderedBuffer shadedData = shadedBuffer.endOrDiscardIfEmpty();
if (shadedData != null) {
resultConsumer.accept(renderType, true, shadedData);
shadedData.release();
}
RenderedBuffer unshadedData = unshadedBuffer.endOrDiscardIfEmpty();
if (unshadedData != null) {
resultConsumer.accept(renderType, false, unshadedData);
unshadedData.release();
}
} }
transformingWrapper.clear();
} }
public interface ResultConsumer { public interface ResultConsumer {
void accept(RenderType renderType, RenderedBuffer data);
}
public interface ShadeSeparatedResultConsumer {
void accept(RenderType renderType, boolean shaded, RenderedBuffer data); void accept(RenderType renderType, boolean shaded, RenderedBuffer data);
} }
@ -291,17 +140,16 @@ final class BakedModelBufferer {
public final PoseStack identityPoseStack = new PoseStack(); public final PoseStack identityPoseStack = new PoseStack();
public final RandomSource random = RandomSource.createNewThreadLocalInstance(); public final RandomSource random = RandomSource.createNewThreadLocalInstance();
public final ShadeSeparatingVertexConsumer shadeSeparatingWrapper = new ShadeSeparatingVertexConsumer();
public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer();
public final BufferBuilder[] shadedBuffers = new BufferBuilder[CHUNK_LAYER_AMOUNT]; public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT];
public final BufferBuilder[] unshadedBuffers = new BufferBuilder[CHUNK_LAYER_AMOUNT];
{ {
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
int initialSize = CHUNK_LAYERS[layerIndex].bufferSize(); var renderType = CHUNK_LAYERS[layerIndex];
shadedBuffers[layerIndex] = new BufferBuilder(initialSize); // FIXME: We leak the memory owned by the BufferBuilder here.
unshadedBuffers[layerIndex] = new BufferBuilder(initialSize); var buffer = new BufferBuilder(renderType.bufferSize());
emitters[layerIndex] = new MeshEmitter(buffer, renderType);
} }
} }
} }

View file

@ -2,6 +2,8 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.model.Model;
@ -9,8 +11,8 @@ import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer; import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
@ -23,11 +25,15 @@ import net.minecraftforge.client.model.data.ModelData;
public class BakedModelBuilder { public class BakedModelBuilder {
private final BakedModel bakedModel; private final BakedModel bakedModel;
@Nullable
private BlockAndTintGetter renderWorld; private BlockAndTintGetter renderWorld;
@Nullable
private BlockState blockState; private BlockState blockState;
@Nullable
private PoseStack poseStack; private PoseStack poseStack;
@Nullable
private ModelData modelData; private ModelData modelData;
private boolean shadeSeparated = true; @Nullable
private BiFunction<RenderType, Boolean, Material> materialFunc; private BiFunction<RenderType, Boolean, Material> materialFunc;
public BakedModelBuilder(BakedModel bakedModel) { public BakedModelBuilder(BakedModel bakedModel) {
@ -54,17 +60,12 @@ public class BakedModelBuilder {
return this; return this;
} }
public BakedModelBuilder disableShadeSeparation() {
shadeSeparated = false;
return this;
}
public BakedModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) { public BakedModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc; this.materialFunc = materialFunc;
return this; return this;
} }
public TessellatedModel build() { public SimpleModel build() {
if (renderWorld == null) { if (renderWorld == null) {
renderWorld = VirtualEmptyBlockGetter.INSTANCE; renderWorld = VirtualEmptyBlockGetter.INSTANCE;
} }
@ -80,30 +81,17 @@ public class BakedModelBuilder {
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
if (shadeSeparated) { ResultConsumer resultConsumer = (renderType, shaded, data) -> {
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> { Material material = materialFunc.apply(renderType, shaded);
Material material = materialFunc.apply(renderType, shaded); if (material != null) {
if (material != null) { VertexView vertexView = new NoOverlayVertexView();
VertexView vertexView = new NoOverlayVertexView(); MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView); var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded);
var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); out.add(new Model.ConfiguredMesh(material, mesh));
out.add(new Model.ConfiguredMesh(material, mesh)); }
} };
}; BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferSingleShadeSeparated(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType);
out.add(new Model.ConfiguredMesh(material, mesh));
}
};
BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
}
return new TessellatedModel(out.build(), shadeSeparated); return new SimpleModel(out.build());
} }
} }

View file

@ -2,6 +2,8 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.model.Model;
@ -9,8 +11,8 @@ import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer; import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
@ -21,10 +23,13 @@ import net.minecraftforge.client.model.data.ModelData;
public class BlockModelBuilder { public class BlockModelBuilder {
private final BlockState state; private final BlockState state;
@Nullable
private BlockAndTintGetter renderWorld; private BlockAndTintGetter renderWorld;
@Nullable
private PoseStack poseStack; private PoseStack poseStack;
@Nullable
private ModelData modelData; private ModelData modelData;
private boolean shadeSeparated = true; @Nullable
private BiFunction<RenderType, Boolean, Material> materialFunc; private BiFunction<RenderType, Boolean, Material> materialFunc;
public BlockModelBuilder(BlockState state) { public BlockModelBuilder(BlockState state) {
@ -46,17 +51,12 @@ public class BlockModelBuilder {
return this; return this;
} }
public BlockModelBuilder disableShadeSeparation() {
shadeSeparated = false;
return this;
}
public BlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) { public BlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc; this.materialFunc = materialFunc;
return this; return this;
} }
public TessellatedModel build() { public SimpleModel build() {
if (renderWorld == null) { if (renderWorld == null) {
renderWorld = VirtualEmptyBlockGetter.INSTANCE; renderWorld = VirtualEmptyBlockGetter.INSTANCE;
} }
@ -69,30 +69,17 @@ public class BlockModelBuilder {
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
if (shadeSeparated) { ResultConsumer resultConsumer = (renderType, shaded, data) -> {
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> { Material material = materialFunc.apply(renderType, shaded);
Material material = materialFunc.apply(renderType, shaded); if (material != null) {
if (material != null) { VertexView vertexView = new NoOverlayVertexView();
VertexView vertexView = new NoOverlayVertexView(); MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView); var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded);
var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded); out.add(new Model.ConfiguredMesh(material, mesh));
out.add(new Model.ConfiguredMesh(material, mesh)); }
} };
}; BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferBlockShadeSeparated(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType);
out.add(new Model.ConfiguredMesh(material, mesh));
}
};
BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
}
return new TessellatedModel(out.build(), shadeSeparated); return new SimpleModel(out.build());
} }
} }

View file

@ -0,0 +1,122 @@
package com.jozufozu.flywheel.lib.model.baked;
import org.jetbrains.annotations.Nullable;
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 BufferBuilder bufferBuilder;
private final RenderType renderType;
private boolean lastQuadWasShaded;
private boolean seenFirstQuad;
@Nullable
private BakedModelBufferer.ResultConsumer resultConsumer;
MeshEmitter(BufferBuilder bufferBuilder, RenderType renderType) {
this.bufferBuilder = bufferBuilder;
this.renderType = renderType;
}
public void begin(BakedModelBufferer.ResultConsumer resultConsumer) {
this.resultConsumer = resultConsumer;
begin();
}
public void end() {
emit();
seenFirstQuad = false;
resultConsumer = null;
}
private void begin() {
bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
}
private void emit() {
var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty();
if (renderedBuffer != null) {
if (resultConsumer != null) {
resultConsumer.accept(renderType, lastQuadWasShaded, renderedBuffer);
}
renderedBuffer.release();
}
}
private void observeQuadAndEmitIfNecessary(BakedQuad quad) {
if (seenFirstQuad && lastQuadWasShaded != quad.isShade()) {
emit();
begin();
}
seenFirstQuad = true;
lastQuadWasShaded = quad.isShade();
}
@Override
public void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
observeQuadAndEmitIfNecessary(quad);
bufferBuilder.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
}
@Override
public void putBulkData(PoseStack.Pose matrixEntry, BakedQuad quad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
observeQuadAndEmitIfNecessary(quad);
bufferBuilder.putBulkData(matrixEntry, quad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv(float u, float v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer overlayCoords(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv2(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer normal(float x, float y, float z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void endVertex() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void defaultColor(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void unsetDefaultColor() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
}

View file

@ -3,6 +3,8 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.model.Model;
@ -10,8 +12,8 @@ import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer; import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
@ -23,10 +25,12 @@ import net.minecraftforge.client.model.data.ModelData;
public class MultiBlockModelBuilder { public class MultiBlockModelBuilder {
private final BlockAndTintGetter renderWorld; private final BlockAndTintGetter renderWorld;
private final Iterable<BlockPos> positions; private final Iterable<BlockPos> positions;
@Nullable
private PoseStack poseStack; private PoseStack poseStack;
@Nullable
private Function<BlockPos, ModelData> modelDataLookup; private Function<BlockPos, ModelData> modelDataLookup;
private boolean renderFluids = false; private boolean renderFluids = false;
private boolean shadeSeparated = true; @Nullable
private BiFunction<RenderType, Boolean, Material> materialFunc; private BiFunction<RenderType, Boolean, Material> materialFunc;
public MultiBlockModelBuilder(BlockAndTintGetter renderWorld, Iterable<BlockPos> positions) { public MultiBlockModelBuilder(BlockAndTintGetter renderWorld, Iterable<BlockPos> positions) {
@ -49,17 +53,12 @@ public class MultiBlockModelBuilder {
return this; return this;
} }
public MultiBlockModelBuilder disableShadeSeparation() {
shadeSeparated = false;
return this;
}
public MultiBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) { public MultiBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc; this.materialFunc = materialFunc;
return this; return this;
} }
public TessellatedModel build() { public SimpleModel build() {
if (modelDataLookup == null) { if (modelDataLookup == null) {
modelDataLookup = pos -> ModelData.EMPTY; modelDataLookup = pos -> ModelData.EMPTY;
} }
@ -69,30 +68,17 @@ public class MultiBlockModelBuilder {
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
if (shadeSeparated) { ResultConsumer resultConsumer = (renderType, shaded, data) -> {
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> { Material material = materialFunc.apply(renderType, shaded);
Material material = materialFunc.apply(renderType, shaded); if (material != null) {
if (material != null) { VertexView vertexView = new NoOverlayVertexView();
VertexView vertexView = new NoOverlayVertexView(); MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView); var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded);
var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); out.add(new Model.ConfiguredMesh(material, mesh));
out.add(new Model.ConfiguredMesh(material, mesh)); }
} };
}; BakedModelBufferer.bufferMultiBlock(ModelUtil.VANILLA_RENDERER, positions.iterator(), renderWorld, poseStack, modelDataLookup, renderFluids, resultConsumer);
BakedModelBufferer.bufferMultiBlockShadeSeparated(ModelUtil.VANILLA_RENDERER, positions.iterator(), renderWorld, poseStack, modelDataLookup, renderFluids, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType);
out.add(new Model.ConfiguredMesh(material, mesh));
}
};
BakedModelBufferer.bufferMultiBlock(ModelUtil.VANILLA_RENDERER, positions.iterator(), renderWorld, poseStack, modelDataLookup, renderFluids, resultConsumer);
}
return new TessellatedModel(out.build(), shadeSeparated); return new SimpleModel(out.build());
} }
} }

View file

@ -1,84 +0,0 @@
package com.jozufozu.flywheel.lib.model.baked;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.block.model.BakedQuad;
class ShadeSeparatingVertexConsumer implements VertexConsumer {
private VertexConsumer shadedConsumer;
private VertexConsumer unshadedConsumer;
public void prepare(VertexConsumer shadedConsumer, VertexConsumer unshadedConsumer) {
this.shadedConsumer = shadedConsumer;
this.unshadedConsumer = unshadedConsumer;
}
public void clear() {
shadedConsumer = null;
unshadedConsumer = null;
}
@Override
public void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
if (quad.isShade()) {
shadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
} else {
unshadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
}
}
@Override
public void putBulkData(PoseStack.Pose matrixEntry, BakedQuad bakedQuad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
if (bakedQuad.isShade()) {
shadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
} else {
unshadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
}
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv(float u, float v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer overlayCoords(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv2(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer normal(float x, float y, float z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void endVertex() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void defaultColor(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void unsetDefaultColor() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
}

View file

@ -1,17 +0,0 @@
package com.jozufozu.flywheel.lib.model.baked;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.lib.model.SimpleModel;
public class TessellatedModel extends SimpleModel {
private final boolean shadeSeparated;
public TessellatedModel(ImmutableList<ConfiguredMesh> meshes, boolean shadeSeparated) {
super(meshes);
this.shadeSeparated = shadeSeparated;
}
public boolean isShadeSeparated() {
return shadeSeparated;
}
}

View file

@ -25,6 +25,18 @@ flat in uint _flw_instanceID;
out vec4 _flw_outputColor; out vec4 _flw_outputColor;
float _flw_diffuseFactor() {
if (flw_material.diffuse) {
if (flw_constantAmbientLight == 1u) {
return diffuseNether(flw_vertexNormal);
} else {
return diffuse(flw_vertexNormal);
}
} else {
return 1.;
}
}
void _flw_main() { void _flw_main() {
flw_sampleColor = texture(flw_diffuseTex, flw_vertexTexCoord); flw_sampleColor = texture(flw_diffuseTex, flw_vertexTexCoord);
flw_fragColor = flw_vertexColor * flw_sampleColor; flw_fragColor = flw_vertexColor * flw_sampleColor;
@ -49,15 +61,8 @@ void _flw_main() {
vec4 color = flw_fragColor; vec4 color = flw_fragColor;
if (flw_material.diffuse) { float diffuseFactor = _flw_diffuseFactor();
float diffuseFactor; color.rgb *= diffuseFactor;
if (flw_constantAmbientLight == 1u) {
diffuseFactor = diffuseNether(flw_vertexNormal);
} else {
diffuseFactor = diffuse(flw_vertexNormal);
}
color.rgb *= diffuseFactor;
}
if (flw_material.useOverlay) { if (flw_material.useOverlay) {
vec4 overlayColor = texelFetch(flw_overlayTex, flw_fragOverlay, 0); vec4 overlayColor = texelFetch(flw_overlayTex, flw_fragOverlay, 0);
@ -90,8 +95,11 @@ void _flw_main() {
case 5u: case 5u:
color = vec4(flw_fragOverlay / 16., 0., 1.); color = vec4(flw_fragOverlay / 16., 0., 1.);
break; break;
#ifdef _FLW_EMBEDDED
case 6u: case 6u:
color = vec4(vec3(diffuseFactor), 1.);
break;
#ifdef _FLW_EMBEDDED
case 7u:
color = vec4(_flw_lightVolumeCoord, 1.); color = vec4(_flw_lightVolumeCoord, 1.);
break; break;
#endif #endif