mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 07:26:48 +01:00
Burning a house to kill a spider
- Fix garbage indices on instancing engine - glDrawElements* wants indices as a byte offset, we were giving it a word offset - Rename BufferedMesh -> PooledMesh - PooledMesh no longer stores things it can fetch from the inner mesh - Use one vao for all drawcalls in instancing engine - PooledMesh issues *BaseVertex calls - Move crumbling logic into InstancedDrawManager - Move programs acquisition into InstancedDrawManager - Move owned gl objects into InstancedDrawManager
This commit is contained in:
parent
dd998058a3
commit
94ae32b9a7
9 changed files with 250 additions and 292 deletions
|
@ -1,7 +1,6 @@
|
|||
package com.jozufozu.flywheel.backend.engine;
|
||||
|
||||
import com.jozufozu.flywheel.api.model.IndexSequence;
|
||||
import com.jozufozu.flywheel.backend.gl.GlNumericType;
|
||||
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||
|
@ -60,7 +59,7 @@ public class IndexPool {
|
|||
totalIndexCount += count;
|
||||
}
|
||||
|
||||
final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth());
|
||||
final var indexBlock = MemoryBlock.malloc(totalIndexCount * Integer.BYTES);
|
||||
final long indexPtr = indexBlock.ptr();
|
||||
|
||||
int firstIndex = 0;
|
||||
|
@ -70,7 +69,7 @@ public class IndexPool {
|
|||
|
||||
firstIndices.put(indexSequence, firstIndex);
|
||||
|
||||
indexSequence.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount);
|
||||
indexSequence.fill(indexPtr + (long) firstIndex * Integer.BYTES, indexCount);
|
||||
|
||||
firstIndex += indexCount;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
@ -17,13 +16,11 @@ import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
|||
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
|
||||
|
||||
public class MeshPool {
|
||||
private final VertexView vertexView;
|
||||
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
|
||||
private final List<BufferedMesh> meshList = new ArrayList<>();
|
||||
private final List<BufferedMesh> recentlyAllocated = new ArrayList<>();
|
||||
private final Map<Mesh, PooledMesh> meshes = new HashMap<>();
|
||||
private final List<PooledMesh> meshList = new ArrayList<>();
|
||||
private final List<PooledMesh> recentlyAllocated = new ArrayList<>();
|
||||
|
||||
private final GlBuffer vbo;
|
||||
private final IndexPool indexPool;
|
||||
|
@ -46,12 +43,12 @@ public class MeshPool {
|
|||
* @param mesh The model to allocate.
|
||||
* @return A handle to the allocated model.
|
||||
*/
|
||||
public BufferedMesh alloc(Mesh mesh) {
|
||||
public PooledMesh alloc(Mesh mesh) {
|
||||
return meshes.computeIfAbsent(mesh, this::_alloc);
|
||||
}
|
||||
|
||||
private BufferedMesh _alloc(Mesh m) {
|
||||
BufferedMesh bufferedModel = new BufferedMesh(m);
|
||||
private PooledMesh _alloc(Mesh m) {
|
||||
PooledMesh bufferedModel = new PooledMesh(m);
|
||||
meshList.add(bufferedModel);
|
||||
recentlyAllocated.add(bufferedModel);
|
||||
|
||||
|
@ -60,7 +57,7 @@ public class MeshPool {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public BufferedMesh get(Mesh mesh) {
|
||||
public MeshPool.PooledMesh get(Mesh mesh) {
|
||||
return meshes.get(mesh);
|
||||
}
|
||||
|
||||
|
@ -75,14 +72,15 @@ public class MeshPool {
|
|||
|
||||
// Might want to shrink the index pool if something was removed.
|
||||
indexPool.reset();
|
||||
for (BufferedMesh mesh : meshList) {
|
||||
for (PooledMesh mesh : meshList) {
|
||||
indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount());
|
||||
}
|
||||
} else {
|
||||
// Otherwise, just update the index with the new counts.
|
||||
for (BufferedMesh mesh : recentlyAllocated) {
|
||||
for (PooledMesh mesh : recentlyAllocated) {
|
||||
indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount());
|
||||
}
|
||||
recentlyAllocated.clear();
|
||||
}
|
||||
|
||||
// Always need to flush the index pool.
|
||||
|
@ -94,10 +92,10 @@ public class MeshPool {
|
|||
|
||||
private void processDeletions() {
|
||||
// remove deleted meshes
|
||||
meshList.removeIf(bufferedMesh -> {
|
||||
boolean deleted = bufferedMesh.deleted();
|
||||
meshList.removeIf(pooledMesh -> {
|
||||
boolean deleted = pooledMesh.deleted();
|
||||
if (deleted) {
|
||||
meshes.remove(bufferedMesh.mesh);
|
||||
meshes.remove(pooledMesh.mesh);
|
||||
}
|
||||
return deleted;
|
||||
});
|
||||
|
@ -105,7 +103,7 @@ public class MeshPool {
|
|||
|
||||
private void uploadAll() {
|
||||
long neededSize = 0;
|
||||
for (BufferedMesh mesh : meshList) {
|
||||
for (PooledMesh mesh : meshList) {
|
||||
neededSize += mesh.byteSize();
|
||||
}
|
||||
|
||||
|
@ -114,18 +112,15 @@ public class MeshPool {
|
|||
|
||||
int byteIndex = 0;
|
||||
int baseVertex = 0;
|
||||
for (BufferedMesh mesh : meshList) {
|
||||
mesh.byteIndex = byteIndex;
|
||||
for (PooledMesh mesh : meshList) {
|
||||
mesh.baseVertex = baseVertex;
|
||||
|
||||
vertexView.ptr(vertexPtr + mesh.byteIndex);
|
||||
vertexView.vertexCount(mesh.vertexCount);
|
||||
vertexView.ptr(vertexPtr + byteIndex);
|
||||
vertexView.vertexCount(mesh.vertexCount());
|
||||
mesh.mesh.write(vertexView);
|
||||
|
||||
byteIndex += mesh.byteSize();
|
||||
baseVertex += mesh.vertexCount();
|
||||
|
||||
mesh.boundTo.clear();
|
||||
}
|
||||
|
||||
vbo.upload(vertexBlock);
|
||||
|
@ -146,29 +141,24 @@ public class MeshPool {
|
|||
meshList.clear();
|
||||
}
|
||||
|
||||
public class BufferedMesh {
|
||||
public class PooledMesh {
|
||||
public static final int INVALID_BASE_VERTEX = -1;
|
||||
private final Mesh mesh;
|
||||
private final int vertexCount;
|
||||
private final int byteSize;
|
||||
|
||||
private long byteIndex;
|
||||
private int baseVertex;
|
||||
private int baseVertex = INVALID_BASE_VERTEX;
|
||||
|
||||
private int referenceCount = 0;
|
||||
private final Set<GlVertexArray> boundTo = new ReferenceArraySet<>();
|
||||
|
||||
private BufferedMesh(Mesh mesh) {
|
||||
private PooledMesh(Mesh mesh) {
|
||||
this.mesh = mesh;
|
||||
vertexCount = mesh.vertexCount();
|
||||
byteSize = vertexCount * InternalVertex.STRIDE;
|
||||
}
|
||||
|
||||
public int vertexCount() {
|
||||
return vertexCount;
|
||||
return mesh.vertexCount();
|
||||
}
|
||||
|
||||
public int byteSize() {
|
||||
return byteSize;
|
||||
return mesh.vertexCount() * InternalVertex.STRIDE;
|
||||
}
|
||||
|
||||
public int indexCount() {
|
||||
|
@ -180,7 +170,11 @@ public class MeshPool {
|
|||
}
|
||||
|
||||
public int firstIndex() {
|
||||
return indexPool.firstIndex(mesh.indexSequence());
|
||||
return MeshPool.this.indexPool.firstIndex(mesh.indexSequence());
|
||||
}
|
||||
|
||||
public long firstIndexByteOffset() {
|
||||
return (long) firstIndex() * Integer.BYTES;
|
||||
}
|
||||
|
||||
public boolean deleted() {
|
||||
|
@ -188,31 +182,22 @@ public class MeshPool {
|
|||
}
|
||||
|
||||
public boolean invalid() {
|
||||
return mesh.vertexCount() == 0 || deleted() || byteIndex == -1;
|
||||
return mesh.vertexCount() == 0 || baseVertex == INVALID_BASE_VERTEX || deleted();
|
||||
}
|
||||
|
||||
public void draw(int instanceCount) {
|
||||
if (instanceCount > 1) {
|
||||
GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex(), instanceCount);
|
||||
GL32.glDrawElementsInstancedBaseVertex(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndexByteOffset(), instanceCount, baseVertex);
|
||||
} else {
|
||||
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex());
|
||||
GL32.glDrawElementsBaseVertex(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndexByteOffset(), baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
public void setup(GlVertexArray vao) {
|
||||
if (!boundTo.add(vao)) {
|
||||
return;
|
||||
}
|
||||
MeshPool.this.indexPool.bind(vao);
|
||||
vao.bindVertexBuffer(0, MeshPool.this.vbo.handle(), byteIndex, InternalVertex.STRIDE);
|
||||
vao.bindAttributes(0, 0, InternalVertex.ATTRIBUTES);
|
||||
}
|
||||
|
||||
public void acquire() {
|
||||
referenceCount++;
|
||||
}
|
||||
|
||||
public void drop() {
|
||||
public void release() {
|
||||
if (--referenceCount == 0) {
|
||||
MeshPool.this.dirty = true;
|
||||
MeshPool.this.anyToRemove = true;
|
||||
|
|
|
@ -182,8 +182,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
instancers.add(instancer);
|
||||
|
||||
for (var entry : model.meshes()) {
|
||||
MeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.mesh());
|
||||
var draw = new IndirectDraw(instancer, entry.material(), bufferedMesh, stage);
|
||||
MeshPool.PooledMesh pooledMesh = meshPool.alloc(entry.mesh());
|
||||
var draw = new IndirectDraw(instancer, entry.material(), pooledMesh, stage);
|
||||
indirectDraws.add(draw);
|
||||
instancer.addDraw(draw);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.jozufozu.flywheel.backend.engine.MeshPool;
|
|||
public class IndirectDraw {
|
||||
private final IndirectInstancer<?> model;
|
||||
private final Material material;
|
||||
private final MeshPool.BufferedMesh mesh;
|
||||
private final MeshPool.PooledMesh mesh;
|
||||
private final RenderStage stage;
|
||||
|
||||
private final int materialVertexIndex;
|
||||
|
@ -20,7 +20,7 @@ public class IndirectDraw {
|
|||
private final int packedMaterialProperties;
|
||||
private boolean deleted;
|
||||
|
||||
public IndirectDraw(IndirectInstancer<?> model, Material material, MeshPool.BufferedMesh mesh, RenderStage stage) {
|
||||
public IndirectDraw(IndirectInstancer<?> model, Material material, MeshPool.PooledMesh mesh, RenderStage stage) {
|
||||
this.model = model;
|
||||
this.material = material;
|
||||
this.mesh = mesh;
|
||||
|
@ -42,7 +42,7 @@ public class IndirectDraw {
|
|||
return material;
|
||||
}
|
||||
|
||||
public MeshPool.BufferedMesh mesh() {
|
||||
public MeshPool.PooledMesh mesh() {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class IndirectDraw {
|
|||
return;
|
||||
}
|
||||
|
||||
mesh.drop();
|
||||
mesh.release();
|
||||
|
||||
deleted = true;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,22 @@
|
|||
package com.jozufozu.flywheel.backend.engine.instancing;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.MeshPool;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
||||
|
||||
public class DrawCall {
|
||||
public final ShaderState shaderState;
|
||||
private final InstancedInstancer<?> instancer;
|
||||
private final MeshPool.BufferedMesh mesh;
|
||||
private final MeshPool.PooledMesh mesh;
|
||||
|
||||
private final GlVertexArray vao;
|
||||
@Nullable
|
||||
private GlVertexArray vaoScratch;
|
||||
private boolean deleted;
|
||||
|
||||
public DrawCall(InstancedInstancer<?> instancer, MeshPool.BufferedMesh mesh, ShaderState shaderState) {
|
||||
public DrawCall(InstancedInstancer<?> instancer, MeshPool.PooledMesh mesh, ShaderState shaderState) {
|
||||
this.instancer = instancer;
|
||||
this.mesh = mesh;
|
||||
this.shaderState = shaderState;
|
||||
|
||||
mesh.acquire();
|
||||
|
||||
vao = GlVertexArray.create();
|
||||
}
|
||||
|
||||
public boolean deleted() {
|
||||
|
@ -37,9 +29,6 @@ public class DrawCall {
|
|||
}
|
||||
|
||||
instancer.bind(buffer);
|
||||
mesh.setup(vao);
|
||||
|
||||
vao.bindForDraw();
|
||||
|
||||
mesh.draw(instancer.instanceCount());
|
||||
}
|
||||
|
@ -54,36 +43,17 @@ public class DrawCall {
|
|||
return;
|
||||
}
|
||||
|
||||
var vao = lazyScratchVao();
|
||||
|
||||
instancer.bind(buffer);
|
||||
mesh.setup(vao);
|
||||
|
||||
vao.bindForDraw();
|
||||
|
||||
mesh.draw(1);
|
||||
}
|
||||
|
||||
private GlVertexArray lazyScratchVao() {
|
||||
if (vaoScratch == null) {
|
||||
vaoScratch = GlVertexArray.create();
|
||||
}
|
||||
return vaoScratch;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
vao.delete();
|
||||
|
||||
if (vaoScratch != null) {
|
||||
vaoScratch.delete();
|
||||
vaoScratch = null;
|
||||
}
|
||||
|
||||
mesh.drop();
|
||||
mesh.release();
|
||||
|
||||
deleted = true;
|
||||
}
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.engine.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jozufozu.flywheel.api.backend.Engine;
|
||||
import com.jozufozu.flywheel.api.context.TextureSource;
|
||||
import com.jozufozu.flywheel.api.instance.Instance;
|
||||
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
|
||||
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
|
||||
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
|
||||
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
|
||||
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
|
||||
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.lib.context.ContextShaders;
|
||||
import com.jozufozu.flywheel.lib.context.Contexts;
|
||||
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
||||
public class InstancedCrumbling {
|
||||
public static void render(List<Engine.CrumblingBlock> crumblingBlocks, InstancingPrograms programs, TextureSource textureSource, TextureBuffer instanceTexture) {
|
||||
// Sort draw calls into buckets, so we don't have to do as many shader binds.
|
||||
var byShaderState = doCrumblingSort(crumblingBlocks);
|
||||
|
||||
if (byShaderState.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var crumblingMaterial = SimpleMaterial.builder();
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
for (var shaderStateEntry : byShaderState.entrySet()) {
|
||||
var byProgress = shaderStateEntry.getValue();
|
||||
|
||||
if (byProgress.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderState shader = shaderStateEntry.getKey();
|
||||
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, shader.material());
|
||||
|
||||
var program = programs.get(shader.instanceType(), ContextShaders.CRUMBLING);
|
||||
program.bind();
|
||||
|
||||
Uniforms.bindForDraw();
|
||||
InstancingEngine.uploadMaterialUniform(program, crumblingMaterial);
|
||||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
var drawCalls = progressEntry.getValue();
|
||||
|
||||
if (drawCalls.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var context = Contexts.CRUMBLING.get(progressEntry.getIntKey());
|
||||
context.prepare(crumblingMaterial, program, textureSource);
|
||||
|
||||
GlTextureUnit.T3.makeActive();
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (Consumer<TextureBuffer> drawCall : drawCalls) {
|
||||
drawCall.accept(instanceTexture);
|
||||
}
|
||||
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> doCrumblingSort(List<Engine.CrumblingBlock> instances) {
|
||||
Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> out = new HashMap<>();
|
||||
|
||||
for (Engine.CrumblingBlock triple : instances) {
|
||||
int progress = triple.progress();
|
||||
|
||||
if (progress < 0 || progress >= ModelBakery.DESTROY_TYPES.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Instance instance : triple.instances()) {
|
||||
// Filter out instances that weren't created by this engine.
|
||||
// If all is well, we probably shouldn't take the `continue`
|
||||
// branches but better to do checked casts.
|
||||
if (!(instance.handle() instanceof InstanceHandleImpl impl)) {
|
||||
continue;
|
||||
}
|
||||
if (!(impl.instancer instanceof InstancedInstancer<?> instancer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (DrawCall draw : instancer.drawCalls()) {
|
||||
out.computeIfAbsent(draw.shaderState, $ -> new Int2ObjectArrayMap<>())
|
||||
.computeIfAbsent(progress, $ -> new ArrayList<>())
|
||||
.add(buf -> draw.renderOne(buf, impl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -1,31 +1,72 @@
|
|||
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 java.util.function.Consumer;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
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.material.Material;
|
||||
import com.jozufozu.flywheel.backend.ShaderIndices;
|
||||
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
|
||||
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
|
||||
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.InstancerKey;
|
||||
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
|
||||
import com.jozufozu.flywheel.backend.engine.MaterialEncoder;
|
||||
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
|
||||
import com.jozufozu.flywheel.backend.engine.MeshPool;
|
||||
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
|
||||
import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
|
||||
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.lib.context.ContextShaders;
|
||||
import com.jozufozu.flywheel.lib.context.Contexts;
|
||||
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
||||
public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> {
|
||||
/**
|
||||
* The set of draw calls to make in each {@link RenderStage}.
|
||||
*/
|
||||
private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class);
|
||||
private final InstancingPrograms programs;
|
||||
/**
|
||||
* A map of vertex types to their mesh pools.
|
||||
*/
|
||||
private final MeshPool meshPool = new MeshPool();
|
||||
private final MeshPool meshPool;
|
||||
private final GlVertexArray vao;
|
||||
private final TextureSourceImpl textures;
|
||||
private final TextureBuffer instanceTexture;
|
||||
|
||||
public DrawSet get(RenderStage stage) {
|
||||
return drawSets.getOrDefault(stage, DrawSet.EMPTY);
|
||||
public InstancedDrawManager(InstancingPrograms programs) {
|
||||
programs.acquire();
|
||||
this.programs = programs;
|
||||
|
||||
meshPool = new MeshPool();
|
||||
vao = GlVertexArray.create();
|
||||
textures = new TextureSourceImpl();
|
||||
instanceTexture = new TextureBuffer();
|
||||
|
||||
meshPool.bind(vao);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
|
@ -52,17 +93,70 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
|
|||
meshPool.flush();
|
||||
}
|
||||
|
||||
public void renderStage(RenderStage stage) {
|
||||
var drawSet = drawSets.getOrDefault(stage, DrawSet.EMPTY);
|
||||
|
||||
if (drawSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
render(drawSet);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
instancers.values()
|
||||
.forEach(InstancedInstancer::delete);
|
||||
|
||||
super.delete();
|
||||
|
||||
meshPool.delete();
|
||||
|
||||
drawSets.values()
|
||||
.forEach(DrawSet::delete);
|
||||
drawSets.clear();
|
||||
|
||||
meshPool.delete();
|
||||
instanceTexture.delete();
|
||||
programs.release();
|
||||
vao.delete();
|
||||
|
||||
super.delete();
|
||||
}
|
||||
|
||||
private void render(InstancedDrawManager.DrawSet drawSet) {
|
||||
Uniforms.bindForDraw();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
for (var entry : drawSet) {
|
||||
var shader = entry.getKey();
|
||||
var drawCalls = entry.getValue();
|
||||
|
||||
if (drawCalls.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var context = shader.context();
|
||||
var material = shader.material();
|
||||
|
||||
var program = programs.get(shader.instanceType(), context.contextShader());
|
||||
program.bind();
|
||||
|
||||
uploadMaterialUniform(program, material);
|
||||
|
||||
context.prepare(material, program, textures);
|
||||
MaterialRenderState.setup(material);
|
||||
|
||||
GlTextureUnit.T3.makeActive();
|
||||
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (var drawCall : drawCalls) {
|
||||
drawCall.render(instanceTexture);
|
||||
}
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -89,6 +183,106 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
|
|||
}
|
||||
}
|
||||
|
||||
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {
|
||||
// Sort draw calls into buckets, so we don't have to do as many shader binds.
|
||||
var byShaderState = doCrumblingSort(crumblingBlocks);
|
||||
|
||||
if (byShaderState.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var crumblingMaterial = SimpleMaterial.builder();
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
Uniforms.bindForDraw();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
for (var shaderStateEntry : byShaderState.entrySet()) {
|
||||
var byProgress = shaderStateEntry.getValue();
|
||||
|
||||
if (byProgress.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderState shader = shaderStateEntry.getKey();
|
||||
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, shader.material());
|
||||
|
||||
var program = programs.get(shader.instanceType(), ContextShaders.CRUMBLING);
|
||||
program.bind();
|
||||
|
||||
uploadMaterialUniform(program, crumblingMaterial);
|
||||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
var drawCalls = progressEntry.getValue();
|
||||
|
||||
if (drawCalls.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var context = Contexts.CRUMBLING.get(progressEntry.getIntKey());
|
||||
context.prepare(crumblingMaterial, program, textures);
|
||||
|
||||
GlTextureUnit.T3.makeActive();
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (Consumer<TextureBuffer> drawCall : drawCalls) {
|
||||
drawCall.accept(instanceTexture);
|
||||
}
|
||||
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> doCrumblingSort(List<Engine.CrumblingBlock> instances) {
|
||||
Map<ShaderState, Int2ObjectMap<List<Consumer<TextureBuffer>>>> out = new HashMap<>();
|
||||
|
||||
for (Engine.CrumblingBlock triple : instances) {
|
||||
int progress = triple.progress();
|
||||
|
||||
if (progress < 0 || progress >= ModelBakery.DESTROY_TYPES.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Instance instance : triple.instances()) {
|
||||
// Filter out instances that weren't created by this engine.
|
||||
// If all is well, we probably shouldn't take the `continue`
|
||||
// branches but better to do checked casts.
|
||||
if (!(instance.handle() instanceof InstanceHandleImpl impl)) {
|
||||
continue;
|
||||
}
|
||||
if (!(impl.instancer instanceof InstancedInstancer<?> instancer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (DrawCall draw : instancer.drawCalls()) {
|
||||
out.computeIfAbsent(draw.shaderState, $ -> new Int2ObjectArrayMap<>())
|
||||
.computeIfAbsent(progress, $ -> new ArrayList<>())
|
||||
.add(buf -> draw.renderOne(buf, impl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static void uploadMaterialUniform(GlProgram program, Material material) {
|
||||
int uniformLocation = program.getUniformLocation("_flw_packedMaterial");
|
||||
int vertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders());
|
||||
int fragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders());
|
||||
int packedFogAndCutout = MaterialEncoder.packFogAndCutout(material);
|
||||
int packedMaterialProperties = MaterialEncoder.packProperties(material);
|
||||
GL32.glUniform4ui(uniformLocation, vertexIndex, fragmentIndex, packedFogAndCutout, packedMaterialProperties);
|
||||
}
|
||||
|
||||
public static class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> {
|
||||
public static final DrawSet EMPTY = new DrawSet(ImmutableListMultimap.of());
|
||||
|
||||
|
|
|
@ -2,43 +2,28 @@ package com.jozufozu.flywheel.backend.engine.instancing;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
import com.jozufozu.flywheel.api.task.Plan;
|
||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||
import com.jozufozu.flywheel.backend.ShaderIndices;
|
||||
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.InstancerStorage;
|
||||
import com.jozufozu.flywheel.backend.engine.MaterialEncoder;
|
||||
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
|
||||
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
|
||||
import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl;
|
||||
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
|
||||
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.lib.task.Flag;
|
||||
import com.jozufozu.flywheel.lib.task.NamedFlag;
|
||||
import com.jozufozu.flywheel.lib.task.SyncedPlan;
|
||||
|
||||
public class InstancingEngine extends AbstractEngine {
|
||||
private final InstancingPrograms programs;
|
||||
private final TextureSourceImpl textures = new TextureSourceImpl();
|
||||
private final TextureBuffer instanceTexture = new TextureBuffer();
|
||||
private final InstancedDrawManager drawManager = new InstancedDrawManager();
|
||||
private final InstancedDrawManager drawManager;
|
||||
private final Flag flushFlag = new NamedFlag("flushed");
|
||||
|
||||
public InstancingEngine(InstancingPrograms programs, int maxOriginDistance) {
|
||||
super(maxOriginDistance);
|
||||
programs.acquire();
|
||||
this.programs = programs;
|
||||
}
|
||||
drawManager = new InstancedDrawManager(programs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plan<RenderContext> createFramePlan() {
|
||||
|
@ -60,16 +45,7 @@ public class InstancingEngine extends AbstractEngine {
|
|||
flushFlag.lower();
|
||||
}
|
||||
|
||||
var drawSet = drawManager.get(stage);
|
||||
|
||||
if (drawSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
Uniforms.bindForDraw();
|
||||
render(drawSet);
|
||||
}
|
||||
drawManager.renderStage(stage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,7 +53,7 @@ public class InstancingEngine extends AbstractEngine {
|
|||
// Need to wait for flush before we can inspect instancer state.
|
||||
executor.syncUntil(flushFlag::isRaised);
|
||||
|
||||
InstancedCrumbling.render(crumblingBlocks, programs, textures, instanceTexture);
|
||||
drawManager.renderCrumbling(crumblingBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,52 +64,5 @@ public class InstancingEngine extends AbstractEngine {
|
|||
@Override
|
||||
public void delete() {
|
||||
drawManager.delete();
|
||||
programs.release();
|
||||
instanceTexture.delete();
|
||||
}
|
||||
|
||||
private void render(InstancedDrawManager.DrawSet drawSet) {
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
for (var entry : drawSet) {
|
||||
var shader = entry.getKey();
|
||||
var drawCalls = entry.getValue();
|
||||
|
||||
if (drawCalls.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var context = shader.context();
|
||||
var material = shader.material();
|
||||
|
||||
var program = programs.get(shader.instanceType(), context.contextShader());
|
||||
program.bind();
|
||||
|
||||
uploadMaterialUniform(program, material);
|
||||
|
||||
context.prepare(material, program, textures);
|
||||
MaterialRenderState.setup(material);
|
||||
|
||||
GlTextureUnit.T3.makeActive();
|
||||
|
||||
program.setSamplerBinding("_flw_instances", 3);
|
||||
|
||||
for (var drawCall : drawCalls) {
|
||||
drawCall.render(instanceTexture);
|
||||
}
|
||||
TextureBinder.resetTextureBindings();
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
public static void uploadMaterialUniform(GlProgram program, Material material) {
|
||||
int uniformLocation = program.getUniformLocation("_flw_packedMaterial");
|
||||
int vertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders());
|
||||
int fragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders());
|
||||
int packedFogAndCutout = MaterialEncoder.packFogAndCutout(material);
|
||||
int packedMaterialProperties = MaterialEncoder.packProperties(material);
|
||||
GL32.glUniform4ui(uniformLocation, vertexIndex, fragmentIndex, packedFogAndCutout, packedMaterialProperties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ public class LineModelBuilder {
|
|||
|
||||
public static class LineMesh implements Mesh {
|
||||
public static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> {
|
||||
int numVertices = 4 * (count / 6);
|
||||
int numVertices = 2 * count / 3;
|
||||
int baseVertex = 0;
|
||||
while (baseVertex < numVertices) {
|
||||
// triangle a
|
||||
|
|
Loading…
Reference in a new issue