BatchingDrawManager

- Merge BatchLists, BatchedModel, and some parts of BatchingEngine into
BatchingDrawManager for consistency with instancing code
- Have InstancingEngine accept the max origin distance as a constructor
parameter
- Rename TransformSet to TransformCall
This commit is contained in:
PepperCode1 2022-08-19 17:24:13 -07:00
parent 2fe69350ef
commit ba30aca869
12 changed files with 204 additions and 177 deletions

View file

@ -37,7 +37,7 @@ public class InstanceWorld implements AutoCloseable {
public static InstanceWorld create(LevelAccessor level) {
var engine = switch (Backend.getBackendType()) {
case INSTANCING -> new InstancingEngine(Components.WORLD);
case INSTANCING -> new InstancingEngine(Components.WORLD, 100 * 100);
case BATCHING -> new BatchingEngine();
case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off.");
};

View file

@ -7,6 +7,8 @@ import net.minecraft.client.Camera;
public interface RenderDispatcher {
void beginFrame(TaskEngine taskEngine, RenderContext context);
void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage);
/**
@ -16,7 +18,5 @@ public interface RenderDispatcher {
*/
boolean maintainOriginCoordinate(Camera camera);
void beginFrame(TaskEngine taskEngine, RenderContext context);
void delete();
}

View file

@ -1,14 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import net.minecraft.client.renderer.RenderType;
public class BatchLists {
public final Multimap<RenderType, TransformSet<?>> renderLists = ArrayListMultimap.create();
public void add(TransformSet<?> set) {
renderLists.put(set.getMaterial().getBatchingRenderType(), set);
}
}

View file

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

View file

@ -0,0 +1,98 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.core.model.Model;
import net.minecraft.client.renderer.RenderType;
public class BatchingDrawManager {
private final List<UninitializedModel> uninitializedModels = new ArrayList<>();
private final List<CPUInstancer<?>> allInstancers = new ArrayList<>();
public final Map<RenderStage, TransformSet> renderLists = new EnumMap<>(RenderStage.class);
public final BatchDrawingTracker batchTracker = new BatchDrawingTracker();
public TransformSet get(RenderStage stage) {
return renderLists.getOrDefault(stage, TransformSet.EMPTY);
}
public void create(CPUInstancer<?> instancer, Model model) {
uninitializedModels.add(new UninitializedModel(instancer, model));
}
public void flush() {
for (var model : uninitializedModels) {
add(model.instancer(), model.model());
}
uninitializedModels.clear();
}
public void delete() {
allInstancers.forEach(CPUInstancer::delete);
allInstancers.clear();
}
public void clearInstancers() {
allInstancers.forEach(CPUInstancer::clear);
}
private void add(CPUInstancer<?> instancer, Model model) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
TransformCall<?> transformCall = new TransformCall<>(instancer, entry.getKey(), entry.getValue());
var material = transformCall.getMaterial();
var renderType = material.getBatchingRenderType();
// renderLists.computeIfAbsent(material.getRenderStage(), TransformSet::new)
renderLists.computeIfAbsent(RenderStage.AFTER_FINAL_END_BATCH, TransformSet::new)
.put(renderType, transformCall);
}
allInstancers.add(instancer);
}
public static class TransformSet implements Iterable<Map.Entry<RenderType, Collection<TransformCall<?>>>> {
public static final TransformSet EMPTY = new TransformSet(ImmutableListMultimap.of());
final ListMultimap<RenderType, TransformCall<?>> transformCalls;
public TransformSet(RenderStage renderStage) {
transformCalls = ArrayListMultimap.create();
}
public TransformSet(ListMultimap<RenderType, TransformCall<?>> transformCalls) {
this.transformCalls = transformCalls;
}
public void put(RenderType shaderState, TransformCall<?> transformCall) {
transformCalls.put(shaderState, transformCall);
}
public boolean isEmpty() {
return transformCalls.isEmpty();
}
@NotNull
@Override
public Iterator<Map.Entry<RenderType, Collection<TransformCall<?>>>> iterator() {
return transformCalls.asMap()
.entrySet()
.iterator();
}
}
private record UninitializedModel(CPUInstancer<?> instancer, Model model) {
}
}

View file

@ -1,10 +1,11 @@
package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
@ -23,36 +24,44 @@ import net.minecraft.world.phys.Vec3;
public class BatchingEngine implements Engine {
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<>();
protected final BatchingDrawManager drawManager = new BatchingDrawManager();
protected final Map<StructType<?>, CPUInstancerFactory<?>> factories = new HashMap<>();
@SuppressWarnings("unchecked")
@NotNull
@Override
public <D extends InstancedPart> CPUInstancerFactory<D> factory(StructType<D> type) {
return (CPUInstancerFactory<D>) factories.computeIfAbsent(type, this::createFactory);
}
public <D extends InstancedPart> CPUInstancerFactory<D> createFactory(StructType<D> type) {
return new CPUInstancerFactory<>(type, uninitializedModels::add);
@NotNull
private <D extends InstancedPart> CPUInstancerFactory<D> createFactory(StructType<D> type) {
return new CPUInstancerFactory<>(type, drawManager::create);
}
@Override
public Vec3i getOriginCoordinate() {
return BlockPos.ZERO;
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
drawManager.flush();
Vec3 cameraPos = context.camera().getPosition();
var stack = FlwUtil.copyPoseStack(context.stack());
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
submitTasks(taskEngine, stack, context.level());
}
public void submitTasks(TaskEngine taskEngine, PoseStack stack, ClientLevel level) {
batchLists.renderLists.asMap().forEach((renderType, renderList) -> {
BatchingDrawManager.TransformSet drawSet = drawManager.get(RenderStage.AFTER_FINAL_END_BATCH);
for (var entry : drawSet) {
var renderType = entry.getKey();
var renderList = entry.getValue();
int vertices = 0;
for (var transformSet : renderList) {
vertices += transformSet.getTotalVertexCount();
}
DrawBuffer buffer = batchTracker.getBuffer(renderType);
DrawBuffer buffer = drawManager.batchTracker.getBuffer(renderType);
buffer.prepare(vertices);
int startVertex = 0;
@ -60,7 +69,7 @@ public class BatchingEngine implements Engine {
transformSet.submitTasks(taskEngine, buffer, startVertex, stack, level);
startVertex += transformSet.getTotalVertexCount();
}
});
};
}
@Override
@ -72,11 +81,7 @@ public class BatchingEngine implements Engine {
return;
}
batchTracker.endBatch();
}
@Override
public void delete() {
drawManager.batchTracker.endBatch();
}
@Override
@ -86,23 +91,19 @@ public class BatchingEngine implements Engine {
}
@Override
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
for (var model : uninitializedModels) {
model.init(batchLists);
}
uninitializedModels.clear();
Vec3 cameraPos = context.camera().getPosition();
var stack = FlwUtil.copyPoseStack(context.stack());
stack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
submitTasks(taskEngine, stack, context.level());
public void attachManagers(InstanceManager<?>... listener) {
// noop
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
// noop
public Vec3i getOriginCoordinate() {
return BlockPos.ZERO;
}
@Override
public void delete() {
factories.clear();
drawManager.delete();
}
@Override

View file

@ -2,7 +2,7 @@ package com.jozufozu.flywheel.backend.instancing.batching;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer;
@ -12,34 +12,31 @@ import com.jozufozu.flywheel.core.model.Model;
public class CPUInstancerFactory<D extends InstancedPart> implements InstancerFactory<D> {
protected final Map<Model, BatchedModel<D>> models;
private final StructType<D> type;
private final Consumer<BatchedModel<D>> creationListener;
protected final StructType<D> type;
private final BiConsumer<CPUInstancer<?>, Model> creationListener;
protected final Map<Model, CPUInstancer<D>> models = new HashMap<>();
public CPUInstancerFactory(StructType<D> type, Consumer<BatchedModel<D>> creationListener) {
public CPUInstancerFactory(StructType<D> type, BiConsumer<CPUInstancer<?>, Model> creationListener) {
this.type = type;
this.creationListener = creationListener;
this.models = new HashMap<>();
}
@Override
public Instancer<D> model(Model modelKey) {
return models.computeIfAbsent(modelKey, this::createModel).getInstancer();
return models.computeIfAbsent(modelKey, this::createInstancer);
}
/**
* Clear all instance data without freeing resources.
*/
public void clear() {
models.values()
.forEach(BatchedModel::clear);
}
// /**
// * Clear all instance data without freeing resources.
// */
// public void clear() {
// models.values()
// .forEach(BatchedModel::clear);
// }
private BatchedModel<D> createModel(Model k) {
var out = new BatchedModel<>(type, k);
creationListener.accept(out);
return out;
private CPUInstancer<D> createInstancer(Model model) {
var instancer = new CPUInstancer<>(type);
creationListener.accept(instancer, model);
return instancer;
}
}

View file

@ -17,13 +17,13 @@ import com.mojang.math.Vector4f;
import net.minecraft.client.multiplayer.ClientLevel;
public class TransformSet<D extends InstancedPart> {
public class TransformCall<D extends InstancedPart> {
private final CPUInstancer<D> instancer;
private final Material material;
private final Mesh mesh;
public TransformSet(CPUInstancer<D> instancer, Material material, Mesh mesh) {
public TransformCall(CPUInstancer<D> instancer, Material material, Mesh mesh) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
@ -51,19 +51,19 @@ public class TransformSet<D extends InstancedPart> {
ReusableVertexList sub = buffer.slice(startVertex, vertexCount);
startVertex += vertexCount;
pool.submit(() -> drawRange(sub, start, end, stack, level));
pool.submit(() -> transformRange(sub, start, end, stack, level));
}
}
private void drawRange(ReusableVertexList vertexList, int from, int to, PoseStack stack, ClientLevel level) {
drawList(vertexList, instancer.getRange(from, to), stack, level);
private void transformRange(ReusableVertexList vertexList, int from, int to, PoseStack stack, ClientLevel level) {
transformList(vertexList, instancer.getRange(from, to), stack, level);
}
void drawAll(ReusableVertexList vertexList, PoseStack stack, ClientLevel level) {
drawList(vertexList, instancer.getAll(), stack, level);
void transformAll(ReusableVertexList vertexList, PoseStack stack, ClientLevel level) {
transformList(vertexList, instancer.getAll(), stack, level);
}
private void drawList(ReusableVertexList vertexList, List<D> list, PoseStack stack, ClientLevel level) {
private void transformList(ReusableVertexList vertexList, List<D> parts, PoseStack stack, ClientLevel level) {
long anchorPtr = vertexList.ptr();
int totalVertexCount = vertexList.getVertexCount();
@ -72,7 +72,7 @@ public class TransformSet<D extends InstancedPart> {
StructType.VertexTransformer<D> structVertexTransformer = instancer.type.getVertexTransformer();
for (D d : list) {
for (D d : parts) {
mesh.write(vertexList);
structVertexTransformer.transform(vertexList, d, level);

View file

@ -16,9 +16,9 @@ import com.jozufozu.flywheel.core.model.Model;
*/
public class GPUInstancerFactory<D extends InstancedPart> implements InstancerFactory<D> {
protected final Map<Model, GPUInstancer<D>> models = new HashMap<>();
protected final StructType<D> type;
private final BiConsumer<GPUInstancer<?>, Model> creationListener;
protected final Map<Model, GPUInstancer<D>> models = new HashMap<>();
public GPUInstancerFactory(StructType<D> type, BiConsumer<GPUInstancer<?>, Model> creationListener) {
this.type = type;
@ -32,7 +32,7 @@ public class GPUInstancerFactory<D extends InstancedPart> implements InstancerFa
private GPUInstancer<D> createInstancer(Model model) {
var instancer = new GPUInstancer<>(type);
this.creationListener.accept(instancer, model);
creationListener.accept(instancer, model);
return instancer;
}
}

View file

@ -29,8 +29,8 @@ public class InstancingDrawManager {
return renderLists.getOrDefault(stage, DrawSet.EMPTY);
}
public void create(GPUInstancer<?> gpuInstancer, Model model) {
uninitializedModels.add(new UninitializedModel(gpuInstancer, model));
public void create(GPUInstancer<?> instancer, Model model) {
uninitializedModels.add(new UninitializedModel(instancer, model));
}
public void flush() {
@ -67,12 +67,12 @@ public class InstancingDrawManager {
private void add(GPUInstancer<?> instancer, Model model) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
DrawCall layer = new DrawCall(instancer, entry.getKey(), alloc(entry.getValue()));
var material = layer.getMaterial();
var shaderState = new ShaderState(material, layer.getVertexType(), layer.instancer.type);
DrawCall drawCall = new DrawCall(instancer, entry.getKey(), alloc(entry.getValue()));
var material = drawCall.getMaterial();
var shaderState = new ShaderState(material, drawCall.getVertexType(), drawCall.instancer.type);
renderLists.computeIfAbsent(material.getRenderStage(), DrawSet::new)
.put(shaderState, layer);
.put(shaderState, drawCall);
}
allInstancers.add(instancer);
}
@ -102,8 +102,8 @@ public class InstancingDrawManager {
drawCalls.clear();
}
public void put(ShaderState shaderState, DrawCall layer) {
drawCalls.put(shaderState, layer);
public void put(ShaderState shaderState, DrawCall drawCall) {
drawCalls.put(shaderState, drawCall);
}
public boolean isEmpty() {
@ -120,6 +120,5 @@ public class InstancingDrawManager {
}
private record UninitializedModel(GPUInstancer<?> instancer, Model model) {
}
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -33,25 +34,22 @@ import net.minecraft.world.phys.Vec3;
public class InstancingEngine implements Engine {
public static int MAX_ORIGIN_DISTANCE = 100;
protected BlockPos originCoordinate = BlockPos.ZERO;
protected final ContextShader context;
protected final Map<StructType<?>, GPUInstancerFactory<?>> factories = new HashMap<>();
protected final InstancingDrawManager drawManager = new InstancingDrawManager();
protected final Map<StructType<?>, GPUInstancerFactory<?>> factories = new HashMap<>();
/**
* The set of instance managers that are attached to this engine.
*/
private final WeakHashSet<InstanceManager<?>> instanceManagers;
private final WeakHashSet<InstanceManager<?>> instanceManagers = new WeakHashSet<>();
public InstancingEngine(ContextShader context) {
protected final ContextShader context;
protected final int sqrMaxOriginDistance;
protected BlockPos originCoordinate = BlockPos.ZERO;
public InstancingEngine(ContextShader context, int sqrMaxOriginDistance) {
this.context = context;
this.instanceManagers = new WeakHashSet<>();
this.sqrMaxOriginDistance = sqrMaxOriginDistance;
}
@SuppressWarnings("unchecked")
@ -66,6 +64,11 @@ public class InstancingEngine implements Engine {
return new GPUInstancerFactory<>(type, drawManager::create);
}
@Override
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
drawManager.flush();
}
@Override
public void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage) {
var drawSet = drawManager.get(stage);
@ -79,7 +82,7 @@ public class InstancingEngine implements Engine {
render(drawSet);
}
private void setup() {
protected void setup() {
GlTextureUnit.T2.makeActive();
Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer();
@ -91,10 +94,6 @@ public class InstancingEngine implements Engine {
}
protected void render(InstancingDrawManager.DrawSet drawSet) {
if (drawSet.isEmpty()) {
return;
}
for (var entry : drawSet) {
var shader = entry.getKey();
var drawCalls = entry.getValue();
@ -118,7 +117,6 @@ public class InstancingEngine implements Engine {
}
protected void setup(ShaderState desc) {
VertexType vertexType = desc.vertex();
FileResolution instanceShader = desc.instance()
.getInstanceShader();
@ -131,22 +129,6 @@ public class InstancingEngine implements Engine {
UniformBuffer.getInstance().sync();
}
@Override
public void delete() {
factories.clear();
drawManager.delete();
}
@Override
public Vec3i getOriginCoordinate() {
return originCoordinate;
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
instanceManagers.addAll(List.of(listener));
}
@Override
public boolean maintainOriginCoordinate(Camera camera) {
Vec3 cameraPos = camera.getPosition();
@ -155,18 +137,13 @@ public class InstancingEngine implements Engine {
.subtract(cameraPos)
.lengthSqr();
if (distanceSqr > MAX_ORIGIN_DISTANCE * MAX_ORIGIN_DISTANCE) {
if (distanceSqr > sqrMaxOriginDistance) {
shiftListeners(Mth.floor(cameraPos.x), Mth.floor(cameraPos.y), Mth.floor(cameraPos.z));
return true;
}
return false;
}
@Override
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
drawManager.flush();
}
private void shiftListeners(int cX, int cY, int cZ) {
originCoordinate = new BlockPos(cX, cY, cZ);
@ -175,6 +152,22 @@ public class InstancingEngine implements Engine {
instanceManagers.forEach(InstanceManager::onOriginShift);
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
Collections.addAll(instanceManagers, listener);
}
@Override
public Vec3i getOriginCoordinate() {
return originCoordinate;
}
@Override
public void delete() {
factories.clear();
drawManager.delete();
}
@Override
public void addDebugInfo(List<String> info) {
info.add("GL33 Instanced Arrays");

View file

@ -21,7 +21,6 @@ import com.jozufozu.flywheel.core.model.Mesh;
public class MeshPool {
private final VertexType vertexType;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> allBuffered = new ArrayList<>();