Store bought engines

- Add InstancerStorage so engines can share common code.
- Instanced and Indirect DrawManagers extend InstancerStorage, while
  BatchingEngine keeps an anonymous class for it.
- AbstractEngine now has a InstancerStorage getter and does some
  delegation so the implementations don't have to.
- InstancedInstancer directly stores the list of DrawCalls it belongs
  to.
- InstancingEngine no longer accepts a context parameter.
- Make the /flywheel backend command default to the flywheel nampspace.
This commit is contained in:
Jozufozu 2023-11-26 12:07:05 -08:00
parent 8fa095cd16
commit 2039a964e2
11 changed files with 274 additions and 247 deletions

View file

@ -10,7 +10,6 @@ import com.jozufozu.flywheel.backend.engine.indirect.IndirectEngine;
import com.jozufozu.flywheel.backend.engine.instancing.InstancingEngine;
import com.jozufozu.flywheel.gl.GlCompat;
import com.jozufozu.flywheel.lib.backend.SimpleBackend;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
import net.minecraft.ChatFormatting;
@ -33,7 +32,7 @@ public class Backends {
public static final Backend INSTANCING = SimpleBackend.builder()
.engineMessage(Component.literal("Using Instancing Engine")
.withStyle(ChatFormatting.GREEN))
.engineFactory(level -> new InstancingEngine(256, Contexts.WORLD))
.engineFactory(level -> new InstancingEngine(256))
.fallback(() -> Backends.BATCHING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.supportsInstancing() && InstancingPrograms.allLoaded())
.register(Flywheel.rl("instancing"));

View file

@ -1,6 +1,11 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
@ -15,6 +20,11 @@ public abstract class AbstractEngine implements Engine {
sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance;
}
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return getStorage().getInstancer(type, model, stage);
}
@Override
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
@ -28,7 +38,7 @@ public abstract class AbstractEngine implements Engine {
}
renderOrigin = BlockPos.containing(cameraPos);
onRenderOriginChanged();
getStorage().onRenderOriginChanged();
return true;
}
@ -37,6 +47,5 @@ public abstract class AbstractEngine implements Engine {
return renderOrigin;
}
protected void onRenderOriginChanged() {
}
protected abstract InstancerStorage<? extends AbstractInstancer<?>> getStorage();
}

View file

@ -108,4 +108,8 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
public String toString() {
return "AbstractInstancer[" + getInstanceCount() + ']';
}
public void delete() {
}
}

View file

@ -0,0 +1,96 @@
package com.jozufozu.flywheel.backend.engine;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
/**
* A map of instancer keys to instancers.
* <br>
* This map is populated as instancers are requested and contains both initialized and uninitialized instancers.
* Write access to this map must be synchronized on {@link #creationLock}.
* <br>
* See {@link #getInstancer} for insertion details.
*/
private final Map<InstancerKey<?>, N> instancers = new HashMap<>();
/**
* A list of instancers that have not yet been initialized.
* <br>
* All new instancers land here before having resources allocated in {@link #flush}.
* Write access to this list must be synchronized on {@link #creationLock}.
*/
private final List<UninitializedInstancer<N, ?>> uninitializedInstancers = new ArrayList<>();
/**
* Mutex for {@link #instancers} and {@link #uninitializedInstancers}.
*/
private final Object creationLock = new Object();
/**
* A list of initialized instancers.
* <br>
* These are instancers that may need to be cleared or deleted.
*/
private final List<N> initializedInstancers = new ArrayList<>();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
N instancer = instancers.get(key);
// Happy path: instancer is already initialized.
if (instancer != null) {
return (Instancer<I>) instancer;
}
// Unhappy path: instancer is not initialized, need to sync to make sure we don't create duplicates.
synchronized (creationLock) {
// Someone else might have initialized it while we were waiting for the lock.
instancer = instancers.get(key);
if (instancer != null) {
return (Instancer<I>) instancer;
}
// Create a new instancer and add it to the uninitialized list.
instancer = create(type);
instancers.put(key, instancer);
uninitializedInstancers.add(new UninitializedInstancer<>(key, instancer, model, stage));
return (Instancer<I>) instancer;
}
}
public void invalidate() {
instancers.clear();
uninitializedInstancers.clear();
initializedInstancers.forEach(AbstractInstancer::delete);
initializedInstancers.clear();
}
public void flush() {
for (var instancer : uninitializedInstancers) {
add(instancer.key(), instancer.instancer(), instancer.model(), instancer.stage());
initializedInstancers.add(instancer.instancer());
}
uninitializedInstancers.clear();
}
public void onRenderOriginChanged() {
initializedInstancers.forEach(AbstractInstancer::clear);
}
protected abstract <I extends Instance> N create(InstanceType<I> type);
protected abstract <I extends Instance> void add(InstancerKey<I> key, N instancer, Model model, RenderStage stage);
private record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer, Model model, RenderStage stage) {
}
}

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.backend.engine.batching;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
@ -10,13 +9,14 @@ import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
@ -27,9 +27,27 @@ import net.minecraft.client.renderer.RenderType;
public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan<RenderContext> {
private final BatchedDrawTracker drawTracker = new BatchedDrawTracker();
private final Map<InstancerKey<?>, BatchedInstancer<?>> instancers = new HashMap<>();
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
private final List<BatchedInstancer<?>> initializedInstancers = new ArrayList<>();
// TODO: reintroduce BatchedDrawManager
private final InstancerStorage<BatchedInstancer<?>> storage = new InstancerStorage<>() {
@Override
protected <I extends Instance> BatchedInstancer<?> create(InstanceType<I> type) {
return new BatchedInstancer<>(type);
}
@Override
protected <I extends Instance> void add(InstancerKey<I> key, BatchedInstancer<?> instancer, Model model, RenderStage stage) {
var stagePlan = stagePlans.computeIfAbsent(stage, renderStage -> new BatchedStagePlan(renderStage, drawTracker));
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
var material = entry.getKey();
RenderType renderType = material.getFallbackRenderType();
var transformCall = new TransformCall<>(instancer, material, alloc(entry.getValue(), renderType.format()));
stagePlan.put(renderType, transformCall);
}
}
};
private final Map<RenderStage, BatchedStagePlan> stagePlans = new EnumMap<>(RenderStage.class);
private final Map<VertexFormat, BatchedMeshPool> meshPools = new HashMap<>();
@ -39,20 +57,6 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan
super(maxOriginDistance);
}
@Override
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
BatchedInstancer<I> instancer = (BatchedInstancer<I>) instancers.get(key);
// FIXME: This needs to be synchronized like InstancingEngine
if (instancer == null) {
instancer = new BatchedInstancer<>(type);
instancers.put(key, instancer);
uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage));
}
return instancer;
}
@Override
public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) {
flush();
@ -100,49 +104,29 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan
}
@Override
protected void onRenderOriginChanged() {
initializedInstancers.forEach(BatchedInstancer::clear);
protected InstancerStorage<? extends AbstractInstancer<?>> getStorage() {
return storage;
}
@Override
public void delete() {
instancers.clear();
storage.invalidate();
meshPools.values()
.forEach(BatchedMeshPool::delete);
meshPools.clear();
initializedInstancers.clear();
}
private void flush() {
for (var instancer : uninitializedInstancers) {
add(instancer.instancer(), instancer.model(), instancer.stage());
}
uninitializedInstancers.clear();
storage.flush();
for (var pool : meshPools.values()) {
pool.flush();
}
}
private void add(BatchedInstancer<?> instancer, Model model, RenderStage stage) {
var stagePlan = stagePlans.computeIfAbsent(stage, renderStage -> new BatchedStagePlan(renderStage, drawTracker));
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
var material = entry.getKey();
RenderType renderType = material.getFallbackRenderType();
var transformCall = new TransformCall<>(instancer, material, alloc(entry.getValue(), renderType.format()));
stagePlan.put(renderType, transformCall);
}
initializedInstancers.add(instancer);
}
private BatchedMeshPool.BufferedMesh alloc(Mesh mesh, VertexFormat format) {
return meshPools.computeIfAbsent(format, BatchedMeshPool::new)
.alloc(mesh);
}
private record UninitializedInstancer(BatchedInstancer<?> instancer, Model model, RenderStage stage) {
}
}

View file

@ -1,43 +1,22 @@
package com.jozufozu.flywheel.backend.engine.indirect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.lib.util.Pair;
public class IndirectDrawManager {
private final Map<InstancerKey<?>, IndirectInstancer<?>> instancers = new HashMap<>();
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
private final List<IndirectInstancer<?>> initializedInstancers = new ArrayList<>();
public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>> {
public final Map<Pair<InstanceType<?>, VertexType>, IndirectCullingGroup<?>> renderLists = new HashMap<>();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
// FIXME: This needs to be synchronized like InstancingEngine
IndirectInstancer<I> instancer = (IndirectInstancer<I>) instancers.get(key);
if (instancer == null) {
instancer = new IndirectInstancer<>(type);
instancers.put(key, instancer);
uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage));
}
return instancer;
}
public void flush() {
for (var instancer : uninitializedInstancers) {
add(instancer.instancer(), instancer.model(), instancer.stage());
}
uninitializedInstancers.clear();
super.flush();
for (IndirectCullingGroup<?> value : renderLists.values()) {
value.beginFrame();
@ -45,33 +24,11 @@ public class IndirectDrawManager {
}
public void invalidate() {
instancers.clear();
super.invalidate();
renderLists.values()
.forEach(IndirectCullingGroup::delete);
renderLists.clear();
initializedInstancers.clear();
}
public void clearInstancers() {
initializedInstancers.forEach(IndirectInstancer::clear);
}
@SuppressWarnings("unchecked")
private <I extends Instance> void add(IndirectInstancer<I> instancer, Model model, RenderStage stage) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
var material = entry.getKey();
var mesh = entry.getValue();
var indirectList = (IndirectCullingGroup<I>) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.vertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
indirectList.add(instancer, stage, material, mesh);
break; // TODO: support multiple meshes per model
}
initializedInstancers.add(instancer);
}
public boolean hasStage(RenderStage stage) {
@ -83,6 +40,23 @@ public class IndirectDrawManager {
return false;
}
private record UninitializedInstancer(IndirectInstancer<?> instancer, Model model, RenderStage stage) {
@Override
protected <I extends Instance> IndirectInstancer<?> create(InstanceType<I> type) {
return new IndirectInstancer<>(type);
}
@Override
protected <I extends Instance> void add(InstancerKey<I> key, IndirectInstancer<?> instancer, Model model, RenderStage stage) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
var material = entry.getKey();
var mesh = entry.getValue();
var indirectList = (IndirectCullingGroup<I>) renderLists.computeIfAbsent(Pair.of(key.type(), mesh.vertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second()));
indirectList.add((IndirectInstancer<I>) instancer, stage, material, mesh);
break; // TODO: support multiple meshes per model
}
}
}

View file

@ -7,17 +7,15 @@ import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.gl.GlStateTracker;
import com.jozufozu.flywheel.gl.GlTextureUnit;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.RaisePlan;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
import com.mojang.blaze3d.systems.RenderSystem;
@ -31,44 +29,37 @@ public class IndirectEngine extends AbstractEngine {
super(maxOriginDistance);
}
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}
@Override
public Plan<RenderContext> createFramePlan() {
return SyncedPlan.<RenderContext>of(this::flushDrawManager)
.then(RaisePlan.raise(flushFlag));
return SyncedPlan.of(this::flushDrawManager);
}
private void flushDrawManager() {
try (var state = GlStateTracker.getRestoreState()) {
drawManager.flush();
}
flushFlag.raise();
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
if (drawManager.hasStage(stage)) {
executor.syncUntil(flushFlag::isRaised);
try (var restoreState = GlStateTracker.getRestoreState()) {
setup();
for (var list : drawManager.renderLists.values()) {
list.submit(stage);
}
}
}
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
// Need to sync here to ensure this frame has everything executed
// in case we didn't have any stages to draw this frame.
executor.syncUntil(flushFlag::isRaised);
flushFlag.lower();
}
}
if (!drawManager.hasStage(stage)) {
return;
}
try (var restoreState = GlStateTracker.getRestoreState()) {
setup();
for (var list : drawManager.renderLists.values()) {
list.submit(stage);
}
}
}
@Override
public void renderCrumblingInstances(TaskExecutor executor, RenderContext context, List<Instance> instances, int progress) {
@ -87,8 +78,8 @@ public class IndirectEngine extends AbstractEngine {
}
@Override
protected void onRenderOriginChanged() {
drawManager.clearInstancers();
protected InstancerStorage<? extends AbstractInstancer<?>> getStorage() {
return drawManager;
}
@Override

View file

@ -1,11 +1,9 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
@ -16,40 +14,14 @@ import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
public class InstancedDrawManager {
/**
* A map of instancer keys to instancers.
* <br>
* This map is populated as instancers are requested and contains both initialized and uninitialized instancers.
* Write access to this map must be synchronized on {@link #creationLock}.
* <br>
* See {@link #getInstancer} for insertion details.
*/
private final Map<InstancerKey<?>, InstancedInstancer<?>> instancers = new HashMap<>();
/**
* A list of instancers that have not yet been initialized.
* <br>
* All new instancers land here before having resources allocated in {@link #flush}.
* Write access to this list must be synchronized on {@link #creationLock}.
*/
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
/**
* Mutex for {@link #instancers} and {@link #uninitializedInstancers}.
*/
private final Object creationLock = new Object();
public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> {
/**
* A map of initialized instancers to their draw calls.
* <br>
* This map is populated in {@link #flush} and contains only initialized instancers.
*/
private final Map<InstancedInstancer<?>, List<DrawCall>> initializedInstancers = new HashMap<>();
/**
* The set of draw calls to make in each {@link RenderStage}.
*/
@ -64,37 +36,8 @@ public class InstancedDrawManager {
return drawSets.getOrDefault(stage, DrawSet.EMPTY);
}
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
InstancerKey<I> key = new InstancerKey<>(type, model, stage);
InstancedInstancer<I> instancer = (InstancedInstancer<I>) instancers.get(key);
// Happy path: instancer is already initialized.
if (instancer != null) {
return instancer;
}
// Unhappy path: instancer is not initialized, need to sync to make sure we don't create duplicates.
synchronized (creationLock) {
// Someone else might have initialized it while we were waiting for the lock.
instancer = (InstancedInstancer<I>) instancers.get(key);
if (instancer != null) {
return instancer;
}
// Create a new instancer and add it to the uninitialized list.
instancer = new InstancedInstancer<>(type);
instancers.put(key, instancer);
uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage));
return instancer;
}
}
public void flush() {
for (var instancer : uninitializedInstancers) {
add(instancer.instancer(), instancer.model(), instancer.stage());
}
uninitializedInstancers.clear();
super.flush();
for (var pool : meshPools.values()) {
pool.flush();
@ -102,7 +45,7 @@ public class InstancedDrawManager {
}
public void invalidate() {
instancers.clear();
super.invalidate();
meshPools.values()
.forEach(InstancedMeshPool::delete);
@ -112,23 +55,24 @@ public class InstancedDrawManager {
.forEach(DrawSet::delete);
drawSets.clear();
initializedInstancers.keySet()
.forEach(InstancedInstancer::delete);
initializedInstancers.clear();
eboCache.invalidate();
}
public void clearInstancers() {
initializedInstancers.keySet()
.forEach(InstancedInstancer::clear);
private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) {
return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new)
.alloc(mesh, eboCache);
}
private void add(InstancedInstancer<?> instancer, Model model, RenderStage stage) {
@Override
protected <I extends Instance> InstancedInstancer<I> create(InstanceType<I> type) {
return new InstancedInstancer<>(type);
}
@Override
protected <I extends Instance> void add(InstancerKey<I> key, InstancedInstancer<?> instancer, Model model, RenderStage stage) {
instancer.init();
DrawSet drawSet = drawSets.computeIfAbsent(stage, DrawSet::new);
List<DrawCall> drawCalls = new ArrayList<>();
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
@ -138,18 +82,8 @@ public class InstancedDrawManager {
DrawCall drawCall = new DrawCall(instancer, mesh, shaderState);
drawSet.put(shaderState, drawCall);
drawCalls.add(drawCall);
instancer.addDrawCall(drawCall);
}
initializedInstancers.put(instancer, drawCalls);
}
private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) {
return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new)
.alloc(mesh, eboCache);
}
public List<DrawCall> drawCallsForInstancer(InstancedInstancer<?> instancer) {
return initializedInstancers.getOrDefault(instancer, List.of());
}
public static class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> {
@ -187,7 +121,4 @@ public class InstancedDrawManager {
.iterator();
}
}
private record UninitializedInstancer(InstancedInstancer<?> instancer, Model model, RenderStage stage) {
}
}

View file

@ -1,6 +1,8 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.jozufozu.flywheel.Flywheel;
@ -21,6 +23,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
private final Set<GlVertexArray> boundTo = new HashSet<>();
private GlBuffer vbo;
private final List<DrawCall> drawCalls = new ArrayList<>();
public InstancedInstancer(InstanceType<I> type) {
super(type);
instanceFormat = type.getLayout();
@ -111,4 +115,12 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
vbo.delete();
vbo = null;
}
public void addDrawCall(DrawCall drawCall) {
drawCalls.add(drawCall);
}
public List<DrawCall> drawCalls() {
return drawCalls;
}
}

View file

@ -12,23 +12,20 @@ import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.UniformBuffer;
import com.jozufozu.flywheel.backend.engine.indirect.Textures;
import com.jozufozu.flywheel.gl.GlStateTracker;
import com.jozufozu.flywheel.gl.GlTextureUnit;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.RaisePlan;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
import com.mojang.blaze3d.systems.RenderSystem;
@ -36,54 +33,45 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelBakery;
public class InstancingEngine extends AbstractEngine {
private final Context context;
private final InstancedDrawManager drawManager = new InstancedDrawManager();
private final Flag flushFlag = new NamedFlag("flushed");
public InstancingEngine(int maxOriginDistance, Context context) {
public InstancingEngine(int maxOriginDistance) {
super(maxOriginDistance);
this.context = context;
}
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}
}
@Override
public Plan<RenderContext> createFramePlan() {
return SyncedPlan.<RenderContext>of(this::flushDrawManager)
.then(RaisePlan.raise(flushFlag));
return SyncedPlan.of(this::flushDrawManager);
}
private void flushDrawManager() {
try (var restoreState = GlStateTracker.getRestoreState()) {
drawManager.flush();
}
flushFlag.raise();
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
var drawSet = drawManager.get(stage);
if (!drawSet.isEmpty()) {
executor.syncUntil(flushFlag::isRaised);
try (var state = GlStateTracker.getRestoreState()) {
setup();
render(drawSet);
}
}
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
// Need to sync here to ensure this frame has everything executed
// in case we didn't have any stages to draw this frame.
executor.syncUntil(flushFlag::isRaised);
flushFlag.lower();
}
}
var drawSet = drawManager.get(stage);
if (drawSet.isEmpty()) {
return;
}
try (var state = GlStateTracker.getRestoreState()) {
setup();
render(drawSet);
}
}
@Override
public void renderCrumblingInstances(TaskExecutor executor, RenderContext context, List<Instance> instances, int progress) {
@ -139,7 +127,7 @@ public class InstancingEngine extends AbstractEngine {
continue;
}
List<DrawCall> draws = drawManager.drawCallsForInstancer(instancer);
List<DrawCall> draws = instancer.drawCalls();
draws.removeIf(DrawCall::isInvalid);
@ -173,7 +161,7 @@ public class InstancingEngine extends AbstractEngine {
continue;
}
setup(shader, context);
setup(shader, Contexts.WORLD);
shader.material().setup();
@ -201,8 +189,8 @@ public class InstancingEngine extends AbstractEngine {
}
@Override
protected void onRenderOriginChanged() {
drawManager.clearInstancers();
protected InstancerStorage<? extends AbstractInstancer<?>> getStorage() {
return drawManager;
}
@Override

View file

@ -4,21 +4,39 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.backend.Backend;
import com.jozufozu.flywheel.lib.util.ResourceUtil;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.ResourceLocationException;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
public class BackendArgument implements ArgumentType<Backend> {
private static final List<String> STRING_IDS = Backend.REGISTRY.getAllIds().stream().map(ResourceLocation::toString).toList();
private static final List<String> STRING_IDS = Backend.REGISTRY.getAllIds()
.stream()
.map(rl -> {
if (Flywheel.ID
.equals(rl.getNamespace())) {
return rl.getPath();
} else {
return rl.toString();
}
})
.toList();
private static final SimpleCommandExceptionType ERROR_INVALID = new SimpleCommandExceptionType(Component.translatable("argument.id.invalid"));
public static final DynamicCommandExceptionType ERROR_UNKNOWN_BACKEND = new DynamicCommandExceptionType(arg -> {
return Component.literal("Unknown backend '" + arg + "'");
@ -28,7 +46,7 @@ public class BackendArgument implements ArgumentType<Backend> {
@Override
public Backend parse(StringReader reader) throws CommandSyntaxException {
ResourceLocation id = ResourceLocation.read(reader);
ResourceLocation id = getRead(reader);
Backend backend = Backend.REGISTRY.get(id);
if (backend == null) {
@ -38,6 +56,27 @@ public class BackendArgument implements ArgumentType<Backend> {
return backend;
}
/**
* Copied from {@link ResourceLocation#read}, but defaults to flywheel namespace.
*/
@NotNull
private static ResourceLocation getRead(StringReader reader) throws CommandSyntaxException {
int i = reader.getCursor();
while(reader.canRead() && ResourceLocation.isAllowedInResourceLocation(reader.peek())) {
reader.skip();
}
String s = reader.getString().substring(i, reader.getCursor());
try {
return ResourceUtil.defaultToFlywheelNamespace(s);
} catch (ResourceLocationException resourcelocationexception) {
reader.setCursor(i);
throw ERROR_INVALID.createWithContext(reader);
}
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(STRING_IDS, builder);