From 7c0959be9a4c1c73a81a4040c321f926561336ee Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 15 Dec 2023 13:09:38 -0800 Subject: [PATCH] Spooky crumbling from a distance - Add crumbling renderer for indirect backend. - As usual, it's ugly. - Submit one indirect draw per crumbling instance. Could do multidraw here, but we'd have to perform the material sort which seems unnecessary. --- .../backend/engine/CommonCrumbling.java | 38 ++++++ .../engine/indirect/IndirectBuffers.java | 11 ++ .../engine/indirect/IndirectCullingGroup.java | 23 +++- .../backend/engine/indirect/IndirectDraw.java | 15 +++ .../engine/indirect/IndirectDrawManager.java | 108 ++++++++++++++++++ .../engine/indirect/IndirectEngine.java | 25 +++- .../engine/indirect/IndirectInstancer.java | 12 ++ .../engine/instancing/InstancedCrumbling.java | 31 +---- 8 files changed, 234 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/CommonCrumbling.java diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/CommonCrumbling.java b/src/main/java/com/jozufozu/flywheel/backend/engine/CommonCrumbling.java new file mode 100644 index 000000000..7cd0eff64 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/CommonCrumbling.java @@ -0,0 +1,38 @@ +package com.jozufozu.flywheel.backend.engine; + +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.material.Transparency; +import com.jozufozu.flywheel.api.material.WriteMask; +import com.jozufozu.flywheel.gl.GlTextureUnit; +import com.jozufozu.flywheel.lib.material.CutoutShaders; +import com.jozufozu.flywheel.lib.material.FogShaders; +import com.jozufozu.flywheel.lib.material.SimpleMaterial; +import com.mojang.blaze3d.systems.RenderSystem; + +import net.minecraft.client.Minecraft; + +public class CommonCrumbling { + public static void applyCrumblingProperties(SimpleMaterial.Builder crumblingMaterial, Material baseMaterial) { + crumblingMaterial.copyFrom(baseMaterial) + .fog(FogShaders.NONE) + .cutout(CutoutShaders.ONE_TENTH) + .polygonOffset(true) + .transparency(Transparency.CRUMBLING) + .writeMask(WriteMask.COLOR) + .useOverlay(false) + .useLight(false); + } + + public static int getDiffuseTexture(Material material) { + return Minecraft.getInstance() + .getTextureManager() + .getTexture(material.texture()) + .getId(); + } + + public static void setActiveAndBindForCrumbling(int diffuseTexture) { + GlTextureUnit.T1.makeActive(); + RenderSystem.setShaderTexture(1, diffuseTexture); + RenderSystem.bindTexture(diffuseTexture); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java index 8b3e33df9..65e282046 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java @@ -22,6 +22,12 @@ public class IndirectBuffers { public static final long DRAW_COMMAND_STRIDE = 40; public static final long DRAW_COMMAND_OFFSET = 0; + public static final int OBJECT_INDEX = 0; + public static final int TARGET_INDEX = 1; + public static final int MODEL_INDEX = 2; + public static final int DRAW_INDEX = 3; + + // Offsets to the 3 segments private static final long HANDLE_OFFSET = 0; private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE; @@ -106,6 +112,11 @@ public class IndirectBuffers { nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } + public void bindForCrumbling() { + final long ptr = multiBindBlock.ptr(); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, 3, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + } + public void delete() { multiBindBlock.free(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 0e8ed99f4..b671f2cfc 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -44,11 +44,13 @@ public class IndirectCullingGroup { private final List indirectModels = new ArrayList<>(); private final List indirectDraws = new ArrayList<>(); private final Map> multiDraws = new EnumMap<>(RenderStage.class); + private final InstanceType instanceType; private boolean needsDrawBarrier; private boolean hasNewDraws; private int instanceCountThisFrame; IndirectCullingGroup(InstanceType instanceType) { + this.instanceType = instanceType; var programs = IndirectPrograms.get(); cullProgram = programs.getCullingProgram(instanceType); applyProgram = programs.getApplyProgram(); @@ -167,7 +169,9 @@ public class IndirectCullingGroup { for (Map.Entry entry : model.meshes().entrySet()) { IndirectMeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue()); - indirectDraws.add(new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage)); + var draw = new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage); + indirectDraws.add(draw); + instancer.addDraw(draw); } hasNewDraws = true; @@ -193,6 +197,23 @@ public class IndirectCullingGroup { } } + public void bindForCrumbling() { + var program = IndirectPrograms.get() + .getIndirectProgram(instanceType, Contexts.CRUMBLING); + + program.bind(); + + UniformBuffer.get() + .sync(); + meshPool.bindForDraw(); + buffers.bindForCrumbling(); + + drawBarrier(); + + var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw"); + glUniform1ui(flwBaseDraw, 0); + } + private void drawBarrier() { if (needsDrawBarrier) { glMemoryBarrier(DRAW_BARRIER_BITS); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java index 718d198e0..ca2ffeb99 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java @@ -56,4 +56,19 @@ public class IndirectDraw { MemoryUtil.memPutInt(ptr + 32, packedFogAndCutout); // packedFogAndCutout MemoryUtil.memPutInt(ptr + 36, packedMaterialProperties); // packedMaterialProperties } + + public void writeWithOverrides(long ptr, int instanceIndex, Material materialOverride) { + MemoryUtil.memPutInt(ptr, mesh.indexCount()); // count + MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance + MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex + MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex + MemoryUtil.memPutInt(ptr + 16, model.baseInstance() + instanceIndex); // baseInstance + + MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex + + MemoryUtil.memPutInt(ptr + 24, ShaderIndices.getVertexShaderIndex(materialOverride.shaders())); // materialVertexIndex + MemoryUtil.memPutInt(ptr + 28, ShaderIndices.getFragmentShaderIndex(materialOverride.shaders())); // materialFragmentIndex + MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packFogAndCutout(materialOverride)); // packedFogAndCutout + MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java index 68f7db148..862ba229b 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -1,18 +1,42 @@ package com.jozufozu.flywheel.backend.engine.indirect; +import static org.lwjgl.opengl.GL11.GL_TRIANGLES; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL30.glBindBufferRange; +import static org.lwjgl.opengl.GL40.glDrawElementsIndirect; +import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import org.jetbrains.annotations.NotNull; + +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.instance.InstanceType; import com.jozufozu.flywheel.api.model.Model; +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.MaterialRenderState; +import com.jozufozu.flywheel.gl.buffer.GlBuffer; +import com.jozufozu.flywheel.gl.buffer.GlBufferType; +import com.jozufozu.flywheel.lib.material.SimpleMaterial; +import com.jozufozu.flywheel.lib.memory.MemoryBlock; +import com.jozufozu.flywheel.lib.util.Pair; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.client.resources.model.ModelBakery; public class IndirectDrawManager extends InstancerStorage> { private final StagingBuffer stagingBuffer = new StagingBuffer(); private final Map, IndirectCullingGroup> cullingGroups = new HashMap<>(); + private final GlBuffer crumblingDrawBuffer = new GlBuffer(); @Override protected IndirectInstancer create(InstanceType type) { @@ -71,5 +95,89 @@ public class IndirectDrawManager extends InstancerStorage> cullingGroups.clear(); stagingBuffer.delete(); + + crumblingDrawBuffer.delete(); + } + + public void renderCrumbling(List crumblingBlocks) { + var byType = doCrumblingSort(crumblingBlocks); + + if (byType.isEmpty()) { + return; + } + + var crumblingMaterial = SimpleMaterial.builder(); + + // Scratch memory for writing draw commands. + var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE); + + GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle()); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, IndirectBuffers.DRAW_INDEX, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); + + for (var instanceTypeEntry : byType.entrySet()) { + var byProgress = instanceTypeEntry.getValue(); + + // Set up the crumbling program buffers. Nothing changes here between draws. + cullingGroups.get(instanceTypeEntry.getKey()) + .bindForCrumbling(); + + for (var progressEntry : byProgress.int2ObjectEntrySet()) { + for (var instanceHandlePair : progressEntry.getValue()) { + IndirectInstancer instancer = instanceHandlePair.first(); + int instanceIndex = instanceHandlePair.second().index; + + for (IndirectDraw draw : instancer.draws()) { + var baseMaterial = draw.material(); + int diffuseTexture = CommonCrumbling.getDiffuseTexture(baseMaterial); + + // Transform the material to be suited for crumbling. + CommonCrumbling.applyCrumblingProperties(crumblingMaterial, baseMaterial); + crumblingMaterial.texture(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); + + // Set up gl state for the draw. + MaterialRenderState.setup(crumblingMaterial); + CommonCrumbling.setActiveAndBindForCrumbling(diffuseTexture); + + // Upload the draw command. + draw.writeWithOverrides(block.ptr(), instanceIndex, crumblingMaterial); + crumblingDrawBuffer.upload(block); + + // Submit! Everything is already bound by here. + glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0); + } + } + } + } + + block.free(); + } + + @NotNull + private static Map, Int2ObjectMap, InstanceHandleImpl>>>> doCrumblingSort(List crumblingBlocks) { + Map, Int2ObjectMap, InstanceHandleImpl>>>> byType = new HashMap<>(); + for (Engine.CrumblingBlock block : crumblingBlocks) { + int progress = block.progress(); + + if (progress < 0 || progress >= ModelBakery.DESTROY_TYPES.size()) { + continue; + } + + for (Instance instance : block.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 IndirectInstancer instancer)) { + continue; + } + + byType.computeIfAbsent(instancer.type, $ -> new Int2ObjectArrayMap<>()) + .computeIfAbsent(progress, $ -> new ArrayList<>()) + .add(Pair.of(instancer, impl)); + } + } + return byType; } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java index 0f0c23233..0cbb4d989 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java @@ -73,7 +73,30 @@ public class IndirectEngine extends AbstractEngine { @Override public void renderCrumbling(TaskExecutor executor, RenderContext context, List crumblingBlocks) { - // TODO: implement + executor.syncUntil(flushFlag::isRaised); + + try (var restoreState = GlStateTracker.getRestoreState()) { + int prevActiveTexture = GlStateManager._getActiveTexture(); + Minecraft.getInstance().gameRenderer.overlayTexture() + .setupOverlayColor(); + Minecraft.getInstance().gameRenderer.lightTexture() + .turnOnLightLayer(); + + GlTextureUnit.T1.makeActive(); + RenderSystem.bindTexture(RenderSystem.getShaderTexture(1)); + GlTextureUnit.T2.makeActive(); + RenderSystem.bindTexture(RenderSystem.getShaderTexture(2)); + + drawManager.renderCrumbling(crumblingBlocks); + + MaterialRenderState.reset(); + + Minecraft.getInstance().gameRenderer.overlayTexture() + .teardownOverlayColor(); + Minecraft.getInstance().gameRenderer.lightTexture() + .turnOffLightLayer(); + GlStateManager._activeTexture(prevActiveTexture); + } } @Override diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java index 68e6ea9c8..ded8946ce 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -1,5 +1,8 @@ package com.jozufozu.flywheel.backend.engine.indirect; +import java.util.ArrayList; +import java.util.List; + import org.lwjgl.system.MemoryUtil; import com.jozufozu.flywheel.api.instance.Instance; @@ -10,6 +13,7 @@ import com.jozufozu.flywheel.backend.engine.AbstractInstancer; public class IndirectInstancer extends AbstractInstancer { private final long objectStride; private final InstanceWriter writer; + private final List associatedDraws = new ArrayList<>(); private int modelIndex; private long lastStartPos = -1; @@ -22,6 +26,14 @@ public class IndirectInstancer extends AbstractInstancer writer = this.type.getWriter(); } + public void addDraw(IndirectDraw draw) { + associatedDraws.add(draw); + } + + public List draws() { + return associatedDraws; + } + public void update() { removeDeletedInstances(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java index 88f550ee2..112b1cbaa 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedCrumbling.java @@ -9,24 +9,17 @@ import org.jetbrains.annotations.NotNull; import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.instance.Instance; -import com.jozufozu.flywheel.api.material.Material; -import com.jozufozu.flywheel.api.material.Transparency; -import com.jozufozu.flywheel.api.material.WriteMask; 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.UniformBuffer; import com.jozufozu.flywheel.gl.GlStateTracker; -import com.jozufozu.flywheel.gl.GlTextureUnit; import com.jozufozu.flywheel.lib.context.Contexts; -import com.jozufozu.flywheel.lib.material.CutoutShaders; -import com.jozufozu.flywheel.lib.material.FogShaders; import com.jozufozu.flywheel.lib.material.SimpleMaterial; -import com.mojang.blaze3d.systems.RenderSystem; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import net.minecraft.client.Minecraft; import net.minecraft.client.resources.model.ModelBakery; public class InstancedCrumbling { @@ -51,16 +44,9 @@ public class InstancedCrumbling { ShaderState shader = shaderStateEntry.getKey(); var baseMaterial = shader.material(); - int diffuseTexture = getDiffuseTexture(baseMaterial); + int diffuseTexture = CommonCrumbling.getDiffuseTexture(baseMaterial); - crumblingMaterial.copyFrom(baseMaterial) - .fog(FogShaders.NONE) - .cutout(CutoutShaders.ONE_TENTH) - .polygonOffset(true) - .transparency(Transparency.CRUMBLING) - .writeMask(WriteMask.COLOR) - .useOverlay(false) - .useLight(false); + CommonCrumbling.applyCrumblingProperties(crumblingMaterial, baseMaterial); var program = InstancingPrograms.get() .get(shader.instanceType(), Contexts.CRUMBLING); @@ -79,10 +65,7 @@ public class InstancedCrumbling { crumblingMaterial.texture(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); MaterialRenderState.setup(crumblingMaterial); - - GlTextureUnit.T1.makeActive(); - RenderSystem.setShaderTexture(1, diffuseTexture); - RenderSystem.bindTexture(diffuseTexture); + CommonCrumbling.setActiveAndBindForCrumbling(diffuseTexture); drawCalls.forEach(Runnable::run); } @@ -128,10 +111,4 @@ public class InstancedCrumbling { return out; } - private static int getDiffuseTexture(Material material) { - return Minecraft.getInstance() - .getTextureManager() - .getTexture(material.texture()) - .getId(); - } }