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
* 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>
*
* @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 dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import org.joml.Vector4fc;
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>Meshes will be rendered in the order they appear in this list, though
* no render order guarantees are made for meshes between different models.</p>
* <p>Meshes will be rendered in the order they appear in this list. See
* {@link InstancerProvider#instancer(InstanceType, Model, int)} for a complete explanation</p>
*
* @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<>();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType), this::createAndDeferInit);
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, bias), this::createAndDeferInit);
}
public void flush(LightStorage lightStorage) {

View File

@ -109,8 +109,8 @@ public class EngineImpl implements Engine {
lightStorage.delete();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) {
return drawManager.getInstancer(environment, type, model, 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, bias);
}
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;
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 {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType);
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType, bias);
}
}

View File

@ -39,9 +39,9 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
instancerProvider = new InstancerProvider() {
@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.
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.backend.compile.ContextShader;
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.MeshPool;
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> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::bias)
.thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
@ -174,16 +176,17 @@ public class IndirectCullingGroup<I extends Instance> {
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();
instancers.add(instancer);
List<Model.ConfiguredMesh> meshes = model.meshes();
List<Model.ConfiguredMesh> meshes = key.model()
.meshes();
for (int i = 0; i < meshes.size(); i++) {
var entry = meshes.get(i);
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);
instancer.addDraw(draw);
}

View File

@ -13,6 +13,7 @@ public class IndirectDraw {
private final Material material;
private final MeshPool.PooledMesh mesh;
private final VisualType visualType;
private final int bias;
private final int indexOfMeshInModel;
private final int materialVertexIndex;
@ -21,11 +22,12 @@ public class IndirectDraw {
private final int packedMaterialProperties;
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.material = material;
this.mesh = mesh;
this.visualType = visualType;
this.bias = bias;
this.indexOfMeshInModel = indexOfMeshInModel;
mesh.acquire();
@ -52,6 +54,10 @@ public class IndirectDraw {
return visualType;
}
public int bias() {
return bias;
}
public int 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) {
var groupKey = new GroupKey<>(key.type(), key.environment());
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) {

View File

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

View File

@ -142,7 +142,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
var mesh = meshPool.alloc(entry.mesh());
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);
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;
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);
private final Map<GroupKey<?>, DrawGroup> groups = new HashMap<>();