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

View file

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

View file

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

View file

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

View file

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

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;
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());

View file

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

View file

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