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:
Jozufozu 2024-02-25 12:40:39 -08:00
parent dd998058a3
commit 94ae32b9a7
9 changed files with 250 additions and 292 deletions

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.backend.engine; package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.model.IndexSequence; 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.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
@ -60,7 +59,7 @@ public class IndexPool {
totalIndexCount += count; totalIndexCount += count;
} }
final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth()); final var indexBlock = MemoryBlock.malloc(totalIndexCount * Integer.BYTES);
final long indexPtr = indexBlock.ptr(); final long indexPtr = indexBlock.ptr();
int firstIndex = 0; int firstIndex = 0;
@ -70,7 +69,7 @@ public class IndexPool {
firstIndices.put(indexSequence, firstIndex); firstIndices.put(indexSequence, firstIndex);
indexSequence.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount); indexSequence.fill(indexPtr + (long) firstIndex * Integer.BYTES, indexCount);
firstIndex += indexCount; firstIndex += indexCount;
} }

View file

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32; 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.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
public class MeshPool { public class MeshPool {
private final VertexView vertexView; private final VertexView vertexView;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>(); private final Map<Mesh, PooledMesh> meshes = new HashMap<>();
private final List<BufferedMesh> meshList = new ArrayList<>(); private final List<PooledMesh> meshList = new ArrayList<>();
private final List<BufferedMesh> recentlyAllocated = new ArrayList<>(); private final List<PooledMesh> recentlyAllocated = new ArrayList<>();
private final GlBuffer vbo; private final GlBuffer vbo;
private final IndexPool indexPool; private final IndexPool indexPool;
@ -46,12 +43,12 @@ public class MeshPool {
* @param mesh The model to allocate. * @param mesh The model to allocate.
* @return A handle to the allocated model. * @return A handle to the allocated model.
*/ */
public BufferedMesh alloc(Mesh mesh) { public PooledMesh alloc(Mesh mesh) {
return meshes.computeIfAbsent(mesh, this::_alloc); return meshes.computeIfAbsent(mesh, this::_alloc);
} }
private BufferedMesh _alloc(Mesh m) { private PooledMesh _alloc(Mesh m) {
BufferedMesh bufferedModel = new BufferedMesh(m); PooledMesh bufferedModel = new PooledMesh(m);
meshList.add(bufferedModel); meshList.add(bufferedModel);
recentlyAllocated.add(bufferedModel); recentlyAllocated.add(bufferedModel);
@ -60,7 +57,7 @@ public class MeshPool {
} }
@Nullable @Nullable
public BufferedMesh get(Mesh mesh) { public MeshPool.PooledMesh get(Mesh mesh) {
return meshes.get(mesh); return meshes.get(mesh);
} }
@ -75,14 +72,15 @@ public class MeshPool {
// Might want to shrink the index pool if something was removed. // Might want to shrink the index pool if something was removed.
indexPool.reset(); indexPool.reset();
for (BufferedMesh mesh : meshList) { for (PooledMesh mesh : meshList) {
indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount()); indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount());
} }
} else { } else {
// Otherwise, just update the index with the new counts. // Otherwise, just update the index with the new counts.
for (BufferedMesh mesh : recentlyAllocated) { for (PooledMesh mesh : recentlyAllocated) {
indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount()); indexPool.updateCount(mesh.mesh.indexSequence(), mesh.indexCount());
} }
recentlyAllocated.clear();
} }
// Always need to flush the index pool. // Always need to flush the index pool.
@ -94,10 +92,10 @@ public class MeshPool {
private void processDeletions() { private void processDeletions() {
// remove deleted meshes // remove deleted meshes
meshList.removeIf(bufferedMesh -> { meshList.removeIf(pooledMesh -> {
boolean deleted = bufferedMesh.deleted(); boolean deleted = pooledMesh.deleted();
if (deleted) { if (deleted) {
meshes.remove(bufferedMesh.mesh); meshes.remove(pooledMesh.mesh);
} }
return deleted; return deleted;
}); });
@ -105,7 +103,7 @@ public class MeshPool {
private void uploadAll() { private void uploadAll() {
long neededSize = 0; long neededSize = 0;
for (BufferedMesh mesh : meshList) { for (PooledMesh mesh : meshList) {
neededSize += mesh.byteSize(); neededSize += mesh.byteSize();
} }
@ -114,18 +112,15 @@ public class MeshPool {
int byteIndex = 0; int byteIndex = 0;
int baseVertex = 0; int baseVertex = 0;
for (BufferedMesh mesh : meshList) { for (PooledMesh mesh : meshList) {
mesh.byteIndex = byteIndex;
mesh.baseVertex = baseVertex; mesh.baseVertex = baseVertex;
vertexView.ptr(vertexPtr + mesh.byteIndex); vertexView.ptr(vertexPtr + byteIndex);
vertexView.vertexCount(mesh.vertexCount); vertexView.vertexCount(mesh.vertexCount());
mesh.mesh.write(vertexView); mesh.mesh.write(vertexView);
byteIndex += mesh.byteSize(); byteIndex += mesh.byteSize();
baseVertex += mesh.vertexCount(); baseVertex += mesh.vertexCount();
mesh.boundTo.clear();
} }
vbo.upload(vertexBlock); vbo.upload(vertexBlock);
@ -146,29 +141,24 @@ public class MeshPool {
meshList.clear(); meshList.clear();
} }
public class BufferedMesh { public class PooledMesh {
public static final int INVALID_BASE_VERTEX = -1;
private final Mesh mesh; private final Mesh mesh;
private final int vertexCount;
private final int byteSize;
private long byteIndex; private int baseVertex = INVALID_BASE_VERTEX;
private int baseVertex;
private int referenceCount = 0; private int referenceCount = 0;
private final Set<GlVertexArray> boundTo = new ReferenceArraySet<>();
private BufferedMesh(Mesh mesh) { private PooledMesh(Mesh mesh) {
this.mesh = mesh; this.mesh = mesh;
vertexCount = mesh.vertexCount();
byteSize = vertexCount * InternalVertex.STRIDE;
} }
public int vertexCount() { public int vertexCount() {
return vertexCount; return mesh.vertexCount();
} }
public int byteSize() { public int byteSize() {
return byteSize; return mesh.vertexCount() * InternalVertex.STRIDE;
} }
public int indexCount() { public int indexCount() {
@ -180,7 +170,11 @@ public class MeshPool {
} }
public int firstIndex() { 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() { public boolean deleted() {
@ -188,31 +182,22 @@ public class MeshPool {
} }
public boolean invalid() { public boolean invalid() {
return mesh.vertexCount() == 0 || deleted() || byteIndex == -1; return mesh.vertexCount() == 0 || baseVertex == INVALID_BASE_VERTEX || deleted();
} }
public void draw(int instanceCount) { public void draw(int instanceCount) {
if (instanceCount > 1) { 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 { } 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() { public void acquire() {
referenceCount++; referenceCount++;
} }
public void drop() { public void release() {
if (--referenceCount == 0) { if (--referenceCount == 0) {
MeshPool.this.dirty = true; MeshPool.this.dirty = true;
MeshPool.this.anyToRemove = true; MeshPool.this.anyToRemove = true;

View file

@ -182,8 +182,8 @@ public class IndirectCullingGroup<I extends Instance> {
instancers.add(instancer); instancers.add(instancer);
for (var entry : model.meshes()) { for (var entry : model.meshes()) {
MeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.mesh()); MeshPool.PooledMesh pooledMesh = meshPool.alloc(entry.mesh());
var draw = new IndirectDraw(instancer, entry.material(), bufferedMesh, stage); var draw = new IndirectDraw(instancer, entry.material(), pooledMesh, stage);
indirectDraws.add(draw); indirectDraws.add(draw);
instancer.addDraw(draw); instancer.addDraw(draw);
} }

View file

@ -11,7 +11,7 @@ import com.jozufozu.flywheel.backend.engine.MeshPool;
public class IndirectDraw { public class IndirectDraw {
private final IndirectInstancer<?> model; private final IndirectInstancer<?> model;
private final Material material; private final Material material;
private final MeshPool.BufferedMesh mesh; private final MeshPool.PooledMesh mesh;
private final RenderStage stage; private final RenderStage stage;
private final int materialVertexIndex; private final int materialVertexIndex;
@ -20,7 +20,7 @@ public class IndirectDraw {
private final int packedMaterialProperties; private final int packedMaterialProperties;
private boolean deleted; 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.model = model;
this.material = material; this.material = material;
this.mesh = mesh; this.mesh = mesh;
@ -42,7 +42,7 @@ public class IndirectDraw {
return material; return material;
} }
public MeshPool.BufferedMesh mesh() { public MeshPool.PooledMesh mesh() {
return mesh; return mesh;
} }
@ -85,7 +85,7 @@ public class IndirectDraw {
return; return;
} }
mesh.drop(); mesh.release();
deleted = true; deleted = true;
} }

View file

@ -1,30 +1,22 @@
package com.jozufozu.flywheel.backend.engine.instancing; 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.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.MeshPool; import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.gl.TextureBuffer; import com.jozufozu.flywheel.backend.gl.TextureBuffer;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
public class DrawCall { public class DrawCall {
public final ShaderState shaderState; public final ShaderState shaderState;
private final InstancedInstancer<?> instancer; 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; 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.instancer = instancer;
this.mesh = mesh; this.mesh = mesh;
this.shaderState = shaderState; this.shaderState = shaderState;
mesh.acquire(); mesh.acquire();
vao = GlVertexArray.create();
} }
public boolean deleted() { public boolean deleted() {
@ -37,9 +29,6 @@ public class DrawCall {
} }
instancer.bind(buffer); instancer.bind(buffer);
mesh.setup(vao);
vao.bindForDraw();
mesh.draw(instancer.instanceCount()); mesh.draw(instancer.instanceCount());
} }
@ -54,36 +43,17 @@ public class DrawCall {
return; return;
} }
var vao = lazyScratchVao();
instancer.bind(buffer); instancer.bind(buffer);
mesh.setup(vao);
vao.bindForDraw();
mesh.draw(1); mesh.draw(1);
} }
private GlVertexArray lazyScratchVao() {
if (vaoScratch == null) {
vaoScratch = GlVertexArray.create();
}
return vaoScratch;
}
public void delete() { public void delete() {
if (deleted) { if (deleted) {
return; return;
} }
vao.delete(); mesh.release();
if (vaoScratch != null) {
vaoScratch.delete();
vaoScratch = null;
}
mesh.drop();
deleted = true; deleted = true;
} }

View file

@ -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;
}
}

View file

@ -1,31 +1,72 @@
package com.jozufozu.flywheel.backend.engine.instancing; package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; 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.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap; 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.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance; 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.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage; 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.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<?>> { public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> {
/** /**
* The set of draw calls to make in each {@link RenderStage}. * The set of draw calls to make in each {@link RenderStage}.
*/ */
private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class); private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class);
private final InstancingPrograms programs;
/** /**
* A map of vertex types to their mesh pools. * 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) { public InstancedDrawManager(InstancingPrograms programs) {
return drawSets.getOrDefault(stage, DrawSet.EMPTY); programs.acquire();
this.programs = programs;
meshPool = new MeshPool();
vao = GlVertexArray.create();
textures = new TextureSourceImpl();
instanceTexture = new TextureBuffer();
meshPool.bind(vao);
} }
public void flush() { public void flush() {
@ -52,17 +93,70 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
meshPool.flush(); 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() { public void delete() {
instancers.values() instancers.values()
.forEach(InstancedInstancer::delete); .forEach(InstancedInstancer::delete);
super.delete();
meshPool.delete();
drawSets.values() drawSets.values()
.forEach(DrawSet::delete); .forEach(DrawSet::delete);
drawSets.clear(); 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 @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 class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> {
public static final DrawSet EMPTY = new DrawSet(ImmutableListMultimap.of()); public static final DrawSet EMPTY = new DrawSet(ImmutableListMultimap.of());

View file

@ -2,43 +2,28 @@ package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.List; import java.util.List;
import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.event.RenderContext; import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage; 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.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor; 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.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.AbstractEngine; import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer; import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerStorage; 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.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker; 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.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag; import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan; import com.jozufozu.flywheel.lib.task.SyncedPlan;
public class InstancingEngine extends AbstractEngine { public class InstancingEngine extends AbstractEngine {
private final InstancingPrograms programs; private final InstancedDrawManager drawManager;
private final TextureSourceImpl textures = new TextureSourceImpl();
private final TextureBuffer instanceTexture = new TextureBuffer();
private final InstancedDrawManager drawManager = new InstancedDrawManager();
private final Flag flushFlag = new NamedFlag("flushed"); private final Flag flushFlag = new NamedFlag("flushed");
public InstancingEngine(InstancingPrograms programs, int maxOriginDistance) { public InstancingEngine(InstancingPrograms programs, int maxOriginDistance) {
super(maxOriginDistance); super(maxOriginDistance);
programs.acquire(); drawManager = new InstancedDrawManager(programs);
this.programs = programs; }
}
@Override @Override
public Plan<RenderContext> createFramePlan() { public Plan<RenderContext> createFramePlan() {
@ -60,16 +45,7 @@ public class InstancingEngine extends AbstractEngine {
flushFlag.lower(); flushFlag.lower();
} }
var drawSet = drawManager.get(stage); drawManager.renderStage(stage);
if (drawSet.isEmpty()) {
return;
}
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.bindForDraw();
render(drawSet);
}
} }
@Override @Override
@ -77,7 +53,7 @@ public class InstancingEngine extends AbstractEngine {
// Need to wait for flush before we can inspect instancer state. // Need to wait for flush before we can inspect instancer state.
executor.syncUntil(flushFlag::isRaised); executor.syncUntil(flushFlag::isRaised);
InstancedCrumbling.render(crumblingBlocks, programs, textures, instanceTexture); drawManager.renderCrumbling(crumblingBlocks);
} }
@Override @Override
@ -88,52 +64,5 @@ public class InstancingEngine extends AbstractEngine {
@Override @Override
public void delete() { public void delete() {
drawManager.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);
} }
} }

View file

@ -98,7 +98,7 @@ public class LineModelBuilder {
public static class LineMesh implements Mesh { public static class LineMesh implements Mesh {
public static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> { public static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> {
int numVertices = 4 * (count / 6); int numVertices = 2 * count / 3;
int baseVertex = 0; int baseVertex = 0;
while (baseVertex < numVertices) { while (baseVertex < numVertices) {
// triangle a // triangle a