Biased towards artistic control

- Extend InstancerProvider to allow visuals to bias the render order of
  their instancers
- Keep the old InstancerProvider#instancer method with a bias of 0
- Add an explanation of render order in InstancerProvider
This commit is contained in:
Jozufozu 2024-07-26 16:32:05 -07:00
parent eb2ba12a98
commit b8f6bf841d
13 changed files with 70 additions and 22 deletions

View File

@ -11,10 +11,38 @@ public interface InstancerProvider {
* <p>Calling this method twice with the same arguments in the * <p>Calling this method twice with the same arguments in the
* same frame will return the same instancer.</p> * same frame will return the same instancer.</p>
* *
* <p>It is not safe to store instancers between frames. Each * <p>It is NOT safe to store instancers between frames. Each
* time you need an instancer, you should call this method.</p> * time you need an instancer, you should call this method.</p>
* *
* @return An instancer for the given instance type rendering the given model. * <h2>Render Order</h2>
* <p>In general, you can assume all instances in the same instancer will be rendered in a single draw call.
* Backends are free to optimize the ordering of draw calls to a certain extent, but utilities are provided to let
* you control the order of draw calls
* <h4>Mesh Order</h4>
* <br>For one, Meshes within a Model are guaranteed to render in the order they appear in their containing list.
* This lets you e.g. preserve (or break!) vanilla's chunk RenderType order guarantees or control which Meshes of
* your Model render over others.
* <h4>Bias Order</h4>
* <br>The other method is via the {@code bias} parameter to this method. An instancer with a lower bias will have
* its instances draw BEFORE an instancer with a higher bias. This allows you to control the render order between
* your instances to e.g. create an "overlay" instance to selectively color or apply decals to another instance.</p>
*
* @param type The instance type to parameterize your instances by.
* @param model The Model to instance.
* @param bias A weight to control render order between instancers.
* Instancers are rendered in ascending order by bias.
* @return An instancer.
*/ */
<I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model); <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias);
/**
* Get an instancer with no bias for the given instance type rendering the given model with.
*
* @param type The instance type to parameterize your instances by.
* @param model The model to instance.
* @return An instancer with {@code bias == 0}.
*/
default <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
return instancer(type, model, 0);
}
} }

View File

@ -2,6 +2,10 @@ package dev.engine_room.flywheel.api.model;
import java.util.List; import java.util.List;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import org.joml.Vector4fc; import org.joml.Vector4fc;
import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.material.Material;
@ -12,8 +16,8 @@ public interface Model {
* *
* <p>The contents of the returned list will be queried, but never modified.</p> * <p>The contents of the returned list will be queried, but never modified.</p>
* *
* <p>Meshes will be rendered in the order they appear in this list, though * <p>Meshes will be rendered in the order they appear in this list. See
* no render order guarantees are made for meshes between different models.</p> * {@link InstancerProvider#instancer(InstanceType, Model, int)} for a complete explanation</p>
* *
* @return A list of meshes. * @return A list of meshes.
*/ */

View File

@ -36,8 +36,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>(); protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) { public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType), this::createAndDeferInit); return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit);
} }
public void flush(LightStorage lightStorage) { public void flush(LightStorage lightStorage) {

View File

@ -109,8 +109,8 @@ public class EngineImpl implements Engine {
lightStorage.delete(); lightStorage.delete();
} }
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) { public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return drawManager.getInstancer(environment, type, model, visualType); return drawManager.getInstancer(environment, type, model, visualType, bias);
} }
public EnvironmentStorage environmentStorage() { public EnvironmentStorage environmentStorage() {

View File

@ -7,5 +7,5 @@ import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.embed.Environment;
public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model, public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model,
VisualType visualType) { VisualType visualType, int bias) {
} }

View File

@ -10,7 +10,7 @@ import dev.engine_room.flywheel.backend.engine.embed.GlobalEnvironment;
public record InstancerProviderImpl(EngineImpl engine, VisualType visualType) implements InstancerProvider { public record InstancerProviderImpl(EngineImpl engine, VisualType visualType) implements InstancerProvider {
@Override @Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) { public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType); return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType, bias);
} }
} }

View File

@ -39,9 +39,9 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
instancerProvider = new InstancerProvider() { instancerProvider = new InstancerProvider() {
@Override @Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) { public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias) {
// Kinda cursed usage of anonymous classes here, but it does the job. // Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(EmbeddedEnvironment.this, type, model, visualType); return engine.instancer(EmbeddedEnvironment.this, type, model, visualType, bias);
} }
}; };
} }

View File

@ -21,6 +21,7 @@ import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType; import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms; import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.embed.Environment;
@ -31,6 +32,7 @@ import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> { public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType) private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::bias)
.thenComparing(IndirectDraw::indexOfMeshInModel) .thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR); .thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
@ -174,16 +176,17 @@ public class IndirectCullingGroup<I extends Instance> {
return multiDraws.containsKey(visualType); return multiDraws.containsKey(visualType);
} }
public void add(IndirectInstancer<I> instancer, Model model, VisualType visualType, MeshPool meshPool) { public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) {
instancer.modelIndex = instancers.size(); instancer.modelIndex = instancers.size();
instancers.add(instancer); instancers.add(instancer);
List<Model.ConfiguredMesh> meshes = model.meshes(); List<Model.ConfiguredMesh> meshes = key.model()
.meshes();
for (int i = 0; i < meshes.size(); i++) { for (int i = 0; i < meshes.size(); i++) {
var entry = meshes.get(i); var entry = meshes.get(i);
MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh()); MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh());
var draw = new IndirectDraw(instancer, entry.material(), mesh, visualType, i); var draw = new IndirectDraw(instancer, entry.material(), mesh, key.visualType(), key.bias(), i);
indirectDraws.add(draw); indirectDraws.add(draw);
instancer.addDraw(draw); instancer.addDraw(draw);
} }

View File

@ -13,6 +13,7 @@ public class IndirectDraw {
private final Material material; private final Material material;
private final MeshPool.PooledMesh mesh; private final MeshPool.PooledMesh mesh;
private final VisualType visualType; private final VisualType visualType;
private final int bias;
private final int indexOfMeshInModel; private final int indexOfMeshInModel;
private final int materialVertexIndex; private final int materialVertexIndex;
@ -21,11 +22,12 @@ public class IndirectDraw {
private final int packedMaterialProperties; private final int packedMaterialProperties;
private boolean deleted; private boolean deleted;
public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, VisualType visualType, int indexOfMeshInModel) { public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, VisualType visualType, int bias, int indexOfMeshInModel) {
this.instancer = instancer; this.instancer = instancer;
this.material = material; this.material = material;
this.mesh = mesh; this.mesh = mesh;
this.visualType = visualType; this.visualType = visualType;
this.bias = bias;
this.indexOfMeshInModel = indexOfMeshInModel; this.indexOfMeshInModel = indexOfMeshInModel;
mesh.acquire(); mesh.acquire();
@ -52,6 +54,10 @@ public class IndirectDraw {
return visualType; return visualType;
} }
public int bias() {
return bias;
}
public int indexOfMeshInModel() { public int indexOfMeshInModel() {
return indexOfMeshInModel; return indexOfMeshInModel;
} }

View File

@ -63,7 +63,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) { protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) {
var groupKey = new GroupKey<>(key.type(), key.environment()); var groupKey = new GroupKey<>(key.type(), key.environment());
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.instanceType(), t.environment(), programs)); var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.instanceType(), t.environment(), programs));
group.add((IndirectInstancer<I>) instancer, key.model(), key.visualType(), meshPool); group.add((IndirectInstancer<I>) instancer, key, meshPool);
} }
public boolean hasVisualType(VisualType visualType) { public boolean hasVisualType(VisualType visualType) {

View File

@ -10,20 +10,26 @@ public class InstancedDraw {
private final InstancedInstancer<?> instancer; private final InstancedInstancer<?> instancer;
private final MeshPool.PooledMesh mesh; private final MeshPool.PooledMesh mesh;
private final Material material; private final Material material;
private final int bias;
private final int indexOfMeshInModel; private final int indexOfMeshInModel;
private boolean deleted; private boolean deleted;
public InstancedDraw(InstancedInstancer<?> instancer, MeshPool.PooledMesh mesh, GroupKey<?> groupKey, Material material, int indexOfMeshInModel) { public InstancedDraw(InstancedInstancer<?> instancer, MeshPool.PooledMesh mesh, GroupKey<?> groupKey, Material material, int bias, int indexOfMeshInModel) {
this.instancer = instancer; this.instancer = instancer;
this.mesh = mesh; this.mesh = mesh;
this.groupKey = groupKey; this.groupKey = groupKey;
this.material = material; this.material = material;
this.bias = bias;
this.indexOfMeshInModel = indexOfMeshInModel; this.indexOfMeshInModel = indexOfMeshInModel;
mesh.acquire(); mesh.acquire();
} }
public int bias() {
return bias;
}
public int indexOfMeshInModel() { public int indexOfMeshInModel() {
return indexOfMeshInModel; return indexOfMeshInModel;
} }

View File

@ -142,7 +142,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
var mesh = meshPool.alloc(entry.mesh()); var mesh = meshPool.alloc(entry.mesh());
GroupKey<?> groupKey = new GroupKey<>(key.type(), key.environment()); GroupKey<?> groupKey = new GroupKey<>(key.type(), key.environment());
InstancedDraw instancedDraw = new InstancedDraw(instancer, mesh, groupKey, entry.material(), i); InstancedDraw instancedDraw = new InstancedDraw(instancer, mesh, groupKey, entry.material(), key.bias(), i);
stage.put(groupKey, instancedDraw); stage.put(groupKey, instancedDraw);
instancer.addDrawCall(instancedDraw); instancer.addDrawCall(instancedDraw);

View File

@ -15,7 +15,8 @@ import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.gl.TextureBuffer; import dev.engine_room.flywheel.backend.gl.TextureBuffer;
public class InstancedRenderStage { public class InstancedRenderStage {
private static final Comparator<InstancedDraw> DRAW_COMPARATOR = Comparator.comparing(InstancedDraw::indexOfMeshInModel) private static final Comparator<InstancedDraw> DRAW_COMPARATOR = Comparator.comparing(InstancedDraw::bias)
.thenComparing(InstancedDraw::indexOfMeshInModel)
.thenComparing(InstancedDraw::material, MaterialRenderState.COMPARATOR); .thenComparing(InstancedDraw::material, MaterialRenderState.COMPARATOR);
private final Map<GroupKey<?>, DrawGroup> groups = new HashMap<>(); private final Map<GroupKey<?>, DrawGroup> groups = new HashMap<>();