mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 07:26:48 +01:00
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:
parent
8fa095cd16
commit
2039a964e2
11 changed files with 274 additions and 247 deletions
|
@ -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"));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -108,4 +108,8 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
|||
public String toString() {
|
||||
return "AbstractInstancer[" + getInstanceCount() + ']';
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue