MeshP... InstancedMo... DrawC... RenderLists refactor

- RenderLists -> InstancingDrawManager, keeps track of:
  - Uninitialized models
  - All Instancers
  - All DrawCalls via DrawSet
  - All MeshPools
 - One MeshPool is now locked to a single VertexType
 - DrawCall binds instance attributes to avoid making assumptions about mesh attribute count
 - Yeet crumbling
 - Simplify GPUInstancerFactory
This commit is contained in:
Jozufozu 2022-08-17 14:58:46 -07:00
parent bb7d971ebe
commit cfa79ec550
13 changed files with 207 additions and 554 deletions

View file

@ -7,7 +7,6 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.ShadersModHandler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.instancing.MeshPool;
import com.jozufozu.flywheel.config.BackendTypeArgument;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
@ -16,7 +15,6 @@ import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.StitchedSprite;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.model.Models;
import com.jozufozu.flywheel.event.EntityWorldHandler;
import com.jozufozu.flywheel.event.ForgeEvents;
@ -80,10 +78,8 @@ public class Flywheel {
forgeEventBus.addListener(FlwCommands::registerClientCommands);
forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onRendererReload);
forgeEventBus.<ReloadRenderersEvent>addListener(ProgramCompiler::invalidateAll);
forgeEventBus.addListener(ProgramCompiler::invalidateAll);
forgeEventBus.addListener(Models::onReload);
forgeEventBus.addListener(MeshPool::reset);
forgeEventBus.addListener(CrumblingRenderer::onReloadRenderers);
forgeEventBus.addListener(InstancedRenderDispatcher::onReloadRenderers);
forgeEventBus.addListener(InstancedRenderDispatcher::onRenderStage);

View file

@ -7,7 +7,6 @@ import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.compile.ContextShader;
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.ShaderSources;
@ -83,9 +82,7 @@ public class Loader implements ResourceManagerReloadListener {
ClientLevel world = Minecraft.getInstance().level;
if (Backend.canUseInstancing(world)) {
// TODO: looks like it might be good to have another event here
InstancedRenderDispatcher.resetInstanceWorld(world);
CrumblingRenderer.reset();
}
}

View file

@ -1,155 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.instancing.GPUInstancer;
import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.mixin.LevelRendererAccessor;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.server.level.BlockDestructionProgress;
public class SadCrumbling {
// public void renderCrumbling(LevelRenderer levelRenderer, ClientLevel level, PoseStack stack, Camera camera, Matrix4f projectionMatrix) {
// var dataByStage = getDataByStage(levelRenderer, level);
// if (dataByStage.isEmpty()) {
// return;
// }
//
// var map = modelsToParts(dataByStage);
// var stateSnapshot = GameStateRegistry.takeSnapshot();
//
// Vec3 cameraPosition = camera.getPosition();
// var camX = cameraPosition.x - originCoordinate.getX();
// var camY = cameraPosition.y - originCoordinate.getY();
// var camZ = cameraPosition.z - originCoordinate.getZ();
//
// // don't want to mutate viewProjection
// var vp = projectionMatrix.copy();
// vp.multiplyWithTranslation((float) -camX, (float) -camY, (float) -camZ);
//
// GlBuffer instanceBuffer = GlBuffer.requestPersistent(GlBufferType.ARRAY_BUFFER);
//
// GlVertexArray crumblingVAO = new GlVertexArray();
//
// crumblingVAO.bind();
//
// // crumblingVAO.bindAttributes();
//
// for (var entry : map.entrySet()) {
// var model = entry.getKey();
// var parts = entry.getValue();
//
// if (parts.isEmpty()) {
// continue;
// }
//
// StructType<?> structType = parts.get(0).type;
//
// for (var meshEntry : model.get()
// .entrySet()) {
// Material material = meshEntry.getKey();
// Mesh mesh = meshEntry.getValue();
//
// MeshPool.BufferedMesh bufferedMesh = MeshPool.getInstance()
// .get(mesh);
//
// if (bufferedMesh == null || !bufferedMesh.isGpuResident()) {
// continue;
// }
//
// material.getRenderType().setupRenderState();
//
// CoreShaderInfo coreShaderInfo = CoreShaderInfo.get();
//
//
// CrumblingProgram program = Contexts.CRUMBLING.getProgram(new ProgramCompiler.Context(Formats.POS_TEX_NORMAL,
// structType.getInstanceShader(), material.getVertexShader(), material.getFragmentShader(),
// coreShaderInfo.getAdjustedAlphaDiscard(), coreShaderInfo.fogType(),
// GameStateRegistry.takeSnapshot()));
//
// program.bind();
// program.uploadUniforms(camX, camY, camZ, vp, level);
//
// // bufferedMesh.drawInstances();
// }
// }
// }
@NotNull
private Map<Model, List<InstancedPart>> modelsToParts(Int2ObjectMap<List<BlockEntityInstance<?>>> dataByStage) {
var map = new HashMap<Model, List<InstancedPart>>();
for (var entry : dataByStage.int2ObjectEntrySet()) {
RenderType currentLayer = ModelBakery.DESTROY_TYPES.get(entry.getIntKey());
// something about when we call this means that the textures are not ready for use on the first frame they should appear
if (currentLayer == null) {
continue;
}
for (var blockEntityInstance : entry.getValue()) {
for (var part : blockEntityInstance.getCrumblingParts()) {
if (part.getOwner() instanceof GPUInstancer<?> instancer) {
// queue the instances for copying to the crumbling instance buffer
map.computeIfAbsent(instancer.parent.getModel(), k -> new ArrayList<>()).add(part);
}
}
}
}
return map;
}
@Nonnull
private Int2ObjectMap<List<BlockEntityInstance<?>>> getDataByStage(LevelRenderer levelRenderer, ClientLevel level) {
var destructionProgress = ((LevelRendererAccessor) levelRenderer).flywheel$getDestructionProgress();
if (destructionProgress.isEmpty()) {
return Int2ObjectMaps.emptyMap();
}
if (!(InstancedRenderDispatcher.getInstanceWorld(level)
.getBlockEntities() instanceof BlockEntityInstanceManager beim)) {
return Int2ObjectMaps.emptyMap();
}
var dataByStage = new Int2ObjectArrayMap<List<BlockEntityInstance<?>>>();
for (var entry : destructionProgress.long2ObjectEntrySet()) {
SortedSet<BlockDestructionProgress> progresses = entry.getValue();
if (progresses == null || progresses.isEmpty()) {
continue;
}
int progress = progresses.last()
.getProgress();
var data = dataByStage.computeIfAbsent(progress, $ -> new ArrayList<>());
long pos = entry.getLongKey();
beim.getCrumblingInstances(pos, data);
}
return dataByStage;
}
}

View file

@ -4,23 +4,23 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.layout.BufferLayout;
public class DrawCall {
private final GPUInstancer<?> instancer;
private final Material material;
final GPUInstancer<?> instancer;
final Material material;
private final int meshAttributes;
MeshPool.BufferedMesh bufferedMesh;
GlVertexArray vao;
DrawCall(GPUInstancer<?> instancer, Material material, Mesh mesh) {
DrawCall(GPUInstancer<?> instancer, Material material, MeshPool.BufferedMesh mesh) {
this.instancer = instancer;
this.material = material;
this.vao = new GlVertexArray();
this.bufferedMesh = MeshPool.getInstance()
.alloc(mesh);
this.instancer.attributeBaseIndex = this.bufferedMesh.getAttributeCount();
this.vao.enableArrays(this.bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount());
this.bufferedMesh = mesh;
this.meshAttributes = this.bufferedMesh.getAttributeCount();
this.vao.enableArrays(this.meshAttributes + instancer.instanceFormat.getAttributeCount());
}
public Material getMaterial() {
@ -32,11 +32,15 @@ public class DrawCall {
}
public void render() {
if (invalid()) return;
if (invalid()) {
return;
}
try (var ignored = GlStateTracker.getRestoreState()) {
this.instancer.renderSetup(vao);
this.instancer.update();
bindInstancerToVAO();
if (this.instancer.glInstanceCount > 0) {
bufferedMesh.drawInstances(vao, this.instancer.glInstanceCount);
@ -55,8 +59,24 @@ public class DrawCall {
return this.instancer.vbo == null || bufferedMesh == null || vao == null;
}
private void bindInstancerToVAO() {
if (!this.instancer.boundTo.add(vao)) {
return;
}
var instanceFormat = this.instancer.instanceFormat;
vao.bindAttributes(this.instancer.vbo, this.meshAttributes, instanceFormat, 0L);
for (int i = 0; i < instanceFormat.getAttributeCount(); i++) {
vao.setAttributeDivisor(this.meshAttributes + i, 1);
}
}
public void delete() {
if (invalid()) return;
if (invalid()) {
return;
}
vao.delete();
bufferedMesh.delete();

View file

@ -17,19 +17,17 @@ import com.jozufozu.flywheel.core.layout.BufferLayout;
public class GPUInstancer<D extends InstancedPart> extends AbstractInstancer<D> {
public final BufferLayout instanceFormat;
public final StructType<D> structType;
public final InstancedModel<D> parent;
final BufferLayout instanceFormat;
final StructType<D> structType;
final Set<GlVertexArray> boundTo = new HashSet<>();
GlBuffer vbo;
int attributeBaseIndex;
int glInstanceCount = 0;
boolean anyToUpdate;
public GPUInstancer(InstancedModel<D> parent, StructType<D> type) {
public GPUInstancer(StructType<D> type) {
super(type);
this.parent = parent;
this.instanceFormat = type.getLayout();
this.structType = type;
}
@ -40,7 +38,9 @@ public class GPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
}
public void init() {
if (vbo != null) return;
if (vbo != null) {
return;
}
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER, GlBufferUsage.DYNAMIC_DRAW);
vbo.setGrowthMargin(instanceFormat.getStride() * 16);
@ -50,17 +50,7 @@ public class GPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
return !anyToUpdate && !anyToRemove && glInstanceCount == 0;
}
private final Set<GlVertexArray> boundTo = new HashSet<>();
void renderSetup(GlVertexArray vao) {
update();
if (boundTo.add(vao)) {
bindInstanceAttributes(vao);
}
}
private void update() {
void update() {
if (anyToRemove) {
removeDeletedInstances();
}
@ -115,14 +105,6 @@ public class GPUInstancer<D extends InstancedPart> extends AbstractInstancer<D>
return vbo.ensureCapacity(requiredSize);
}
private void bindInstanceAttributes(GlVertexArray vao) {
vao.bindAttributes(this.vbo, this.attributeBaseIndex, this.instanceFormat, 0L);
for (int i = 0; i < this.instanceFormat.getAttributeCount(); i++) {
vao.setAttributeDivisor(this.attributeBaseIndex + i, 1);
}
}
@Override
public void delete() {
vbo.delete();

View file

@ -2,13 +2,13 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.instancer.InstancerFactory;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.instancing.AbstractInstancer;
import com.jozufozu.flywheel.core.model.Model;
/**
@ -17,53 +17,23 @@ import com.jozufozu.flywheel.core.model.Model;
*/
public class GPUInstancerFactory<D extends InstancedPart> implements InstancerFactory<D> {
protected final Map<Model, InstancedModel<D>> models = new HashMap<>();
protected final Map<Model, GPUInstancer<D>> models = new HashMap<>();
protected final StructType<D> type;
private final Consumer<InstancedModel<D>> creationListener;
private final BiConsumer<GPUInstancer<?>, Model> creationListener;
public GPUInstancerFactory(StructType<D> type, Consumer<InstancedModel<D>> creationListener) {
public GPUInstancerFactory(StructType<D> type, BiConsumer<GPUInstancer<?>, Model> creationListener) {
this.type = type;
this.creationListener = creationListener;
}
@Override
public Instancer<D> model(Model modelKey) {
return models.computeIfAbsent(modelKey, this::createInstancer).getInstancer();
return models.computeIfAbsent(modelKey, this::createInstancer);
}
public int getInstanceCount() {
return models.values()
.stream()
.map(InstancedModel::getInstancer)
.mapToInt(AbstractInstancer::getInstanceCount)
.sum();
}
public int getVertexCount() {
return models.values()
.stream()
.mapToInt(InstancedModel::getVertexCount)
.sum();
}
public void delete() {
models.values().forEach(InstancedModel::delete);
models.clear();
}
/**
* Clear all instance data without freeing resources.
*/
public void clear() {
models.values()
.stream()
.map(InstancedModel::getInstancer)
.forEach(AbstractInstancer::clear);
}
private InstancedModel<D> createInstancer(Model model) {
var instancer = new InstancedModel<>(type, model);
this.creationListener.accept(instancer);
private GPUInstancer<D> createInstancer(Model model) {
var instancer = new GPUInstancer<>(type);
this.creationListener.accept(instancer, model);
return instancer;
}
}

View file

@ -1,57 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.core.model.Model;
public class InstancedModel<D extends InstancedPart> {
private final StructType<D> type;
private final Model model;
private final GPUInstancer<D> instancer;
private List<DrawCall> layers;
public InstancedModel(StructType<D> type, Model model) {
this.type = type;
this.model = model;
this.instancer = new GPUInstancer<>(this, type);
}
public void init(RenderLists renderLists) {
instancer.init();
layers = model.getMeshes()
.entrySet()
.stream()
.map(entry -> new DrawCall(instancer, entry.getKey(), entry.getValue()))
.toList();
for (DrawCall layer : layers) {
renderLists.add(new ShaderState(layer.getMaterial(), layer.getVertexType(), type), layer);
}
}
public Model getModel() {
return model;
}
public GPUInstancer<D> getInstancer() {
return instancer;
}
public int getVertexCount() {
return model.getVertexCount() * instancer.glInstanceCount;
}
public void delete() {
if (instancer.vbo == null) return;
instancer.delete();
for (var layer : layers) {
layer.delete();
}
}
}

View file

@ -0,0 +1,125 @@
package com.jozufozu.flywheel.backend.instancing.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 org.jetbrains.annotations.NotNull;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.core.model.Model;
public class InstancingDrawManager {
private final List<UninitializedModel> uninitializedModels = new ArrayList<>();
private final List<GPUInstancer<?>> allInstancers = new ArrayList<>();
private final Map<RenderStage, DrawSet> renderLists = new EnumMap<>(RenderStage.class);
private final Map<VertexType, MeshPool> meshPools = new HashMap<>();
public DrawSet get(RenderStage stage) {
return renderLists.getOrDefault(stage, DrawSet.EMPTY);
}
public void create(GPUInstancer<?> gpuInstancer, Model model) {
uninitializedModels.add(new UninitializedModel(gpuInstancer, model));
}
public void flush() {
for (var model : uninitializedModels) {
model.instancer()
.init();
add(model.instancer(), model.model());
}
uninitializedModels.clear();
for (var pool : meshPools.values()) {
pool.flush();
}
}
public void delete() {
meshPools.values()
.forEach(MeshPool::delete);
meshPools.clear();
renderLists.values()
.forEach(DrawSet::delete);
renderLists.clear();
allInstancers.forEach(GPUInstancer::delete);
allInstancers.clear();
}
public void clearInstancers() {
allInstancers.forEach(GPUInstancer::clear);
}
private void add(GPUInstancer<?> instancer, Model model) {
var meshes = model.getMeshes();
for (var entry : meshes.entrySet()) {
DrawCall layer = new DrawCall(instancer, entry.getKey(), alloc(entry.getValue()));
var material = layer.getMaterial();
var shaderState = new ShaderState(material, layer.getVertexType(), layer.instancer.type);
renderLists.computeIfAbsent(material.getRenderStage(), DrawSet::new)
.put(shaderState, layer);
}
allInstancers.add(instancer);
}
private MeshPool.BufferedMesh alloc(Mesh mesh) {
return meshPools.computeIfAbsent(mesh.getVertexType(), MeshPool::new)
.alloc(mesh);
}
public static class DrawSet implements Iterable<Map.Entry<ShaderState, Collection<DrawCall>>> {
public static final DrawSet EMPTY = new DrawSet(ImmutableListMultimap.of());
final ListMultimap<ShaderState, DrawCall> drawCalls;
public DrawSet(RenderStage renderStage) {
drawCalls = ArrayListMultimap.create();
}
public DrawSet(ListMultimap<ShaderState, DrawCall> drawCalls) {
this.drawCalls = drawCalls;
}
private void delete() {
drawCalls.values()
.forEach(DrawCall::delete);
drawCalls.clear();
}
public void put(ShaderState shaderState, DrawCall layer) {
drawCalls.put(shaderState, layer);
}
public boolean isEmpty() {
return drawCalls.isEmpty();
}
@NotNull
@Override
public Iterator<Map.Entry<ShaderState, Collection<DrawCall>>> iterator() {
return drawCalls.asMap()
.entrySet()
.iterator();
}
}
private record UninitializedModel(GPUInstancer<?> instancer, Model model) {
}
}

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -8,7 +7,6 @@ import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL32;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material;
@ -43,8 +41,7 @@ public class InstancingEngine implements Engine {
protected final Map<StructType<?>, GPUInstancerFactory<?>> factories = new HashMap<>();
protected final List<InstancedModel<?>> uninitializedModels = new ArrayList<>();
protected final RenderLists renderLists = new RenderLists();
protected final InstancingDrawManager drawManager = new InstancingDrawManager();
/**
* The set of instance managers that are attached to this engine.
@ -66,30 +63,20 @@ public class InstancingEngine implements Engine {
@NotNull
private <D extends InstancedPart> GPUInstancerFactory<D> createFactory(StructType<D> type) {
return new GPUInstancerFactory<>(type, uninitializedModels::add);
return new GPUInstancerFactory<>(type, drawManager::create);
}
@Override
public void renderStage(TaskEngine taskEngine, RenderContext context, RenderStage stage) {
var multimap = renderLists.get(stage);
var drawSet = drawManager.get(stage);
setup();
render(multimap);
}
// TODO: Is this useful? Should it be added to the base interface? Currently it is only used for the old CrumblingRenderer.
@Deprecated
public void renderAll(TaskEngine taskEngine, RenderContext context) {
if (renderLists.isEmpty()) {
if (drawSet.isEmpty()) {
return;
}
setup();
for (var multimap : renderLists.getAll()) {
render(multimap);
}
render(drawSet);
}
private void setup() {
@ -103,12 +90,12 @@ public class InstancingEngine implements Engine {
RenderSystem.enableCull();
}
protected void render(ListMultimap<ShaderState, DrawCall> multimap) {
if (multimap.isEmpty()) {
protected void render(InstancingDrawManager.DrawSet drawSet) {
if (drawSet.isEmpty()) {
return;
}
for (var entry : multimap.asMap().entrySet()) {
for (var entry : drawSet) {
var shader = entry.getKey();
var drawCalls = entry.getValue();
@ -144,16 +131,10 @@ public class InstancingEngine implements Engine {
UniformBuffer.getInstance().sync();
}
public void clearAll() {
factories.values().forEach(GPUInstancerFactory::clear);
}
@Override
public void delete() {
factories.values()
.forEach(GPUInstancerFactory::delete);
factories.clear();
drawManager.delete();
}
@Override
@ -183,19 +164,13 @@ public class InstancingEngine implements Engine {
@Override
public void beginFrame(TaskEngine taskEngine, RenderContext context) {
for (var model : uninitializedModels) {
model.init(renderLists);
}
uninitializedModels.clear();
MeshPool.getInstance()
.flush();
drawManager.flush();
}
private void shiftListeners(int cX, int cY, int cZ) {
originCoordinate = new BlockPos(cX, cY, cZ);
factories.values().forEach(GPUInstancerFactory::clear);
drawManager.clearInstancers();
instanceManagers.forEach(InstanceManager::onOriginShift);
}

View file

@ -17,28 +17,12 @@ import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.model.Mesh;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
public class MeshPool {
private static MeshPool allocator;
public static MeshPool getInstance() {
if (allocator == null) {
allocator = new MeshPool();
}
return allocator;
}
public static void reset(ReloadRenderersEvent ignored) {
if (allocator != null) {
allocator.delete();
allocator = null;
}
}
private final VertexType vertexType;
private final Map<Mesh, BufferedMesh> meshes = new HashMap<>();
private final List<BufferedMesh> allBuffered = new ArrayList<>();
@ -54,10 +38,12 @@ public class MeshPool {
/**
* Create a new mesh pool.
*/
public MeshPool() {
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
public MeshPool(VertexType vertexType) {
this.vertexType = vertexType;
int stride = vertexType.getStride();
this.vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.setGrowthMargin(2048);
this.vbo.setGrowthMargin(stride * 32);
}
/**
@ -68,6 +54,10 @@ public class MeshPool {
*/
public BufferedMesh alloc(Mesh mesh) {
return meshes.computeIfAbsent(mesh, m -> {
if (m.getVertexType() != vertexType) {
throw new IllegalArgumentException("Mesh has wrong vertex type");
}
BufferedMesh bufferedModel = new BufferedMesh(m, byteSize);
byteSize += m.size();
allBuffered.add(bufferedModel);
@ -173,25 +163,24 @@ public class MeshPool {
pendingUpload.clear();
}
@Override
public String toString() {
return "MeshPool{" + "vertexType=" + vertexType + ", byteSize=" + byteSize + ", meshCount=" + meshes.size() + '}';
}
public class BufferedMesh {
private final ElementBuffer ebo;
private final Mesh mesh;
private final BufferLayout layout;
private long byteIndex;
private boolean deleted;
private boolean gpuResident = false;
private final Set<GlVertexArray> boundTo = new HashSet<>();
public BufferedMesh(Mesh mesh, long byteIndex) {
this.mesh = mesh;
this.byteIndex = byteIndex;
this.ebo = mesh.createEBO();
this.layout = mesh.getVertexType()
.getLayout();
}
public void drawCall(GlVertexArray vao) {
@ -199,7 +188,9 @@ public class MeshPool {
}
public void drawInstances(GlVertexArray vao, int instanceCount) {
if (hasAnythingToRender()) return;
if (hasAnythingToRender()) {
return;
}
setup(vao);
@ -221,7 +212,7 @@ public class MeshPool {
private void setup(GlVertexArray vao) {
if (this.boundTo.add(vao)) {
vao.enableArrays(getAttributeCount());
vao.bindAttributes(MeshPool.this.vbo, 0, this.layout, this.byteIndex);
vao.bindAttributes(MeshPool.this.vbo, 0, vertexType.getLayout(), this.byteIndex);
}
vao.bindElementArray(this.ebo.buffer);
vao.bind();
@ -241,19 +232,14 @@ public class MeshPool {
this.mesh.write(ptr + byteIndex);
this.boundTo.clear();
this.gpuResident = true;
}
public int getAttributeCount() {
return this.layout.getAttributeCount();
}
public boolean isGpuResident() {
return gpuResident;
return vertexType.getLayout().getAttributeCount();
}
public VertexType getVertexType() {
return this.mesh.getVertexType();
return vertexType;
}
}

View file

@ -1,39 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.material.Material;
public class RenderLists {
public final Map<RenderStage, ListMultimap<ShaderState, DrawCall>> renderLists = new EnumMap<>(RenderStage.class);
public ListMultimap<ShaderState, DrawCall> get(RenderStage stage) {
var renderList = renderLists.get(stage);
if (renderList == null) {
return ImmutableListMultimap.of();
}
return renderList;
}
public void add(ShaderState shaderState, DrawCall layer) {
Material material = shaderState.material();
renderLists.computeIfAbsent(material.getRenderStage(), k -> ArrayListMultimap.create())
.put(shaderState, layer);
}
public boolean isEmpty() {
return renderLists.isEmpty();
}
public Collection<ListMultimap<ShaderState, DrawCall>> getAll() {
return renderLists.values();
}
}

View file

@ -1,146 +0,0 @@
package com.jozufozu.flywheel.core.crumbling;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.SerialTaskEngine;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.mixin.LevelRendererAccessor;
import com.jozufozu.flywheel.util.Lazy;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.BlockDestructionProgress;
import net.minecraft.world.level.block.entity.BlockEntity;
// TODO: merge directly into InstancingEngine for efficiency
/**
* Responsible for rendering the crumbling overlay for instanced block entities.
*/
public class CrumblingRenderer {
private static Lazy<State> STATE;
static {
_init();
}
public static void renderCrumbling(RenderContext context) {
// TODO: one pass base/crumbling
if (true) return;
Int2ObjectMap<List<BlockEntity>> activeStages = getActiveStageBlockEntities(context.renderer(), context.level());
if (activeStages.isEmpty()) return;
try (var restoreState = GlStateTracker.getRestoreState()) {
State state = STATE.get();
var instanceManager = state.instanceManager;
var engine = state.instancerManager;
renderCrumblingInner(activeStages, instanceManager, engine, context);
}
}
private static void renderCrumblingInner(Int2ObjectMap<List<BlockEntity>> activeStages, InstanceManager<BlockEntity> instanceManager, CrumblingEngine engine, RenderContext ctx) {
for (Int2ObjectMap.Entry<List<BlockEntity>> stage : activeStages.int2ObjectEntrySet()) {
RenderType currentLayer = ModelBakery.DESTROY_TYPES.get(stage.getIntKey());
// something about when we call this means that the textures are not ready for use on the first frame they should appear
if (currentLayer != null) {
stage.getValue().forEach(instanceManager::add);
instanceManager.beginFrame(SerialTaskEngine.INSTANCE, ctx);
engine.beginFrame(SerialTaskEngine.INSTANCE, ctx);
engine.renderAll(SerialTaskEngine.INSTANCE, ctx);
instanceManager.invalidate();
}
}
}
/**
* Associate each breaking stage with a list of all block entities at that stage.
*/
private static Int2ObjectMap<List<BlockEntity>> getActiveStageBlockEntities(LevelRenderer levelRenderer, ClientLevel level) {
Long2ObjectMap<SortedSet<BlockDestructionProgress>> destructionProgress = ((LevelRendererAccessor) levelRenderer).flywheel$getDestructionProgress();
if (destructionProgress.isEmpty()) {
return Int2ObjectMaps.emptyMap();
}
Int2ObjectMap<List<BlockEntity>> breakingEntities = new Int2ObjectArrayMap<>();
BlockPos.MutableBlockPos breakingPos = new BlockPos.MutableBlockPos();
for (Long2ObjectMap.Entry<SortedSet<BlockDestructionProgress>> entry : destructionProgress.long2ObjectEntrySet()) {
breakingPos.set(entry.getLongKey());
SortedSet<BlockDestructionProgress> progresses = entry.getValue();
if (progresses != null && !progresses.isEmpty()) {
int progress = progresses.last()
.getProgress();
if (progress >= 0) {
BlockEntity blockEntity = level.getBlockEntity(breakingPos);
if (blockEntity != null) {
List<BlockEntity> blockEntities = breakingEntities.computeIfAbsent(progress, $ -> new ArrayList<>());
blockEntities.add(blockEntity);
}
}
}
}
return breakingEntities;
}
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel world = event.getWorld();
if (Backend.isOn() && world != null) {
reset();
}
}
public static void reset() {
STATE.ifPresent(State::kill);
_init();
}
private static void _init() {
STATE = Lazy.of(State::new);
}
private static class State {
private final CrumblingEngine instancerManager;
private final InstanceManager<BlockEntity> instanceManager;
private State() {
instancerManager = new CrumblingEngine();
instanceManager = new CrumblingInstanceManager(instancerManager);
instancerManager.attachManagers(instanceManager);
}
private void kill() {
instancerManager.delete();
instanceManager.invalidate();
}
}
private static class CrumblingEngine extends InstancingEngine {
public CrumblingEngine() {
super(Components.CRUMBLING);
}
}
}

View file

@ -14,7 +14,6 @@ import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.event.RenderStageEvent;
@ -66,7 +65,7 @@ public class LevelRendererMixin {
), method = "renderLevel")
private void renderCrumbling(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci) {
if (renderContext != null) {
CrumblingRenderer.renderCrumbling(renderContext);
// TODO: Crumbling
}
}