mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-06 04:16:36 +01:00
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:
parent
bb7d971ebe
commit
cfa79ec550
13 changed files with 207 additions and 554 deletions
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue