- Re-implement batching under new api topology
 - Lots of duplicated code between instancing and batching, room for abstraction
 - StructTypes now require their type argument to extend InstancedPart
 - Fix crash when re-allocating drawbuffers
 - Disable crumbling pending refactor
 - Separate RenderDispatcher#beginFrame from RenderDispatcher#maintainOriginCoordinate
 - Inline OriginShiftListener
 - Refactor InstancedRenderDispatcher hooks
This commit is contained in:
Jozufozu 2022-06-30 14:29:35 -07:00
parent fa76e3e454
commit 7acf4a8aeb
28 changed files with 522 additions and 333 deletions

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.api;
import com.jozufozu.flywheel.api.struct.StructType;
@Deprecated
public interface MaterialGroup {
/**
* Get the material as defined by the given {@link StructType type}.
*
* @param spec The material you want to create instances with.
* @param <D> The type representing the per instance data.
* @return A material you can use to render models.
*/
<D extends InstancedPart> InstancerFactory<D> material(StructType<D> spec);
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.api.struct;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.model.ModelTransformer;
import com.jozufozu.flywheel.core.source.FileResolution;
@ -10,7 +11,7 @@ import com.jozufozu.flywheel.core.source.FileResolution;
* A StructType contains metadata for a specific instance struct that Flywheel can interface with.
* @param <S> The java representation of the instance struct.
*/
public interface StructType<S> {
public interface StructType<S extends InstancedPart> {
/**
* @return A new, zeroed instance of S.

View file

@ -9,7 +9,7 @@ import com.jozufozu.flywheel.api.struct.StructType;
public abstract class AbstractInstancer<D extends InstancedPart> implements Instancer<D> {
protected final StructType<D> type;
public final StructType<D> type;
protected final ArrayList<D> data = new ArrayList<>();
protected boolean anyToRemove;
@ -104,6 +104,8 @@ public abstract class AbstractInstancer<D extends InstancedPart> implements Inst
return instanceData;
}
public abstract void delete();
@Override
public String toString() {
return "Instancer[" + getInstanceCount() + ']';

View file

@ -16,12 +16,11 @@ import net.minecraft.client.renderer.RenderType;
*
* The number of vertices needs to be known ahead of time.
*/
public class DrawBuffer implements AutoCloseable {
public class DrawBuffer {
private final RenderType parent;
private ByteBuffer backingBuffer;
private int expectedVertices;
private Cleaner.Cleanable cleanable;
public DrawBuffer(RenderType parent) {
this.parent = parent;
@ -46,12 +45,8 @@ public class DrawBuffer implements AutoCloseable {
if (backingBuffer == null) {
backingBuffer = MemoryTracker.create(byteSize);
cleanable = FlywheelMemory.track(this, backingBuffer);
}
if (byteSize > backingBuffer.capacity()) {
cleanable.clean();
} else if (byteSize > backingBuffer.capacity()) {
backingBuffer = MemoryTracker.resize(backingBuffer, byteSize);
cleanable = FlywheelMemory.track(this, backingBuffer);
}
return new DirectVertexConsumer(backingBuffer, format, vertexCount);
@ -80,11 +75,4 @@ public class DrawBuffer implements AutoCloseable {
public void reset() {
this.expectedVertices = 0;
}
@Override
public void close() {
if (cleanable != null) {
cleanable.clean();
}
}
}

View file

@ -13,7 +13,6 @@ import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.backend.instancing.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.backend.instancing.ratelimit.DistanceUpdateLimiter;
import com.jozufozu.flywheel.backend.instancing.ratelimit.NonLimiter;
@ -25,7 +24,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
public abstract class InstanceManager<T> implements InstancingEngine.OriginShiftListener {
public abstract class InstanceManager<T> {
public final InstancerManager instancerManager;
@ -350,7 +349,6 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
}
@Override
public void onOriginShift() {
dynamicInstances.clear();
tickableInstances.clear();

View file

@ -13,6 +13,7 @@ import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.util.ClientLevelExtension;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.RenderType;
@ -36,21 +37,21 @@ public class InstanceWorld {
public static InstanceWorld create(LevelAccessor level) {
return switch (Backend.getBackendType()) {
case INSTANCING -> {
InstancingEngine<WorldProgram> manager = new InstancingEngine<>(Contexts.WORLD);
InstancingEngine<WorldProgram> engine = new InstancingEngine<>(Contexts.WORLD);
var entityInstanceManager = new EntityInstanceManager(manager);
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
manager.addListener(entityInstanceManager);
manager.addListener(blockEntityInstanceManager);
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
engine.attachManager(entityInstanceManager);
engine.attachManager(blockEntityInstanceManager);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
case BATCHING -> {
var manager = new BatchingEngine();
var entityInstanceManager = new EntityInstanceManager(manager);
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
var engine = new BatchingEngine();
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
default -> throw new IllegalArgumentException("Unknown engine type");
};
@ -89,12 +90,17 @@ public class InstanceWorld {
* </p>
*/
public void beginFrame(BeginFrameEvent event) {
engine.beginFrame(event.getCamera());
Camera camera = event.getCamera();
boolean shifted = engine.maintainOriginCoordinate(camera);
taskEngine.syncPoint();
blockEntityInstanceManager.beginFrame(taskEngine, event.getCamera());
entityInstanceManager.beginFrame(taskEngine, event.getCamera());
if (!shifted) {
blockEntityInstanceManager.beginFrame(taskEngine, camera);
entityInstanceManager.beginFrame(taskEngine, camera);
}
engine.beginFrame(taskEngine, camera);
}
/**

View file

@ -12,11 +12,13 @@ public interface RenderDispatcher {
void renderSpecificType(TaskEngine taskEngine, RenderContext context, RenderType type);
/**
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions.
* <p>
* This prevents floating point precision issues at high coordinates.
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions,
* preventing floating point precision issues at high coordinates.
* @return {@code true} if the origin coordinate was changed, {@code false} otherwise.
*/
void beginFrame(Camera info);
boolean maintainOriginCoordinate(Camera camera);
void beginFrame(TaskEngine taskEngine, Camera info);
void delete();
}

View file

@ -0,0 +1,17 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.RenderType;
public class BatchLists {
public final Map<RenderType, List<TransformSet<?>>> renderLists = new HashMap<>();
public void add(TransformSet<?> set) {
renderLists.computeIfAbsent(set.material.renderType(), k -> new ArrayList<>()).add(set);
}
}

View file

@ -1,81 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.MaterialGroup;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.BatchDrawingTracker;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
public class BatchedMaterialGroup implements MaterialGroup {
protected final RenderType state;
private final Map<StructType<? extends InstancedPart>, CPUInstancerFactory<?>> materials = new HashMap<>();
private int vertexCount;
private int instanceCount;
public BatchedMaterialGroup(RenderType state) {
this.state = state;
}
@SuppressWarnings("unchecked")
@Override
public <D extends InstancedPart> CPUInstancerFactory<D> material(StructType<D> type) {
return (CPUInstancerFactory<D>) materials.computeIfAbsent(type, CPUInstancerFactory::new);
}
public void render(PoseStack stack, BatchDrawingTracker source, TaskEngine pool) {
// vertexCount = 0;
// instanceCount = 0;
// for (BatchedMaterial<?> material : materials.values()) {
// for (CPUInstancer<?> instancer : material.models.values()) {
// instancer.setup();
// vertexCount += instancer.getVertexCount();
// instanceCount += instancer.getInstanceCount();
// }
// }
//
// DirectVertexConsumer consumer = source.getDirectConsumer(state, vertexCount);
//
// // avoids rendering garbage, but doesn't fix the issue of some instances not being buffered
// consumer.memSetZero();
//
// for (BatchedMaterial<?> material : materials.values()) {
// for (CPUInstancer<?> instancer : material.models.values()) {
// instancer.sbb.context.outputColorDiffuse = !consumer.hasOverlay() && !OptifineHandler.isUsingShaders();
// instancer.submitTasks(stack, pool, consumer);
// }
// }
}
public void clear() {
materials.values().forEach(CPUInstancerFactory::clear);
}
public void delete() {
materials.clear();
}
/**
* Get the number of instances drawn last frame.
* @return The instance count.
*/
public int getInstanceCount() {
return instanceCount;
}
/**
* Get the number of vertices drawn last frame.
* @return The vertex count.
*/
public int getVertexCount() {
return vertexCount;
}
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.List;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.core.model.ModelSupplier;
public class BatchedModel<D extends InstancedPart> {
CPUInstancer<D> instancer;
ModelSupplier model;
StructType<D> type;
private List<TransformSet<D>> layers;
public BatchedModel(StructType<D> type, ModelSupplier model) {
this.type = type;
this.model = model;
this.instancer = new CPUInstancer<>(type);
}
public void init(BatchLists batchLists) {
layers = model.get()
.entrySet()
.stream()
.map(entry -> new TransformSet<>(instancer, entry.getKey(), entry.getValue()))
.toList();
for (TransformSet<D> layer : layers) {
batchLists.add(layer);
}
}
public void clear() {
instancer.clear();
}
}

View file

@ -1,32 +1,45 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.OptifineHandler;
import com.jozufozu.flywheel.backend.instancing.BatchDrawingTracker;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
public class BatchingEngine implements Engine {
private final Map<StructType<? extends InstancedPart>, CPUInstancerFactory<?>> factories = new HashMap<>();
private final Map<StructType<?>, CPUInstancerFactory<?>> factories = new HashMap<>();
private final BatchDrawingTracker batchTracker = new BatchDrawingTracker();
private final BatchLists batchLists = new BatchLists();
protected final List<BatchedModel<?>> uninitializedModels = new ArrayList<>();
@SuppressWarnings("unchecked")
@Override
public <D extends InstancedPart> CPUInstancerFactory<D> factory(StructType<D> type) {
return (CPUInstancerFactory<D>) factories.computeIfAbsent(type, CPUInstancerFactory::new);
return (CPUInstancerFactory<D>) factories.computeIfAbsent(type, this::createFactory);
}
public <D extends InstancedPart> CPUInstancerFactory<D> createFactory(StructType<D> type) {
return new CPUInstancerFactory<>(type, uninitializedModels::add);
}
@Override
@ -34,6 +47,25 @@ public class BatchingEngine implements Engine {
return BlockPos.ZERO;
}
public void submitTasks(PoseStack stack, TaskEngine taskEngine) {
batchLists.renderLists.forEach((renderType, renderList) -> {
int vertices = 0;
for (var transformSet : renderList) {
vertices += transformSet.getTotalVertexCount();
}
var consumer = batchTracker.getDirectConsumer(renderType, vertices);
consumer.memSetZero();
var outputColorDiffuse = !consumer.hasOverlay() && !OptifineHandler.isUsingShaders();
for (var transformSet : renderList) {
transformSet.setOutputColorDiffuse(outputColorDiffuse);
transformSet.submitTasks(stack, taskEngine, consumer);
}
});
}
@Override
public void renderSpecificType(TaskEngine taskEngine, RenderContext context, RenderType type) {
@ -41,27 +73,6 @@ public class BatchingEngine implements Engine {
@Override
public void renderAllRemaining(TaskEngine taskEngine, RenderContext context) {
// vertexCount = 0;
// instanceCount = 0;
// for (BatchedMaterial<?> material : materials.values()) {
// for (CPUInstancer<?> instancer : material.models.values()) {
// instancer.setup();
// vertexCount += instancer.getVertexCount();
// instanceCount += instancer.getInstanceCount();
// }
// }
//
// DirectVertexConsumer consumer = batchTracker.getDirectConsumer(state, vertexCount);
//
// // avoids rendering garbage, but doesn't fix the issue of some instances not being buffered
// consumer.memSetZero();
//
// for (BatchedMaterial<?> material : materials.values()) {
// for (CPUInstancer<?> instancer : material.models.values()) {
// instancer.sbb.context.outputColorDiffuse = !consumer.hasOverlay() && !OptifineHandler.isUsingShaders();
// instancer.submitTasks(stack, pool, consumer);
// }
// }
// FIXME: this probably breaks some vanilla stuff but it works much better for flywheel
Matrix4f mat = new Matrix4f();
@ -81,8 +92,24 @@ public class BatchingEngine implements Engine {
}
@Override
public void beginFrame(Camera info) {
public boolean maintainOriginCoordinate(Camera camera) {
// do nothing
return false;
}
@Override
public void beginFrame(TaskEngine taskEngine, Camera info) {
for (var model : uninitializedModels) {
model.init(batchLists);
}
uninitializedModels.clear();
Vec3 cameraPos = info.getPosition();
var stack = FlwUtil.copyPoseStack(RenderContext.CURRENT.stack());
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
submitTasks(stack, taskEngine);
}
@Override

View file

@ -1,74 +1,39 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.List;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.model.DirectVertexConsumer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
public class CPUInstancer<D extends InstancedPart> extends AbstractInstancer<D> {
// private final Batched<D> batchingType;
//
// final ModelTransformer sbb;
public CPUInstancer(StructType<D> type) {
super(type);
// batchingType = type;
//
// sbb = new ModelTransformer(modelData.get());
}
void submitTasks(PoseStack stack, TaskEngine pool, DirectVertexConsumer consumer) {
// int instances = getInstanceCount();
//
// while (instances > 0) {
// int end = instances;
// instances -= 512;
// int start = Math.max(instances, 0);
//
// int verts = getModelVertexCount() * (end - start);
//
// DirectVertexConsumer sub = consumer.split(verts);
//
// pool.submit(() -> drawRange(stack, sub, start, end));
// }
}
private void drawRange(PoseStack stack, VertexConsumer buffer, int from, int to) {
// ModelTransformer.Params params = new ModelTransformer.Params();
//
// for (D d : data.subList(from, to)) {
// params.loadDefault();
//
// batchingType.transform(d, params);
//
// sbb.renderInto(params, stack, buffer);
// }
}
void drawAll(PoseStack stack, VertexConsumer buffer) {
// ModelTransformer.Params params = new ModelTransformer.Params();
// for (D d : data) {
// params.loadDefault();
//
// batchingType.transform(d, params);
//
// sbb.renderInto(params, stack, buffer);
// }
}
void setup() {
// if (anyToRemove) {
// data.removeIf(InstanceData::isRemoved);
// anyToRemove = false;
// }
if (anyToRemove) {
data.removeIf(InstancedPart::isRemoved);
anyToRemove = false;
}
}
public List<D> getRange(int start, int end) {
return data.subList(start, end);
}
public List<D> getAll() {
return data;
}
@Override
public void notifyDirty() {
// noop
}
@Override
public void delete() {
// noop
}
}

View file

@ -2,36 +2,31 @@ package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.Instancer;
import com.jozufozu.flywheel.api.InstancerFactory;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.core.model.ModelSupplier;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
public class CPUInstancerFactory<D extends InstancedPart> implements InstancerFactory<D> {
protected final Map<ModelSupplier, CPUInstancer<D>> models;
protected final Map<ModelSupplier, BatchedModel<D>> models;
private final StructType<D> type;
private final Consumer<BatchedModel<D>> creationListener;
public CPUInstancerFactory(StructType<D> type) {
public CPUInstancerFactory(StructType<D> type, Consumer<BatchedModel<D>> creationListener) {
this.type = type;
this.creationListener = creationListener;
this.models = new HashMap<>();
}
@Override
public Instancer<D> model(ModelSupplier modelKey) {
return models.computeIfAbsent(modelKey, k -> new CPUInstancer<>(type));
}
public void setupAndRenderInto(PoseStack stack, VertexConsumer buffer) {
for (CPUInstancer<D> instancer : models.values()) {
instancer.setup();
instancer.drawAll(stack, buffer);
}
return models.computeIfAbsent(modelKey, this::createModel).instancer;
}
/**
@ -39,6 +34,12 @@ public class CPUInstancerFactory<D extends InstancedPart> implements InstancerFa
*/
public void clear() {
models.values()
.forEach(CPUInstancer::clear);
.forEach(BatchedModel::clear);
}
private BatchedModel<D> createModel(ModelSupplier k) {
var out = new BatchedModel<>(type, k);
creationListener.accept(out);
return out;
}
}

View file

@ -0,0 +1,74 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.List;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.model.DirectVertexConsumer;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.ModelTransformer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
public class TransformSet<D extends InstancedPart> {
public final Material material;
public final Mesh mesh;
private final CPUInstancer<D> instancer;
private final ModelTransformer modelTransformer;
public TransformSet(CPUInstancer<D> instancer, Material material, Mesh mesh) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
modelTransformer = new ModelTransformer(mesh);
}
void submitTasks(PoseStack stack, TaskEngine pool, DirectVertexConsumer consumer) {
instancer.setup();
int instances = instancer.getInstanceCount();
while (instances > 0) {
int end = instances;
instances -= 512;
int start = Math.max(instances, 0);
int verts = mesh.getVertexCount() * (end - start);
DirectVertexConsumer sub = consumer.split(verts);
pool.submit(() -> drawRange(stack, sub, start, end));
}
}
private void drawRange(PoseStack stack, VertexConsumer buffer, int from, int to) {
drawList(stack, buffer, instancer.getRange(from, to));
}
void drawAll(PoseStack stack, VertexConsumer buffer) {
drawList(stack, buffer, instancer.getAll());
}
private void drawList(PoseStack stack, VertexConsumer buffer, List<D> list) {
ModelTransformer.Params params = new ModelTransformer.Params();
for (D d : list) {
params.loadDefault();
instancer.type.transform(d, params);
modelTransformer.renderInto(params, stack, buffer);
}
}
public int getTotalVertexCount() {
return mesh.getVertexCount() * instancer.getInstanceCount();
}
public void setOutputColorDiffuse(boolean outputColorDiffuse) {
modelTransformer.context.outputColorDiffuse = outputColorDiffuse;
}
}

View file

@ -129,4 +129,10 @@ public class GPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
vao.setAttributeDivisor(this.attributeBaseIndex + i, 1);
}
}
@Override
public void delete() {
vbo.delete();
vbo = null;
}
}

View file

@ -8,6 +8,7 @@ import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.Instancer;
import com.jozufozu.flywheel.api.InstancerFactory;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
import com.jozufozu.flywheel.core.model.ModelSupplier;
import net.minecraft.client.renderer.RenderType;
@ -36,7 +37,7 @@ public class GPUInstancerFactory<D extends InstancedPart> implements InstancerFa
return models.values()
.stream()
.map(InstancedModel::getInstancer)
.mapToInt(GPUInstancer::getInstanceCount)
.mapToInt(AbstractInstancer::getInstanceCount)
.sum();
}
@ -59,7 +60,7 @@ public class GPUInstancerFactory<D extends InstancedPart> implements InstancerFa
models.values()
.stream()
.map(InstancedModel::getInstancer)
.forEach(GPUInstancer::clear);
.forEach(AbstractInstancer::clear);
}
private InstancedModel<D> createInstancer(ModelSupplier model) {

View file

@ -4,13 +4,14 @@ import java.util.List;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
import com.jozufozu.flywheel.core.model.ModelSupplier;
public class InstancedModel<D extends InstancedPart> {
public final GPUInstancer<D> instancer;
public final ModelSupplier model;
private final ModelSupplier model;
private final StructType<D> type;
private final GPUInstancer<D> instancer;
private List<DrawCall> layers;
public InstancedModel(StructType<D> type, ModelSupplier model) {
@ -48,8 +49,7 @@ public class InstancedModel<D extends InstancedPart> {
public void delete() {
if (instancer.vbo == null) return;
instancer.vbo.delete();
instancer.vbo = null;
instancer.delete();
for (var layer : layers) {
layer.delete();

View file

@ -20,6 +20,7 @@ import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance;
@ -65,19 +66,22 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
protected final ProgramCompiler<P> context;
protected final Map<StructType<? extends InstancedPart>, GPUInstancerFactory<?>> factories = new HashMap<>();
protected final Map<StructType<?>, GPUInstancerFactory<?>> factories = new HashMap<>();
protected final List<InstancedModel<?>> uninitializedModels = new ArrayList<>();
protected final RenderLists renderLists = new RenderLists();
private final WeakHashSet<OriginShiftListener> listeners;
/**
* The set of instance managers that are attached to this engine.
*/
private final WeakHashSet<InstanceManager<?>> instanceManagers;
private int vertexCount;
private int instanceCount;
public InstancingEngine(ProgramCompiler<P> context) {
this.context = context;
this.listeners = new WeakHashSet<>();
this.instanceManagers = new WeakHashSet<>();
}
@SuppressWarnings("unchecked")
@ -198,21 +202,29 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
return originCoordinate;
}
public void addListener(OriginShiftListener listener) {
listeners.add(listener);
public void attachManager(InstanceManager<?> listener) {
instanceManagers.add(listener);
}
/**
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions.
*
* This prevents floating point precision issues at high coordinates.
*/
@Override
public void beginFrame(Camera info) {
checkOriginDistance(info);
public boolean maintainOriginCoordinate(Camera camera) {
Vec3 cameraPos = camera.getPosition();
for (var factory : uninitializedModels) {
factory.init(renderLists);
double distanceSqr = Vec3.atLowerCornerOf(originCoordinate)
.subtract(cameraPos)
.lengthSqr();
if (distanceSqr > MAX_ORIGIN_DISTANCE * MAX_ORIGIN_DISTANCE) {
shiftListeners(Mth.floor(cameraPos.x), Mth.floor(cameraPos.y), Mth.floor(cameraPos.z));
return true;
}
return false;
}
@Override
public void beginFrame(TaskEngine taskEngine, Camera info) {
for (var model : uninitializedModels) {
model.init(renderLists);
}
uninitializedModels.clear();
@ -222,26 +234,12 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
.flush();
}
private void checkOriginDistance(Camera info) {
int cX = Mth.floor(info.getPosition().x);
int cY = Mth.floor(info.getPosition().y);
int cZ = Mth.floor(info.getPosition().z);
int dX = cX - originCoordinate.getX();
int dY = cY - originCoordinate.getY();
int dZ = cZ - originCoordinate.getZ();
if (Math.abs(dX) > MAX_ORIGIN_DISTANCE || Math.abs(dY) > MAX_ORIGIN_DISTANCE || Math.abs(dZ) > MAX_ORIGIN_DISTANCE) {
shiftListeners(cX, cY, cZ);
}
}
private void shiftListeners(int cX, int cY, int cZ) {
originCoordinate = new BlockPos(cX, cY, cZ);
factories.values().forEach(GPUInstancerFactory::clear);
listeners.forEach(OriginShiftListener::onOriginShift);
instanceManagers.forEach(InstanceManager::onOriginShift);
}
@Override
@ -336,7 +334,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
if (part.getOwner() instanceof GPUInstancer instancer) {
// queue the instances for copying to the crumbling instance buffer
map.computeIfAbsent(instancer.parent.model, k -> new ArrayList<>()).add(part);
map.computeIfAbsent(instancer.parent.getModel(), k -> new ArrayList<>()).add(part);
}
}
}
@ -378,8 +376,4 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
return dataByStage;
}
@FunctionalInterface
public interface OriginShiftListener {
void onOriginShift();
}
}

View file

@ -12,7 +12,7 @@ import net.minecraft.client.renderer.RenderType;
public class RenderLists {
private Map<RenderType, ListMultimap<ShaderState, DrawCall>> renderLists = new HashMap<>();
private final Map<RenderType, ListMultimap<ShaderState, DrawCall>> renderLists = new HashMap<>();
public final Set<RenderType> layersToProcess = new HashSet<>();
public ListMultimap<ShaderState, DrawCall> get(RenderType type) {

View file

@ -2,10 +2,11 @@ package com.jozufozu.flywheel.backend.struct;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
public abstract class BufferWriter<S> implements StructWriter<S> {
public abstract class BufferWriter<S extends InstancedPart> implements StructWriter<S> {
protected final ByteBuffer backingBuffer;
protected final int stride;

View file

@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
/**
@ -14,7 +15,7 @@ import com.jozufozu.flywheel.api.struct.StructType;
* better optimized code than other implementations. The implementation does not check for invalid memory accesses,
* meaning that errors can corrupt process memory.
*/
public abstract class UnsafeBufferWriter<S> extends BufferWriter<S> {
public abstract class UnsafeBufferWriter<S extends InstancedPart> extends BufferWriter<S> {
/**
* The write pointer into the buffer storage. This is advanced by the stride every time
* {@link UnsafeBufferWriter#advance()} is called.

View file

@ -44,7 +44,8 @@ public class CrumblingRenderer {
}
public static void renderCrumbling(LevelRenderer levelRenderer, ClientLevel level, PoseStack poseStack, Camera camera, Matrix4f projectionMatrix) {
if (!Backend.canUseInstancing(level)) return;
// TODO: one pass base/crumbling
if (true) return;
Int2ObjectMap<List<BlockEntity>> activeStages = getActiveStageBlockEntities(levelRenderer, level);
if (activeStages.isEmpty()) return;
@ -78,7 +79,7 @@ public class CrumblingRenderer {
stage.getValue().forEach(instanceManager::add);
instanceManager.beginFrame(SerialTaskEngine.INSTANCE, camera);
engine.beginFrame(camera);
engine.beginFrame(SerialTaskEngine.INSTANCE, camera);
engine.renderAllRemaining(SerialTaskEngine.INSTANCE, ctx);
@ -143,7 +144,7 @@ public class CrumblingRenderer {
private State() {
instancerManager = new CrumblingEngine();
instanceManager = new CrumblingInstanceManager(instancerManager);
instancerManager.addListener(instanceManager);
instancerManager.attachManager(instanceManager);
}
private void kill() {

View file

@ -0,0 +1,183 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraftforge.common.MinecraftForge;
@Mixin(value = LevelRenderer.class, priority = 1001) // Higher priority to go after sodium
public class LevelRendererDispatchMixin {
@Inject(at = @At("TAIL"), method = "renderChunkLayer")
private void renderChunkLayer(RenderType pRenderType, PoseStack pPoseStack, double pCamX, double pCamY, double pCamZ, Matrix4f pProjectionMatrix, CallbackInfo ci) {
try (var restoreState = GlStateTracker.getRestoreState()) {
// TODO: Is this necessary?
InstancedRenderDispatcher.renderSpecificType(RenderContext.CURRENT, pRenderType);
MinecraftForge.EVENT_BUS.post(new RenderLayerEvent(RenderContext.CURRENT, pRenderType));
}
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch()V"))
private void endBatch(CallbackInfo ci) {
if (RenderContext.CURRENT != null && Backend.isGameActive() && Backend.isOn()) {
try (var restoreState = GlStateTracker.getRestoreState()) {
InstancedRenderDispatcher.renderAllRemaining(RenderContext.CURRENT);
}
}
}
@Unique
private void flywheel$dispatch(RenderType pRenderType) {
if (RenderContext.CURRENT != null && Backend.isGameActive() && Backend.isOn()) {
try (var restoreState = GlStateTracker.getRestoreState()) {
InstancedRenderDispatcher.renderSpecificType(RenderContext.CURRENT, pRenderType);
}
}
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 0, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entitySolid(CallbackInfo ci) {
flywheel$dispatch(RenderType.entitySolid(TextureAtlas.LOCATION_BLOCKS));
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 1, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entityCutout(CallbackInfo ci) {
flywheel$dispatch(RenderType.entityCutout(TextureAtlas.LOCATION_BLOCKS));
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 2, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entityCutoutNoCull(CallbackInfo ci) {
flywheel$dispatch(RenderType.entityCutoutNoCull(TextureAtlas.LOCATION_BLOCKS));
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 3, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entitySmoothCutout(CallbackInfo ci) {
flywheel$dispatch(RenderType.entitySmoothCutout(TextureAtlas.LOCATION_BLOCKS));
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 4, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$solid(CallbackInfo ci) {
flywheel$dispatch(RenderType.solid());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 5, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$endPortal(CallbackInfo ci) {
flywheel$dispatch(RenderType.endPortal());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 6, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$endGateway(CallbackInfo ci) {
flywheel$dispatch(RenderType.endGateway());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 7, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$solidBlockSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.solidBlockSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 8, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$cutoutBlockSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.cutoutBlockSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 9, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$bedSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.bedSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 10, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$shulkerBoxSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.shulkerBoxSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 11, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$signSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.signSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 12, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$chestSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.chestSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 13, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$translucentCullBlockSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.translucentCullBlockSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 14, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$bannerSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.bannerSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 15, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$shieldSheet(CallbackInfo ci) {
flywheel$dispatch(Sheets.shieldSheet());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 16, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$armorGlint(CallbackInfo ci) {
flywheel$dispatch(RenderType.armorGlint());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 17, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$armorEntityGlint(CallbackInfo ci) {
flywheel$dispatch(RenderType.armorEntityGlint());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 18, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$glint(CallbackInfo ci) {
flywheel$dispatch(RenderType.glint());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 19, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$glintDirect(CallbackInfo ci) {
flywheel$dispatch(RenderType.glintDirect());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 20, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$glintTranslucent(CallbackInfo ci) {
flywheel$dispatch(RenderType.glintTranslucent());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 21, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entityGlint(CallbackInfo ci) {
flywheel$dispatch(RenderType.entityGlint());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 22, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$entityGlintDirect(CallbackInfo ci) {
flywheel$dispatch(RenderType.entityGlintDirect());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 23, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$waterMask(CallbackInfo ci) {
flywheel$dispatch(RenderType.waterMask());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 24, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$lines1(CallbackInfo ci) {
flywheel$dispatch(RenderType.lines());
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", shift = At.Shift.AFTER, ordinal = 25, target = "Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;endBatch(Lnet/minecraft/client/renderer/RenderType;)V"))
private void renderLayer$lines2(CallbackInfo ci) {
flywheel$dispatch(RenderType.lines());
}
}

View file

@ -0,0 +1,32 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
@Mixin(LevelRenderer.class)
public class LevelRendererInstanceUpdateMixin {
@Shadow
private ClientLevel level;
/**
* This gets called when a block is marked for rerender by vanilla.
*/
@Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V")
private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
if (Backend.isOn()) {
InstancedRenderDispatcher.getBlockEntities(level)
.update(level.getBlockEntity(pos));
}
}
}

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
@ -26,8 +27,6 @@ import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
@ -51,16 +50,6 @@ public class LevelRendererMixin {
}
}
@Inject(at = @At("TAIL"), method = "renderChunkLayer")
private void renderChunkLayer(RenderType pRenderType, PoseStack pPoseStack, double pCamX, double pCamY, double pCamZ, Matrix4f pProjectionMatrix, CallbackInfo ci) {
try (var restoreState = GlStateTracker.getRestoreState()) {
// TODO: Is this necessary?
InstancedRenderDispatcher.renderSpecificType(RenderContext.CURRENT, pRenderType);
MinecraftForge.EVENT_BUS.post(new RenderLayerEvent(RenderContext.CURRENT, pRenderType));
}
}
@Inject(at = @At("TAIL"), method = "renderLevel")
private void endRender(PoseStack pPoseStack, float pPartialTick, long pFinishNanoTime, boolean pRenderBlockOutline, Camera pCamera, GameRenderer pGameRenderer, LightTexture pLightTexture, Matrix4f pProjectionMatrix, CallbackInfo ci) {
RenderContext.CURRENT = null;
@ -78,17 +67,4 @@ public class LevelRendererMixin {
private void renderBlockBreaking(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci) {
CrumblingRenderer.renderCrumbling((LevelRenderer) (Object) this, level, poseStack, camera, projectionMatrix);
}
// Instancing
/**
* This gets called when a block is marked for rerender by vanilla.
*/
@Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V")
private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
if (Backend.isOn()) {
InstancedRenderDispatcher.getBlockEntities(level)
.update(level.getBlockEntity(pos));
}
}
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.mixin;
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 com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.RenderContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
@Mixin(MultiBufferSource.BufferSource.class)
public class MultiBufferSourceMixin {
@Inject(method = "endBatch(Lnet/minecraft/client/renderer/RenderType;)V", at = @At("TAIL"))
private void renderLayer(RenderType renderType, CallbackInfo ci) {
if (RenderContext.CURRENT != null && Backend.isGameActive() && Backend.isOn()) {
try (var restoreState = GlStateTracker.getRestoreState()) {
InstancedRenderDispatcher.renderSpecificType(RenderContext.CURRENT, renderType);
}
}
}
@Inject(method = "endBatch()V", at = @At("TAIL"))
private void endBatch(CallbackInfo ci) {
if (RenderContext.CURRENT != null && Backend.isGameActive() && Backend.isOn()) {
try (var restoreState = GlStateTracker.getRestoreState()) {
InstancedRenderDispatcher.renderAllRemaining(RenderContext.CURRENT);
}
}
}
}

View file

@ -5,6 +5,7 @@ import java.util.Map;
import java.util.stream.Stream;
import com.jozufozu.flywheel.mixin.BlockEntityRenderDispatcherAccessor;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
@ -29,6 +30,13 @@ public class FlwUtil {
return new String(arr);
}
public static PoseStack copyPoseStack(PoseStack stack) {
PoseStack copy = new PoseStack();
copy.last().pose().load(stack.last().pose());
copy.last().normal().load(stack.last().normal());
return copy;
}
public static int numDigits(int number) {
// cursed but allegedly the fastest algorithm, taken from https://www.baeldung.com/java-number-of-digits-in-int
if (number < 100000) {

View file

@ -19,8 +19,9 @@
"InstanceAddMixin",
"InstanceRemoveMixin",
"LevelRendererAccessor",
"LevelRendererDispatchMixin",
"LevelRendererInstanceUpdateMixin",
"LevelRendererMixin",
"MultiBufferSourceMixin",
"PausedPartialTickAccessor",
"RenderTypeMixin",
"light.LightUpdateMixin",