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.
This commit is contained in:
Jozufozu 2023-12-15 13:09:38 -08:00
parent c79e94cd18
commit 7c0959be9a
8 changed files with 234 additions and 29 deletions

View file

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

View file

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

View file

@ -44,11 +44,13 @@ public class IndirectCullingGroup<I extends Instance> {
private final List<IndirectModel> indirectModels = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
private final InstanceType<I> instanceType;
private boolean needsDrawBarrier;
private boolean hasNewDraws;
private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType) {
this.instanceType = instanceType;
var programs = IndirectPrograms.get();
cullProgram = programs.getCullingProgram(instanceType);
applyProgram = programs.getApplyProgram();
@ -167,7 +169,9 @@ public class IndirectCullingGroup<I extends Instance> {
for (Map.Entry<Material, Mesh> 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<I extends Instance> {
}
}
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);

View file

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

View file

@ -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<IndirectInstancer<?>> {
private final StagingBuffer stagingBuffer = new StagingBuffer();
private final Map<InstanceType<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final GlBuffer crumblingDrawBuffer = new GlBuffer();
@Override
protected <I extends Instance> IndirectInstancer<?> create(InstanceType<I> type) {
@ -71,5 +95,89 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
cullingGroups.clear();
stagingBuffer.delete();
crumblingDrawBuffer.delete();
}
public void renderCrumbling(List<Engine.CrumblingBlock> 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<InstanceType<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, InstanceHandleImpl>>>> doCrumblingSort(List<Engine.CrumblingBlock> crumblingBlocks) {
Map<InstanceType<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, 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;
}
}

View file

@ -73,7 +73,30 @@ public class IndirectEngine extends AbstractEngine {
@Override
public void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> 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

View file

@ -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<I extends Instance> extends AbstractInstancer<I> {
private final long objectStride;
private final InstanceWriter<I> writer;
private final List<IndirectDraw> associatedDraws = new ArrayList<>();
private int modelIndex;
private long lastStartPos = -1;
@ -22,6 +26,14 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
writer = this.type.getWriter();
}
public void addDraw(IndirectDraw draw) {
associatedDraws.add(draw);
}
public List<IndirectDraw> draws() {
return associatedDraws;
}
public void update() {
removeDeletedInstances();
}

View file

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